瑞吉外卖项目

软件开发流程

1、需求分析

产品原型、需求规格说明书

2、设计

产品文档、UI界面设计、概要设计、详细设计、数据库设计

3、编码

项目代码、单元测试

4、测试

测试用例、设施报告

5、上线运维

软件环境安装、配置

 

角色分工

 

 

软件环境

 

瑞吉外卖项目介绍

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

 

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

  1. 第一期主要实现基本需求,其中移动端应用通过H5实现,用户可以通过手机浏览器访问。
  2. 第二期主要针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便。
  3. 第三期主要针对系统进行优化升级,提高系统的访问性能。

 

产品原型

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

 

 

导入pom

 

配置springboot启动类

 

Slf4j

Simple Logging Facade for Java(SLF4J)作为各种日志框架(例如Java.util.Logging、logback、log4j)的简单外观或抽象

首先slf4j可以理解为规则的制定者,是一个抽象层,定义了日志相关的接口。log4j,logback,JDK Logging都是slf4j的实现层,只是出处不同,当然使用起来也就各有千秋

 

引用文章

为什么加了@slf4j注解 就可以直接使用log了呢?

如果不使用注解,那我们使用log,需要这样定义

其实在编译完成生成字节码文件的时候idea就已经帮我们生成了这段代码

这就得益于lombok插件

其实这就是因为Lombok项目是一种自动接通你的编辑器和构建工具的一个Java库

可以理解为有了@slf4j,编辑器里就会给你加log的智能提示,但这并不代表log对象已生成

 

 

Lombok

Lombok官网

Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能,不需要再写getter、setter或equals方法,只要有一个注解,你的类就有一个功能齐全的构建器、自动记录变量等等

@Data

整合了Getter、Setter、ToString、EqualsAndHashCode、RequiredArgsConstructor注解

@Getter

快速构建Getter方法

@Setter

快速构建Setter方法

@ToString

快速将当前对象转换成字符串类型,便于log

@EqualsAndHashCode

快速进行相等判断

@NonNull

判断变量(对象)是否为空

 

https://blog.csdn.net/weixin_45433031/article/details/121846207

配置核心配置类

方法1 实现WebMvcConfigurer接口

WebMvcConfigurer 接口其实是Spring内部的一种配置方式,我们需要去实现这个接口

(接口中的default关键字规定了接口方法有方法体并且能被实现类继承和重写)

 

方法2 继承WebMvcConfigurationSupport类

由于服务器无法访问后端的backend和front资源,我们需要配置一个WebMvcConfig配置类,我们需要让这个类去继承WebMvcConfigurationSupport这个类

 

这个类为我们提供了一个资源拦截器方法

我们想自定义静态资源映射目录的话,只需重写addResourceHandlers方法即可

SpringBoot拦截器addResourceHandlers()

addResourceLocations() 需要映射到的后端资源地址

 

 

我们在持久层接口上加Mapper标签

让EmployeeMapper去继承MyBatisPlus提供的BaseMapper接口,需要提供一个实体类泛型

 

表现层

@RequestBody注解 使得接收到的值指定为json串,必须是post方式提交

@PostMapping(“/login”)注解

等价于@RequestMapping(value = “/user/login”,method = RequestMethod.POST)

 

返回结果类

本次工程内封装的返回结果类,用于给前端响应数据

 

功能实现

 

1.1用户登录后端校验

需要对前端传来的用户密码进行md5加密

在后端查询是否有此用户名(用户名在数据库中设置了不能重)

进行密码校验

需要将用户数据存入到session域中

 

1.2用户注销

 

☆完善登录功能(过滤器)

如果用户不登陆,直接访问系统页面,照样可以正常访问

这种设计并不合理,我们希望看到的效果应该是,只有登录成功后才可以访问系统中的页面,如果没有登录则跳转登录页面

具体如何实现?

使用过滤器或者拦截器,在过滤器或拦截器中判断用户是否已经无参登录,如果没有登录则跳转登录页面

 

步骤

1、创建自定义过滤器LoginCheckFilter

2、在启动类上加入注解@ServletCompontentScan

3、完善过滤器的处理逻辑

 

 

创建过滤器

 

首先在springboot启动类上加@ServletComponentScan注解

 

@WebFilter注解

在servlet3.0以后,我们可以不用在web.xml文件内配置Filter,只需要加上@WebFilter注解就可以实现,以达到简化配置的目的

该注解用来声明servlet过滤器,将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器

filterName属性

指定过滤器的名字

urlPatterns属性

指定一组过滤器的URL匹配模式

 

过滤器具体的处理逻辑如下:

1、获取本次请求的URI

2、判断本次请求是否需要处理

3、如果不需要处理,则直接放行

4、判断登录状态,如果已登录,则直接放行

