附上本人代码连接:xiaoming325/sky-take-out (github.com)
项目概述
此项目是一个外卖点餐项目,分为商家端(管理员端)和用户端,商家端是一个网站,用户端是一个微信小程序,由于微信小程序没有上线,目前只能用微信开发者工具模拟手机进行操作测试。
后端环境
sky-common
constant:存放常量类,将一些常量(数字和字符串)封装为是实体类的属性,便于调用
context:存放BaseContext类,是基于ThreadLocal封装工具类,用于保存和获取当前登录用户id
enumeration:存放枚举类,将数据库的操作类型封装为枚举类的属性。将枚举类作为自定义注解类的属性,在AOP中对切入点方法进行注解时,可直接使用注解类的属性指定数据库操作类型,便于在AOP通知时用反射获取注解中指定的数据库操作类型,以便根据不同操作类型进行不同的通知
exception:存放各种自定义异常类,例如:登录失败异常,密码错误异常,套餐启用失败异常……
json:存放JacksonObjectMapper,是一个对象映射器,可以将Java和JSON互相序列化和反序列化,并可以指定格式
properties:
存放配置属性类,阿里云、微信小程序、微信支付、JWT的相关配置属性类。
- 配置属性类中存放相关变量属性,在配置文件中写好相关属性的值
- 加注解,@Component,@ConfigurationProperties(将配置文件中相关属性的值赋给类中的成员),@Data
result:存放结果类,PageResult和Result,用于返回给前端。加相关lombok注解
utils:存放工具类,和配置属性类相对应。工具类中存放相关方法,工具类一般不加注解(视情况而定)
sky-pojo
存放DTO,VO和entity,需要视情况加相关lombok注解和@Builder(便于构造对象)
sky-server
后端服务,存放后端的三层架构、配置文件等。此模块中除了自定义注解类和全局异常处理类,其余都需要加注解注册成为IOC容器中的bean。
其中controller,service,mapper不在这里做多解释,其余包依次为:
annotation:存放自定义注解类,上文已解释过
aspect:自定义切面类,用来编写切入点表达式和通知,实现AOP
@Aspect//设置当前类为切面类,告诉spring这个类是用来做AOP的
@Component//通知类(切面类)必须配置成Spring管理的bean
config:配置类,加上@Configuration注解。在这些配置类中通常编写方法,并将加入@Bean注解将方法返回对象注册为Bean
handler:存放全局异常处理器类,类上加@RestControllerAdvice注解,方法上加@ExceptionHandler注解,方法中捕获各种业务异常和sql异常并封装为Result进行返回(业务中的异常通过层层上抛最终到达这里被处理)
interceptor:存放管理员端和用户端的jwt令牌校验拦截器,类上加@Component。类中编写校验JWT的方法,除登录请求和用户端的查询营业状态请求外都需要校验JWT
task:利用Spring Task实现定时执行任务功能
WebSocket:存放WebSocketServer类,在类中编写代码实现客户端和服务端的连接,和互相发送消息。在其他地方依赖注入WebSocketServer类,可使用其内部的方法发送消息。
数据库表
employee员工表
user用户表
category分类表:分类的相关信息,根据type的值1和2分为两类(菜品类和套餐类),每一个菜品类包含可多种菜品,每一个套餐类可包含多种套餐
shopping_cart购物车表:有user_id,dish_id,setmeal_id,dish_flavor分别关联用户表,菜品表,套餐表和菜品口味表
address_book地址簿表:有user_id关联用户表
菜品类:
dish菜品表:菜品(除口味)的相关信息,包含逻辑外键category_id关联category分类表中的菜品类
dish_flavor菜品口味表:菜品口味的相关信息,包含逻辑外键dish_id关联dish菜品表
套餐类:
setmeal套餐表:套餐的相关信息,包含逻辑外键category_id关联category分类表中的套餐类
setmeal_dish套餐菜品关系表:套餐和菜品的关联关系,包含逻辑外键dish_id关联dish菜品表,逻辑外键category_id关联category分类表
订单类:
orders订单表:address_book_id关联地址簿表
order_detail订单细节表:order_id,dish_id,setmeal_id关联订单表,菜品表,套餐表
总结(部分):
category表:
category表中的菜品类对dish表中的菜品是一对多
category表中的套餐类对setmeal表中的套餐是一对多
dish表:
dish表中的菜品对dish_flavor口味表中的口味是一对多
setmeal_dish表:
一个套餐可能包含多个菜品,并且不同套餐的菜品可以重复,同一个套餐的菜品不能重复
所以setmeal_dish表中的dish_id、name、category_id都可以重复
业务需求分析
商家端(管理员端)
员工管理功能
接口 | controller层 | service层 | mapper层 |
员工登录 | 参数: EmployeeLoginDTO 返回值: Result | 参数: EmployeeLoginDTO 返回值: Employee | 参数: String username 返回值: Employee |
员工退出 | 参数: void 返回值: Result | 无 | 无 |
新增员工 | 参数: EmployeeDTO 返回值: Result | 参数: EmployeeDTO 返回值: void | 参数: Employee 返回值: void |
员工分页查询 | 参数: EmployeePageQueryDTO 返回值: Result | 参数: EmployeePageQueryDTO 返回值: PageResult | 参数: EmployeePageQueryDTO 返回值: Page |
启用禁用员工账号 | 参数: Integer status, Long id 返回值: Result | 参数: Integer status, Long id 返回值: void | 参数: Employee 返回值: void |
根据id查询员工信息 | 参数: Long id 返回值: Result | 参数: Long id 返回值: Employee | 参数: Long id 返回值: Employee |
修改员工信息 | 参数: EmployeeDTO 返回值: Result | 参数: EmployeeDTO 返回值: void | 参数: Employee 返回值: void |
总结
- Controller层接收的对象参数为DTO,一般不进行业务逻辑处理(除了登录功能要在Controller层中为登录成功的用户生成JWT令牌返回给前端),直接调用Service层方法将DTO传给Service层,并将返回结果封装给Result返回给前端
- Service层进行各种业务处理,一般是将DTO转换为entity实体类(并补全实体类缺少的属性,可使用AOP),直接调用Mapper层方法将entity实体类传给Mapper层
- Mapper层接收实体类参数,直接对数据库进行操作(复杂逻辑用动态sql编写)
1、Controller层的方法一般和Service层是一一对应的(一对一),但不同Service层中的请求可以调用同一个Mapper层方法(可以多对一),例如:修改员工信息和启用禁用员工账号是两个不同的请求,但调用的都是Mapper层中的update方法
2、Mapper层是整个业务的重点难点,要考虑如何操作数据库从而的到想要的数据。Mapper层也是最容易报错的地方,SQL编写容易出错,尤其注意动态SQL的语法
公共字段填充(AOP)
见以前博客和笔记
分类管理,菜品管理和套餐管理
三者类似,主要有新增,修改,删除,查询,分页查询,启用禁用等接口
店铺营业状态
只需要Controller层,设置营业状态时将其放入缓存,获取时直接从缓存中取出
订单管理
订单状态:1待付款 2待接单 3已接单 4派送中 5已完成 6已取消
支付状态:0未支付 1已支付 2退款
订单搜索(分页查询):根据条件对订单进行分页查询
各个状态的订单数量统计:查询待接单、待派送、派送中的订单数量
订单详情:分别查询order订单表和order_detail订单细节表
接单:直接修改订单状态为已接单(前端已写好,只有已付款的订单才有接单按钮)
拒单:1、只有状态为待接单的订单才可以拒单 2、如果用户已支付则要退款 3、将订单状态改为已取消,并设置订单拒单原因和取消时间
取消订单:1、前端中已设置好,只有已完成的订单才可取消 2、如果用户已支付则要退款 3、将订单状态改为已取消,并设置订单取消原因和取消时间
派送订单:1、只有状态为已接单的订单才可以进行派送 2、将订单状态改为派送中
完成订单:1、只有状态为派送中的订单才可以改为已完成 2、更新订单状态为已完成并设置送达时间
数据统计
营业额统计、用户统计、订单统计、销量top10:这四者类似,根据开始日期和截止日期查询相关的数据
导出运营数据报表
工作台
今日数据查询:营业额、有效订单、订单完成率、平均客单价、新增用户
查询订单管理数据、查询菜品总览、查询套餐总览:类似以上,设置开始日期,截止日期和状态,用动态SQL进行查询
用户端
用户登录
微信用户登录,有相应的专门API
店铺营业状态
直接从缓存中读取店铺营业状态
分类管理
查询分类:根据类型查询分类
菜品管理
根据id查询菜品:先从缓存中查,查不到再去查数据库并放入缓存
套餐管理
条件查询套餐:利用缓存
查询套餐包含的菜品列表:查询dish菜品表和setmeal_dish套餐菜品表
订单管理
用户下单:1、地址簿不为空,收货地址没有超出配送范围、购物车不为空 2、向订单表和订单明细表插入数据,支付状态为待支付,订单状态为待付款 3、清空用户购物车
订单支付:1、调用微信支付接口 2、设置订单状态为待接单,更新支付时间 3、利用WebSocket向客户端浏览器发消息出发订单提醒
历史订单查询:查询订单表和订单明细表
查询订单详情:查询订单
用户取消订单:1、订单必须存在且状态为待付款或待接单,如果是待接单则要退款 2、更新订单状态为已取消、取消原因和取消时间
再来一单:1、查询订单明细表,将订单详情对象转换为购物车对象 2、将购物车对象集合批量添加到购物车表
订单催单:1、校验订单是否存在 2、利用WebSocket向客户端浏览器发消息出发催单提醒
购物车管理
添加购物车:1、先查看购物车是否有此商品,如果有只需将其数量加一,如果没有则直接添加 2、添加时判断是菜品还是套餐,添加方式不同
查看购物车:根据用户id查询购物车数据
清空购物车:根据用户id清空购物车数据
地址簿管理
查询当前用户的所有地址、根据id查询地址、根据id修改地址、根据id删除地址、查询默认地址
新增地址:新增地址都设置为不是默认地址
设置默认地址:1、将当前用户的所有地址修改为非默认地址 2、将当前地址改为默认地址
缓存
缓存套餐
用户端的查询方法使用缓存,先从缓存中查,查不到再从数据库中查,并放入缓存中
用户端的查询为根据分类id查询该分类下的菜品和菜品口味信息,将分类id作为键,多个菜品和菜品口味(DishVo)集合作为值加入缓存
管理员端的删改操作清理全部缓存,新增操作精确清理缓存(新增哪个),查询操作不涉及缓存
缓存菜品
同理缓存套餐
经验
- 进行修改操作时:动态sql中的不要用if标签非空判断拼接实体类中int类型的属性,因为int类型有默认值0,int类型肯定是非空并且会把表中本来的数据修改为默认值0。解决方法:修改前先进行查询将原来的数据作为实体类查询出来,在实体类的基础上改变属性后,将实体类作为参数传给mapper层进行修改操作,这样实体类中未修改的属性不会改变