瑞吉外卖01-介绍+环境搭建+后台登录和退出

这是一个使用Springboot+SSM的实战练习项目,实现了一个外卖系统的前后台功能。

本笔记源自黑马程序员的视频课程——《瑞吉外卖》,总结了课程笔记、相关知识点以及可能遇到的问题解决方案,并且增加了课程中未实现的功能,供读者参考。笔记全面且条理清晰,希望帮助读者学习和理解这个外卖项目。
本项目全部笔记见:外卖项目笔记合集

下图是项目效果展示,左侧是移动端的效果,右侧是管理后台的效果。

1

1. 软件开发整体介绍

1.1 软件开发流程

2

1.2 角色分工

3

1.3 软件环境

4

2. 外卖项目介绍

2.1 项目介绍

本外卖项目是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。

本项目共分为3期进行开发:

第一期主要实现基本需求,其中移动端应用通过H5实现,用户可以通过手机浏览器访问。

第二期主要针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便。

第三期主要针对系统进行优化升级,提高系统的访问性能。

2.2 产品原型展示

产品原型,就是一款产品成型之前的一个简单的框架,就是将页面的排版布局展现出来,使产品的初步构思有一个可视化的展示。通过原型展示,可以更加直观的了解项目的需求和提供的功能。

一般来说,产品原型由产品经理在“需求分析”阶段制作。

5-产品原型

例如:

6-产品原型具体

注意事项:产品原型主要用于展示项目的功能,并不是最终的页面效果。

2.3 技术选型

7-技术选型

2.4 功能架构

8-功能架构

2.5 角色

后台系统管理员:登录后台管理系统,拥有后台系统中的所有操作权限。

后台系统普通员工:登录后台管理系统,对菜品、套餐、订单等进行管理。

C端用户:登录移动端应用,可以浏览菜品、添加购物车、设置地址、在线下单等。

3. 开发环境搭建

3.1 数据库环境搭建

3.1.1 创建项目对应的数据库

图形界面或者命令行方式任选一个

方法一:使用图形界面创建数据库(navicat等)

9

方法二:使用命令行创建数据库

10

创建后在图形界面内刷新一下,就可以看到新创建的数据库了。

3.1.2 导入表结构

在资料/数据模型/db_reggie.sql有脚本文件。

在图形界面中,选中数据库点右键,选择“运行SQL文件”,文件选择资料中提供的db_reggie.sql文件,点击开始。刷新后可以看到所有的表。

11

3.1.3 数据表

12

3.2 maven项目搭建

3.2.1 创建maven项目

在IDEA中点击new,选择project,选择maven项目。

13

3.2.2 检查项目的编码、maven仓库配置、jdk配置

创建完项目后,注意检查项目的编码、maven仓库配置、jdk配置等。

项目编码:

14

maven仓库配置:

15

jdk配置:

16

3.2.3 pom文件

pom文件中添加依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<!--继承父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kxzhu</groupId>
<artifactId>reggie</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--fastjson:对象转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>

<!--mysql驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

3.2.4 配置文件application.yml

将资料/项目文件中提供的application.yml复制到项目的src/main/resources下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 8080 # 启动时tomcat的端口号

spring:
application:
name: reggie # 填写应用的名称,非必需项。如果不配置,默认使用工程名
datasource:
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: root # 填写自己的密码

mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true # 开启后,表名address_book可以映射为实体类名AddressBook;字段名user_name可以映射为属性名userName
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID # 主键的生成策略

3.2.5 编写Springboot启动类

1
2
3
4
5
6
7
8
@Slf4j  //加上这个注解后,可以在主程序中使用log.xxxx方法,便于打印日志
@SpringBootApplication
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class, args);
log.info("项目启动成功");
}
}

注解@Slf4j是lombok自带的。加在类上之后,类中的方法可以调用log.info()方法。

3.2.6 导入前端页面并设置静态资源映射

导入项目前端页面

将资料/前端资源中的backend复制,粘贴到项目的src/main/resources。

注意:资料/前端资源/front部分代码不全,在后期需要手动修改。为了简便,此处可以复制资料中day05的front资源。

设置静态资源映射

原因:

此时存在一个问题,springboot默认情况下只能访问以下静态资源路径:

  • classpath:/static/
  • classpath:/templates/
  • classpath:/META-INF/resources/
  • classpath:/resources/

此处的类路径“classpath”相当于src/main/resources目录。现在我们想将静态资源放在resources下的backend和front目录,并能访问,需要设置MVC框架静态资源映射。

方法:

在与spring boot启动类(Springboot2Application)的同级文件下,创建config包,并创建配置类WebMvcConfig,继承WebMvcConfigurationSupport类,并重写addResourceHandlers(ResourceHandlerRegistry registry)方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@Configuration // 这是一个配置类
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//前端发来的请求是什么样的(http://ip:port/backend/xxxx)-->映射到什么地方(类路径resources下的backend下的xxx)
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
log.info("开始进行静态资源映射");
}
}