5、如果未登录则返回未登录结果

 

 

AntPathMatcher: 路径匹配器,支持通配符

 

1.3新增员工

为数据库新增员工

 

☆全局异常捕获(AOP)

前面的程序还存在一个问题,就是当我们在新增员工时输入的账号已经存在,由于employee表中对该字段加入了唯一约束,此时程序会抛出异常

 

此时需要我们的程序进行异常捕获,通常由两种处理方式:

1、在Controller方法中加入try、catch进行异常捕获(不推荐)

2、使用异常处理器进行全局异常捕获

 

第二种方法其实就是通过AOP底层动态代理来对目标方法的增强(异常通知)

 

@ControllerAdvice注解

annotations属性: 被其中存放的注解修饰的方法会被拦截,传入注解字节码数组

 

@ExceptionHandler注解

传入异常类的字节码,修饰方法(异常通知的内容),指定异常的处理类型

 

 

总结

1、根据产品原型明确业务需求

2、重点分析数据的流转过程和数据格式

3、通过debug断点调试跟踪程序执行过程

 

2.1员工信息的分页查询

系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据

 

代码开发

1、页面发送ajax请求,将分页查询参数(page,pageSize,name)提交到服务器

2、服务器Controller接收页面提交的数据并调用Service查询数据

3、Service调用Mapper操作数据库,查询分页数据

4、Controller将查询到的分页数据响应给页面

5、页面接收到分页数据并通过ElementUI的Table组件展示到页面上

 

 

 

2.2启用/禁用员工账号

需求分析

在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作,账号禁用的员工不能登录系统,启用后的员工可以正常登录

需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示

 

管理员admin登录系统可以对所有员工账号进行启用,禁用操作

如果某个员工账号状态为正常,则按钮显示“禁用”,如果员工账号状态为已禁用,则按钮显示为“启用”

 

普通员工登录系统后,启用、禁用按钮不显示

 

代码开发

启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作,在Controller中创建update方法,此方法时一个通用的修改员工信息的方法

 

☆JS处理Long类型精度丢失

代码修复

通过观察控制台输出的SQL发现页面传递过来的员工id的值和数据库中的id值不一致,这是怎么回事?

 

如何解决这个问题?

我们可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串

 

方法一

使用@JsonSerialize注解

该注解用于属性或者getter⽅法上,⽤于在序列化时嵌⼊开发者⾃定义的代码

@JsonSerialize(using = ToStringSerializer.class)

使用以下包

 

 

方法二

1、提供对象转换器JacksonObjectMapper,基于jackson进行java对象到json数据的转换

2、在WebMvcConfig配置类中扩展SpringMVC的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到Json数据的转换

 

直接使用JacksonObjectMapper类

 

我们需要去扩展mvc框架的消息转换器

WebMvcConfig配置类中重写extendMessageConverters方法

 

消息转换器

添加自定义消息转换器不覆盖默认转换器

使用springboot中默认的Json消息转换器MappingJackson2HttpMessageConverter类创建转换器

这个类的主要实现逻辑是在AbstractJackson2HttpMessageConverter抽象类中实现的

这个列实现序列化与反序列化的最核心组件是ObjectMapper这个类

这些组件之间是如何协助,怎样才能合理的改写组件而实现自定义呢,这就需要了解起原理了

 

消息转换器创建和生效原理

CSDN博文引用

 

 

通用修改员工数据方法

 

2.3编辑员工信息

在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按钮完成编辑操作

 

代码开发

1、点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]

2、在add.html页面获取url中的参数[员工id]

3、发送ajax请求,请求服务端,同时提交员工id参数

4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面

5、页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显

6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端

7、服务端接收员工信息,并进行处理,完成后给页面响应

8、页面接收到服务端响应信息后进行相应处理

 

 

 

@PathVariable注解

通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中

 

 

3.分类管理业务开发

☆公共字段自动填充

前面我们已经完成了后台系统的员工管理功能开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改人等字段,这些字段属于公共字段,也就是很多表中都有这些字段

 

能不能对于这些公共字段在某个地方统一处理,来简化开发呢?

使用MybatisPlus提供的公共字段自动填充功能

也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码

 

实现步骤:

1、在实体类的属性上加入@TableField注解,指定自动填充的策略

2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,需要实现MetaObjectHandler接口

 

使用@TableField注解

@TableField(fill = FieldFill.INSERT)

指定填充策略

 

元数据对象处理器(填充策略的具体实现)

要实现一个MetaObjectHandler接口

注意:当前我们设置createUser和updateUser为固定值,后面我们需要进行改造,改为动态获得当前登录用户的id

 

功能完善

前面我们已经完成了公共字段自动填充功能的代码开发,但是还有一个问题没有解决

 

