外送平台
项目介绍
管理端
餐饮企业内部员工使用。 主要功能有:
| 模块 | 描述 |
|---|---|
| 登录/退出 | 内部员工必须登录后,才可以访问系统管理后台 |
| 员工管理 | 管理员可以在系统后台对员工信息进行管理,包含查询、新增、编辑、禁用等功能 |
| 分类管理 | 主要对当前餐厅经营的 菜品分类 或 套餐分类 进行管理维护, 包含查询、新增、修改、删除等功能 |
| 菜品管理 | 主要维护各个分类下的菜品信息,包含查询、新增、修改、删除、启售、停售等功能 |
| 套餐管理 | 主要维护当前餐厅中的套餐信息,包含查询、新增、修改、删除、启售、停售等功能 |
| 订单管理 | 主要维护用户在移动端下的订单信息,包含查询、取消、派送、完成,以及订单报表下载等功能 |
| 数据统计 | 主要完成对餐厅的各类数据统计,如营业额、用户数量、订单等 |
用户端
移动端应用主要提供给消费者使用。主要功能有:
| 模块 | 描述 |
|---|---|
| 登录/退出 | 用户需要通过微信授权后登录使用小程序进行点餐 |
| 点餐-菜单 | 在点餐界面需要展示出菜品分类/套餐分类, 并根据当前选择的分类加载其中的菜品信息, 供用户查询选择 |
| 点餐-购物车 | 用户选中的菜品就会加入用户的购物车, 主要包含 查询购物车、加入购物车、删除购物车、清空购物车等功能 |
| 订单支付 | 用户选完菜品/套餐后, 可以对购物车菜品进行结算支付, 这时就需要进行订单的支付 |
| 个人信息 | 在个人中心页面中会展示当前用户的基本信息, 用户可以管理收货地址, 也可以查询历史订单数据 |
技术选型

Mybatis: 本项目持久层将会使用Mybatis开发。
spring data redis: 简化java代码操作Redis的API。
工程的模块说明
| 序号 | 名称 | 说明 |
|---|---|---|
| 1 | sky-take-out | maven父工程,统一管理依赖版本,聚合其他子模块 |
| 2 | sky-common | 子模块,存放公共类,例如:工具类、常量类、异常类等 |
| 3 | sky-pojo | 子模块,存放实体类、VO、DTO等 |
| 4 | sky-server | 子模块,后端服务,存放配置文件、Controller、Service、Mapper等 |
分析各个模块每个包的作用
- sky-common: 模块中存放的是一些公共类,可以供其他模块使用
| 名称 | 说明 |
|---|---|
| constant | 存放相关常量类 |
| context | 存放上下文类 |
| enumeration | 项目的枚举类存储 |
| exception | 存放自定义异常类 |
| json | 处理json转换的类 |
| properties | 存放SpringBoot相关的配置属性类 |
| result | 返回结果类的封装 |
| utils | 常用工具类 |
- sky-pojo: 模块中存放的是一些 entity、DTO、VO
| 名称 | 说明 |
|---|---|
| Entity | 实体,entity类中的每一个字段都是与数据库表的字段相对应的 |
| DTO | 数据传输对象,通常用于程序中各层之间传递数据,前端需要的参数在数据库的表里存的是不一样的 |
| VO | 视图对象,为前端展示数据提供的对象 |
| POJO | 普通Java对象,只有属性和对应的getter和setter |
- sky-server: 模块中存放的是 配置文件、配置类、拦截器、controller、service、mapper、启动类等
| 名称 | 说明 |
|---|---|
| config | 存放配置类 |
| controller | 存放controller类 |
| interceptor | 存放拦截器类 |
| mapper | 存放mapper接口 |
| service | 存放service类 |
| SkyApplication | 启动类 |
数据库每张表的说明
| 序号 | 表名 | 中文名 |
|---|---|---|
| 1 | employee | 员工表 |
| 2 | category | 分类表 |
| 3 | dish | 菜品表 |
| 4 | dish_flavor | 菜品口味表 |
| 5 | setmeal | 套餐表 |
| 6 | setmeal_dish | 套餐菜品关系表 |
| 7 | user | 用户表 |
| 8 | address_book | 地址表 |
| 9 | shopping_cart | 购物车表 |
| 10 | orders | 订单表 |
| 11 | order_detail | 订单明细表 |

