瑞吉外賣實戰項目全攻略——第一天

瑞吉外賣實戰項目全攻略——第一天

該系列將記錄一份完整的實戰項目的完成過程,該篇屬於第一天

案例來自B站黑馬程式設計師Java項目實戰《瑞吉外賣》,請結合課程資料閱讀以下內容

該篇我們將完成以下內容:

  • 軟體開發整體介紹
  • 瑞吉外賣項目介紹
  • 開發環境搭建
  • 後台登錄系統功能開發
  • 後台退出系統功能開發

軟體開發整體介紹

這個項目屬於我的第一個完整項目,所以我們將從軟體開發的概念來開始介紹

軟體開發流程

軟體開發主要分為五個階段,每個階段帶有不同的需求:

  • 需求分析

需求分析需要設計產品原型,產生需求規格說明書

  • 設計

設計主要負責產品文檔,UI介面設計,概要設計,詳細設計,資料庫設計等設計資訊

  • 編碼

編碼主要負責項目程式碼以及單元測試,也就是我們著重介紹的部分

  • 測試

測試主要負責準備測試用例,書寫測試報告

  • 上線運維

上線運維主要包括軟體環境安裝,配置等

角色分工

我們的公司中通常具有不同的崗位,這些崗位被稱為角色

每個角色都具有不同的項目作用:

  • 項目經理

對整個項目負責,任務分配,把控進度

  • 產品經理

進行需求調研,輸出需求調研文檔,產品原型等

  • UI設計師

根據產品原型輸出介面效果圖

  • 架構師

項目整體架構設計,技術選型等

  • 開發工程師

程式碼實現

  • 測試工程師

編寫測試用例,輸出測試報告

  • 運維工程師

軟體環境搭建,項目上線

軟體環境

我們的項目軟體在不同的情況下要處於不同的軟體環境下

軟體環境通常分為三種:

  • 開發環境(development)

開發人員在開發階段使用的環境,一般外部用戶無法訪問

  • 測試環境(test)

專門給測試人員使用的環境,用於測試項目,一般外部用戶無法訪問

  • 生產環境(production)

即上線環境,正式提供對外服務的環境

瑞吉外賣項目介紹

我們想要開發產品,就要對產品具有一定的了解

項目介紹

首先我們介紹項目本身:

  • 本項目(瑞吉外賣)是專門為餐飲企業(餐廳、飯店)訂製的一款軟體產 品,包括系統管理後台和移動端應用兩部分。

  • 其中系統管理後台主要提供給餐飲企業內部員工使用,可以對餐廳的菜品、套餐、訂單等進行管理維護。

  • 移動端應用主要提供給消費者使用,可以在線瀏覽菜品添加購物車、下單等。

再來介紹我們的開發計劃:

  • 本項目共分為3期進行開發
  • 第一期主要實現基本需求,其中移動端應用通過H5實現,用戶可以通過手機瀏覽器訪問。
  • 第二期主要針對移動端應用進行改進,使用微信小程式實現,用戶使用起來更加方便。
  • 第三期主要針對系統進行優化升級,提高系統的訪問性能。

產品原型展示

首先我們先來介紹產品原型:

  • 產品原型就是一個產品成型前的簡單框架,將頁面的排版布局展現出來,使初步構思有一個可視化的展示
  • 通過原型展示,可以更加直觀的了解項目的需求和提供的功能

注意點:

  • 產品原型主要用於展示項目的功能,並不是最終的頁面效果

關於產品原型的展示我們不再展示,產品原型在資料中已全部提供~

技術選型

我們給出整個項目的技術棧展示:

功能架構

我們同樣給出整個項目需要實現的功能架構:

角色展示

我們需要將項目中所出現的相關角色同列出來

本項目中大概出現三類角色:

  • 後台系統管理員

登錄後台系統,擁有後台系統中的所有操作許可權

  • 後台系統普通員工

登錄後台系統,對菜品,套餐,訂單進行管理

  • C端用戶

登錄移動端應用,可以瀏覽菜品,添加購物車,設置地址,在線下單等

開發環境搭建

在正式開始編程之前,我們需要將準備工作完成

我們需要從兩方面進行環境搭建,我們的資料庫使用MYSQL,開發工具採用IDEA

資料庫環境搭建

我們直接進入MYSQL資料庫,這裡使用MYSQL便捷工具Navicat:

  1. 創建資料庫reggie

  1. 增添數據
# 資料中包含有我們所使用的數據,直接導包即可,因數據過長,這裡不做展示

到這裡我們的資料庫環境搭建就結束了

最後我們將導入資料庫的表羅列出來進行一定說明展示:

Maven項目搭建

我們的程式碼開發採用IDEA的Maven搭建:

  1. 創建maven(直接創建即可)

  1. pom.xml相對應依賴坐標導入
<!--pom.xml資料也有提供,當然直接複製下列內容也可以-->

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="//maven.apache.org/POM/4.0.0"
         xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <groupId>com.qiuluo</groupId>
    <artifactId>mydelivery</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>

        <!-- 將對象 轉化為JSON格式-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.5</version>
            </plugin>
        </plugins>
    </build>

</project>
  1. yml配置文件導入
server:
  port: 8080
spring:
  application:
    name: reggie_take_out
  datasource:
  # 德魯伊mysql配置,根據自己資料庫狀況修改
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: 123456
mybatis-plus:
  configuration:
    #在映射實體或者屬性時,將資料庫中表名和欄位名中的下劃線去掉,按照駝峰命名法映射,默認情況下開啟(這裡僅做科普)
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
  1. 書寫主方法
// 在我們的java文件夾下創建相對應的包
// 我創建的是com.qiuluo

package com.qiuluo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 我們添加註解@Slf4j,可以使用log的方法添加日誌,便於程式管理
@Slf4j
// @SpringBootApplication表示是程式的啟動類
@SpringBootApplication
public class ReggieApplication {
    public static void main(String[] args) {
        // 啟動程式的關鍵程式碼
        SpringApplication.run(ReggieApplication.class,args);
        // 日誌輸出
        log.info("項目啟動成功");
    }
}

  1. 前端頁面導入
// 我們該項目主要側重後端開發,前端知識我們直接採用資料中所給數據即可

// 前端頁面:backend和front文件,我們放置於resources文件夾下即可
  1. 書寫配置類設置靜態資源
// 正常情況下,我們的頁面訪問時會被Contoller攔截下來返回數據,這時我們就需要設置靜態資源的轉發路徑

package com.qiuluo.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

// @Configuration設置為配置類,讓Spring可以讀取到該配置類
@Slf4j
@Configuration
// 注意:主要繼承WebMvcConfigurationSupport成為配置類
public class WebMvcConfig extends WebMvcConfigurationSupport {


    // 設置靜態資源的映射關係,繼承方法addResourceHandlers即可
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 日誌輸出
        log.info("即將進行靜態資源的映射:");

        // 將請求路徑 /backend/** 映射到 項目靜態資源目錄 resources/backend 下
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");

    }
}
  1. 查看網頁是否映射成功
// 注意:需要先對項目整體進行clean操作後,再查看頁面,否則可能無法查看

後台登錄功能開發

我們在進行功能開發時一般分為三個步驟進行開發

需求分析

首先我們需要得知,登錄是在前端哪個頁面通過什麼方法請求數據

我們打開頁面後,通過F12來查看點擊相關功能後所進行的頁面請求或者直接在後端查看請求

頁面F12獲取請求:(這裡由於我這裡已經完成功能,點擊後直接跳轉,無法獲得數據)

後端查看請求:

function loginApi(data) {
  return $axios({
    'url': '/employee/login',
    'method': 'post',
    data
  })
}

通過查詢後我們可以發現點擊登錄後發送請求格式為下列:

URL://localhost:8080/employee/login
Request Method:POST

因此我們需要書寫Controller來完成該請求的相對應程式碼

另一方面,我們都知道我們前端和後端在交互時會通過一個固定的格式來返回請求

我們可以通過查詢相關程式碼來確定返回格式:

methods: {
        async handleLogin() {
          this.$refs.loginForm.validate(async (valid) => {
            if (valid) {
              this.loading = true
              let res = await loginApi(this.loginForm)
              if (String(res.code) === '1') {
                localStorage.setItem('userInfo',JSON.stringify(res.data))
                window.location.href= '/backend/index.html'
              } else {
                this.$message.error(res.msg)
                this.loading = false
              }
            }
          })
        }
      }

// 我們可以發現res屬性包含有code,data,msg等屬性

那麼我們在後端程式碼中需要規定一個實體類來充當返回參數類

程式碼開發

下面我們來到IDEA中書寫我們前面所需要的內容:

  1. 構造實體類Employee
// 我們創建entity文件夾專門書寫實體類(資料中包含有實體類構造程式碼)

// Employee是我們的員工表,相當於我們的帳號和密碼的主體類

package com.qiuluo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

@Data
public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    private String username;

    private String name;

    private String password;

    private String phone;

    private String sex;

    private String idNumber;

    private Integer status;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;

}
  1. 構造數據層
package com.qiuluo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qiuluo.entity.Employee;
import org.apache.ibatis.annotations.Mapper;

// @Mapper幫助Spring自動識別數據層,採用全權代理開發
@Mapper
// 繼承BaseMapper<Employee>,其中包括有許多基本資料庫方法
public interface EmployeeMapper extends BaseMapper<Employee> {
}
  1. 構造業務層介面
package com.qiuluo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.qiuluo.entity.Employee;

// 繼承IService<Employee>實現多種方法
public interface EmployerService extends IService<Employee> {
}
  1. 構造業務層
package com.qiuluo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiuluo.entity.Employee;
import com.qiuluo.mapper.EmployeeMapper;
import com.qiuluo.service.EmployerService;
import org.springframework.stereotype.Service;

