瑞吉外卖笔记


概述

功能架构图

图片[1] - 瑞吉外卖笔记 - MaxSSL

数据库建库建表

表说明

图片[2] - 瑞吉外卖笔记 - MaxSSL

开发环境

Maven搭建

直接创建新工程
继承父工程的形式来做这个,这里新建父工程
图片[3] - 瑞吉外卖笔记 - MaxSSL
pom文件

server:port: 9001spring:application:name: ccTakeOutdatasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ruiji" />&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: 333redis:host: localhost # 本地IP 或是 虚拟机IPport: 6379#password: rootdatabase: 0# 默认使用 0号dbcache:redis:time-to-live: 1800000# 设置缓存数据的过期时间,30分钟mybatis-plus:configuration:#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,开启按照驼峰命名法映射map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:id-type: ASSIGN_ID

启动测试

创建测试类并启动
图片[4] - 瑞吉外卖笔记 - MaxSSL

导入前端页面

图片[5] - 瑞吉外卖笔记 - MaxSSL

导入

在默认页面和前台页面的情况下,直接把这俩拖到resource目录下直接访问是访问不到的,因为被mvc框架拦截了
所以我们要编写一个映射类放行这些资源

创建配置映射类

图片[6] - 瑞吉外卖笔记 - MaxSSL
图片[7] - 瑞吉外卖笔记 - MaxSSL

访问成功
图片[8] - 瑞吉外卖笔记 - MaxSSL

后台开发

数据库实体类映射

用mybatis plus来实现逆向工程
这里是老版本的逆向工程

<dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.30</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.3.1</version></dependency><!--mybatis-plus 代码生成器依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.3.2</version></dependency>

具体怎么玩看这里
MP逆向工程教程
图片[9] - 瑞吉外卖笔记 - MaxSSL

账户操作

登陆功能

前端页面
图片[10] - 瑞吉外卖笔记 - MaxSSL
图片[11] - 瑞吉外卖笔记 - MaxSSL
图片[12] - 瑞吉外卖笔记 - MaxSSL

数据库
图片[13] - 瑞吉外卖笔记 - MaxSSL
业务逻辑
图片[14] - 瑞吉外卖笔记 - MaxSSL
这里两个字符串的比较没法用!=来实现,只能equals再取反来判断
直接上代码,这里没有涉及service层的操作