具体技术细节
一、Nginx
1.nginx反向代理:前端把请求发送给nginx,再由nginx将请求发送给后端服务器。
2.负载均衡
- 提高访问速度:因为nginx本身可以进行缓存,如果访问的同一接口,并且做了数据缓存,nginx就直接可把数据返回,不需要真正地访问服务端,从而提高访问速度。
- 进行负载均衡:所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器
- 保证后端服务安全:因为一般后台服务地址不会暴露,所以使用浏览器不能直接访问,可以把nginx作为请求访问的入口,请求到达nginx后转发到具体的服务中,从而保证后端服务的安全。
二、Swagger
- Swagger的作用?
自动的帮助开发人员生成接口文档,并对接口进行测试。
直接调试后端请求响应。
三、Redis
- Redis常见数据类型及使用场景?
String、Hash、List、Set、Zset
- Redis与MySQL的区别?
1.数据库类型不同
(1)MySQL是关系型数据库;
(2)Redis是基于key-value格式存储的缓存数据库/非关系型数据库
2.数据存放位置不同
(1)MySQL的数据存在磁盘中
(2)Redis的数据存在内存中
3.应用场景不同
(1)MySQL存放在硬盘中,数据读取要I/O操作,速度慢,适合持久化的数据存取;
(2)Redis存放在内存中,用CPU读取,非常快,适合热点数据的存取。
4.存放数据类型不同
(1)MySQL:数值、日期/时间、字符串
(2)Redis:String、Hash、List、Set、Zset
- Redis在你的项目中作用?
项目中引入Redis的地方是:
- ==查询店铺营业状态==,像这种店铺营业状态,本项目无非就两个状态:营业中/打样。而且它属于高频查询。只要用户浏览到这个店铺,前端就要自动发送请求到后端查询店铺状态。Redis把将数据放到缓存中,而不是磁盘,有效缓解了这种高频查询给磁盘带来的压力。
查询店铺营业状态:起售停售都要清理缓存数据

四、MySQL
- mysql导出大量数据
不能将全量数据一次性加载到内存之中。
全量加载不可行,那我们的目标就是如何实现数据的分批加载了。实事上,Mysql本身支持Stream查询,我们可以通过Stream流获取数据,然后将数据逐条刷入到文件中,每次刷入文件后再从内存中移除这条数据,从而避免OOM。
由于采用了数据逐条刷入文件,而且数据量达到百万级,所以文件格式就不要采用excel了,excel2007最大才支持104万行的数据。这里推荐:
以csv代替excel。
考虑到当前SpringBoot持久层框架通常为JPA和mybatis,我们可以分别从这两个框架实现百万级数据导出的方案。
MyBatis实现百万级数据导出
MyBatis实现逐条获取数据,必须要自定义ResultHandler,然后在mapper.xml文件中,对应的select语句中添加fetchSize=”-2147483648”。
最后将自定义的ResultHandler传给SqlSession来执行查询,并将返回的结果进行处理。1.我们先定义一个工具类DownloadProcessor,它内部封装一个HttpServletResponse对象,用来将对象写入到csv。
2.然后通过实现org.apache.ibatis.session.ResultHandler,自定义我们的ResultHandler,它用于获取java对象,然后传递给上面的DownloadProcessor处理类进行写文件操作:
3.Mapper xml文件核心片段,以下两条select的唯一差异就是在stream获取数据的方式中多了一条属性:fetchSize=”-2147483648”
4.获取数据的核心service如下,由于只做个简单演示,就懒得写成接口了。其中 streamDownload 方法即为stream取数据写文件的实现,它将以很低的内存占用从MySQL获取数据;此外还提供traditionDownload方法,它是一种传统的下载方式,批量获取全部数据,然后将每个对象写入文件。
- 数据库表你是怎么设计的?
分为员工表、用户表、分类表、菜品表、套餐表、套餐菜品对应表、口味表、购物车表、订单表、订单详细表、地址表。
- 为什么用逻辑外键,而不用数据库自带的外键?
数据库通过外键来保证数据的完整性一致性,外键的缺点–对海量数据,性能低,因而使用逻辑外键来保证数据的完整性和一致性,当然,如果是对安全性要求高以及对性能要求不高的场景,可以使用外键这样更安全。
五、HTTPClient
- HTTPClient的作用?
发送HTTP请求,接收响应数据
六、SpringCache
由Spring提供的数据缓存框架
@EnableCaching 加在启动类上开启
@Cacheable 放要缓存的查询上,快速查询redis,没有则查询后加入redis
@CachePut 放新增上,将结果放入redis
@CacheEvict 放删上,去掉一条;放改上,去掉全部重新记
七、SpringTask,cron
SpringTask作用?
由Spring提供的定时任务框架。
任务调度,在特定时间执行指定的Java代码,定时做任务。
cron表达式有哪些域?
秒 分 时 日 月 周 年
SpringTask只支持前6个域,不支持年的域。然后用*表示每个单位都执行,比如每日、每秒等;?表示冲突时的缺省,比如日月和周的冲突;/10表示每10个单位,比如每10分钟、每10个月等。
八、Websocket
- WebSocket是什么?
Websocket是双向通信的一个==网络协议==。
- WebSocket的应用?
- TCP 协议本身是全双工的,但我们最常用的 HTTP/1.1,虽然是基于 TCP 的协议,但它是半双工的,对于大部分需要服务器主动推送数据到客户端的场景,都不太友好,因此我们需要使用支持全双工的 WebSocket 协议。
- 在 HTTP/1.1 里,只要客户端不问,服务端就不答。基于这样的特点,对于登录页面这样的简单场景,可以使用定时轮询或者长轮询的方式实现服务器推送(comet)的效果。
- 对于客户端和服务端之间需要频繁交互的复杂场景,比如网页游戏,都可以考虑使用 WebSocket 协议。
- WebSocket的缺点?
- WebSocket也有一定的缺点:
- 服务器长期维护长连接需要一定的成本
- 各个浏览器支持程度不一
- WebSocket 是长连接,受网络限制比较大,需要处理好重连
- WebSocket在项目中的应用?
九、POI,apache Echarts
- POI的作用?
POI: 封装了对Excel表格的常用操作。
操作Java读写Office,项目中主要是Excel文件。
- 你的数据统计模块是什么意思?怎么做的?
是一些营业额统计、用户统计、订单统计,销量统计等等。利用apache Echarts做的。
- 大流量下如何设计一个全局唯一的订单号?
在大流量的环境下,我们可以通过 redis 的
incr函数实现序列号自增的特性,同时搭配订单的设计规则,从而保证高并发的环境下,订单唯一性,其中的一个订单规则如下:业务编码+年的后 2 位+月+日+秒+订单数,固定长度为16,这种订单号规则可以保证 100 年不会重复!