// @Service幫助Spring自動識別
@Service
// 繼承ServiceImpl<EmployeeMapper,Employee>中的各種方法,第一個參數是Mapper,第二個參數是實體類
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper,Employee> implements EmployerService {
}
  1. 構造服務層
package com.qiuluo.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qiuluo.common.R;
import com.qiuluo.entity.Employee;
import com.qiuluo.service.EmployerService;
import com.qiuluo.service.impl.EmployeeServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

// 書寫基本註解

// 日誌註解
@Slf4j
// REST形式的Controller
@RestController
// 設置整體映射地址
@RequestMapping("/employee")
public class EmployeeController {

    // 自動裝配employeeService
    @Autowired
    private EmployeeServiceImpl employeeService;

}
  1. 構造返回結構類R
// 我們構造一個返回結構類作為返回類型

package com.qiuluo.common;

import lombok.Data;
import java.util.HashMap;
import java.util.Map;

@Data
public class R<T> {

    private Integer code; //編碼:1成功,0和其它數字為失敗

    private String msg; //錯誤資訊

    private T data; //數據

    private Map map = new HashMap(); //動態數據

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}
  1. 邏輯分析前端需求並實現
package com.qiuluo.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qiuluo.common.R;
import com.qiuluo.entity.Employee;
import com.qiuluo.service.EmployerService;
import com.qiuluo.service.impl.EmployeeServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeServiceImpl employeeService;

    /**
     * 員工登錄
     * */
    @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){

        // 1. 將頁面提交的密碼經過md5加密(DigestUtils.md5DigestAsHex方法需要byte參數)
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());

        // 2.根據頁面提交的用戶名username查詢資料庫(採用LambdaQueryWrapper進行條件篩選)
        LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(lambdaQueryWrapper);

        // 3.如果沒有查詢到,判定失敗
        if (emp == null){
            return R.error("登陸失敗");
        }

        // 4.密碼比對,密碼不一致失敗
        if (!emp.getPassword().equals(password)){
            return R.error("登陸失敗");
        }

        // 5.查看員工狀態
        if (emp.getStatus() == 0){
            return R.error("賬戶已禁用");
        }

        // 6.登錄成功,並將員工id存入Session並返回登錄成功結果
        request.getSession().setAttribute("employee",employee.getId());
        return R.success(emp);
    }

}
  1. 前端登錄測試
# 測試時盡量將程式碼中書寫的各種情況都測試一遍確保無誤

後台退出功能開發

我們在進行功能開發時一般分為三個步驟進行開發

需求分析

員工登錄成功後,頁面跳轉到系統首頁頁面(backend/index.html),此時顯示當前用戶名

當我們點擊退出時,直接點擊退出按鈕即可退出頁面,回到登錄頁面

同樣我們採用F12或者後台請求查看:

URL://localhost:8080/employee/logout
Request Method:POST

程式碼開發

我們回到EmployeeController程式中開發請求地址為employee/logout的POST請求即可

具體步驟包括有:

  • 清理Session中的id
  • 返回結果

我們的實際開發步驟分為兩步:

  1. 在EmployeeController中開發相對應的功能
package com.qiuluo.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qiuluo.common.R;
import com.qiuluo.entity.Employee;
import com.qiuluo.service.EmployerService;
import com.qiuluo.service.impl.EmployeeServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeServiceImpl employeeService;

    /**
     * 員工登錄
     * */
    @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){

        // 1. 將頁面提交的密碼經過md5加密(DigestUtils.md5DigestAsHex方法需要byte參數)
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());

        // 2.根據頁面提交的用戶名username查詢資料庫(採用LambdaQueryWrapper進行比對)
        LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(lambdaQueryWrapper);

        // 3.如果沒有查詢到,判定失敗
        if (emp == null){
            return R.error("登陸失敗");
        }

        // 4.密碼比對,密碼不一致失敗
        if (!emp.getPassword().equals(password)){
            return R.error("登陸失敗");
        }

        // 5.查看員工狀態
        if (emp.getStatus() == 0){
            return R.error("賬戶已禁用");
        }

        // 6.登錄成功,並將員工id存入Session並返回登錄成功結果
        request.getSession().setAttribute("employee",employee.getId());
        return R.success(emp);
    }


    /**
     * 退出管理員
     * */
    @PostMapping("/logout")
    public R<String> logout(HttpServletRequest request){
        // 1. 將用戶id從Session中刪去
        request.getSession().removeAttribute("employee");
        // 2. 返回推出成功的資訊即可
        return R.success("退出成功");
    }
}
  1. 實際測試(點擊退出鍵返回登錄頁面即可)

結束語

該篇內容到這裡就結束了,希望能為你帶來幫助~

附錄

該文章屬於學習內容,具體參考B站黑馬程式設計師的Java項目實戰《瑞吉外賣》

這裡附上影片鏈接:業務開發Day1-01-本章內容介紹_嗶哩嗶哩_bilibili