源码在 https://gitee.com/pluto8/take-out
一、软件开发整体介绍
1、软件开发流程
- 需求分析 :产品原型,需求规格说明书(文档形式)
- 设计:产品文档、UI界面设计、概要设计、详细设计、数据库设计
- 编码:项目代码,单元测试
- 测试:测试用例,测试报告
- 上线运维:软件环境安装,配置
2、角色分工:
- 项目经理:对整个项目负责,任务分配、把控进度
- 产品经理:进行需求调研,输出需求调研文档、产品原型等
- UI设计师:根据产品原型输出界面效果图
- 架构师:项目整体架构设计、技术选型等
- 开发工程师:代码实现
- 测试工程师:编写测试用例,输出测试报告
- 运维工程师:软件环境搭建、项目上线
3、软件环境
- 开发环境(development):开发人员在开发阶段使用的环境,一般外部用户无法访问
- 测试环境(testing):专门给测试人员使用的环境,用于测试项目,一般外部用户无法访问
- 生产环境(production):即线上环境,正式提供对外服务的环境
二、项目介绍
1、项目介绍
本项目是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护、移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等
2、产品原型(产品经理提供)
产品原型,就是一款产品成型之前的一个简单的框架,就是将页面的排版布局展现出来,使产品的初步构思有一个可视化的展示。通过原型展示,可以更加直观的了解项目的需求和提供的功能。产品原型主要用于展示项目的功能,并不是最终的页面效果
3、技术选型
- 用户层:H5,VUE.JS,ElementUI,微信小程序
- 网关层:Nginx
- 应用层:SpringBoot,SpringMVC,Spring Session,Spring,Swagger,lombok
- 数据层:Mysql,Mybatis,MybatisPlus,Redis
- 工具:git,maven,junit
4、功能架构
5、角色
- 后台系统管理员:登录后台管理系统,拥有后台系统中的所有操作权限
- 后台系统普通员工:登录后台管理系统,对菜品、套餐、订单等进行管理
- C端用户:登录移动端用户,可以浏览菜品、添加购物车、设置地址、在线下单等
三、环境搭建
1、数据库环境搭建
导入sql文件
2、maven项目搭建
-
创建maven项目,导入pom.xml和application.yml
-
直接将前端后端页面复制在resource下,会出现tomcat找不到静态资源页面的情况(报404)
可以通过配置文件来解决。创建一个WebMvcConfig
package com.xqh.reggie.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
*设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射...");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
}
四、管理后台功能开发
登录功能
1、需求分析
1)页面原型展示
2)登录页面展示
页面请求—>controller—>service—>Mapper—>DB
3)封装结果类。通用返回结果,服务端响应的数据最终都会封装成此对象
4)controller中创建登录方法
逻辑:页面提交的密码进行md5加密—>根据页面提交的用户名查询数据库,查询不到则返回登录失败,查询到---->密码比对,错误则登录失败,正确---->查看员工状态,已禁用则返回已禁用状态,正常—>登录成功,将员工id存入Session并返回登陆成功结果。
退出登录
1、处理逻辑:清理Session中的用户id,返回结果
员工管理
1、完善登录功能
存在一个问题:用户如果不登录,直接访问系统首页面,照样可以正常访问,这是不合理的。
我们希望看到的是,只有登录成功后才可以访问系统中的页面,如果没有登录则跳转到登录页面
如何实现?使用过滤器或者拦截器
2、新增员工
- 有个问题:当我们在新增员工输入账号,输入之前数据库已经存在的账号,由于表中对这个字段做了唯一约束,所以程序会报错抛出异常
- 解决:异常捕获。try…catch捕获异常,或者做全局异常捕获
使用异常处理器进行全局异常捕获
总结:
- 根据产品原型明确业务需求-----实现添加员工,跳转路由、携带参数等都可以在前端页面中找到
- 通过分析数据的流传过程和数据格式
- 通过debug断点调试跟踪程序执行过程
3、员工信息分页查询
业务逻辑:
- 页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端
- 服务端controller接收页面提交的数据并调用Service查询数据
- Service调用Mapper操作数据库,查询分页数据
- Controller将查询到的分页数据响应给页面
- 页面接收到分页数据并通过element-ui的table组件展示到页面上
4、禁用、启用员工账号
只有admin账户可以使用这个功能。其他用户不显示这个功能(这主要是前端实现,if判断)
出现问题:js对long型数据进行处理时丢失精度,导致提交的id和数据库的id不一致,如何解决?
我们可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串
具体实现步骤:
- 提供对象转换器JacksonObjectMapper,基于Jackson进行java对象到json数据的转换
- 在webMvcConfig配置类中扩展SpringMVC的消息转换器,在此消息转换器中使用提供的对象转换器进行java对象到json数据的转换。
5、编辑员工信息
- 需求分析:在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按钮完成编辑操作
- 修改和添加共用一个接口保存到数据库中
分类管理
1、公共字段自动填充
在员工管理功能开发时,新增员工时要设置创建时间、更新时间、创建人、更新人,这些属于公共字段,很多表中都有这些字段,那么能不能对于这些公共字段在某个地方统一处理,来简化开发呢?对于此,MyBatisPlus提供了公共字段自动填充 功能
-
实现步骤:
- 在实体类的属性上加入@TableField注解,指定自动填充的策略
- 按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。在这个类中写需要自动填充字段
-
如何在MyMetaObjectHandler类中获取当前登录用户的id呢?
首先,因为在MyMetaObjectHandler类中是不能获得HttpSession对象的,所以我们不能通过session来获取到登录用户的id。可以使用ThreadLocal来解决此问题,它是jdk中提供的一个类。
需要先确认一个事情,就是客户端每次发送的http请求,对应的在服务端都会分配一个新的线程来处理 。当发送编辑请求时,同时经过下面三个方法,这三个方法对应的线程id是相同的。
:LoginCheckFilter的doFilter方法
:EmployeeController中的update方法
:MyMetaObjectHandler的updateFill方法
一次请求对应的线程id是相同的!
- 什么是ThreadLocal
ThreadLocal并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立的改变自己的副本,而不会影响其他线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
- 我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获取当前线程所对应的线程局部变量的值(用户id)
2、新增分类
需求分析:后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。可以在后台系统的分类管理页面分别添加菜品分类和套餐分类。新增菜品分类、新增套餐分类。之后的菜品管理和套餐管理,是要关联上这里有的菜品和套餐。
3、分类信息分页查询
构建分页构造器,构造条件构造器,执行分页查询
4、删除分类
- 需求分析:
在分类管理列表页面,可以对某个分类进行删除操作,需要注意的是,当分类关联了菜品或者套餐时,此分类不允许删除。即菜品和套餐有没有关联上级菜品分类和上级套餐分类,如果有关联,则这个上级菜品分类或上级套餐分类是不允许删除的,没有关联下级的分类才可以被删除。
5、修改分类
菜品管理
1、文件上传和下载
- 文件上传
服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件: commons-fileupload,commons-io
本质都是对流的操作。Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件。
- 文件下载
是指从服务器传输到本地计算机的过程。通过浏览器进行文件下载,通常有两种表现形式:以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录;直接在浏览器中打开(比如直接显示某个菜品的图片)
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程
2、新增菜品
需求分析:
后台管理中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。
一步是做add业务,一步是要根据type查询分类列表
- 新增菜品时,菜品信息存到dish表,而口味则要存到dishFlavor表,因此在写controller时,参数不能直接写Dish,因为flavor字段在dish中并没有。对于这种无法概括所有传来参数的情况,需要另外创建一个dto,可以包括传来的数据的所有字段,将这个作为保存业务的参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-szr7aW2F-1686311832446)(D:\桌面\校园外卖\3.jpg)]
3、更新菜品
同样是对两张表进行操作。
套餐管理
1、新增套餐
- 通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐
- 进入添加套餐页面,前端会回显套餐分类。这是直接请求后后端接口根据type查询分类列表,这个功能之前在开发添加菜品时已经实现。所以这里前端直接调用这个接口,就可以得到已经有的套餐分类
-
2、分页查询
3、删除套餐
删除单个套餐和批量删除套餐,这两种请求的地址和请求方式都是相同的。不同的则是传递的id的个数,所以在服务端可以提供一个方法来统一处理
五、短信服务
1、阿里云短息服务–介绍
阿里云短信服务是广大企业客户快速触达手机用户所优选使用的通信能力,调用API或用群发助手,即可发送验证码、通知类和营销类短信。
应用场景:
- 验证码
- 短信通知
- 推广短信
2、设置短信签名
短信签名是短信发送者的署名,表示发送方的身份
3、设置模板管理
用户登录名称 xqhruige@1388062537589368.onaliyun.com
AccessKey ID LTAI5t7DTMFcMRWLJp4BJArf
AccessKey Secret 6XPca4cbldESNPws1JyuCWL3CNRP1D
4、需求分析
为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。手机验证码登录的优点:
- 方便快捷,无需注册,直接登录
- 使用短信验证码作为登录凭证,无需记忆密码
- 安全
六、用户前台功能开发
十一、菜单列表
十二、购物车
十三、用户下单
十四、后台中订单管理
十五、前后台交互
后台控制订单的状态,例如从已下单到已派送再到已完成,而前台用户端,会显示订单已派送,已完成,当显示已完成,用户端可以直接点击再来一单,可以将订单再次添加到购物车中
七、测试
测试
1、添加员工—添加一个测试账号,测试后面的所有功能 test61 123456
2、移动端—注册一个新号码 15083555837 查看数据库是否正常
3、员工管理
- 新增
- 编辑
- 查询
4、分类管理
- 新增分类—菜品,套餐
- 修改分类
- 删除分类
5、菜品管理
- 批量停售/批量启售
- 批量删除
- 修改
- 新建菜品
6、套餐管理
- 批量停售/启售
- 批量删除
- 修改
- 新建套餐
7、订单管理
- 显示用户端传过来的订单
- 查看订单详情
- 派送
- 完成
8、用户端–查看菜品
- 点击套餐图片可以看到具体的套餐菜品
9、加入购物车
- 添加
- 减少
- 清空
10.下单
11、查看订单,查询订单状态
12、订单已完成,可以再来一单
八、代码优化
通过redis+SpringCache缓存
- 问题:用户数量多,系统访问量大,频繁访问数据库,系统性能下降,用户体验差
- 主要缓存一些需要频繁访问数据库的数据和不会改变的数据:缓存短信验证码、缓存菜品数据、缓存套餐数据
缓存短信验证码
- 将随机生成的验证码缓存到Redis中,并设置有效期为5分钟
- 从Redis中获取缓存的验证码,如果登录成功则删除redis中的验证码
缓存菜品数据
- 问题:服务端每次点击分类,后端都要查询数据库,当访问人多或者频繁点击分类时,需要频繁查询数据库,效率低,查询速度慢。高并发情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。
- 先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询到的菜品数据放入Redis
- 加入清理缓存的逻辑。在使用缓存过程中,要注意保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据
缓存菜品是通过菜品分类和状态来动态创建一个key
通过Spring cache缓存
- Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能
在SpringBoot项目中使用Spring Cache的操作步骤(使用Redis缓存技术)
- 导入maven依赖
- 配置application.yml
- 启动类上加注解
- 在controller中方法上加注解
缓存套餐数据
- 移动端套餐查看功能,对应的服务端方法,此方法会根据前端提交的查询条件进行数据库查询操作,在高并发情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长,现在需要对此方法进行缓存优化,提高系统的性能。
数据库优化
1、读写分离
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈,对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
- 问题:读和写所有压力都由一台数据库承担,压力大,数据库服务器磁盘损坏则数据丢失,单点故障。
2、MySQL主从复制
- 介绍:
MySQL主从复制是一个异步的复制过程,底层是基于MySQL数据库自带的二进制日志功能。就是一台或多台MySQL数据库(slave,从库)从另一台MySQL数据库(master,主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。MySql主从复制是MySQL数据库自带功能,无需借助第三方工具。
- MySQL复制过程分为三步:
master将改变记录到二进制日志
slave将master的binary log拷贝到它的中继日志(relay log)
slave重做中继日志中的事件,将改变应用到自己数据库中
整合Nginx
Nginx是高性能的HTTP和反向代理的web服务器,处理高并发能力十分强大,能经受高负载的考验;其特点是,占有内存少,并发能力强,事实上nginx的并发能力比同类型的网页服务器表现较好。
常用命令
- 在sbin目录下, ./nginx -v 查看nginx的版本号
- sbin目录下,./nginx -t 检查配置文件是否有错误
- sbin目录下,./nginx启动nginx
默认会有这两个进程
- sbin目录下, ./nginx -s stop 暂停nginx
打开nginx,会在logs中生成nginx.pid,里面放的就是运行的nginx的ip
- ./nginx -s reload 重新加载配置文件 ,修改了配置文件时用这个命令
- ps -ef | grep nginx 查看当前运行的nginx
1、静态资源处理
- Nginx可以作为静态web服务器来部署静态资源,静态资源是指服务端真实存在并且能够直接展示的一些文件,比如常见的html页面,css文件,js文件,图片,视频等资源。相对于Tomcat,nginx处理静态资源的能力更加高效,所以在生产环境下,一般都会将静态资源部署到nginx中,只需要将文件复制到Nginx安装目录中即可
listen 是访问nginx的端口号
index就是打开nginx显示的主页
2、反向代理
- 我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,再返回给客户端,此时反向代理服务器和目标服务器就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器的ip地址
- -正向代理 :是一个位于客户端和原始服务器之前的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。正向代理的典型用途是为在防火墙内的局域网客户端提供访问internet的途径----通过访问代理服务器,让代理服务器转发请求给目标服务器,来达到访问目标服务器的目的
- 区别是:正向代理是在客户端设置代理服务器,客户端是知道代理服务器的存在的,需要知道目标服务器的地址;而反向代理,客户端不需要知道目标服务器的地址,也无需在用户端作任何设定,是直接访问反向代理服务器就可以获得目标服务器的资源,代理的是服务端。
理解:正向代理是在客户端设置代理,那么我客户端仍然需要知道我要访问哪个目标服务器,只是说变成了由代理服务器帮我去请求这个目的地址;而反向代理,我只需要访问代理服务器,代理服务器会将目标服务器的东西返回给我,我不需要知道目标服务器的地址
server{
listen 82;
server_name localhost;
location /{
proxy_pass http://192.168.100.101:8080; #反向代理配置,将请求转发到指定服务
}
}
这个设置的意思:将本ip地址端口号82的请求转发到 192.168.180.100:80端口上去。所以现在访问本ip地址端口号82也可以访问 192.168.180.100:80这个ip的服务
- 作用
- 提高访问速度,当经过首次链接后,代理服务器会将此次数据缓存下来,当下次用户来访问时,可以优先在缓存中查找,提高了访问速度
- 充当防火墙的作用:可以在代理服务器上设置规则用来过滤一些不良信息
- 访问受限的站点:原理和vpn一样
- 进行负载均衡:当一台服务器访问量短时间过大到不能承受时就会发生奔溃,所以可以按照需求使用多台服务器组成一个服务器集群来平均分担数据访问的压力,给用户带来良好的体验
- 进行动静分离:使用nginx反向代理功能分发请求:所有动态资源的请求全交给服务器处理,而静态资源的请求则由代理服务器直接返回给用户。PS(nginx处理静态资源的能力远高于tomcat,也远高于其他服务器 )
3、负载均衡
- (基于反向代理)增加服务器的数量,然后将请求分发到各个服务器上,将原先请求集中到单个服务器上的情况改为将请求分发到多个服务器上,将负载分发到不同服务器,也就是我们所说的负载均衡。
- 早期网站流量和业务功能比较简单,单台服务器就可以满足基本需求,但是随着互联网的发展,业务流量越来越大并且业务逻辑也越来越复杂,单台服务器的性能及单点故障问题就凸显出来,因此需要多台服务器组成应用集群,进行性能的水平扩展以及避免单点故障出现
- 应用集群:将同一台应用部署到多台机器上,组成应用集群,接收负载均衡器分发的请求,进行业务处理并返回相应数据
负载均衡器:将用户请求根据对应的负载均衡算法分发到应用集群中的一台服务器进行处理
默认轮询分发请求
也可以设置其他策略:
权重方式 :通过再server后设置 weight=** ,来设置地址的权重。对应非要分发的访问次数多少
依据ip分配方式:ip_hash
依据最少连接方式:哪个服务器连接少多分发
前后端分离开发
开发工具:
- VScode
- hbuilder
技术框架:
- nodeJS
- vue
- elementUI
- mock
- webpack
YApi
-
介绍:
YApi是高效、易用、功能强大的api管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松、发布、维护API,YApi还为用户提供了优秀的交互体验,开发人员只需利用平台接口的接口数据写入工具以及简单的点击操作就可以实现接口的管理。
YApi让接口开发更简单高效,让接口的管理更具可读性、可维护性,让团队协作更合理。
也可以用api
整合Swagger
Swagger
- 介绍
使用swagger你只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,以及在线接口调试页面等等。
- knife4j
由于原生swagger,定义接口是写json文件,比较繁琐。Knife4j是为java mvc框架集成Swagger生成API文档的增强解决方案。
整合JWT
- JWT:它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证;应用场景如用户登录。
- 为什么使用JWT
随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。
- JWT和传统的cookie+session对比
① 在传统的用户登录认证中,因为http是无状态的,所以都是采用session方式。用户登录成功,服务端会保证一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。
cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。
② JWT方式校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录,验证token更为简单。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。JWT最重要的作用就是对 token信息的防伪作用。
部署
管理员通过internet访问nginx,然后nginx将请求转发到tomcat。
- 部署前端
把前端打包,复制到nginx下的html文件下。修改nginx.conf,配置反向代理。将请求nginx这个服务器的转发到后端服务器
- 部署后端