瑞吉外卖项目复盘

一、黑马编程-瑞吉外卖

1.准备工作

1)通过SQL脚本导入编写好的SQL语句,创建数据库

2)建立Maven工程,导入必要的POM依赖

3)编写启动类,注意添加@SpringBootApplication

 

其中注解@Slf4j是lombok下的注解,用于添加log信息

 

 4)将前端相关文件导入(主要考虑后端代码编程)

 

 其中将文件复制粘贴至resources类路径下,由于springboot的格式限制,需要创建静态资源映射类

使用addResourceHandler(输入url路径)以及addResourceLocations(定义类文件地址)

2.后端编码(员工管理)

1.登录界面

1)需求分析

2)依据层次结构编写Controller、Service、Mapper以及实体类

Controller层,考虑传入JSON格式以及日志功能,结合需求分析,在类上标注页面部分url,自动加载Service层

 

 Service层,接口继承Mybatis-plus的实现类接口

 

 Impl继承Mybatis-plus实现类并实现service接口

 

 Mapper继承MP的Mapper接口简化SQL编程

 

 实体类

 

 3)前后端整合的通用类导入

注意封装的三个方法,采用static修饰,可以直接调用,e.g R.success(T object) 从而直接返回R通用类结果

 

 code/data/msg响应给前端登录界面

 

 4)登录处理逻辑

 

 5)登出

 6)完善登录功能

原因当个人输入index.html界面时依然可以直接访问,不需要输入账号密码

使用过滤器或拦截器改善

设置过滤器,新建过滤器类,添加WebFilter注解,过滤类实现Filter接口(注意是Servlet下的)

完成设置后注意在springboot启动下添加@ServletComponentScan注解

 

 测试过滤功能对过滤器代码进行完善(梳理过滤逻辑),自定义检查方法并使用路径匹配器

 7)成果

 

 2.新增员工

新增员工的思路是页面输入的信息post到后端,后端将获取到的信息记录到数据库

1)基本功能实现,注意可依据实体类属性分析编写传入数据库属性

 

 实现过程中发现当新增员工输入的账号存在时,因为数据库表中对username字段为唯一约束,此时将从数据库抛出异常

此时需要我们在程序中进行异常捕获,考虑采用全局异常捕获

2)异常处理

搭建基础框架,对特定注解下的类进行异常处理,当发生数据库的异常时抛出自定义异常

 逻辑实现

3)成果

 

3.员工信息分页查询

每一步功能实现首先分析前端传回的数据类型以及需要返回给前端的类型

1)配置MP的分页插件

2)基础框架

 3)进行详细代码设计

 4)成果

4.启用、禁用账号

启用、禁用账号的思路是从前端获取员工的Status,id(前端代码实现修改status),之后将时间、更新人员进行update即可实现

1)基础框架

 

 2)详细设计

 

 3)测试问题

意图禁用张三,但是发现功能未实现

 

 原因在于页面返回的ID与数据库对应的ID不匹配,因为Long型在页面js处理时只能精确到前16位

 

 

 

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

4)设置对象映射器及消息转换器

 对象映射器

 

 public class JacksonObjectMapper extends ObjectMapper {

public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)//long型转换为字符串
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}

 消息转换器

 

 protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器, 底层使用JacksonJava转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);//将自己的转换器放在第一位使用
}

 5.编辑员工信息

编辑员工信息逻辑是依据特定员工id将其属性回传至前端显示

若修改后再保存回传给数据库,修改保存这一步与新增员工的后端代码一致,均是又前端Post数据然后后端Update到数据库即可

6.公共字段自动填充

对于创建时间、创建人、更新时间、更新人等字段,涉及到人员修改时,都需要进行设置修改,属于公共字段

因此使用MP的公共字段自动填充功能

1)实体类属性增加注解 @TableField

 

 

 2)编写处理器来处理注解

 

 

 为了在元数据对象处理器中拿到创建者id,引入ThreadLocal类

由于LoginCheckFilter、EmployeeController下的update类型方法(以update为例)以及insertFill方法使用的是同一个线程,可以通过Thread.currentThread.getId进行确认

因此我们可以在LoginCheckFilter的doFilter方法中获取当前用户id,并调用Threadlocal的set方法来设置当前线程的线程局部变量的值,然后在updafill方法中调用ThreadLocal的get方法来获取当前线程对于的线程局部变量的值(用户id)

