苍穹外卖学习文档
软件开发整体介绍
软件开发流程
需求分析
需求规格说明书、产品原型
设计
UI设计、数据库设计、接口设计
编码
项目代码、单元测试
测试
测试用例、测试报告
上线运维
软件环境安装、配置
角色分工
-
项目经理
对整体项目负责,任务分配、把控进度
-
产品经理
进行需求调研。输出需求调研文档、产品原型等
-
UI设计师
根据产品模型输出界面效果图
-
架构师
项目整体架构设计、技术选型等
-
开发工程师
代码实现
-
测试工程师
编写测试用例,输出测试报告
-
运维工程师
软件环境搭建、项目上线
软件环境
开发环境
开发人员在开发阶段使用的环境,一般外部用户无法访问
测试环境
专门给测试人员使用的环境,用于测试项目,一般外部用户无法访问
生产环境
即线上环境,正式提供对外服务的环境
苍穹外卖项目介绍
项目介绍
定位:专门为餐饮企业定制的一款软件产品
功能架构:
产品原型
用于展示项目的业务功能
技术选型
展示项目中使用到的技术框架和中间件等
开发环境搭建
前端环境搭建
整体结构
通过Nginx代理
后端环境搭建
熟悉项目结构
sky-common子模块
- constant:常量类
- context:项目上下文相关
- enumeration:枚举类
- exception:自定义异常类
- json:处理json转换
- properties:springboot配置属性类,把配置文件中的配置项封装成对象
- result:后端返回的结果
- utils:工具类
sky-pojo子模块
sky-server子模块
存放的是 配置文件、配置类、拦截器、controller、service、mapper、启动类等
使用Git进行版本控制
- 创建Git本地仓库
- 创建Git远程仓库
- 将本地文件推送到Git远程仓库
数据库环境搭建
前后端联调
Nginx🆕
反向代理,就是让前端发送的动态请求由Nginx转发到后端服务器
Nginx反向代理的好处
-
提高访问速度
-
进行负载均衡
就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器
-
保证后端服务安全
Nginx反向代理的配置方式
Nginx负载均衡的配置方式
Nginx负载均衡策略
轮询:平均接收到请求
完善登录功能
- 修改数据库中的明文密码,改为MD5加密后的密文
- 修改Java代码,前端提交的密码进行MD5加密后再跟数据库中密码比对
导入接口文档
前后端分离开发流程
操作步骤
这里YApi可换成ApiPost,导入数据选择YApi即可
Swagger
介绍
使用方式
-
导入knife4j的maven坐标
-
在配置类中加入knife4j相关配置
/*** 通过knife4j生成接口文档* @return*/@Beanpublic Docket docket() {ApiInfo apiInfo = new ApiInfoBuilder().title("苍穹外卖项目接口文档").version("2.0").description("苍穹外卖项目接口文档").build();Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo).select()//指定生成接口需要扫描的包.apis(RequestHandlerSelectors.basePackage("com.sky.controller")).paths(PathSelectors.any()).build();return docket;}
-
设置静态资源映射,否则接口文档页面无法访问
/*** 设置静态资源映射* @param registry*/protected void addResourceHandlers(ResourceHandlerRegistry registry) {log.info("开始设置静态资源映射...");registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}
常用注解
通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:
员工管理、分类管理
员工管理界面
分类管理界面
新增员工
需求分析和设计
产品原型
接口设计
本项目约定:
- 管理端发出的请求,统一使用
/admin
作为前缀 - 用户端发出的请求,统一使用
/user
作为前缀
数据库设计
employee表为员工表,用于存储商家内部的员工信息。具体表结构如下:
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增⭐ |
name | varchar(32) | 姓名 | |
username | varchar(32) | 用户名 | 唯一⭐ |
password | varchar(64) | 密码 | |
phone | varchar(11) | 手机号 | |
sex | varchar(2) | 性别 | |
id_number | varchar(18) | 身份证号 | |
status | int | 账号状态 | 1正常 0锁定⭐ |
create_time | datetime | 创建时间 | |
update_time | datetime | 最后修改时间 | |
create_user | bigint | 创建人id | |
update_user | bigint | 最后修改人id |
代码开发
根据新增员工接口设计对应的DTO
注意:当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据
/*** 新增员工* @param employeeDTO*/public void save(EmployeeDTO employeeDTO) {Employee employee = new Employee();//对象属性拷贝,属性名必须一致BeanUtils.copyProperties(employeeDTO, employee);//设置账号的状态,默认正常状态,1表示正常,0表示锁定employee.setStatus(StatusConstant.ENABLE);//设置密码,默认密码为123456employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));//设置当前记录的创建时间和修改时间employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());//设置当前记录的创建人id和修改人id// TODO 后期需要改为当前登录的用户idemployee.setCreateUser(10L);employee.setUpdateUser(10L);employeeMapper.insert(employee);}
功能测试
- 通过Swagger接口文档测试
- 通过前后端联调测试
注意:由于开发阶段前端和后端是并行开发的,后端完成某个功能后,此时前端对应的功能可能还没有开发完成,导致无法进行前后端联调测试。所以在开发阶段,后端测试主要以接口文档测试为主。
代码完善
程序存在的问题:
- 录入的用户名已存在,抛出异常后没有处理
- 新增员工时,创建人id和修改人id设置为了固定值
解析出登录员工id后,如何传递给Service的save方法?
员工分页查询
需求分析和设计
产品原型
业务规则:
- 根据每页展示员工信息
- 每页展示10条数据
- 分页查询时可以根据需要,输入员工姓名进行查询
接口设计
代码开发
员工信息分页查询后端返回的对象类型为:Result<PageResult>
mybatis提供的分页查询框架pagehelper
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>${pagehelper}</version>
</dependency>
功能测试
可以通过接口文档进行测试,也可以进行前后端联调测试。
代码完善
最后操作时间需要修改成年月日
解决方式:
-
方式一:在属性上加入注解,对日期进行格式化
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime
-
方式二:在Webconfiguration中扩展SpringMvc的消息转换器,统一对日期类型进行格式化处理
启用禁用员工账号
需求分析和设计
产品原型
业务规则:
- 可以对状态为“启用”的员工账号进行“禁用”操作
- 可以对状态为“禁用”的员工账号进行“启用”操作
- 状态为“禁用”的员工账号不能登录系统
接口设计
代码开发
/*** 启用/禁用员工账号* @param status* @param id* @return*/@PostMapping("status/{status}")@ApiOperation("启用/禁用员工账号")public Result startOrStop(@PathVariable("status") Integer status, Long id) {log.info("启用/禁用员工账号:{}, {}", status, id);employeeService.startOrStop(status, id);return Result.success();}
/*** 启用/禁用员工账号* @param status* @param id*/public void startOrStop(Integer status, Long id) {// update employee set status = ? where id = ?/*Employee employee = new Employee();employee.setStatus(status);employee.setId(id);*/Employee employee = Employee.builder().status(status).id(id).build();employeeMapper.update(employee);}
<update id="update" parameterType="Employee">update employee<set><if test="name != null">name = #{name},</if><if test="username != null">username = #{username},</if><if test="password != null">password = #{password},</if><if test="phone != null">phone = #{phone},</if><if test="sex != null">sex = #{sex},</if><if test="idNumber != null">id_Number = #{idNumber},</if><if test="updateTime != null">update_Time = #{updateTime},</if><if test="updateUser != null">update_User = #{updateUser},</if><if test="status != null">status = #{status},</if></set>where id = #{id}</update>
功能测试
编辑员工
需求分析和设计
产品原型
编辑员工功能涉及到两个接口:
- 根据id查询员工信息
- 编辑员工信息
代码开发
/*** 编辑员工信息* @param employeeDTO* @return*/@PutMapping@ApiOperation("编辑员工信息")public Result update(@RequestBody EmployeeDTO employeeDTO) {log.info("编辑员工信息: {}", employeeDTO);employeeService.update(employeeDTO);return Result.success();}
/*** 编辑员工信息* @param employeeDTO*/public void update(EmployeeDTO employeeDTO) {Employee employee = new Employee();BeanUtils.copyProperties(employeeDTO, employee);employee.setUpdateTime(LocalDateTime.now());employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.update(employee);}
功能测试
导入分类模块功能代码
需求分析和设计
产品原型
业务规则:
- 分类名称必须是唯一的
- 分类按照类型可以分为菜品分类和套餐分类
- 新添加的分类状态默认为禁用
接口设计:
- 新增分类
- 分类分页查询
- 根据id删除分类
- 修改分类
- 启用禁用分类
- 根据类型查询分类
数据库设计(category表):
category表为分类表,用于存储商品的分类信息。具体表结构如下
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增 |
name | varchar(32) | 分类名称 | 唯一 |
type | int | 分类类型 | 1菜品分类 2套餐分类 |
sort | int | 排序字段 | 用于分类数据的排序 |
status | int | 状态 | 1启用 0禁用 |
create_time | datetime | 创建时间 | |
update_time | datetime | 最后修改时间 | |
create_user | bigint | 创建人id | |
update_user | bigint | 最后修改人id |
代码导入
功能测试
菜品管理
公共字段自动填充🌟
问题分析
业务表中的公共字段:
问题:代码冗余、不便于后期维护
实现思路
- 自定义注解
AutoFill
,用于标识需要进行公共字段自动填充的方法 - 自定义切面类
AutoFillAspect
,统一拦截加入了AutoFill
注解的方法,通过反射为公共字段赋值 - 在Mapper的方法上加入
AutoFill
注解
代码开发
功能测试
新增菜品
需求分析和设计
产品原型:
业务规则:
- 菜品名称必须是唯一的
- 菜品必须属于某个分类下,不能单独存在
- 新增菜品时可以根据情况选择菜品的口味
- 每个菜品必须对应一张图片
接口设计:
-
根据类型查询分类(已完成)
-
文件上传
-
新增菜品
数据库设计(dish菜品表和dish_flavor口味表):
dish表为菜品表,用于存储菜品的信息。具体表结构如下
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增 |
name | varchar(32) | 菜品名称 | 唯一 |
category_id | bigint | 分类id | 逻辑外键 |
price | decimal(10,2) | 菜品价格 | |
image | varchar(255) | 图片路径 | |
description | varchar(255) | 菜品描述 | |
status | int | 售卖状态 | 1起售 0停售 |
create_time | datetime | 创建时间 | |
update_time | datetime | 最后修改时间 | |
create_user | bigint | 创建人id | |
update_user | bigint | 最后修改人id |
dish_flavor表为菜品口味表,用于存储菜品的口味信息。具体表结构如下
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增 |
dish_id | bigint | 菜品id | 逻辑外键 |
name | varchar(32) | 口味名称 | |
value | varchar(255) | 口味值 |
代码开发
开发文件上传接口:
功能测试
菜品分页查询
需求分析和设计
产品原型
业务规则
- 根据页码展示菜品信息
- 每页展示10条数据
- 分页查询时可以根据需要输入菜品名称、菜品分类、菜品状态进行查询
接口设计
代码开发
根据菜品分页查询接口定义设计对应的DTO:
根据菜品分页查询接口定义设计对应的VO:
功能测试
删除菜品
需求分析和设计
产品原型
业务规则:
- 可以一次删除一个菜品,也可以批量删除菜品
- 起售中的菜品不能删除
- 被套餐关联的菜品不能删除
- 删除菜品后,关联的口味数据也需要删除掉
接口设计:
数据库设计:
代码开发
功能测试
修改菜品
需求分析和设计
产品原型
接口设计:
-
根据id查询商品
-
根据类型查询分类(已实现)
-
文件上传(已实现)
-
修改商品
代码开发
功能测试
店铺营业状态设置
Redis入门
Redis简介
Redis是一个基于内存的key-value结构数据库。
- 基于内存存储,读写性能高
- 适合存储热点数据(热点商品、资讯、新闻)
- 企业应用广泛
官网:https://redis.io/
中文网:https://www.redis.net.cn/
Redis下载与安装
Redis服务启动与停止
Redis数据类型
5种常用数据类型介绍
Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:
-
字符串 string
普通字符串,Redis中最简单的数据类型
-
哈希 hash
也叫散列,类似于Java中的HashMap结构
-
列表 list
按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList
-
集合 set
无序集合,没有重复元素,类似于Java中的HashSet
-
有序集合 sorted set/zset
集合中每个元素关联一个分数(score),Redis根据分数升序排序,没有重复元素
各种数据类型的特点
Redis常用命令
字符串操作命令
- SET key value 设置指定key的值
- GET key 获取指定key的值
- SETEX key seconds value 设置指定key的值,并将key的过期时间设为seconds秒 ---> 短信验证码
- SETNX key value 只有在key不存在时设置key的值
哈希操作命令
Redis hash 是一个String类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:
- HSET key field value 将哈希表 key 中的字段 field 的值为 value
- HGET key field 获取存储在哈希表中指定字段的值
- HDEL key field 删除存储在哈希表中的指定字段
- HKEYS key 获取哈希表中所有字段
- HVALS key 获取哈希表中所有值
列表操作命令
Redis列表是简单的字符串列表,按照插入顺序排序,常用命令:
- LPUSH key value1 [value2] 将一个或多个值插入到列表头部
- LRANGE key start stop 获取列表指定范围内的元素
- RPOP key 移除并获取列表最后一个元素
- LLEN key 获取列表长度
集合操作命令
Redis set 是 String 类型的无序集合。集合成员是唯一的,集合中不能出现重复的数据,常用命令:
- SADD key member1 [member2] 向集合添加一个或多个成员
- SMEMBERS key 返回集合中所有的成员
- SCARD key 获取集合的成员数
- SINTER key1 [key2] 返回给定的所有集合的交集
- SUNION key1 [key2] 返回所有给定集合的并集
- SREM key member1 [member2] 删除集合中一个或多个成员
有序集合操作命令
Redis的有序集合是String类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数。常用命令:
- ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员
- ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合中指定区间内的成员
- ZINCRBY key increment member 有序集合中对指定成员的分数加上增量increment
- ZREM key member [member... ] 移除有序集合中的一个或多个成员
通用命令
Redis的通用命令是不分数据类型的,都可以使用的命令:
- KEYS pattern 查找所有符合给定模式(pattern)的key
- EXISTS key 检查给定key是否存在
- TYPE key 返回key所存储的值的类型
- DEL key 该命令用于在key存在时删除key
在Java中操作Redis
Redis的Java客户端
Redis的Java客户端很多,常用的几种:
-
Jedis
-
Lettuce
-
Spring Data Redis
是Spring的一部分,对Redis底层开发包进行了高度封装。在Spring项目中,可以使用Spring Data Redis来简化操作。
Spring Data Redis使用方式
操作步骤:
-
导入Spring Data Redis 的Maven坐标
-
配置Redis数据源
-
编写配置类,创建RedisTemplate对象
-
通过RedisTemplate对象操作Redis
店铺营业状态
需求分析和设计
产品原型
接口设计:
- 设置营业状态
- 管理端查询营业状态
- 用户端查询营业状态
本项目约定:
- 管理端发出的请求,统一使用
/admin
作为前缀 - 用户端发出的请求,统一使用
/user
作为前缀
营业状态数据存储方式:基于Redis的字符串来进行存储