源码在 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,可以包括传来的数据的所有字段,将这个作为保存业务的参数
3、更新菜品
同样是对两张表进行操作。
九、套餐管理业务开发
1、新增套餐
- 通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐
- 进入添加套餐页面,前端会回显套餐分类。这是直接请求后后端接口根据type查询分类列表,这个功能之前在开发添加菜品时已经实现。所以这里前端直接调用这个接口,就可以得到已经有的套餐分类
-
2、分页查询
3、删除套餐
删除单个套餐和批量删除套餐,这两种请求的地址和请求方式都是相同的。不同的则是传递的id的个数,所以在服务端可以提供一个方法来统一处理
十、短信服务
1、阿里云短息服务–介绍
阿里云短信服务是广大企业客户快速触达手机用户所优选使用的通信能力,调用API或用群发助手,即可发送验证码、通知类和营销类短信。
应用场景:
- 验证码
- 短信通知
- 推广短信
2、设置短信签名
短信签名是短信发送者的署名,表示发送方的身份
3、设置模板管理
4、需求分析
为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。手机验证码登录的优点:
- 方便快捷,无需注册,直接登录
- 使用短信验证码作为登录凭证,无需记忆密码
- 安全
十一、菜单列表
十二、购物车
十三、用户下单
十四、后台中订单管理
十五、前后台交互
后台控制订单的状态,例如从已下单到已派送再到已完成,而前台用户端,会显示订单已派送,已完成,当显示已完成,用户端可以直接点击再来一单,可以将订单再次添加到购物车中
十六、
测试
1、添加员工—添加一个测试账号,测试后面的所有功能 test61 123456
2、移动端—注册一个新号码 15083555837 查看数据库是否正常
3、员工管理
- 新增
- 编辑
- 查询
4、分类管理
- 新增分类—菜品,套餐
- 修改分类
- 删除分类
5、菜品管理
- 批量停售/批量启售
- 批量删除
- 修改
- 新建菜品
6、套餐管理
- 批量停售/启售
- 批量删除
- 修改
- 新建套餐
7、订单管理
- 显示用户端传过来的订单
- 查看订单详情
- 派送
- 完成
8、用户端–查看菜品
- 点击套餐图片可以看到具体的套餐菜品
9、加入购物车
- 添加
- 减少
- 清空
10.下单
11、查看订单,查询订单状态
12、订单已完成,可以再来一单
代码优化
- 问题:用户数量多,系统访问量大,频繁访问数据库,系统性能下降,用户体验差
- 主要缓存一些需要频繁访问数据库的数据和不会改变的数据:缓存短信验证码、缓存菜品数据、缓存套餐数据
通过redis缓存
缓存短信验证码
- 将随机生成的验证码缓存到Redis中,并设置有效期为5分钟
- 从Redis中获取缓存的验证码,如果登录成功则删除redis中的验证码
缓存菜品数据
- 问题:服务端每次点击分类,后端都要查询数据库,当访问人多或者频繁点击分类时,需要频繁查询数据库,效率低,查询速度慢。高并发情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。
- 先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询到的菜品数据放入Redis
- 加入清理缓存的逻辑。在使用缓存过程中,要注意保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据
缓存菜品是通过菜品分类和状态来动态创建一个key
通过Spring cache缓存
- Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能
在SpringBoot项目中使用Spring Cache的操作步骤(使用Redis缓存技术)
- 导入maven依赖
- 配置application.yml
- 启动类上加注解
- 在controller中方法上加注解
缓存套餐数据
- 移动端套餐查看功能,对应的服务端方法,此方法会根据前端提交的查询条件进行数据库查询操作,在高并发情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长,现在需要对此方法进行缓存优化,提高系统的性能。
二、数据库优化
读写分离
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈,对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
- 问题:读和写所有压力都由一台数据库承担,压力大,数据库服务器磁盘损坏则数据丢失,单点故障。
MySQL主从复制
- 介绍:
MySQL主从复制是一个异步的复制过程,底层是基于MySQL数据库自带的二进制日志功能。就是一台或多台MySQL数据库(slave,从库)从另一台MySQL数据库(master,主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。MySql主从复制是MySQL数据库自带功能,无需借助第三方工具。
- MySQL复制过程分为三步:
master将改变记录到二进制日志
slave将master的binary log拷贝到它的中继日志(relay log)
slave重做中继日志中的事件,将改变应用到自己数据库中
三、Nginx
- 静态资源处理
- 反向代理
- 负载均衡
四、前后端分离开发
开发流程:
前端技术栈:
开发工具:
- VScode
- hbuilder
技术框架:
- nodeJS
- vue
- elementUI
- mock
- webpack
YApi
-
介绍:
YApi是高效、易用、功能强大的api管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松、发布、维护API,YApi还为用户提供了优秀的交互体验,开发人员只需利用平台接口的接口数据写入工具以及简单的点击操作就可以实现接口的管理。
YApi让接口开发更简单高效,让接口的管理更具可读性、可维护性,让团队协作更合理。
也可以用api
Swagger
- 介绍
使用swagger你只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,以及在线接口调试页面等等。
- knife4j
是为java mvc框架集成Swagger生成API文档的增强解决方案。