瑞吉外卖实战项目全攻略——第一天

时间:2022-10-19 08:07:20

该系列将记录一份完整的实战项目的完成过程,该篇属于第一天

案例来自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="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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:http://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:http://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