3)编写工具类

 

 

 在过滤器中获取用户id并放入工具类实例中

 

 

 通过工具类获取id

3.后端编码(分类管理)

 1.菜品、套餐新增分类

1)需求分析

2)编写实体类、Controller、service、Mapper

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 3)Controller新增分类代码编写

 

 

 同时测试了添加相同菜名时反馈的异常,设置的全局异常依然有效

 

 

 

 

 2.分类信息分页查询

思路与员工管理的分页类似,通过sort属性排序进行展示

 3.删除分类

思路就是将想要删除的菜品id传到后端

1)基础功能实现

 

 2)功能完善

需要检查删除的分类是否关联菜品或者套餐,检查完成如果没有关联才能删除

编写相关的实体类/mapper/service接口及实现类

3)基础框架完善

 

 

 

 4)详细代码补充

 

 定义了一个业务类异常

 

 为了将异常信息传到前端,在全局异常处理中定义

 

 5)成果

 

 4.修改分类

思路就是根据前端传回来的数据update数据库

 

 

4.后端编码(菜品管理)

1.文件上传下载

上传思路是依据前端传回的数据,使用springweb的MultipartFile类传输,注意传输过程产生的是临时文件,需要转存至指定位置

1)基础框架编写

 

 2)动态定义转存地址

在配置文件中定义转存地址

 

 

 再利用spring的value注解将配置信息注入到spring中,在方法中调用即可

3)动态定义转存文件名

 

 为了防止上传图片重复,将之前上传的图片覆盖,考虑随机生成文件名

 

 注意如果文件目录不存在会报错,因此需要判断生成指定目录

 

 4)将存入文件名返回给页面

文件下载

思路是通过输入流读取文件上传后前端返回的对象(实际已存入在指定路径下),之后再通过输出流将文件写回前端浏览器

 

 2.新增菜品

1)准备工作

创建需要的实体类,mapper,service,controller等

 

 

 

 

 

 

 

 

 

 2)新增菜品中菜品分类显示的实现

 思路就是将后端菜品信息回传到前端中

 

 3)新增菜品基础框架

 

 

 

 4)详细设计

这里涉及到了两张表的存入操作

引入了数据传输对象(实际就是该对象继承了Dish类同时扩展了DishFlavor的属性)

在插入过程中,由于前端传回的数据中对于flavor类只有namevalue,并没有对应的菜品id,如果没有菜品id如何在flavor表中插入对应数据?

因此对传回的flavor数据进行处理,通过stream流方法将dish类的id封装如flavors集合中

逻辑方法完成后,由于涉及到多张表的操作,在方法上加上引入事务控制的注解,同时在主方法添加@EnableTransactionManagement注解

 

 

 

 3.菜品信息分页查询

 思路是前端将分页查询参数page/pageSize/name提交到后端获取数据,前端页面还会请求后端进行图片下载,用于页面图片展示

1)分页查询基础功能

 

 结果发现菜品名称没有出现,原因Dish中没有categoryName这个属性

 

2)实现菜品分类名称展示

考虑到DishDto类数据转换类继承Dish类,同时拥有categoryName属性,因此构造DishDto类泛型的Page类

在对dishDtoPage实例进行拷贝的过程中,Page对象中records属性是查询数据列表,也就是分页查询展示的列表,因此不需要拷贝records字段(因为Dish中没有categoryName属性,需要对records字段进行处理

 通过stream流对pageInfo实例处理

map对records中每个元素进行操作,调用getCategoryId()方法获取分类id,再根据id查询分类对象获取各分类,将分类名称再set到dishDto实例即可

同样,在Lambda表达式中,因为dishDto实例是new出来的,没有赋值,再次通过对象拷贝保证其拥有与records中对象相同的值

 注意由于通过stream流获取到了分类id,则需要catergoryServiceImpl类获取分类,故Controller类下应自动装配该接口

3)测试成果

 4.修改菜品

基本思路是当点击修改后,首先需要在菜品分类中显示菜品分类,修改菜品页面与添加菜品是一样的

其次需要根据菜品id将后台菜品信息回传

当修改菜品信息时需要将信息传至后端存储

1)菜品分类在之前的步骤完成

2)根据id修改查询菜品信息和口味设计

由于需要查询两张表,因此在dishService接口封装方法

 

 

 

 在ServiceImpl实现对应方法

同样由于前端传回的flavor没有dishId属性值,因此通过传回的id找到对应的菜品id,在将其set进入DishDto数据传输类中

 

 3)修改保存菜品设计

