0%

sky-take-out

项目介绍

功能架构

image-20241125122839960

技术选型

image-20241125122858635

MD5

1.修改数据库中明文密码,改为MD5加密后的密文

2.修改Java代码,前端提交的密码进行MD5加密后再跟数据库中密码比对

Swagger

使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。

员工管理

面试问题

  • nginx反向代理好处
1
2
3
提高访问速度
进行负载均衡(所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器)
保证后端服务安全
  • DTO
1
当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据
  • JWT

image-20241125192608560

  • 日期处理

在WebMvcConfiguration中扩展SpringMVC的消息转换器,统一对日期类型进行格式处理

  • 公共字段自动填充

使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。

实现步骤:

1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

3). 在 Mapper 的方法上加入 AutoFill 注解

技术点:枚举、注解、AOP、反射

  • 新增菜品,包括图片文件

使用第三方的存储服务,选用了阿里云的OSS服务进行文件存储。

  • 删除菜品的性能优化

在DishServiceImpl中,删除菜品是一条一条传送执行的,大大降低了执行效率,为了提高性能,进行修改,使用动态sql执行删除操作

  • redis

基于键值对的非关系数据库。

常用数据类型

1
2
3
4
5
6
7
8
9
字符串 string

哈希 hash

列表 list

集合 set

有序集合 sorted set / zset

image-20241126104927160

  • 微信登录

业务规则:

基于微信登录实现小程序的登录功能

如果是新用户需要自动完成注册

要完成微信登录的话,最终就要获得微信用户的openid。在小程序端获取授权码后,向后端服务发送请求,并携带授权码,这样后端服务在收到授权码后,就可以去请求微信接口服务。最终,后端向小程序返回openid和token等数据。

  • 应用层

SpringBoot: 快速构建Spring项目, 采用 “约定优于配置” 的思想, 简化Spring项目的配置开发。

SpringMVC:SpringMVC是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合,可以无缝集成。

Spring Task: 由Spring提供的定时任务框架。

httpclient: 主要实现了对http请求的发送。

Spring Cache: 由Spring提供的数据缓存框架

JWT: 用于对应用程序上的用户进行身份验证的标记。

阿里云OSS: 对象存储服务,在项目中主要存储文件,如图片等。

Swagger: 可以自动的帮助开发人员生成接口文档,并对接口进行测试。

POI: 封装了对Excel表格的常用操作。

WebSocket: 一种通信网络协议,使客户端和服务器之间的数据交换更加简单,用于项目的来单、催单功能实现。

  • 数据层

MySQL: 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。

Redis: 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。

Mybatis: 本项目持久层将会使用Mybatis开发。

  • Swagger
  1. 使得前后端分离开发更加方便,有利于团队协作
  2. 接口的文档在线自动生成,降低后端开发人员编写接口文档的负担
  3. 功能测试
  • 解析出登录员工id后,如何传递给Service的save方法

通过ThreadLocal进行传递。

  • 分页

使用 mybatis 的分页插件 PageHelper 来简化分页代码的开发。

1
2
3
4
5
6
7
8
9
PageHelper是MyBatis的一个插件,内部实现了一个PageInterceptor拦截器。Mybatis会加载这个拦截器到拦截器链中。在我们使用过程中先使用PageHelper.startPage这样的语句在当前线程上下文中设置一个ThreadLocal变量,再利用PageInterceptor这个分页拦截器拦截,从ThreadLocal中拿到分页的信息,如果有分页信息拼装分页SQL(limit语句等)进行分页查询,最后再把ThreadLocal中的东西清除掉。

设置分页参数:在执行查询之前,首先通过 PageHelper.startPage(int pageNum, int pageSize) 方法设置分页的参数,调用该方法时,通过 ThreadLocal 存储分页信息。
拦截查询语句:PageHelper 利用 MyBatis 提供的插件 API(Interceptor 接口)来拦截原始的查询语句。MyBatis 执行任何 SQL 语句前,都会先通过其插件体系中的拦截器链,PageHelper 正是在这个环节介入的。
修改原始 SQL 语句:在拦截原始查询语句后,PageHelper 会根据分页参数动态地重写或添加 SQL 语句,使其成为一个分页查询。
执行分页查询:修改后的 SQL 语句被执行,返回当前页的数据。
查询总记录数(可选):如果需要获取总记录数,PageHelper 会自动执行一个派生的查询,以计算原始查询(不包含分页参数)的总记录数。这通常通过移除原始 SQL 的排序(ORDER BY)和分页(LIMIT、OFFSET 等)条件,加上 COUNT(*) 的包装来实现。
返回分页信息:查询结果被封装在 PageInfo 对象中(或其他形式的分页结果对象),这个对象除了包含当前页的数据列表外,还提供了总记录数、总页数、当前页码等分页相关的信息,方便在应用程序中使用。

  • 前端小程序的微信登录流程
1
2
3
4
5
6
7
8
9
10
微信登录的核心是通过微信小程序提供的临时凭证code换取永久凭证openid的过程

首先微信小程序会向微信官方申请一个临时登录code
然后,小程序带着code向后台服务发送请求
后台接收到code后,会调用微信官方接口验证code是否合法,如果合法,官方会返回一个openid;这个openid就是此用户在我们系统中的唯一标识,同时也代表用户身份合法
后台服务接收到来着微信的openid之后,会去数据库查询一下是否存在此账户;如果存在,代表这是一个老用户,如果不存在,则代表这是一个新用户首次使用我们的系统,我们需要将其信息保存到用户表中
登录成功之后,需要生成一个标识用户身份的token,返回给前端,前端会将token保存起来
用户后面访问系统的时候,需要携带着这个token,而我们后端需要编写一个拦截器,用于拦截请求,校验token
校验通过,则放行请求,正常访问;校验失败,则禁止通行,返回提示

  • redis应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们项目中有两处地方用到了Redis,分别是:店铺营业状态标识和小程序端的套餐、菜品列表数据