/** * @param request 如果登陆成功把对象放入Session中,方便后续拿取 * @param employee 利用@RequestBody注解来解析前端传来的Json,同时用对象来封装 * @return */@PostMapping("/login")public Result login(HttpServletRequest request, @RequestBody Employee employee) {String password=employee.getPassword();String username = employee.getUsername();log.info("登陆");//MD5加密MD5Util md5Util = new MD5Util();password=MD5Util.getMD5(password);//通过账户查这个员工对象,这里就不走Service层了LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper();lambdaQueryWrapper.eq(Employee::getUsername, username);Employee empResult=employeeService.getOne(lambdaQueryWrapper);//判断用户是否存在if (!empResult.getUsername().equals(username)){return Result.error("账户不存在");//密码是否正确}else if (!empResult.getPassword().equals(password)){return Result.error("账户密码错误");//员工账户状态是否正常,1状态正常,0封禁}else if (empResult.getStatus()!=1){return Result.error("当前账户正在封禁");//状态正常允许登陆}else {log.info("登陆成功,账户存入session");//员工id存入session,request.getSession().setAttribute("employ",empResult.getId());return Result.success("登陆成功");}}

具体代码可以参考如下路径

com.cc.controller.EmployeeController

关于RequestBody何时使用

退出功能

点击退出
图片[15] - 瑞吉外卖笔记 - MaxSSL
删除session对象

/** * @param request 删除request作用域中的session对象,就按登陆的request.getSession().setAttribute("employ",empResult.getId());删除employee就行 * @return */@PostMapping("/logout")public Result login(HttpServletRequest request) {//尝试删除try {request.getSession().removeAttribute("employ");}catch (Exception e){//删除失败return Result.error("登出失败");}return Result.success("登出成功");}

完善登陆(添加过滤器)

这里的话用户直接url+资源名可以随便访问,所以要加个拦截器,没有登陆时,不给访问,自动跳转到登陆页面
图片[16] - 瑞吉外卖笔记 - MaxSSL
过滤器配置类注解@WebFilter(filterName="拦截器类名首字母小写",urlPartten=“要拦截的路径,比如/*”)
图片[17] - 瑞吉外卖笔记 - MaxSSL判断用户的登陆状态这块之前因为存入session里面有一个名为employee的对象,那么只需要看看这个session还在不在就知道他是否在登陆状态
注意,想存或者想获取的话,就都得用HttpServletRequest的对象来进行获取,别的request对象拿不到的

这里提一嘴
调用Spring核心包的字符串匹配类的对象,对路径进行匹配,并且返回比较结果
如果相等就为true

public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

图片[18] - 瑞吉外卖笔记 - MaxSSL
前端拦截器完成跳转到登陆页面,不在后端做处理
图片[19] - 瑞吉外卖笔记 - MaxSSL
代码太多了,给个路径好啦,直接去Gitee看
request的js代码路径:resource/backend/js/request.js
拦截器的路径:com.cc.filter.LoginCheckFilter

新增员工

新增员工功能,(前端对手机号和身份证号长度做了一个校验)
图片[20] - 瑞吉外卖笔记 - MaxSSL
请求 URL: http://localhost:9001/employee (POST请求)
图片[21] - 瑞吉外卖笔记 - MaxSSL
图片[22] - 瑞吉外卖笔记 - MaxSSL
改造一下Employee实体类,通用id雪花自增算法来新增id
图片[23] - 瑞吉外卖笔记 - MaxSSL
这里用service接口继承的MybatisPlus的功能
图片[24] - 瑞吉外卖笔记 - MaxSSL
注入一下就可以使用了,插入方法
图片[25] - 瑞吉外卖笔记 - MaxSSL
基本上都是自动CRUD,访问路径:com.cc.controller.EmployeeController

全局异常处理

先看看这种代码的try catch
这种try catch来捕获异常固然好,但是,代码量一大起来,超级多的try catch就会很乱
图片[26] - 瑞吉外卖笔记 - MaxSSL
所以我们要加入全局异常处理,在Common包下,和Result同级,这里只是示例,并不完整
图片[27] - 瑞吉外卖笔记 - MaxSSL

图片[28] - 瑞吉外卖笔记 - MaxSSL
当报错信息出现Duplicate entry时,就意味着新增员工异常了
所以,我们对异常类的方法进行一些小改动,让这个异常反馈变得更人性化
图片[29] - 瑞吉外卖笔记 - MaxSSL
这个时候再来客户端试试,就会提供人性化的报错,非常的快乐~
图片[30] - 瑞吉外卖笔记 - MaxSSL
这回再回到Controller,这时就不需要再来try catch这种形式了,不用管他,因为一旦出现错误就会被我们的AOP捕获。所以,不需要再用try catch来抓了
图片[31] - 瑞吉外卖笔记 - MaxSSL
异常类位置:com.cc.common.GloableExceptionHandler

员工信息分页查询

接口分析

老生常谈分页查询了
需求
图片[32] - 瑞吉外卖笔记 - MaxSSL
分页请求接口
图片[33] - 瑞吉外卖笔记 - MaxSSL
图片[34] - 瑞吉外卖笔记 - MaxSSL
查询员工及显示接口
图片[35] - 瑞吉外卖笔记 - MaxSSL
图片[36] - 瑞吉外卖笔记 - MaxSSL
逻辑流程
图片[37] - 瑞吉外卖笔记 - MaxSSL

分页插件配置类

先弄个MP分页插件配置类
原因是和3.2.3版本的代码生成器冲突
分页插件爆红解决方案
图片[38] - 瑞吉外卖笔记 - MaxSSL
直接注释掉
图片[39] - 瑞吉外卖笔记 - MaxSSL
加入配置类
图片[40] - 瑞吉外卖笔记 - MaxSSL

接口设计

前端注意事项
图片[41] - 瑞吉外卖笔记 - MaxSSL
图片[42] - 瑞吉外卖笔记 - MaxSSL
page对象内部
图片[43] - 瑞吉外卖笔记 - MaxSSL
里面包含了查询构造器的使用
具体的细节在这个包下:com.cc.controller.EmployeeController.page

/** * 分页展示员工列表接口、查询某个员工 * @param page 查询第几页 * @param pageSize 每页一共几条数据 * @param name 查询名字=name的数据 * @return 返回Page页 */@GetMapping("/page")public Result<Page> page(int page, int pageSize,String name){//分页构造器,Page(第几页, 查几条)Page pageInfo = new Page(page, pageSize);//查询构造器LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper();//过滤条件.like(什么条件下启用模糊查询,模糊查询字段,被模糊插叙的名称)lambdaQueryWrapper.like(!StringUtils.isEmpty(name), Employee::getName, name);//添加排序lambdaQueryWrapper.orderByDesc(Employee::getCreateTime);//查询分页、自动更新employeeService.page(pageInfo, lambdaQueryWrapper);//返回查询结果 return Result.success(pageInfo);}

启用、禁用员工账号

无非就是修改status,0禁用,1启用
图片[44] - 瑞吉外卖笔记 - MaxSSL
图片[45] - 瑞吉外卖笔记 - MaxSSL
这种根据登陆人物来进行判断的玩法,是前端
这个页面的位置resource/backend/page/member/list.html
图片[46] - 瑞吉外卖笔记 - MaxSSL
看拿出来的对象是什么样子的,如果是admin,vue的v-if指令就会把编辑按钮显示出来
如果是普通用户,就会把编辑按钮隐藏
图片[47] - 瑞吉外卖笔记 - MaxSSL

修复一个小Bug

前端一直不显示编辑按钮,在localStorage里没有发现admin对象
图片[48] - 瑞吉外卖笔记 - MaxSSL
这个值不应该是登陆成功,应该是Employee的对象Json
猜测是登陆的时候往request里存对象没存好
图片[49] - 瑞吉外卖笔记 - MaxSSL
改成对象存入就好了
图片[50] - 瑞吉外卖笔记 - MaxSSL
这回都正常了
图片[51] - 瑞吉外卖笔记 - MaxSSL

功能编写

复习一下
PutMapping是Resultful风格的请求方式
图片[52] - 瑞吉外卖笔记 - MaxSSL
当前状态是1,直接带着目标状态值(状态改禁用)进行更新
图片[53] - 瑞吉外卖笔记 - MaxSSL
Id精度丢失,js独有的bug,直接处理Long处理不了,要Long转String再返回去
图片[54] - 瑞吉外卖笔记 - MaxSSL
图片[55] - 瑞吉外卖笔记 - MaxSSL
利用对象转换器JacksonObjectMapper,将对象转Json
将Long型的Id转换为String类型的数据
图片[56] - 瑞吉外卖笔记 - MaxSSL

在MVC配置类中扩展一个消息转换器
图片[57] - 瑞吉外卖笔记 - MaxSSL
测试功能正常,正常更新员工状态
消息扩展器配置位置:com.cc.common.JacksonObjectMapper
对象映射器位置:com.cc.config.WebMvcConfig
员工状态更新位置:com.cc.controller.EmployeeController

编辑员工信息

图片[58] - 瑞吉外卖笔记 - MaxSSL
请求API,这个是先发请求,查到用户,然后填充到页面上
可以看出来,这种请求方式是ResultFul风格的请求方式
在控制器中要用@PathVariable(“/{参数名称}”)注解来进行接收
图片[59] - 瑞吉外卖笔记 - MaxSSL
图片[60] - 瑞吉外卖笔记 - MaxSSL
完美更新
更新方法位置:com.cc.controller.EmployeeController.getEmployee

公共字段自动填充

像是一部分公共字段,反复填充起来没有意义,简化填充的操作。
把这个功能拿出来,单独拎出来做自动填充处理
图片[61] - 瑞吉外卖笔记 - MaxSSL
图片[62] - 瑞吉外卖笔记 - MaxSSL
为实体类属性上面加入注解@TableField(fill = 填充条件)
看一下源码。fill是填充条件,用枚举来进行处理的
图片[63] - 瑞吉外卖笔记 - MaxSSL
加完注解和条件不算完,还要加入配置类进行处理,对填充的数据做规定
在common包下创建一个自定义类,最关键的是要实现MetaObjectHandler接口下的insertFill和updateFill
确认填充时需要的字段。还有要加入@Component注解,将这个类交给框架来管理,否则的话容易找不到,setValue的值会根据注解加入的字段名称来锁定是否需要更新
位置:com.cc.common.MyMetaObjectHandler
图片[64] - 瑞吉外卖笔记 - MaxSSL
但是这里有个问题,如果我想去更新管理员字段是非常困难的,因为我这里拿不到Request的作用域对象,所以要想个办法来处理。
这个时候就需要ThreadLocal来进行对象的获取,这个线程是贯穿整个运行的,可以通过他来获取

使用时

何为ThreadLocal
重点来了
这个图
图片[65] - 瑞吉外卖笔记 - MaxSSL
图片[66] - 瑞吉外卖笔记 - MaxSSL
我的思路就是在用户登陆的时候,把这个id存进去,等到在填充字段的时候,从ThreadLocal里把这个资源再拿出来。
直接操作不太好,把他封装成一个工具类,这个工具类里方法都是静态的,可以通过类直接调用、并且都是静态方法,来操作保存和读取
我选择在Utils下创建

第一次的Bug

具体包在utils里,有Bug,封装的类ThreadLocal获取不到数据,不太清楚为什么,暂时就把这个写死了

// 基于ThreadLocal 封装工具类,用户保存和获取当前登录的用户id// ThreadLocal以线程为 作用域,保存每个线程中的数据副本public class BaseContext {private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();//设置当前用户idpublic static void setCurrentId(Long id){threadLocal.set(id);}public static Long getCurrentId(){return threadLocal.get();}}

注意,ThreadLocal不是一个线程,只有同一个线程才能拿到,不是一个线程拿不到的

解决方案

更改setId的位置,存储的时候放在过滤器内部,就算是一个线程了,就能拿到。不过我都试过了,确实是一个线程,但是还是拿不到。
换个思路:因为我想拿Request对象里的Id嘛,所以,只要有Request的id就行,不必过于执着一定要用ThreadLocal来存,因此,我这里选择注入一下HttpServletRequest对象来解决这个问题。
图片[67] - 瑞吉外卖笔记 - MaxSSL

菜品页面

菜品分类

图片[68] - 瑞吉外卖笔记 - MaxSSL

涉及的表有分类表category
图片[69] - 瑞吉外卖笔记 - MaxSSL
业务流程
图片[70] - 瑞吉外卖笔记 - MaxSSL

新增菜品分类

请求方式是Post请求
图片[71] - 瑞吉外卖笔记 - MaxSSL图片[72] - 瑞吉外卖笔记 - MaxSSL
控制器位置:com.cc.controller.CategoryController (save)

菜品分类展现

图片[73] - 瑞吉外卖笔记 - MaxSSL
还是那几步

  1. 创建分页构造器 Page pageInfo = new Page(第几页,每页几条数据);
  2. 如果有需要条件过滤的加入条件过滤器LambaQueryWarpper
  3. 注入的service对象(已经继承MP的BaseMapper接口)去调用Page对象
    service对象.page(分页信息,条件过滤器)
  4. 返回结果就可以了

分页查询位置:com.cc.controller.CategoryController.page

删除菜品分类

图片[74] - 瑞吉外卖笔记 - MaxSSL
图片[75] - 瑞吉外卖笔记 - MaxSSL
普通版本,没有考虑分类有关联的情况
图片[76] - 瑞吉外卖笔记 - MaxSSL
完善一下,如果当前菜品分类下有菜品的话,就不许删除
所以在删除之前要先做判断才可以删除,不符合条件的,我们要抛出异常进行提示
因为没有返回异常信息的类,我们这里要做一个自定义的专门返回异常信息的类CustomerException
这个类的位置也在common包下
图片[77] - 瑞吉外卖笔记 - MaxSSL
因为我们之前创建了一个全局异常处理,也要用上,因为要拦截异常统一处理
还是com.cc.common.GloableExceptionHandler
对抛出异常进行处理,就可以对新增的异常提供目标的拦截和异常通知
图片[78] - 瑞吉外卖笔记 - MaxSSL
删除菜品分类的controller接口在:com.cc.controller.CategoryController (delCategory)
因为业务特殊,且比较长,就分离出来把业务放在service包下
service接口位置:com.cc.service.impl.CategoryServiceImpl (removeCategory)

修改套餐信息

图片[79] - 瑞吉外卖笔记 - MaxSSL
非常简单的CRUD,直接调用MP更新一下就行
API位置

com.cc.controller.CategoryController (updateCategory)

文件上传下载(重点)

上传逻辑

第一次接触上传和下载的功能
文件上传逻辑(后端)
图片[80] - 瑞吉外卖笔记 - MaxSSL
参数名有要求的
接收的文件类型一定是 方法名(MultipartFile 前端上传的文件名称)
图片[81] - 瑞吉外卖笔记 - MaxSSL
图片[82] - 瑞吉外卖笔记 - MaxSSL
所以后端的接收名字也得改为file
图片[83] - 瑞吉外卖笔记 - MaxSSL

上传逻辑实现

具体的存储路径写在配置文件里了
图片[84] - 瑞吉外卖笔记 - MaxSSL
用@Value注入到业务里就可以了
图片[85] - 瑞吉外卖笔记 - MaxSSL

具体位置在com.cc.controller.CommonController (upLoadFile)

下载逻辑

图片[86] - 瑞吉外卖笔记 - MaxSSL
图片回显功能
用到了输入输出流
位置:com.cc.controller.CommonController (fileDownload)

菜品管理页面

新增菜品

需求分析

图片[87] - 瑞吉外卖笔记 - MaxSSL
涉及表为dish和dish_flavor
图片[88] - 瑞吉外卖笔记 - MaxSSL
开发逻辑
图片[89] - 瑞吉外卖笔记 - MaxSSL

新增实现

由于是多表的操作,MP直接干肯定不行,所以就把service层抽离出来进行处理

还有,因为涉及两张表,这里还要加入事务进行控制,防止多表操作崩溃

多表操作只能一个一个来,MP没有办法一次性操作多张表因为涉及到多表的问题,所以还要加入注解来处理事务@Transactional 开启事务@EnableTransactionManagement 在启动类加入,支持事务开启

Controller位置:com.cc.controller.DishController (addDish)
Service位置:com.cc.service.DishService
ServiceImpl位置:com.cc.service.impl.DishServiceImpl (addDishWithFlavor)

新增菜品之获取菜品种类

图片[90] - 瑞吉外卖笔记 - MaxSSL
从前端接收一个type=1的标注,目的是在分类表中,菜品分类是1,套餐分类是2,把二者区分开,获取所有的菜品类型
图片[91] - 瑞吉外卖笔记 - MaxSSL
位置:com.cc.controller.CategoryController (listCategory)

菜品分页

顺手把菜品分页也做了,不写太多了,位置在:com.cc.controller.CategoryController (dishPage)
记录一个知识点,如果说后端没有类和前端要的数据对应,那么自己就可以封装一个类来对前端特殊需要的数据进行封装

DTO对象

这个类可以是对一些实体类进行扩展,继承于某个父类,再添加一些内容
比如Dish和DishDto
DishDto就继承于Dish类,并在此基础上进行了扩展
图片[92] - 瑞吉外卖笔记 - MaxSSL
图片[93] - 瑞吉外卖笔记 - MaxSSL

更新菜品信息

就是个update
图片[94] - 瑞吉外卖笔记 - MaxSSL
逻辑
图片[95] - 瑞吉外卖笔记 - MaxSSL
注意,这里回显数据是要用DishDto,因为前端要显示口味等信息,这里如果用Dish是无法完美显示的,所以要用DishDto

回显填充查询

图片[96] - 瑞吉外卖笔记 - MaxSSL

除此之外,这是个多表联查,用MP肯定不行,得自己写
Controller位置:com.cc.controller.DishController (updateDish)
Service位置:com.cc.service.DishService
ServiceImpl位置:com.cc.service.impl.DishServiceImpl

更新实现

实际上就是两个表联动更新和删除操作,所以MP直接操作是不可以的,所以要在Service层自己再封装一个删除方法,给Controller层调用删除就行
对于Dish对象可以直接进行更新,因为DishDto是Dish的子类
因此可以调用DishService的update方法传入DishDto对象,来实现Dish的更新
Controller位置:com.cc.controller.DishController (updateDish) 确实和上面那个一样,因为请求方式不一样
Service位置:com.cc.service.DishService
ServiceImpl位置:com.cc.service.impl.DishServiceImpl (updateDishWithFlavor)

其他功能

完成一些小功能的开发
图片[97] - 瑞吉外卖笔记 - MaxSSL

停售功能

就是把数据库的status值更新一下,两个路径,一个启售,一个停售
图片[98] - 瑞吉外卖笔记 - MaxSSL
停售请求路径
图片[99] - 瑞吉外卖笔记 - MaxSSL
如果状态不一样了,会从停售变成启售,同时对应的请求路径也不一样
图片[100] - 瑞吉外卖笔记 - MaxSSL
图片[101] - 瑞吉外卖笔记 - MaxSSL
Controller位置:com.cc.controller.DishController (updateStatusStop)停止
Controller位置:com.cc.controller.DishController (updateStatusStart)启动

删除功能

图片[102] - 瑞吉外卖笔记 - MaxSSL
菜品删除功能
完成逻辑删除,不是真删
图片[103] - 瑞吉外卖笔记 - MaxSSL
位置:
Controller位置:com.cc.controller.DishController (deleteDish)停止

套餐页面

实际上就是一组菜品的集合

新增套餐概述

涉及到的数据库
图片[104] - 瑞吉外卖笔记 - MaxSSL
图片[105] - 瑞吉外卖笔记 - MaxSSL
图片[106] - 瑞吉外卖笔记 - MaxSSL
导入SetmealDto
图片[107] - 瑞吉外卖笔记 - MaxSSL

图片[108] - 瑞吉外卖笔记 - MaxSSL

新增套餐之菜品列表

图片[109] - 瑞吉外卖笔记 - MaxSSL
图片[110] - 瑞吉外卖笔记 - MaxSSL
Controller位置:com.cc.controller.DishController (listCategory)

新增套餐实现

和新增菜品差不多,这里也是多表的操作
Controller位置:com.cc.controller.SetmealController (saveSetmeal)
Service位置:com.cc.service.SetmealService
ServiceImpl位置:com.cc.service.impl.SetmealServiceImpl(saveWithDish)

套餐分页

这里的套餐分页和以往不同,设计到了多表内容
图片[111] - 瑞吉外卖笔记 - MaxSSL
套餐分页Controller位置:com.cc.controller.SetmealController.pageList
套餐Mapper接口位置:com.cc.mapper.SetmealMapper
Mapper文件位置:resource.mapper.SetmealMapper
图片[112] - 瑞吉外卖笔记 - MaxSSL

更新套餐

添加套餐和更新套餐是几乎完全一致的,字段巴拉巴拉的都一样
图片[113] - 瑞吉外卖笔记 - MaxSSL
但是注意,修改套餐的话,需要先对菜品页面进行填充,这一页都是需要填充满要修改的菜品信息的。图片[114] - 瑞吉外卖笔记 - MaxSSL
先发请求,一看就是Restful风格请求
图片[115] - 瑞吉外卖笔记 - MaxSSL
获取套餐Controller位置:com.cc.controller.SetmealController.getSetmal

更新销售状态

图片[116] - 瑞吉外卖笔记 - MaxSSL
图片[117] - 瑞吉外卖笔记 - MaxSSL
和之前一个业务逻辑很像,不想多赘述了,直接放接口位置
图片[118] - 瑞吉外卖笔记 - MaxSSLController位置:com.cc.controller.SetmealController (startSale/stopSale)

删除套餐

可以单独删,也可以批量删,接口是万金油,都能接,主要看传来的数据是几个
图片[119] - 瑞吉外卖笔记 - MaxSSL
图片[120] - 瑞吉外卖笔记 - MaxSSL
接口
图片[121] - 瑞吉外卖笔记 - MaxSSL
== 多表删除,在Controller直接实现不太现实,所以要在Service把业务写好==
Controller位置:com.cc.controller.SetmealController (deleteSetmeal)
Service位置:com.cc.service.SetmealService
ServiceImpl位置:com.cc.service.impl.SetmealServiceImpl(removeWithDish)
图片[122] - 瑞吉外卖笔记 - MaxSSL

前台开发(手机端)

账户登陆

短信发送

图片[123] - 瑞吉外卖笔记 - MaxSSL
图片[124] - 瑞吉外卖笔记 - MaxSSL
阿里云短信业务教程

代码实现

官方文档地址
导入Maven

<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.16</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-dysmsapi</artifactId><version>1.1.0</version></dependency>

图片[125] - 瑞吉外卖笔记 - MaxSSL
导入短信登陆的工具类,把ACCESSKeyID和Secret更换到位就行
图片[126] - 瑞吉外卖笔记 - MaxSSL

验证码发送

数据模型user表,手机验证码专用的表
图片[127] - 瑞吉外卖笔记 - MaxSSL
开发流程
图片[128] - 瑞吉外卖笔记 - MaxSSL
修改拦截器,放行操作
图片[129] - 瑞吉外卖笔记 - MaxSSL
图片[130] - 瑞吉外卖笔记 - MaxSSL
controller位置:com.cc.controller.UserController (sendMsg)
发送完还需要验证,验证就是另一个login了

用户登陆

图片[131] - 瑞吉外卖笔记 - MaxSSL
controller位置:com.cc.controller.UserController (login)
这里登陆还涉及到过滤器放行的功能,不要忘记了,把用户id存入session,过滤器会进行验证
过滤器
图片[132] - 瑞吉外卖笔记 - MaxSSL
controller
图片[133] - 瑞吉外卖笔记 - MaxSSL

前台页面

导入用户地址簿

图片[134] - 瑞吉外卖笔记 - MaxSSL
地址表
图片[135] - 瑞吉外卖笔记 - MaxSSL
这里直接导入现成的AddressBookController,没有自己写

com.cc.controller.AddressBookController

菜品展示

逻辑梳理
图片[136] - 瑞吉外卖笔记 - MaxSSL
图片[137] - 瑞吉外卖笔记 - MaxSSL
修改DishController的list方法,来符合前台请求的要求
controller位置:com.cc.controller.DishController (listCategory)
套餐内菜品Controller:com.cc.controller.SetmealController (list)

购物车

把菜品加入购物车
图片[138] - 瑞吉外卖笔记 - MaxSSL
图片[139] - 瑞吉外卖笔记 - MaxSSL
逻辑梳理
图片[140] - 瑞吉外卖笔记 - MaxSSL
注意,这里不需要后端去管总价的计算,就是单价*数量的这个操作,不是后端的内容。前端在展示的时候自己就计算了。
位置:com.cc.controller.ShoppingCartController (add)

下单

图片[141] - 瑞吉外卖笔记 - MaxSSL
对应的两个表,一个是orders表,另一个是orders_detail表

orders表
图片[142] - 瑞吉外卖笔记 - MaxSSL
orders_detail表
图片[143] - 瑞吉外卖笔记 - MaxSSL
交互流程
图片[144] - 瑞吉外卖笔记 - MaxSSL
业务比较复杂,在Service里写的com.cc.service.impl.OrdersServiceImpl

至此基础部分完成,开始对项目性能进行优化

小知识点总结

@RequestBody的使用

只有传来的参数是Json才能用RequestBody接收,如果不是Json的情况(比如那种?key=value&key=value)是不可以用的,会400错误
关于RequestBody何时使用

缓存优化

基于Redis进行缓存优化
图片[145] - 瑞吉外卖笔记 - MaxSSL

环境搭建

Redis进行配置

加入Pom文件

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

加入Redis配置类

@Configurationpublic class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();//默认的Key序列化器为:JdkSerializationRedisSerializerredisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setConnectionFactory(connectionFactory);return redisTemplate;}}

yml中加入配置
图片[146] - 瑞吉外卖笔记 - MaxSSL

短信验证码、登陆优化

给验证码加入有效时间的验证,设置好短信验证码的有效时间
图片[147] - 瑞吉外卖笔记 - MaxSSL
如果登陆成功,就自动删除缓存中的验证码
优化位置:com.cc.controller.UserController sendMsg和login
注入RedisTemplete
图片[148] - 瑞吉外卖笔记 - MaxSSL
针对验证码进行优化
图片[149] - 瑞吉外卖笔记 - MaxSSL
针对登录后进行优化
login方法中
图片[150] - 瑞吉外卖笔记 - MaxSSL
图片[151] - 瑞吉外卖笔记 - MaxSSL
这里过滤器也要改,因为登陆的id数据由session变成了redis存放,所以要把过滤器的相关部分进行改造

com.cc.filter.LoginCheckFilter

同样要先注入RedisTemplate
图片[152] - 瑞吉外卖笔记 - MaxSSL

缓存前台菜品数据

图片[153] - 瑞吉外卖笔记 - MaxSSL
缓存思路,要保证缓存数据库和DBMS内的数据保持同步,避免读到脏数据(没更新的数据)
图片[154] - 瑞吉外卖笔记 - MaxSSL
对DishController进行优化,加入了缓存
再次访问可以发现,如果已经缓存过了当前的菜品分类,就不会再查数据库了

更新菜品同时更新缓存

保证少出现脏数据,所以加入清理缓存,不及时清理的话,新数据保存上来,列表数据库无法同步更新。就会出现问题。
这里清理精确数据。大面积清缓存也是比较费性能的
这种就是全清理
图片[155] - 瑞吉外卖笔记 - MaxSSL
这种是精确清理
图片[156] - 瑞吉外卖笔记 - MaxSSL

SpringCache

简介

图片[157] - 瑞吉外卖笔记 - MaxSSL

SpringCache常用注解及功能

图片[158] - 瑞吉外卖笔记 - MaxSSL

快速起步

启动类上要加入@EnableCaching注解,启用缓存框架
图片[159] - 瑞吉外卖笔记 - MaxSSL

@CachePut注解

缓存方法返回值,缓存一条或者多条数据
图片[160] - 瑞吉外卖笔记 - MaxSSL

@CacheEvict注解

删除缓存
图片[161] - 瑞吉外卖笔记 - MaxSSL

@Cacheable注解

先看看Spring是否已经缓存了当前数据,如果已经缓存那么直接返回。
如果没有缓存就直接缓存到内存里
图片[162] - 瑞吉外卖笔记 - MaxSSL
一些特殊情况,condition属性和Unless属性图片[163] - 瑞吉外卖笔记 - MaxSSL

前面都是用SpringCache自带的缓存容器,性能肯定比不了Redis
所以现在开始引入Redis作为SpringCache缓存的产品
切换为Redis作为缓存产品

SpringCache-Redis

图片[164] - 瑞吉外卖笔记 - MaxSSL
导入jar包
图片[165] - 瑞吉外卖笔记 - MaxSSL
注入相对应的缓存产品Manager就可以了,这里以RedisManager为例
图片[166] - 瑞吉外卖笔记 - MaxSSL

利用SpringCache-Redis来缓存套餐数据

图片[167] - 瑞吉外卖笔记 - MaxSSL
启动类上要加入@EnableCaching注解,启用缓存框架
图片[159] - 瑞吉外卖笔记 - MaxSSL
加入注解时的坑
这里相当于是从Return中拿到Setmeal中的属性,但是Return时的数据是Result封装的Setmeal数据,显然无法完成序列化,这里也是需要对Result类进行序列化的改造
图片[169] - 瑞吉外卖笔记 - MaxSSL
继承序列化类,使其可以序列化
图片[170] - 瑞吉外卖笔记 - MaxSSL
=此时就完成了缓存的优化,此时如果缓存中有当前value名字的缓存,就自动返回,如果没有就查询一下。当前缓存自动过期的时间在yml里面有详细配置

保存套餐方法缓存优化
一保存套餐,对应的缓存就得删除,因为数据更新了要重新获取
还有更新套餐,理由同上
删除方法要加
图片[171] - 瑞吉外卖笔记 - MaxSSL
保存方法也要加
图片[172] - 瑞吉外卖笔记 - MaxSSL

数据库优化

MySQL读写分离

将单点数据库改成分布式的数据库服务器
主写从读。
图片[173] - 瑞吉外卖笔记 - MaxSSL

图片[174] - 瑞吉外卖笔记 - MaxSSL

MySQL主从复制搭建

主库设置

主从复制架构图
图片[175] - 瑞吉外卖笔记 - MaxSSL
以上就可以做到主库数据和从库数据保持同步

对主库进行配置
Linux改法
图片[176] - 瑞吉外卖笔记 - MaxSSL

log-bin=mysql-bin #启动二进制server-id=100 #唯一id

图片[177] - 瑞吉外卖笔记 - MaxSSL

windows改法

在mysql安装路径下
图片[178] - 瑞吉外卖笔记 - MaxSSL

图片[179] - 瑞吉外卖笔记 - MaxSSL

修改好了重启MySQL

图片[180] - 瑞吉外卖笔记 - MaxSSL
windows版本的重启教程在这里
重启mysql

=======================================================

图片[181] - 瑞吉外卖笔记 - MaxSSL

GRANT REPLICATION SLAVE ON*.*to'xiaoming'@'%'identified by 'Root@123456';

这里我把本地的MySQL作为主机,把阿里云作为从机
运行一下权限SQL
图片[182] - 瑞吉外卖笔记 - MaxSSL

查看主机状态show master status;
图片[183] - 瑞吉外卖笔记 - MaxSSL

从库设置

从库这里选择了阿里云
还是先修改配置文件,加入端口id
图片[184] - 瑞吉外卖笔记 - MaxSSL
图片[185] - 瑞吉外卖笔记 - MaxSSL
第二步还是从库重启(Linux中)
图片[186] - 瑞吉外卖笔记 - MaxSSL
第三部,设置连接到主机
运行SQL
图片[187] - 瑞吉外卖笔记 - MaxSSL

运行一下
具体的可以去从机用show master status查看

change master to master_host='ip',master_user='xiaoming',master_password='Root@123456',master_log_file='mysql-bin.eo0001',master_log_pos=主机的position

图片[188] - 瑞吉外卖笔记 - MaxSSL

图片[189] - 瑞吉外卖笔记 - MaxSSL
这里我是两台服务器,一台docker安装的mysql(从机)
另一台是普通安装的mysql做主机,配置过程中遇到了很多问题,参考了下面的链接

参考教程

一定一定记着上面从机连接命令运行成功后要启动从机也就是

slave start

最后运行start slave就算是执行成功了
图片[190] - 瑞吉外卖笔记 - MaxSSL
查看一下从机状态

show slave status

这样就算搭建好了
图片[191] - 瑞吉外卖笔记 - MaxSSL

测试

图片[192] - 瑞吉外卖笔记 - MaxSSL
图片[193] - 瑞吉外卖笔记 - MaxSSL
图片[194] - 瑞吉外卖笔记 - MaxSSL
到这里就算搭建完成了

遇到的问题

这里遇到的问题,连不上
图片[195] - 瑞吉外卖笔记 - MaxSSL
想本地当主机,外网当从机好像不太行,我就又弄了台服务器做读写分离

搞到了从机之后,就开始配置,安装MySQL等等

有的时候会提示io冲突,这是因为之前的从机没有关闭,关闭一下就可以了
stop slave 一下 就可以运行了

一个从机启动命令忘记了,改了一晚上
如果不运行从机启动就会变成这样
图片[196] - 瑞吉外卖笔记 - MaxSSL

主写从读实战

概述

图片[197] - 瑞吉外卖笔记 - MaxSSL
难么如何去确定来的SQL应该分配到哪个库上,这个就要靠Sharding-jdbc框架来读写分离的分流处理
图片[198] - 瑞吉外卖笔记 - MaxSSL

实战

步骤如下
图片[199] - 瑞吉外卖笔记 - MaxSSL
导入Maven坐标

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

配置yml文件

spring:application:name: ccTakeOutshardingsphere:datasource:names:master,slave# 主库(增删改操作)master:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://121.89.200.204:3306/ruiji" />333# 从数据源(读操作)slave:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://121.36.51.170:3306/ruiji?characterEncoding=utf-8username: rootpassword: 333masterslave:# 读写分离配置load-balance-algorithm-type: round_robin #轮询(如果有多个从库会轮询着读)# 最终的数据源名称name: dataSource# 主库数据源名称master-data-source-name: master# 从库数据源名称列表,多个逗号分隔slave-data-source-names: slaveprops:sql:show: true #开启SQL显示,默认falsemain:allow-bean-definition-overriding: true #允许bean数据源覆盖

解读一下yml配置
图片[200] - 瑞吉外卖笔记 - MaxSSL
允许Bean定义覆盖很重要

测试

启动项目,可以看到,读写操作分别到达了不同的主机上
读写分离测试
图片[201] - 瑞吉外卖笔记 - MaxSSL
图片[202] - 瑞吉外卖笔记 - MaxSSL

Nginx部署

Nginx笔记

前后端分离开发

图片[203] - 瑞吉外卖笔记 - MaxSSL
图片[204] - 瑞吉外卖笔记 - MaxSSL

开发流程

图片[205] - 瑞吉外卖笔记 - MaxSSL

YApi

图片[206] - 瑞吉外卖笔记 - MaxSSL

Swagger(常用)

主要作用就是帮助后端人员生成后端接口文档的
图片[207] - 瑞吉外卖笔记 - MaxSSL
使用方式
图片[208] - 瑞吉外卖笔记 - MaxSSL
导入坐标

<!--knife4j接口管理--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.2</version></dependency>

导入配置类
图片[209] - 瑞吉外卖笔记 - MaxSSL
具体配置位置com.cc.config.WebMvcConfig

启动服务,访问路径+doc.html
进入之后就可以对已有的接口进行管理了
图片[210] - 瑞吉外卖笔记 - MaxSSL

Swagger常用注解

直接生成的注解内容并不是很完善
图片[211] - 瑞吉外卖笔记 - MaxSSL
Swagger常用注解
图片[212] - 瑞吉外卖笔记 - MaxSSL
以实体类为例
图片[213] - 瑞吉外卖笔记 - MaxSSL
Controller上的注解
图片[214] - 瑞吉外卖笔记 - MaxSSL
以上均为示例,最终完善好注解,文档会更好用,更详细。

项目部署

前端

前端作为一个工程,同样需要打包,打包完为dist目录
图片[215] - 瑞吉外卖笔记 - MaxSSL
把这个dist目录,扔进Nginx里HTML文件夹就可以了,也就是那个静态资源
图片[216] - 瑞吉外卖笔记 - MaxSSL
传上来不算完,还要好好配置一下
一个是静态资源,另一个是反向代理

静态资源配置

先配置静态资源
图片[217] - 瑞吉外卖笔记 - MaxSSL

请求代理配置

重启Nginx,测试一下,访问。
随便一个请求可以看到,带了前缀
图片[218] - 瑞吉外卖笔记 - MaxSSL
后端项目给的端口是9001
请求路径为:http://www.ccsmile.fun:9001/api/employee/login
我们后端是没有这个api的前缀的
通过重写url,就可以把
http://www.ccsmile.fun:9001/api/employee/login
变成
http://www.ccsmile.fun:9001/employee/login的请求地址,这样就完成了请求代理转发操作
图片[219] - 瑞吉外卖笔记 - MaxSSL
配置文件如下

server{listen 80;server_name localhost;#静态资源配置location /{root html/dist;index index.html;}#请求转发代理,重写URL+转发location ^~ /api/{rewrite ^/api/(.*)$ /$1 break;proxy_pass http://后端服务ip:端口号;}#其他error_page 500 502 503 504 /50x.html;location = /50x.html{root html;}}

最后保存文件,重启Nginx,就配置完成了
不过还是不知道为啥不太好用,还有待解决,实在不行就在后端上加入接收请求前缀就好了

后端

图片[220] - 瑞吉外卖笔记 - MaxSSL
上传脚本,自动拉取最新脚本
这样在开发端和Linux端就通过Gitee间接实现同步了
图片[221] - 瑞吉外卖笔记 - MaxSSL
脚本内容

#!/bin/shecho =================================echo自动化部署脚本启动echo =================================echo 停止原来运行中的工程APP_NAME=reggie_take_outtpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`if [ ${tpid} ]; thenecho 'Stop Process...'kill -15 $tpidfisleep 2tpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`if [ ${tpid} ]; thenecho 'Kill Process!'kill -9 $tpidelseecho 'Stop Success!'fiecho 准备从Git仓库拉取最新代码cd /usr/local/javaapp/reggie_take_outecho 开始从Git仓库拉取最新代码git pullecho 代码拉取完成echo 开始打包output=`mvn clean package -Dmaven.test.skip=true`cd targetecho 启动项目nohup java -jar reggie_take_out-1.0-SNAPSHOT.jar &> reggie_take_out.log &echo 项目启动完成

执行脚本就OK了
图片[222] - 瑞吉外卖笔记 - MaxSSL

记得修改yml文件中的部分内容,比如文件路径等等信息~
完结撒花啦

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享