与新增菜品类似,更新了两个表,注意使用@Transactional注解

 

 

 

 这里对口味的更新思路是将之前回显的数据删除清理,之后在添加当前提交的口味数据

最后在controller层中调用service层方法即可

 5.后端编码(套餐管理)

1.新增套餐

1)准备工作

创建套餐菜品关系实体类,套餐参数转换实体类,相关Mapper/Service/ServiceImpl/Controller

 

 

 

 

 

 

 

 

 

 

 

 2)交互过程

前端页面发送请求,请求服务器获取套餐分类数据并展示在下拉框中(实现代码前面已完成)

 

 前端页面发送请求,请求后端获取菜品分类数据并展示在添加菜品窗口中(与上述代码一致,只是前端传输的code不同)

 

  前端页面发送请求,请求后端根据菜品分类查询对应的菜品数据并展示在添加菜品窗口

套餐图片上传下载(之前代码已完成)

点击保存按钮,发送前端请求,将套餐相关数据回传至后端存储

3)在添加菜品窗口查询菜品数据实现

 

 4)前端数据回传保存实现

由于需要操作两张表,套餐表和套餐菜品关系表, 因此在套餐service层封装

 

利用数据传输对象作为传参,因为前端传回的setmealDish对象中不包含套餐id,因此需要将套餐idset到setmealDish对象中再存储到数据库中

 

 

 

 2.套餐信息分页查询

由于前端传回数据时,未传回分类名称,因此不能单独回传Setmeal套餐对象

 

 

 3.删除套餐

1)交互过程

前端将数据回传给后端,后端删除对应套餐数据以及套餐与菜品关联关系,其中若套餐在售,则无法删除

2)代码设计

由于涉及到两张表的操作,因此在service层封装

 

 

 

 

 

6.移动端编码(短信验证码)

 1.手机验证码登录

1)交付过程

在登录页面输入手机号,待获取验证码,在后端调用短信服务API给指定手机号发送验证码

获得验证码之后在登录页面输入验证码,登录时向后端发送请求处理

2)准备工作

创建User实体类,mapper,service,controller等

 

 

 

 

 

 

 

 

对短信发送及随机生成验证码编码

 

 

 3)过滤器修改

修改过滤器,将移动端页面屏蔽(不进行过滤)

 

 同样,在过滤器中需要对移动端添加过滤

 

 4)发送验证码

 使用腾讯云作为短信服务API,新用户免费赠送100条短信,基础设置见https://blog.csdn.net/qq_56233219/article/details/118015291

首先依照模板编写短信发送工具类

 SmsSingleSender request = new SmsSingleSender(140067XXXX,"d31f1db758fb77d3decd5dXXXXXXX");//这里的appid以及appkey为个人申请的账号
SmsSingleSenderResult smsSingleSenderResult = request.sendWithParam("86",phoneNumbers,templateId,param,smsSign,"","");

 编写随机码生成器

 

 

之后在Controller中调用短信发送方法

 

 templateId为个人申请短信的模板ID

5)移动端验证登录

完成移动端验证码发送之后,查看手机,输入验证码完成登录

输入电话号以及验证码之后,前端数据传回,可以使用Map接受键值对,或者采用数据传输对象收集,

 

 完成后端检查登录后需要将user信息存入session中避免校验检查

 

 

7.移动端编码(菜品展示等)

1.用户地址簿

1)准备工作

AddressBook实体类、Mapper/Service、Controller等准备

 

 

 

 

 

 

 2)地址簿相关方法编码

 

 

 

 

 

 

 

 2.菜品展示

1)交互过程

前端发送请求,获取分类数据(菜品分类和套餐分类)并获取第一个分类下的菜品或套餐

2)代码设计

 分类数据代码已经完成

 

 根据分类获取菜品数据,之前已经完成了相关代码,但是由于业务要求在对应菜品中需要显示规格(即Flavor)

 

 

 因此对源代码进行完善,实际上是利用数据传输对象DTO,set对应菜品id的flavor列表即可

 

 

 

 3.购物车

 1)交互过程

点击添加购物车或者加号按钮,页面发送请求,后端将菜品或者套餐添加到购物车

点击购物车图标,页面发送请求,请求查询购物车中的菜品和套餐

点击情况购物车,页面发送请求,则后端执行清空购物车操作

2)准备工作