店铺营业状态标识,仅仅需要在redis中保存一个0|1值即可。这里之所以选择redis,有两个原因

而没有采用数据库来存储,就是因为这个字段太简单了,没有必要在数据库中新建一张表

这个状态访问比较频繁,放在redis中,提高了查询速度的同时,可以减轻数据库的访问压力

小程序端的套餐、菜品列表数据,由于小程序端以后的访问量比较大,所以采用Redis提高访问速度

具体的操作步骤就是:在查询列表的时候,先判断Redis缓存中是否有数据,如果有,直接返回给前端

如果没有,再去查询数据库,并将查询结果保存到redis中的同时,再返回给前端

为了保证Redis和数据库中数据的实时一致性,在对数据库相关数据进行增删改操作时,需要同时清理Redis中数据

  • SpringCache在项目中的应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SpringCache是Spring提供的一个缓存框架,它可以通过简单的注解实现缓存的操作,我们常用的注解有下面几个:

@EnableCaching: 开启基于注解的缓存

@CachePut: 一般用在查询方法上,表示将方法的返回值放到缓存中

@Cacheable: 一般用在查询方法上,表示在方法执行前先查看缓存中是否有数据,如果有直接返回;如果没有,再调用方法体查询数据并将返回结果放到缓存中;他有两个关键属性:

value: 缓存的名称,每个缓存名称下面可以有多个key

key: 缓存的key,支持Spring的表达式语言SPEL语法

@CacheEvict: 一般用在增删改方法上 ,用于清理指定缓存,可以根据key清理,也可以清理整个value下的缓存

SpringCache还有一个有点,就是可以随意切换底层的缓存软件,比如:Redis、内存等等

本项目中菜品和套餐列表的缓存用到了SpringCache
  • SpringTask在项目中的应用
1
2
3
4
5
SpringTask是Spring框架提供的一种任务调度工具,用来按照定义的时间格式执行某段代码。
在我们的项目中,超时订单的状态改变用到了SpringTask,比如:

每隔1分钟检查是否有超过15分钟未支付的订单,如果有就将订单取消
每天凌晨1点检查前一天是否有派送中的订单,如果有将订单状态改成已完成

cron表达式其实就是一个字符串,通过cron表达式可以定义任务的触发时间

  • WebSocket对比HTTP
1
2
3
4
5
6
7
8
HTTP的通信是单向的,要先请求后响应,类似于对讲机

WebSocket的通信双向的、实时的,客户端和服务端可以同时发消息,类似于手机通话

我们在项目中大部分场景下都是使用HTTP协议,只有在高实时场景下,建议使用WebSocket

项目在向商家提醒接单时,用户催单发送提醒时使用了webSocket

  • 核心功能

菜品新增:对菜品表和口味表进行新增操作

首先将前端传过来的菜品信息保存到菜品表并主键返回,然后遍历前端传过来的口味集合,
为每个口味设置刚才返回来的主键并保存到口味表
菜品修改:对菜品表进行更新,对菜品详情表进行增删操作

首先根据前端传过来的菜品信息对菜品表进行修改
然后根据菜品id删除对应的口味列表集合
最后再把前端传过来的口味集合重新加入到口味表中
菜品删除:对菜品表和口味表进行删除操作

遍历前端传过来的菜品id集合得到每个菜品的信息
如果当前菜品是启售状态或者被套餐关联那么就不能被删除
否则就可以通过菜品id对菜品表和口味表中的数据进行删除
套餐新增:对套餐表和套餐菜品关系表进行新增操作

首先将前端传过来的套餐基本信息保存到套餐表中,并返回主键的id
然后为前端传过来的套餐菜品设置套餐id
最后将套餐包含的菜品添加到套餐菜品关系表中
套餐修改:对套餐表进行修改,在对套餐菜品关系表进行增删操作

首先根据前端传过来的套餐基本信息更新到套餐表中
然后根据套餐的id删除所有套餐菜品关系表中包含的菜品信息
最后遍历前端传过来的菜品的列表,设置好套餐的id后重新保存到套餐菜品关系表中
套餐删除:对套餐表和套餐菜品关系表进行删除操作

首先遍历前端传过来套餐id的集合得到每一个套餐的信息
然后根据id查询套餐,判断套餐的状态是否为启售状态,如果是启售状态,则不能删除
如果是在禁售状态,就可以通过套餐的id进行套餐菜品关系表的删除操作
分类删除

分类删除的核心逻辑就是根据前端传过来的分类id去分类表进行一个删除操作
但是要对这个分类里面是否有菜品和套餐做一个判断,拿着这个id去菜品表和套餐表做一个统计查询
如果查出来数量大于0,就不能删除,如果为0,直接删除
添加购物车:将用户选择的商品基本数据信息添加到数据库表中进行保存

利用到的数据库表(本次项目):购物车表,菜品表、套餐表,保存的信息就是从表中查到的

首先根据id查询购物车中是否有相同商品

有:则不用添加,只修改查询到的商品number属性+1并重新赋值即可,执行mapper更新。

无:则判断是菜品还是套餐,查询对应商品的数据库得到基本信息,补全购物车需要的参数执行保存。

  • websocket

img

  • threadlocal

img

项目困难

image-20241126203501259

image-20241126203511824

image-20241126203519631

image-20241126203526823