拓展知识:

/** 当前目录及子目录下的所有文件

/* 当前目录下的所有文件

4. 后台登录功能开发

4.1 需求分析

由于已经将静态资源放到resources下,且已经设置了静态资源映射,因此可以访问resources下的静态资源。此时可以直接在浏览器访问登录页面 http://localhost:8080/backend/page/login/login.html 。并可以打开浏览器调试工具(按F12;或者在浏览器点右键,选择“检查”),切换到“Network”。如下图:

17-浏览器调试

4.1.1 请求过程

在登录页面 http://localhost:8080/backend/page/login/login.html 点击“登录”按钮,可以看到向http://localhost:8080/employee/login 发送了POST请求,并将username和password以json字符串的形式提交到服务端。

18-登录请求

服务端需要写相关的方法处理该POST请求。

4.1.2 数据模型

由于此时是员工登录,所以相关的数据表是employee表。

19-employee表

4.2 代码开发

4.2.1 前端页面分析

我们首先需要先大致读懂html文件,可以分析出,服务端处理数据后,需要给前端什么样的响应信息。

在resources/backend/page/login/login.html中,关键代码是methods方法。

20

点击”登录按钮”时,执行methods中的handleLogin方法。其中先校验用户名和密码是否为空。

如果登录通过,loading改为true,页面显示”登录中”。并调用loginApi(),向/employee/login发送post请求,并且带上loginForm数据,即json形式的username和password。请求发出后,服务端接收、处理并返回相应结果,res就是controller响应回来的结果。响应回来的code为1,则表示成功,跳转到首页。

否则(登录失败)loading为false,页面显示”登录”。

21

注:从前端代码可以看出,服务端处理完,给页面响应回来的数据应该含有code, data, msg。且localStorage.setItem('userInfo',JSON.stringify(res.data))这句说明,需要将’userInfo’作为键,将响应回来的data转为json字符串,作为值,保存在浏览器中。因此,data应该包含登录成功的员工信息。

4.2.2 实体类

创建实体类Employee,和employee表进行映射:

创建entity包,将资料提供的实体类Employee类复制粘贴到entity包中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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; //身份证号。在employee表中对应的是id_number。
// 只要在application.yml中开启驼峰命名(map-underscore-to-camel-case: true),mybatis-plus就可以自动映射

private Integer status;

@TableField(fill = FieldFill.INSERT)//插入的时候,填充字段的值。在“公共字段自动填充”部分使用此注解,此处可以先忽略
private LocalDateTime createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)//插入和更新时填充字段的值
private LocalDateTime updateTime;

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

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

}

4.2.3 创建Controller、Service、Mapper

EmployeeMapper:

在mapper包下,创建EmployeeMapper接口。并记得加上@Mapper注解。

因为这里使用mybatis-plus,所以可以直接继承其提供的BaseMapper类,较方便。

1
2
3
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}

注:BaseMapper是mybatis plus提供的,可以继承其增删改查方法。BaseMapper需要提供一个泛型,是实体类。


EmployeeService:

在service包下,创建EmployeeService接口。

1
2
public interface EmployeeService extends IService<Employee> { 
}

注:IService是mybatis-plus提供的,需要提供一个泛型,是实体类。

EmployeeServiceImpl:

在service/impl包下,创建EmployeeServiceImpl类。并记得加上@Service注解。

1
2
3
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService{
}

EmployeeController:

在controller包下,创建EmployeeController类。加上@RestController、@RequestMapping(“/employee”)、@Slf4j注解。

1
2
3
4
5
6
7
@Slf4j  // 用于输出日志
@RestController // @ResponseBody + @Controller
@RequestMapping("/employee") // 请求地址/employee/** 由本类处理
public class EmployeeController {
@Autowired
private EmployeeService employeeService; //注入
}

4.2.4 导入返回结果类R

R是一个通用结果类,服务端响应的所有结果最终都会包装成此种类型,返回给前端页面。

具体地,服务端controller响应客户端页面发来的请求,controller处理完之后给页面返回结果,把结果封装到此R类对象中。

将资料/服务端返回结果类中的R.java粘贴到common包中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Data
public class R<T> {

private Integer code; //编码:1成功,0和其它数字为失败

private String msg; //错误信息

private T data; //数据

private Map map = new HashMap(); //动态数据

//静态方法。例如,登录成功,返回时可以调用R.success(employee)
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}

//静态方法。例如,登录失败,返回时可以调用R.error(msg)
public static <T> R<T> error(String msg) {
R r = new R();
r.msg = msg;
r.code = 0;
return r;
}

//操作map对象动态数据
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}

4.2.5 在Controller中编写登录方法

22

处理逻辑如下:

  1. 将页面提交的密码password进行md5加密处理
  2. 根据页面提交的用户名username查询数据库
  3. 如果没有查询到则返回登录失败结果
  4. 密码比对,如果不一致则返回登录失败结果
  5. 查看员工状态,如果为已禁用状态,则返回员工已禁用结果
  6. 登录成功,将员工id存入Session并返回登录成功结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Slf4j  //输出日志
@RestController //@ResponseBody + @Controller
@RequestMapping("/employee") // 请求地址/employee/** 由本类处理
public class EmployeeController {
@Autowired
private EmployeeService employeeService;

/**
* 员工登录功能
* 处理请求:/employee/login,method=post
* @param employee 登录时,浏览器发送post请求给服务器。其中带着json形式的员工登录信息,即{"username":"admin","password":"123456"},可以封装到Employee对象中。
* 服务器可通过在形参使用@RequestBody,获取json格式的请求参数。
* @param request 便于获取session,将employee对象的id存一份到session,表示登录成功
* @return
*/
@PostMapping("/login")
public R<Employee> login(@RequestBody Employee employee, HttpServletRequest request){
//根据登录逻辑图,按步骤实现:
//1、将页面提交的密码进行md5加密处理
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());