有的同学可能想到,用户登录成功后我们将用户id存入了HttpSession中,现在我从HttpSession中获取不就行了?

 

ThreadLocal类

在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:

1、LoginCheckFilter的doFilter方法

2、EmployeeController的update方法

3、MyMetaObjectHandler的updateFill方法

可以在上面的三个方法中分别加入下面代码(获取当前线程id):

log.info("线程id:{}",Thread.currentThread().getId());

 

什么是ThreadLocal?

ThreadLocal并不是一个线程,而是线程的局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,而不会影响其它线程所对应的副本,ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问

 

ThreadLocal常用方法:

public void set(T value) 设置当前线程的线程局部变量的值

public T get() 返回当前线程所对应的线程局部变量的值

 

我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。

 

实现步骤

1、编写BaseContext工具类,基于ThreadLocal封装的工具类

2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id

3、在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id

 

编写工具类

 

在doFilter方法中获取id并且存储id值

 

在MyMetaObjectHandler类中获取需要的id值

 

3.1新增分类

需求分析

后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。

 

 

分页列表

 

3.2删除分类

删除分类

 

功能完善

前面我们已经实现了根据id删除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善

 

要完善分类删除功能,需要准备基础的类和接口:

实体类Dish和Setmeal

Mapper接口

Service实接口

Service实现类

 

根据条件删除分类,同时判断是否抛出异常

 

自定义的异常

 

在全局异常处理类中进行处理

 

3.3修改分类

 

4.菜品管理业务开发

 

4.1文件上传上传下载

 

文件上传介绍

文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其它用户浏览或下载的过程,文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能

 

文件上传时,对页面的form表单有如下要求(固定要求):

 

目前一些前端组件库也提供了相应的上传组件,但是底层原理还是基于form表单的文件上传

例如ElementUI中提供的upload上传组件

 

服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件

 

Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如:

 

文件下载介绍

文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程

 

通过浏览器进行文件下载,通常有两种表现形式:

 

通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程

 

文件上传代码实现

MultipartFile类:用于接收文件类型数据

创建一个CommonController来处理平常一些的请求

测试是否能接收到文件

 

在配置文件中指定文件存放路径

在表现层通过Value注解获取

 

文件上传

 

文件下载代码实现

getOutputStream()

该方法所获得的的字节流对象为ServletOutputStream类型, 由于ServletOutputStream是OutputStream的子类,它可以直接输出字节组中的二进制数据

 

 

4.2新增菜品

 

新增菜品,其实就是将新增页面录入的菜品信息插入到dish表

如果添加了口味做法,还需要向dish_flavor表插入数据

所以在新增菜品时,涉及到两个表:

 

交互过程

1、页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中

2、页面发送请求进行图片上传,请求服务端将图片保存到服务器

3、页面发送请求进行图片下载,将上传的图片进行回显

4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端

4.2.1回显分类数据

 

导入DishDto,用于封装页面提交的数据

 

DTO,全称为Data Transfer Object,即数据传输对象,一般用于展示与服务层之间的数据传输

 

 

Stream流的特性

相比forEach效率高,数据多会启用多线程

 

 

Intermediate(中间操作):map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered

Terminal(终止操作):forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator

Short-circuiting(短路):anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

 

开启事务

需要在启动类上添加注解

在新增菜品的方法上开启事务

 

4.2.2新增菜品

 

DishServiceImpl

 

4.3菜品信息分页查询

再查询菜品信息的同时要查询到菜品分类名

我们使用DishDto来构造分页,通过拷贝工具将查询好的菜品信息复制给DishDto模型

spring提供的BeansUtil有拷贝功能(底层是反射)

 

4.4修改菜品

 

回显菜品信息以及对应口味信息

代码实现

 

 

提交修改

 

 

5.套餐部分(独立完成)

 

 

6.手机验证码

6.1短信发送

短信服务介绍

目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只要注册成为会员并且按照提供的开发文档进行调用就可以发送短信,需要说明的是,这些短信服务一般都是收费服务

常用短信服务:

 

阿里云短信服务-介绍

阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。

 

应用场景:

 

使用阿里云短信服务发送短信,可以参照官方提供的文档即可

具体开发步骤:

1、导入maven坐标

2、调用API

 

在开发代码之前,需要梳理一下登录时前端页面和服务端的交互过程:

1、在登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信

2、在登录页面输入验证码,点击【登录】按钮,发送ajax请求,在服务端处理登录请求开发手机验证码登录功能,其实就是在服务端编

写代码去处理前端页面发送的这2次请求即可。

 

添加不需要处理的路径

 

判断移动端登录状态

 

 

手机验证码发送

 

移动端登录验证

 

 

6.2导入用户地址簿相关功能代码

 

6.3订单功能

 

6.4购物车功能

 

6.5地址簿功能