十二、SpringMvc
- 介绍下SpringMvc?
Spring MVC消息转换器是用于在HTTP请求和响应之间进行Java对象和数据格式(如JSON、XML)之间的转换,解决前后端数据交互的问题。
在一个前后端分离的应用中,前端通过==Ajax请求==发送JSON数据到后端,Spring MVC消息转换器可以将JSON数据转换成Java对象,同时也能将Java对象转换成JSON数据返回给前端。
- Spring MVC消息转换器的默认支持哪些数据格式?
- Spring MVC消息转换器默认支持JSON、XML、表单数据等格式。
- 如何自定义消息转换器?
- 可以通过继承
AbstractHttpMessageConverter类或实现HttpMessageConverter接口来自定义消息转换器。然后,在Spring配置中注册这个自定义的消息转换器。
- 你在项目中的哪些场景使用了消息转换器?
- 例如,在RESTful API的开发中,使用JSON格式进行前后端数据的传输。通过==配置消息转换器==,能够自动将==Controller的方法返回的Java对象==转换成==JSON格式==响应给客户端。
十三、AOP
- AOP是什么?解决了什么问题?应用场景?
面向切面编程AOP 的目的是将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。
日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。
性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。
事务管理:
@Transactional注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional注解就是基于 AOP 实现的。权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用
@PreAuthorize注解一行代码即可自定义权限校验。接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。
缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新。
- AOP应用各个通知类型以及执行时间?
- 分别是前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
- AOP的各个关键字?
连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP中表示为在哪里干;
切入点(Pointcut): 选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为在哪里干的集合;
通知(Advice):在连接点上执行的行为,通知提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice),在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知;在AOP中表示为干什么;
方面/切面(Aspect):横切关注点的模块化,比如上边提到的日志组件。可以认为是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;在AOP中表示为在哪干和干什么集合;
- Spring AOP和AspectJ是什么关系?
- AOP的配置方法?
十四、JWT
对应用程序上的用户进行身份验证的标记。
十五、微信登录
- 小程序端,调用wx.login()获取code,就是授权码。
- 小程序端,调用wx.request()发送请求并携带code,请求开发者服务器(自己编写的后端服务)。
- 开发者服务端,通过HttpClient向微信接口服务发送请求,并携带appId+appsecret+code三个参数。
- 开发者服务端,接收微信接口服务返回的数据,session_key+opendId等。opendId是微信用户的唯一标识。
- 开发者服务端,自定义登录态,生成令牌(token)和openid等数据返回给小程序端,方便后绪请求身份校验。
- 小程序端,收到自定义登录态,存储storage。
- 小程序端,后绪通过wx.request()发起业务请求时,携带token。
- 开发者服务端,收到请求后,通过携带的token,解析当前登录用户的id。
- 开发者服务端,身份校验通过后,继续相关的业务逻辑处理,最终返回业务数据。
十六、微信支付
完成微信支付有两个关键的步骤:
需要在商户系统当中调用微信后台的一个下单接口,就是生成预支付交易单。
支付成功之后微信后台会给推送消息。

业务逻辑
一、员工登录流程
1.前端在登录页面登录,发送请求
2.进入拦截器,拦截器放行所有登录页面的请求
3.进入三层架构,查询用户是否存在,若用户存在,则加密,返回JWT的token,存放在请求头部。用户不存在,则不能登录。
二、登录验证如何实现的
1.试图访问任意非登录界面,前端发送请求
2.进入拦截器,开始拦截验证JWT。
3.校验成功,则进入访问界面。否则跳到登录界面。
三、项目开发有哪些角色

四、编辑员工的流程
1.完善之前登录时存储员工id到线程中。
2.完成回显功能,即根据id查询员工。
3.完成修改功能,即update员工数据。
五、公共字段自动填充实现流程
1.确定使用AOP技术。
2.在com.sky.annotation包下创建自定义注解,比如叫AutoFill
3.用一个枚举类代表更新和新增两种状态,并放入自定义注解中。
4.在com.sky.aspect包下自定义切面类
5.完善切面类的自动填充逻辑,判断更新、新增两种状态,进行不同处理。
6.在mapper对应的方法上加自定义注解
六、新增菜品的流程
1.实现文件上传。
(1)在配置文件中引入自己的阿里云。
(2)写一个类,比如AliOssProperties,用来读取配置文件。
(3)写一个工具类,用来上传文件。
(4)用一个配置类,比如OssConfiguration,来生成OSS工具类对象。
(5)在三层架构中注入、调用实现文件上传。其中要用UUID随机生成文件名。
2.实现新增菜品的文本细节新增。注意要增入两个表,一个dish,一个dish_flavor。
七、删除菜品的流程
注意业务层分四步:
1.判断是否起售
2.判断是否被套餐关联
3.删除菜品表中数据
4.删除菜品关联的口味数据
八、修改菜品的流程
首先实现根据id查找
然后实现修改
注意修改口味时要先删除原有口味,再单个for循环插入,或多个一起插入。
九、微信登录流程
1.controller层接收前端传过来的授权码code,调用service层,查找授权码是否通过校验。
2.service层校验授权码是否有效,无效则抛出异常;有效,则调用mapper层查询用户。
3.mapper层查找已存在用户并返回service值。
4.service中,若用户存在,直接返回;若不存在,完成用户的自动注册,并返回用户给controller层。
5.controller拿到返回用户,即用户存在或注册好后,封装响应对象,最后返回给前端。
总结3层架构:
controller层:
1.用DTO接收授权码
2.交给service层校验授权码是否有效,拿到返回值用户
3.将返回用户封装给VO,return给前端
service层:
1.准备参数,利用微信第三方,校验授权码是否有效
2.解析响应参数,获取openid。如果为空,说明校验无效,抛异常
3.根据openid,交给mapper查询user列表,如果没有对应的用户,完成自动注册;
4.返回用户
mapper层:
根据openid,查询是否存在用户
十、缓存菜品流程
在service层添加代码。添加代码如下:
查询redis。
如果redis中没有数据,则用mapper查询出数据。有则直接跳到1.4步
将mapper查出的数据存入redis。
返回数据
十一、添加购物车流程
1.前端传给controller层菜品/套餐与口味等。
2.controller层交给service层进行添加到购物车。
2.1判断购物车中是否已经存在。调用mapper层,select一下。
2.2如果存在,则只修改数量,交给mapper update一下数量。
2.3如果不存在,则分类添加菜品or套餐给shopcart对象。
2.3.1如果是菜品,查出菜品以及id、图片等
2.3.2如果是套餐,查出套餐以及id、图片等。
2.4添加shopcart对象到数据库,mapper insert进去。
3.mapper层照上操作
十二、用户下单流程
1.查询地址
2.查询购物车
3.封装订单,并添加一条
4.封装订单详情,有可能添加多条
5.清空购物车
6.封装VO并返回
十三、微信支付流程
1.调用微信下单接口;
2.返回预支付交易标识;
3.将组合数据再次签名;
4.推送支付结果;
5.更新订单状态。
十四、来单提醒和用户催单实现流程
来单提醒:在提交订单的业务层代码里加入WebSocket的注入,然后传输带订单信息的map。
用户催单:在三层架构里面写催单功能,也用WebSocket来注入、实现双向通信。
十五、导出运营数据实现流程
读取Excel模版到内存中。
准备运营数据
将数据写到Excel模板中。
将Excel文档响应回浏览器(文件下载)
注意的点
ClassLoader能加载的文件位置
ClassLoader能加载的文件位置在resources下。
放入resources后需要的操作
需要用maven构建管理的complie编译一下,才能保证类加载器ClassLoader加载到。
创建的POI与Office对应的下标
下标中getRow(0)与getCell(1)对应的分别是第一列第2行的数据
数据库设计
十六、数据库设计


