文章目录
- 一、项目介绍
- 二、技术选型
- 三、初始化环境
-
- [3.1 common层](#3.1 common层)
- [3.2 pojo层](#3.2 pojo层)
- [3.3 server层](#3.3 server层)
- [3.4 数据库各张表的作用](#3.4 数据库各张表的作用)
- 四、技术
-
- [4.1 JWT令牌加密技术](#4.1 JWT令牌加密技术)
- [4.2 Nginx负载均衡和反向代理](#4.2 Nginx负载均衡和反向代理)
- [4.3 MD5加密登录](#4.3 MD5加密登录)
- [4.4 使用基于Swagger的Knife4j注解](#4.4 使用基于Swagger的Knife4j注解)
- [4.5 ThreadLocal](#4.5 ThreadLocal)
- [4.6 基于消息转换器对时间进行格式化](#4.6 基于消息转换器对时间进行格式化)
- [4.7 基于PageHelper的分页查询](#4.7 基于PageHelper的分页查询)
- [4.8 基于注解和AOP的公共字段填充](#4.8 基于注解和AOP的公共字段填充)
- [4.9 阿里云OSS云存储服务](#4.9 阿里云OSS云存储服务)
- [4.10 对方法开启事务](#4.10 对方法开启事务)
- [4.11 初步引入Redis](#4.11 初步引入Redis)
- [4.12 利用Redis来对业务优化](#4.12 利用Redis来对业务优化)
- [4.13 利用Spring cache来对业务进行优化](#4.13 利用Spring cache来对业务进行优化)
- [4.14 Httpclient](#4.14 Httpclient)
- [4.15 微信登录接口](#4.15 微信登录接口)
- [4.16 微信支付接口](#4.16 微信支付接口)
- [4.17 内网穿透工具Cpolar](#4.17 内网穿透工具Cpolar)
- [4.18 Spring Task](#4.18 Spring Task)
- [4.19 引入Websocket来实现用户端和商家端通信](#4.19 引入Websocket来实现用户端和商家端通信)
- [4.20 Apache POI技术实现导出文件](#4.20 Apache POI技术实现导出文件)
一、项目介绍
该项目是一个在线买菜系统,顾客可以通过微信小程序订购蔬菜。
员工管理:管理员可以在系统后台对员工信息进行管理,包含查询、新增、编辑、禁用等功能
分类管理:主要对当前店铺经营的 菜品分类 或 套餐分类 进行管理维护, 包含查询、新增、修改、删除等功能
菜品管理:主要维护各个分类下的菜品信息,包含查询、新增、修改、删除、启售、停售等功能
套餐管理:主要维护当前店中的套餐信息,包含查询、新增、修改、删除、启售、停售等功能
订单管理:主要维护用户在移动端下的订单信息,包含查询、取消、派送、完成,以及订单报表下载等功能
工作台:展示各种操作界面
数据统计:主要完成对店里的各类数据统计,如营业额、用户数量、订单等
用户端:
微信登录:用户端微信用户生成jwt令牌相关配置
商品浏览:在点餐界面需要展示出菜品分类/套餐分类, 并根据当前选择的分类加载其中的菜品信息,供用户查询选择
购物车:用户选中的菜品就会加入用户的购物车,主要包含 查询购物车、加入购物车、删除购物车、清空购物车等功能
微信支付:用户选完菜品/套餐后,可以对购物车菜品进行结算支付,这时就需要进行订单的支付
历史订单:可在历史订单中查看自己以往的订购情况
地址管理:在个人中心页面中会展示当前用户的基本信息,用户可以管理收货地址,也可以查询历史订单数据
用户催单:支付后商家久未接单,可以点击催单选项,提醒商家接单
二、技术选型
用户层:HTML,CSS,JS,ElementUI,apache echarts,微信小程序
网关层:Nginx。使用Nginx来部署前端,Nginx的负载均衡来合理请求到多台服务器,减小后端服务器压力。
应用层:
- Spring Task:定时任务依赖,完成相关配置,后端可以定时完成任务
- Httpclient:HTTP开源的通信库,使得后端可以发送和处理后端请求,常用于后端请求各种接口时用到
- Spring Cache:缓存依赖,把数据存储到缓存中,如果前端再次请求相同数据,使得后端可以从缓存中拿数据,减少了对数据库的IO操作,降低了后端服务器的压力。
- JWT: 令牌技术,校验用户身份。
- 阿里云OSS:第三方云存储技术,后端调用阿里云OSS来存储菜品等图片。
- Swagger:Swagger的优化版本Knife4j,它基于注解的方式注解在启动类上,在后端启动之后,在网页输入localhost 8080/doc.html就可以打开相关接口管理界面。
- POI: 读取和写入Microsoft Office格式文件,如Word文档、Excel电子表格和PowerPoint演示文稿。
- WebSocket: 一种通信协议,允许客户端与服务器进行持久化连接,并且区别于请求-响应模式,WebSocket协议实现了客户端与服务器的双向通信。
数据层: - Redis:键值型存储系统,基于键值对的形式对数据进行存储,数据会被存储到缓存当中
- Mybatis: 开源的持久层框架,简化了java与关系型数据的之间的操作
- pagehelper: 分页框架,以注解的形式使用,简化分页查询中的分页查询操作
- spring data redis: spring框架提供的与Redis数据库进行交互的模块,简化在java中使用Redis的操作步骤。
三、初始化环境
3.1 common层
constant:封装各种常量类,代替硬编码
context:封装基础上下文类
enumeration:封装各种枚举类
exception:封装各种异常类
json: 处理json转换的类
properties: 存放Spring boot的相关配置的类
result: 封装各种返回结果的类
utils: 封装各种工具类
3.2 pojo层
dto: 提供实体,封装前端发送给后端的数据,方便后端处理
entity: 提供各种实体类,例如员工类
vo: 提供实体,封装后端发送给前端的数据,方便前端处理
3.3 server层
annotation: 存放自定义注解
aspect: 存放各种切面
config: 存放各种配置类
controller: 存放各种处理前端请求的方法,向下还细分为管理端,用户端,通用端
handler: 封装和处理异步任务,事件或消息。
interceptor: 存放拦截器,按照指定条件拦截前端请求
mapper: 存放mapper,是Java与MySQL直接进行交互的包
service: 存放各种业务功能的具体实现逻辑方法
Task: 任务类,存放各种任务
websocket: 封装websocket,简化websocket的使用。
3.4 数据库各张表的作用
address_book: 存放用户下单地址
category: 存放菜品类型
dish: 存放菜品
dish_flavor: 存放菜品口味
employee: 存放管理端员工信息
order_detail: 存放各个订单的明细
orders: 存放各个订单
setmeal: 存放套餐
setmeal_dish: 存放套餐内的具体菜品
shopping_cart: 购物车,方便前端购物车回显
user: 用户表,存放用户账号密码
四、技术
4.1 JWT令牌加密技术
见4.5
4.2 Nginx负载均衡和反向代理
负载均衡:Nginx 的负载均衡功能允许将请求分发给多个应用服务器,以均衡负载和提高系统的可扩展性和可靠性。
下面是一些常用的 Nginx 负载均衡配置方法:
- 轮询(Round Robin):这是默认的负载均衡策略。Nginx 将请求依次分发给每个后端服务器,确保每个服务器都能获得相同的请求数量。
- IP 哈希(IP Hash):Nginx 使用客户端 IP 地址的哈希值来决定将请求发送给哪个后端服务器。这种方式可以确保同一客户端的请求始终发送到同一个后端服务器,适用于某些需要会话保持的场景。
- 加权轮询(Weighted Round Robin):可以为每个后端服务器设置权重,高权重的服务器将获得更多的请求。这种方式可以根据服务器的性能和处理能力来分配负载。
- 最少连接(Least Connections):Nginx 根据当前连接数来选择最空闲的后端服务器,将请求发送给它。这样可以确保负载更均衡,避免某些服务器过载。
反向代理隐藏服务器,正向代理隐藏客户端。
反向代理可以缓存后端响应,使得相同的请求不需要再次发送到服务器,有效降低了服务器的访问压力。
4.3 MD5加密登录
把用户输入的密码与正确密码加密后得到的MD5字符串进行比较
4.4 使用基于Swagger的Knife4j注解
Knife4j是Swagger的一个增强工具,是基于Swagger构建的一款功能强大的文档工具。它提供了一系列注解,用于增强对API文档的描述和可视化展示。
- @Api:用在类上,例如Controller,表示对类的说明
- @ApiModel: 用在类上,例如entity、DTO、VO
- @ApiModelProperty :用在属性上,描述属性信息
- @ApiOperation: 用在方法上,例如Controller的方法,说明方法的用途、作用
4.5 ThreadLocal
ThreadLocal是Java中的一个线程级别的变量,它可以在当前线程中存储数据。在这种情况下,可以使用ThreadLocal来存储解析后的JWT令牌中的用户信息,例如用户ID、角色等。这样,在后续的请求处理过程中,可以直接从ThreadLocal中获取用户信息,避免了多次解析JWT令牌的性能开销。
JWT优点 1.支持PC端、移动端 2.解决集群环境下的认证问题 3.减轻服务器的存储压力(无需在服务器端存储)
4.6 基于消息转换器对时间进行格式化
我们无法控制前端给我们传递过来的时间参数的格式,因此我们要对前端传递过来的时间参数进行格式化。
而进行格式化,如果时间参数少,我们可以使用 @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")来对某个属性指定格式。
但是如果时间参数过多,我们再一个一个标注就太麻烦了。因此我们选择在Spirng MVC中再扩展一个消息转换器,统一对前端的发送给后端的时间数据进行处理。
消息转换器在Spring MVC中负责处理请求和响应的数据格式转换,例如将Java对象转换为JSON格式或者把JSON格式转换为Java。
4.7 基于PageHelper的分页查询
PageHelper的底层原理是拦截,拦截需要进行分页查询的SQL请求,读取用户传入参数,自主构造分页SQL语句。
先调用PageHelper的stratPage函数,传递要查询的页码以及每一页的数据条数。
再调用pageQuery方法进行实际的分页查询操作,返回类型为Page。
然后,通过page.getTotal方法获取查询结果的总数,即满足条件的数据总条数。
通过 方法获取当前页的数据列表,即符合分页条件的数据集合。
最后,将总数和当前页的数据列表封装成一个PageResult对象,并返回给调用方。
4.8 基于注解和AOP的公共字段填充
首先先创建一个注解: AutoFill,其次完成切面的代码:先通过切入点表达式,拦截到带有AutoFill的注解,之后再写通知。
4.9 阿里云OSS云存储服务
1.我们使用UUID来生成一串随机数,这样就确保了文件不被新文件覆盖。
2.不要把配置类写死,我们并不是直接把配置写到application.yml 而是在application-dev.yml 写具体配置,在application.yml中应用application-dev.yml的配置。
4.10 对方法开启事务
对任何一张表的修改可能对会影响到其他的表,例如我们在插入dish中的菜品的时候,也应该一并插入dish_flavor中该菜品的对应的口味。而这两张表的数据的插入,应该是确保都完成的,不可以出现只插入了菜品或者只插入了口味的情况。因此我们要把对这两张表的操作设置为一个事务。
在启动类上方添加@EnableTransactionManagement,开启事务注解之后,我们只需要在需要捆绑成为一个事务的方法上添加@Transactional
4.11 初步引入Redis
Redis是一个开源的内存存储系统,它支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等。
查询店铺营业状态 ,像这种店铺营业状态,本项目无非就两个状态:营业中/打样。而且它属于高频查询。只要用户浏览到这个店铺,前端就要自动发送请求到后端查询店铺状态。
Redis也把数据放到缓存中,而不是磁盘,有效缓解了这种高频查询给磁盘带来的压力。
利用Spring Data Redis的RedisTemplate 对象操作Redis。
4.12 利用Redis来对业务优化
用户一旦点进店铺,店铺就需要向用户展示菜品,套餐等等数据。这种通过少量的操作可以调起大量后端操作的行为,是一个很危险的杠杆操作。而在高并发环境下,这无疑又是在拷打服务器。
缓存请求相应内容,如果小程序又发送相同请求,那么我们就从缓存中直接返回相应内容。这样就减少了直接对后端的数据库的查询。
如何保证数据库的数据与缓存的数据一致?
因为我们在添加redis作为缓冲区之后,如果缓冲区中存在数据,我们是直接从缓冲区拿数据的,如果我们更改了数据库,可能就会造成数据库与缓冲区数据不一致的情况。
读写双写(Write-through):在更新数据库时,同时更新Redis缓存。这意味着在写入数据库的同时,将相同的数据写入Redis缓存。这种方式确保了数据库和缓存中的数据始终保持一致。但是需要注意的是,双写操作会增加系统的写入负载和延迟,并且需要保证写入操作的原子性。
读写更新(Write-behind):在更新数据库时,延迟更新Redis缓存。这种方式先更新数据库,然后异步地更新Redis缓存,以提高写入的性能和响应速度。在这种情况下,可能会出现一小段时间内数据库和缓存数据的不一致,但后续的读取操作会从数据库中获取最新的数据并更新缓存。
缓存失效策略:通过在缓存中设置适当的过期时间或失效策略,确保缓存中的数据在一定时间后会过期并从数据库中重新加载。这样可以保证在数据更新或过期后,下一次读取操作将从数据库中获取最新的数据,并更新缓存。这种方式适用于数据变化不频繁、对数据实时性要求不高的场景。
发布订阅模式(Pub/Sub):使用Redis的发布订阅功能,当数据库中的数据发生变化时,通过发布消息的方式通知订阅者(Redis缓存)进行更新。这样可以保证在数据发生变化时,及时通知Redis缓存更新,以保持数据的一致性。
而Redis自身也存在问题
穿透(Cache Penetration):当一个不存在的键被频繁查询时,会导致缓存无效并且每次查询都需要访问数据库。这种情况下,恶意用户可以通过构造不存在的键来绕过缓存,直接请求数据库。这不仅浪费了数据库的资源,还可能导致数据库压力过大。为了解决穿透问题,可以使用布隆过滤器或者在查询得到空结果时也进行缓存,设置一个较短的过期时间。
击穿(Cache Breakdown):当一个热点键过期或被清除时,同时又有大量的请求访问该键,导致这些请求直接访问数据库,称为击穿。这种情况下,数据库会承受巨大的压力,可能导致宕机或性能下降。为了解决击穿问题,可以使用互斥锁或者分布式锁,保证只有一个请求能够访问数据库,并在请求获取到数据后更新缓存。
雪崩(Cache Avalanche):当大量的缓存键在同一时间失效,或者缓存服务器发生故障,导致大量的请求直接访问数据库,称为雪崩效应。这种情况下,数据库会承受巨大的压力,可能导致宕机或性能下降。为了解决雪崩问题,可以采用多级缓存架构,将请求分散到多个缓存服务器,或者使用热点数据预加载的方式,提前加载热门数据到缓存中。
4.13 利用Spring cache来对业务进行优化
- 缓存技术支持:Spring Cache 支持多种主流的缓存技术,包括 EHCache、Redis、Guava 等。
- 基于注解的缓存:Spring Cache 提供了基于注解的缓存,可以在方法上直接使用 @Cacheable、@CachePut、@CacheEvict 等注解,实现对方法结果的自动缓存和更新。
4.14 Httpclient
Httpclient是一个服务器端进行HTTP通信的库,他使得后端可以发送各种HTTP请求和接收HTTP响应,使用HTTPClient,可以轻松的发送GET,POST,PUT,DELETE等各种类型的的请求.
他是一个很常用的技术,因为很多第三方接口的使用方式就需要我们的后端发送请求到指定资源路径,这样才可以调用相关服务。例如我们下方的微信登录接口,后端在使用登录凭证校验接口的时候就需要发送指定请求到给定的URL中。
4.15 微信登录接口
1.我们的小程序会调用wx.login()来获得一个code。该 code 的作用是用于后续的用户身份验证和获取用户信息。
2.小程序的wx.request会把code发送给后端,后端再打包自己的小程序ID(appid)和小程序密钥(appsecert) 最后加上小程序发送给自己的code,利用Httpclient从后端发送给微信接口服务。而微信接口服务会在校验之后返回session_key和openid.
3.在后端获取到微信接口服务发送给自己的session_key和openid,自定义用户登录态,并且发送给小程序
4.小程序把后端发送过来的自定义登录态存入到storge中。
4.16 微信支付接口
1.用户进入小程序下单 ,小程序会发送下单请求给商家系统后台,商家后端会生成平台订单,请求微信支付系统的下单接口,创建订单。微信支付系统接收到商家系统后台的请求后,会生成预付单,并且返回预付单标识给商家系统后台,此时商户系统后台利用算法生成带签名支付信息,并且把相关支付参数返回给商家小程序
2.当小程序接收到相关的参数之后,就会调用wx.requestPayment发起微信支付,此时小程序会先向微信支付系统发送请求,检查当前用户身份,微信支付系统在检验当前用户符合权限之后,会给小程序返回支付授权,允许小程序调起支付页面。
3.当小程序调起支付页面之后,用户输入密码确定支付,此时微信小程序会打包相关参数给微信支付系统,校验身份通过之后,就异步通知平台支付交易结果给商家后端,商家后端对其进行保存通知处理,并且在这同时返回支付结果,并且发送微信消息提醒。
4.上述已经完整的介绍了一次微信小程序调用微信支付的具体过程。如果我们后续要查询判断微信支付结果,就在后端调用查寻订单接口,查询支付结果。而微信支付平台就会为商家后端返送支付结果,供我们进行各种判断。
4.17 内网穿透工具Cpolar
在微信支付接口的流程中,当支付成功之后,微信后台要向我们的服务器后台返送支付结果,但是存在一个问题:我们的IP地址都是私有IP地址,微信后台根本访问不了,这样就接收不到支付结果,因此我们需要一个公有的IP地址
而我们给出的解决方案是使用内网穿透工具Capolar
4.18 Spring Task
Spring Task为我们提供了一种基于注解的方式来使得我们的后端具有定时处理任务的能力,这项功能可以说是十分常见:我们CSDN的每日周报,就是定时任务.
而这项工具,在我们的项目中的主要作用是:处理异常订单
在我们的数据库中,总会有一些异常订单,例如用户一直未点击送达的订单,而我们需要对这些订单进行集中的处理。
@Scheduled它是用来设置定时的注解,它里面采用的表达式叫做cron表达式,通过这个表达式,我们可以指定任务多久执行一次。
4.19 引入Websocket来实现用户端和商家端通信
在项目中,有外卖催单和来单提醒这两个功能。
WebSocket 是一种在 Web 应用程序中实现双向通信的协议。它允许客户端和服务器之间建立持久的、双向的通信通道,使得服务器可以主动向客户端推送消息,而无需客户端发送请求。
4.20 Apache POI技术实现导出文件
Apache POI(Poor Obfuscation Implementation)是一个用于处理Microsoft Office格式文档的开源Java库。
在本项目中,我们并不使用ApachePOI建表,我们直接就提供一张创建好的模板表,这样我们只需要使用ApachePOI来实现填充数据就好了。