//2、根据页面提交的用户名username来查询数据库
//包装查询对象
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
//添加查询条件
queryWrapper.eq(Employee::getUsername, employee.getUsername()); // 这一句判断在数据库中遍历出的用户名,是否和前端页面用户输入的用户名(封装在employee中)相同,相同的话即代表数据库中有这个值,反之则无
Employee emp = employeeService.getOne(queryWrapper);//从数据库查出唯一的数据(表中username字段是UNIQUE的,所以用getOne就可以)

//3、如果没有查询到,则返回失败结果
if(emp == null){
return R.error("登录失败"); // 不要提示"用户名不存在",否则有用户名遍历的漏洞风险
}

//4、比对密码,将从前端页面获取的密码md5加密后 与 数据库查出来的密码(也是加密的)进行比对,如果不一致则返回提示信息
if(! emp.getPassword().equals(password)){
return R.error("登录失败");
}

//5、查看员工状态,如果已禁用状态,则返回员工已禁用结果
if(emp.getStatus() == 0){
return R.error("账号已禁用");
}

//6、登录成功,将用户id存入Session并返回成功结果
HttpSession session = request.getSession();
session.setAttribute("employee", emp.getId());//将查出来的emp对象的id 存一份到session,表示该用户登录成功,便于后续获取登录用户
return R.success(emp);

拓展:

第2步中的LambdaQueryWrapper是mybatis-plus的一个工具类。如果你已经学过mybatis-plus,可以忽略这一段。如果没学过,可以看这段介绍简单了解用法。

LambdaQueryWrapper常见功能:

23

LambdaQueryWrapper最基础的使用方式是这样:

1
2
3
4
5
6
7
8
9
10
11
12
// 查询条件构造器
QueryWrapper<BannerItem> wrapper = new QueryWrapper<>();
wrapper.eq("banner_id", id);
// 查询操作
List<BannerItem> bannerItems = bannerItemMapper.selectList(wrapper);

// 包装查询对象
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
// 添加查询条件
queryWrapper.eq(Employee::getUsername, employee.getUsername());
// 查询操作
Employee emp = employeeService.getOne(queryWrapper);

其中,queryWrapper.eq(Employee::getUsername, employee.getUsername()); 这一句用来判断:在数据库中遍历出的用户名,是否和前端页面用户输入的用户名(封装在employee中)相同。相同的话,代表数据库中有这个值,反之则无这个值。

对应的sql语句:select * from employee where username = ?

4.3 功能测试

启动服务,进行登录功能的测试。

报错:timeout of 10000ms exceeded

24

前端页面设置了超时时间,10秒不响应就会抛异常。测试阶段可以将backend/js/request.js中的超时时间改大,改为1000000

25

5. 后台退出功能开发

5.1 需求分析

员工登录成功后,页面跳转到后台系统首页面(backend/index.html),此时会显示当前登录用户的姓名。

26

如果员工需要退出系统,点击右侧的退出按钮。打开浏览器调试工具,可以看到向http://localhost:8080/employee/logout 发送了POST请求,此处需要服务端处理。

27-退出登录请求

退出系统后页面跳转回登录页面。

5.2 代码开发

服务端controller需要写相关的类处理该POST请求(/employee/logout)。具体的处理逻辑:

  1. 清理Session中的用户id
  2. 返回结果
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 退出登录
* @return
*/
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){ // 因为需要操作session,所以参数写request
// 清理session中保存的当前登录员工的id
HttpSession session = request.getSession();
session.removeAttribute("employee"); //登录时,放进session "employee",保存登录成功的员工id。此时需要清理。
// 返回结果
return R.success("退出成功");
}

由于此处不需要返回详细数据,返回类型R即可。

5.3 功能测试

启动服务,测试功能即可。