ShoppingCart实体类及Mapper/service/controller等的创建

 

 

 

 

 

 

 

 

 

 3)购物车菜品、套餐添加

 

 

 4)查看购物车

 

 5)清空购物车

 

 4.用户下单

1)准备工作

orders/orderDetails实体、mapper/service/controller的创建

2)下单请求实现

思路是页面返回请求,从后端申请购物车信息

之后将购物车信息等传入后端订单等表,最后删除购物车信息

利用service层封装

涉及到多张表的操作,注意在方法上注解@Transactional

需要操作什么表,就要自动装配对应的Service接口

 

 

 

注意AtomicInteger原子类的使用

 

 

 设置订单明细数据

 

 设置订单表数据

 8.缓存优化

1.环境搭建

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

 

 配置Redis配置类

 

 2.缓存短信验证码

实现思路:

注入RedisTemplate对象,从而操作redis

将随机生成的验证码缓存到Redis中,并设置有效期5分钟

登录方法中获取的缓存的验证码,如果登录成功则删除验证码

 

 

 

 

 

 

 3.缓存菜品数据

Redis的使用

实现思路:

改造移动端查看方法,先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询的菜品数据放入Redis

改造新增和更新菜品方法,加入清理缓存的逻辑

总体而已就是使用缓存时要注意保证数据库中的数据和缓存中的数据一致

 1)查看时代码修改

 

 

 

 2)新增及修改时的代码修改

 4.缓存套餐数据

Spring Cache的使用

1)环境配置

<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>

 

 

 

 2)代码修改

查找时记录缓存

 

 第一次测试报错,原因在于返回对象R没有实现序列化接口,R实现序列化接口即可

 新增或者删除时需要删除缓存

 

 

 

 5.数据库主从复制读写分离

1)环境搭建

 <dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>

配置文件下

spring:
  application:
    #应用名称,可选
    name: reggie_take_out
  shardingsphere:
    datasource:
      names:
        master,slave #,slave2 多个从库以逗号隔开
      # 主数据源
      master:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/reggiemasterslave?characterEncoding=utf-8
        username: root
        password: 'xxx'
      # 从数据源
      slave:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.xxx:3306/reggiemasterslave?characterEncoding=utf-8
        username: root
        password: 'xxx'
    masterslave:
      # 读写分离配置
      load-balance-algorithm-type: round_robin #轮询
      # 最终的数据源名称
      name: dataSource
      # 主库数据源名称
      master-data-source-name: master
      # 从库数据源名称列表,多个逗号分隔
      slave-data-source-names: slave #,slave2多个从库以逗号隔开
    props:
      sql:
        show: true #开启SQL显示,默认false
    main:
      allow-bean-definition-overriding: true

2)测试

登录后查看sql执行

 

 查询语句走的是从库,说明功能没问题

 

 更新语句则走的是主库,因此功能实现

 6.接口文档及项目部署

1)使用Swagger实现接口文档的应用

导入knife4j的maven坐标

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>

在配置类中进行注册

 

 

 

 增添静态资源映射路径

 

 增添不处理路径

 

 2)启动项目查看接口

 

 3)调试

 

 响应内容,同时在IDEA中也可以看到反馈信息

 

 

 

 4)下载离线文档

 

 5)常用注解

@Api 用在请求的类上,例如Controller,表示对类的说明

@ApiModel 用在类上,通常是实体类,表示一个返回响应数据的信息

@ApiModelProperty 用在属性上,描述响应类的属性

@ApiOperation 用在请求的方法上,说明方法的用途、作用

@ApiImplicitParams 用在请求的方法上,表示一组参数说明

@ApiImplicitParam 用在 @ApiImplicitParams注解中,指定一个请求参数的各个方面

实体类设置

 

说明中添加了注释信息

 Controller设置

 

 

 

 实现效果

6)项目部署

 9.自己修改的部分

后台按条件查询客户订单,用户个人查询自己的订单,菜品,套餐的启售,停售,购物车中菜品或者是套餐数量减少,后台套餐的修改,客户端再来一单,移动端点击套餐图片查看套餐具体菜品等等功能

1)菜品启停售功能

 代码编写,封装条件构造器后利用service接口下方法更新数据库即可

由于需要实现批量启停售功能,因此修改前端传回的类型

 

 

 

 

2)菜品批量操作功能

批量删除,请求地址为http://localhost:8080/dish?ids=1528562704640417794,1528562287525273601



 

posted @ 2022-04-28 12:35  面向机器编程  阅读(67)  评论(0编辑  收藏  举报