一、项目介绍&Github
线上点单小程序和后台管理系统 后端,Github项目地址:springboot-order-out
1. 管理端
模块 | 描述 |
---|---|
登录/退出 | 内部员工必须登录后,才可以访问系统管理后台 |
员工管理 | 管理员可以在系统后台对员工信息进行管理,包含查询、新增、编辑、禁用等功能 |
分类管理 | 主要对当前餐厅经营的 菜品分类 或 套餐分类 进行管理维护, 包含查询、新增、修改、删除等功能 |
菜品管理 | 主要维护各个分类下的菜品信息,包含查询、新增、修改、删除、启售、停售等功能 |
套餐管理 | 主要维护当前餐厅中的套餐信息,包含查询、新增、修改、删除、启售、停售等功能 |
订单管理 | 主要维护用户在移动端下的订单信息,包含查询、取消、派送、完成,以及订单报表下载等功能 |
数据统计 | 主要完成对餐厅的各类数据统计,如营业额、用户数量、订单等 |
2. 用户端
模块 | 描述 |
---|---|
登录/退出 | 用户需要通过微信授权后登录使用小程序进行点餐 |
点餐-菜单 | 在点餐界面需要展示出菜品分类/套餐分类, 并根据当前选择的分类加载其中的菜品信息, 供用户查询选择 |
点餐-购物车 | 用户选中的菜品就会加入用户的购物车, 主要包含 查询购物车、加入购物车、删除购物车、清空购物车等功能 |
订单支付 | 用户选完菜品/套餐后, 可以对购物车菜品进行结算支付, 这时就需要进行订单的支付 |
个人信息 | 在个人中心页面中会展示当前用户的基本信息, 用户可以管理收货地址, 也可以查询历史订单数据 |
二、技术选型
1). 用户层
本项目中在构建系统管理后台的前端页面,我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时,我们会使用到微信小程序。
2). 网关层
Nginx是一个服务器,主要用来作为Http服务器,部署静态资源,访问性能高。在Nginx中还有两个比较重要的作用: 反向代理和负载均衡, 在进行项目部署时,要实现Tomcat的负载均衡,就可以通过Nginx来实现。
3). 应用层
SpringBoot: 快速构建Spring项目, 采用 “约定优于配置” 的思想, 简化Spring项目的配置开发。
SpringMVC:SpringMVC是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合,可以无缝集成。
Spring Task: 由Spring提供的定时任务框架。
httpclient: 主要实现了对http请求的发送。
Spring Cache: 由Spring提供的数据缓存框架
JWT: 用于对应用程序上的用户进行身份验证的标记。
阿里云OSS: 对象存储服务,在项目中主要存储文件,如图片等。
Swagger: 可以自动的帮助开发人员生成接口文档,并对接口进行测试。
POI: 封装了对Excel表格的常用操作。
WebSocket: 一种通信网络协议,使客户端和服务器之间的数据交换更加简单,用于项目的来单、催单功能实现。
4). 数据层
MySQL: 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。
Redis: 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。
Mybatis: 本项目持久层将会使用Mybatis开发。
pagehelper: 分页插件。
spring data redis: 简化java代码操作Redis的API。
5). 工具
git: 版本控制工具, 在团队协作中, 使用该工具对项目中的代码进行管理。
maven: 项目构建工具。
junit:单元测试工具,开发人员功能实现完毕后,需要通过junit对功能进行单元测试。
postman: 接口测工具,模拟用户发起的各类HTTP请求,获取对应的响应结果。
三、开发环境搭建
后端模块:
序号 | 名称 | 说明 |
---|---|---|
1 | sky-take-out | maven父工程,统一管理依赖版本,聚合其他子模块 |
2 | sky-common | 子模块,存放公共类,例如:工具类、常量类、异常类等 |
3 | sky-pojo | 子模块,存放实体类、VO、DTO等 |
4 | sky-server | 子模块,后端服务,存放配置文件、Controller、Service、Mapper等 |
- sky-common: 模块中存放的是一些公共类,可以供其他模块使用
名称 | 说明 |
---|---|
constant | 存放相关常量类 |
context | 存放上下文类 |
enumeration | 项目的枚举类存储 |
exception | 存放自定义异常类 |
json | 处理json转换的类 |
properties | 存放SpringBoot相关的配置属性类 |
result | 返回结果类的封装 |
utils | 常用工具类 |
- sky-pojo: 模块中存放的是一些 entity、DTO、VO
名称 | 说明 |
---|---|
Entity | 实体,通常和数据库中的表对应 |
DTO | 数据传输对象,通常用于程序中各层之间传递数据 |
VO | 视图对象,为前端展示数据提供的对象 |
POJO | 普通Java对象,只有属性和对应的getter和setter |
- sky-server: 模块中存放的是 配置文件、配置类、拦截器、controller、service、mapper、启动类等
名称 | 说明 |
---|---|
config | 存放配置类 |
controller | 存放controller类 |
interceptor | 存放拦截器类 |
mapper | 存放mapper接口 |
service | 存放service类 |
SkyApplication | 启动类 |
- 数据库:
序号 | 表名 | 中文名 |
---|---|---|
1 | employee | 员工表 |
2 | category | 分类表 |
3 | dish | 菜品表 |
4 | dish_flavor | 菜品口味表 |
5 | setmeal | 套餐表 |
6 | setmeal_dish | 套餐菜品关系表 |
7 | user | 用户表 |
8 | address_book | 地址表 |
9 | shopping_cart | 购物车表 |
10 | orders | 订单表 |
11 | order_detail | 订单明细表 |
四、员工管理
4.1 新增员工
原型:
接口设计:
① sql
sql 不要写错,插入字段和values属性一一对应,否则无法插入数据。
/**
* 插入员工数据
* @param employee
* @return
*/
@Insert("insert into employee(name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user,status)" +
" VALUES" +
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status}) ")
void insert(Employee employee);
yml文件中设置驼峰命名所以插入字段中不需要起别名
② 对象拷贝 DTO 与 Entity
DTO是根据请求参数建立的实体类,在与原实体有较大差别时候,另外建立一个实体,用来接收请求参数
使用DTO接收前端请求数据,之后 再用Entity实体类封装,使用对象拷贝,剩下的数据手动SET设置。
③ 异常捕获与处理
当录入的用户名已存在,抛出了异常,但是没有处理
全局异常处理 GlobalExceptionHandler
添加对报错异常的处理方法:
查看是否包含特定异常信息,再截取报错消息,返回给前端显示。
/**
* 重复数据 Duplicate entry 异常
*
* @param ex SQLIntegrityConstraintViolationException
* @return
*/
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
log.error("异常信息:{}", ex.getMessage());
//Duplicate entry 'dougwake' for key 'employee.idx_username'
String message = ex.getMessage();
if (message.contains("Duplicate entry")) {
String[] split = message.split(" ");
String username = split[2];
String msg = username + MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
}
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
捕获异常,并且处理:
④ 动态获取当前登录者Id
新增员工时,创建人id和修改人id设置为固定值
如何动态获取当前登录员工的Id?
- 新增员工时,需要确定操作者的ID:(当前登录Id)
- jwt,生成使用流程:
- 用户登录,生成token,把用户信息(Id)也存放进去,然后响应给前端
- 除登录请求外,其他请求都要进入拦截器。
- 所以,在拦截器解析token中拿到这次登录的用户Id存储,由service层获取Id,赋值给属性:
- 如何存储,以及怎样传递给service层的save方法使用?
- 通过
ThreadLocal
进行传递。 - 项目使用工具类BaseContext
- 通过
BaseContext.setCurrentId(empId); //存入id
Long currentId = BaseContext.getCurrentId();//获取Id
⑤ ThreadLocal
介绍:
- ThreadLocal 并不是一个Thread,而是Thread的局部变量。
- ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
- 举例:点击一次新增用户的功能,即发送一次请求,这就是一个线程。
- 再点击一次,就是再次发送一次请求,又是一个线程。
- 每次发送请求中每个线程都是独立的,都有独立的存储空间。
常用方法:
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
- public void remove() 移除当前线程的线程局部变量
注意:客户端发送的每次请求,后端的Tomcat服务器都会分配一个单独的线程来处理请求
测试:
//测试执行一次请求中,当前线程ID
System.out.println(Thread.currentThread().getId());
两次请求的线程Id是不同的:
4.2 员工分页查询
原型:
接口:
① 请求参数实体 与 响应数据实体
EmployeePageQueryDTO
实体用来接收请求参数:
PageResult
实体用于返回数据:
② controller层
- get请求方式 不需要 @RequestBody
- 返回的对象类型为:
Result<PageResult>
③ service层 使用pageHelper分页
- pageHelper 底层是拦截器
- 拼接limit
/**
* 员工分页查询
* @param employeePageQueryDTO
* @return
*/
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
Page<Employee> employeePage = employeeMapper.pageQuery(employeePageQueryDTO);
long total = employeePage.getTotal();//获取总数
List<Employee> records = employeePage.getResult();//获取员工数据
return new PageResult(total,records);
}
④ 模糊查询sql 使用 concat()
模糊查询
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from employee
<where>
<if test="name != null and name!=''">
and name like concat('%',#{name},'%')
</if>
</where>
order by create_time desc
</select>
④ 时间格式问题
解决方式一:
-
在属性上加上注解,对日期进行格式化
-
但这种方式,需要在每个时间属性上都要加上该注解,使用较麻烦,不能全局处理。
//@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
解决方式二:
- 在springmvc配置类
WebMvcConfiguration
中扩展SpringMVC的消息转换器,统一对日期类型进行格式处理
/**
* 扩展Spring MVC框架的消息转化器
* @param converters
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转化器加入容器中
//0表示优先使用自己的消息转化器
converters.add(0,converter);
}
时间格式定义,sky-common模块中的
JacksonObjectMapper()
:对象映射器,工具类,固定写法
结果:
4.3 启用、禁用员工账号
原型:
业务规则:
- 可以对状态为“启用” 的员工账号进行“禁用”操作
- 可以对状态为“禁用”的员工账号进行“启用”操作
- 状态为“禁用”的员工账号不能登录系统
接口:
① Result 工具类使用
- 查询的情况 : 加上泛型,实体类
- 非查询:不需要加泛型
controller,非查询,更新操作,所以不用加泛型实体
/**
* 启用、禁用员工账号
* @return
*/
@ApiOperation("启用、禁用员工账号")
@PostMapping("status/{status}")
public Result startOrStop(@PathVariable Integer status,Long id){
log.info("启用、禁用员工账号:{},{}",status,id);
employeeService.startOrStop(status,id);
return Result.success();
}
② 创建对象的不同方式 build
方式一:原始
正常创建对象:
// 原始方法构建对象
Employee employee = new Employee();
employee.setStatus(status);
employee.setId(id);
方式二:build
//使用build的方法构建对象
Employee employee = Employee.builder()
.id(id)
.status(status)
.build();
需要在被构建的类上 加@build注解
③ 更新操作
通过创建实体对象,set里面的值,再传递实体对象:
/**
* 启用、禁用员工账号
*
* @param status 状态
* @param id 根据ID修改状态
*/
@Override
public void startOrStop(Integer status, Long id) {
//update emp set status=? where id=?
//可以写成动态sql语句,根据Id修改不同的属性字段
//使用build的方法构建对象
Employee employee = Employee.builder()
.id(id)
.status(status)
.build();
employeeMapper.update(employee);
}
④ 动态SQL
来自不同的方法 (修改不同值) 调用,但是使用同一个sql语句。
/**
* 通用sql
* 根据Id修改员工信息
* @param employee
*/
void update(Employee employee);
更新操作 update,通用sql
<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>
4.4 编辑员工
编辑员工信息(根据Id查询员工实现回显数据,根据回显数据修改员工信息)
原型:
接口:
编辑员工功能涉及到两个接口:
- 根据id查询员工信息 (回显)查看具体接口YApi
- 编辑员工信息 查看具体接口YApi
① 编辑员工需要设置修改人与时间
/**
* 编辑员工信息
* @param employeeDTO
*/
@Override
public void update(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO,employee);
//设置修改人和修改时间
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.update(employee);
}
使用之前定义的update通用动态sql,根据Id修改员工信息
② 编辑员工请求的参数
可以用EmployeeDTO接收,再对象拷贝
五、分类管理
原型:
业务规则:
- 分类名称必须是唯一的
- 分类按照类型可以分为菜品分类和套餐分类
- 新添加的分类状态默认为“禁用”
接口:
菜品分类模块共涉及6个接口。
- 新增分类
- 分类分页查询
- 根据id删除分类
- 修改分类
- 启用禁用分类
- 根据类型查询分类
数据库表: ( category 表)
5.1 新增分类
接口根据页面原型:
请求参数包括: post
新建菜品分类/新建套餐分类,1菜品分类 2套餐分类,前端根据点的按钮不同,会自动返回1/2
5.2 分类分页查询
Get 请求: /admin/category/page
条件:
5.3 启用、禁用分类
- Path: /admin/category/status/{status}
- Method: POST
update动态sql
<update id="update">
update category
<set>
<if test="type != null">
type = #{type},
</if>
<if test="name != null">
name = #{name},
</if>
<if test="sort != null">
sort = #{sort},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="updateUser != null">
update_user = #{updateUser}
</if>
</set>
where id=#{id}
</update>
5.4 根据类型查询分类
- Path: /admin/category/list
- Method: GET
<select id="list" resultType="com.sky.entity.Category">
select * from category
where status = 1
<if test="type != null">
and type = #{type}
</if>
order by sort asc,create_time desc
</select>
用于其他模块使用,根据类型查询旗下分类品项
5.5 修改分类
put 请求 @RequestBody,使用动态sql update
5.6 根据id删除分类
- 删除分类前,需要判断分类下是否有菜品和套餐,有就报异常不能删除。
- 涉及到其他表的查询
/**
* 根据id删除分类
*
* @param id
*/
@Override
public void deleteById(Long id) {
Integer count = dishMapper.countByCategoryId(id);
if (count != null && count > 0) {
throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_DISH);
}
count=setmealMapper.countByCategoryId(id);
if (count != null && count > 0){
throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_SETMEAL);
}
categoryMapper.deleteById(id);
}
六、公共字段自动填充
问题:两个类都出现要修改或新增时,设置时间/人的属性(公共字段)
使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。
6.1 实现步骤:
-
自定义注解
AutoFill
,用于标识需要进行公共字段自动填充的方法- 自定义注解用于指定方法,区别于execution用于指定某包下的类
- 只有在某包下的类,并且用注解指定的方法,执行切面方法
-
自定义切面类
AutoFillAspect
,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值 -
在 Mapper 的方法上加入
@AutoFill
注解
6.2 代码实现:
自定义注解:
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();
}
切面类:
- 获取对象方法签名(定义)
- 拿到方法上的注解
- 拿到方法参数 (实体类)
- 根据注解参数定义的不同操作,对方法参数进行 反射赋值
package com.sky.aspect;
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut() {
}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {
//可先进行调试,是否能进入该方法 提前在mapper方法添加AutoFill注解
log.info("开始进行公共字段自动填充...");
//获取到当前被拦截的方法上的数据库操作类型
//方法签名对象(方法的定义 -> void xxx(xx xx);
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
System.out.println(signature);//void com.sky.mapper.EmployeeMapper.update(Employee)
//获得方法上的注解对象 @AutoFill(value=xxx)
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
System.out.println(autoFill);//@com.sky.annotation.AutoFill(UPDATE)
//拿到注解@AutoFill(value=xxx) 里面的value数值
OperationType operationType = autoFill.value();
//获取到当前被拦截的方法的参数--实体对象
//防止参数为空
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
//拿到参数
Object entity = args[0];
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据当前不同的操作类型,为对应的属性通过反射来赋值
//根据拿到的实体类参数进行反射赋值
if (operationType == OperationType.INSERT) {
try {
//为4个公共字段赋值
Method setCreateTime = entity.getClass()
.getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass()
.getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass()
.getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass()
.getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity, now);
setCreateUser.invoke(entity, currentId);
setUpdateTime.invoke(entity, now);
setUpdateUser.invoke(entity, currentId);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
} else if (operationType == OperationType.UPDATE) {
try {
Method setUpdateTime = entity.getClass()
.getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass()
.getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
setUpdateTime.invoke(entity, now);
setUpdateUser.invoke(entity, currentId);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
Mapper的update/insert方法都加上注解 @AutoFill
,注释service方法中之前设置的公共字段赋值代码。
同时,将业务层为公共字段赋值的代码注释掉。
1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。
2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。
七、菜品管理
7.1 新增菜品
7.1.1 原型分析&&接口设计&&数据库表
7.1.2 上传文件
controller:
/**
* 通用接口
*/
@RestController
@Slf4j
@Api(tags = "通过接口")
@RequestMapping("/admin/common/") //注意复制过来不要有空格
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
/**
* 文件上传
* @param file
* @return
* @throws IOException
*/
@ApiOperation("文件上传")
@PostMapping("/upload")
public Result<String> upload(MultipartFile file){
log.info("文件上传:{}",file);
try {
// UUID设置上传文件名
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileName = UUID.randomUUID()+extension;
String filePath = aliOssUtil.upload(file.getBytes(),fileName);
return Result.success(filePath);
} catch (IOException e) {
log.error("文件上传失败:{}",e);
}
return Result.error(MessageConstant.UPLOAD_FAILED);
}
}
上传文件的接口测试只能用 Postman 或是 前后端联调,swagger不能用。
7.1.3 新增菜品
- 涉及到多表增删改操作 service层要加上
@Transactional
开启事务管理@AutoFill
注解不要乱加,注意看实体类中是否需要加这个注解
- service:菜品表 与 口味表 一对多关系
- dishmapper.xml设置 :
useGeneratedKeys
用于返回主键数据
- 批量插入 使用
<foreach collection=
<mapper namespace="com.sky.mapper.DishFlavorMapper">
<insert id="insertBatch">
insert into dish_flavor(dish_id, name, value)
values
<foreach collection="flavors" item="df" separator=",">
(#{df.dishId},#{df.name},#{df.value})
</foreach>
</insert>
</mapper>
7.2 菜品分页查询
7.2.1 原型
7.2.1 接口设计
7.2.2 代码实现
dishmapper.xml :
- 可以先在MySQL图形化工具里面,先写完sql运行,再复制到IDEA填充修改
<if test="categoryId!=null and categoryId!=''">and d.category_id=#{categoryId}
- 中的 test=“xxx” 是表示属性名字,不要写成数据库的列名
<select id="pageQuery" resultType="com.sky.entity.Dish">
SELECT d.*,c.`name` AS categoryName FROM dish d LEFT JOIN category c ON d.category_id = c.id
<where>
<if test="name!=null and name!=''">and d.name like concat('%',#{name},'%')</if>
<if test="status!=null and status!=''">and d.status=#{status}</if>
<if test="categoryId!=null and categoryId!=''">and d.category_id=#{categoryId}</if>
</where>
</select>
dish 表中有 name
category 表中 也有 name
所以 category的name 起别名
7.3 删除菜品
原型:
数据库设计:
controller :
service,xml
7.3 修改菜品
八、套餐管理
8.1 需求
完成套餐管理模块所有业务功能,包括:
- 新增套餐
- 套餐分页查询
- 删除套餐
- 修改套餐
- 起售停售套餐
要求:
- 根据产品原型进行需求分析,分析出业务规则
- 设计接口
- 梳理表之间的关系(分类表、菜品表、套餐表、口味表、套餐菜品关系表)
- 根据接口设计进行代码实现
- 分别通过swagger接口文档和前后端联调进行功能测试
8.2 新增套餐
8.2.1 原型分析
8.2.2 接口设计
8.2.3 代码实现
九、CRUD 小结
- 新增 post
- 修改 post/put
- 查询 get
- 删除 delete
9.1 Create 新增
9.1.1 概述
9.1.2 单表/多表新增
- 单表新增
- 多表新增
- Controller:
- Service:
多表插入需要注意:另外一张表插入
- Mapper:
9.2 Read
- 查询 都是 GET请求方式
- 响应返回数据是 前端需要的实体对象
9.2.1 分页查询
接口分析设计:
- controller
- service
分页菜品使用:
Page<DishVO>
DishVO实体类 进行分页返回数据
- mapper
9.2.2 根据Id查询XX
- 请求:GetMapper
- 请求路径:/{id}
- 路径参数@PathVariable
9.2.3 根据类型查询XX
- 同一类型的进行罗列,使用List集合存储
- 请求:Get
- 请求路径:/list
- 简单参数 无需注解
9.3 Update 更新/修改
9.3.1 根据Id修改XX
需求:
点击启用禁用按钮完成修改
接口设计:
- 请求方式:post
- 请求路径:admin/emp/XX/{XX}
- XX使用路径参数注解
@PathVariable
- Id 为简单参数 不需要注解
使用动态SQL
9.3.2 修改单表
接口设计:
- 请求方式:put
- 请求路径:admin/emp
- 参数注解:接收JSON实体 用@RequestBody
前端修改页面传递,更改后的JSON对象,实体类封装传递后端数据库
9.3.3 修改多表
接口设计:
- 请求方式:put
- 请求路径:admin/emp
- 参数注解:接收JSON实体 用@RequestBody
controller与单表修改一致,只是在service业务层 需要对另一张表进行更新
9.4 Delete 删除
9.4.1 单表删除
接口设计:
- 请求方式:DELETE
- 请求路径:admin/emp
- 根据Id删除 Id为简单参数 无需注解
删除前要做判断,根据业务规则
9.4.2 多表删除
接口设计:
- 请求方式:DELETE
- 请求路径:admin/xxx
- 参数:
@RequestParam List<Long> ids
9.5 注意点
- 多表增删改 在service层开启事务管理
@Transactional(rollbackFor = Exception.class)
- 修改/更新某些数据,可以直接删除原数据,插入新数据的方式,只是对外显示更新操作
总结
下一期: