员工与分类模块功能实现笔记
本章节起,原型设计与需求分析将合并编写,建议结合网站侧边栏定位功能阅读,以获得更佳阅读体验。
本模块包含两个主要功能:
- 员工管理(CRUD)
- 菜品分类管理
功能效果展示
员工管理界面效果
分类管理界面效果
一、新增员工
1.1 需求分析
1.1.1 产品原型
后台系统支持管理员工信息,点击“新增员工”按钮,填写表单后提交,即可将员工信息存入数据库。
新增员工界面原型:
实现要点:
- 账号唯一
- 手机号为合法的11位数字
- 身份证号为合法的18位
- 默认密码为
123456
1.1.2 接口设计
接口地址:/admin/employee
请求方式:POST
请求数据类型:application/json
请求示例:
{
"id": 0,
"idNumber": "",
"name": "",
"phone": "",
"sex": "",
"username": ""
}
请求参数:
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| employeeDTO | employeeDTO | body | true | EmployeeDTO | EmployeeDTO |
| id | false | integer(int64) | |||
| idNumber | false | string | |||
| name | false | string | |||
| phone | false | string | |||
| sex | false | string | |||
| username | false | string |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
1.1.3 表结构设计
员工信息表 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 |
1.2 功能实现
1.2.1 设计 DTO
由于表单参数与实体类字段差异较大,使用 EmployeeDTO 作为数据传输对象。
com.qi.dto.EmployeeDTO:
@Data
public class EmployeeDTO implements Serializable {
private Long id;
private String idNumber;
private String name;
private String phone;
private String sex;
private String username;
}
1.2.2 Controller 层
在 EmployeeController 中添加新增员工接口:
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}", employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}
1.2.3 返回封装使用统一响应类 Result<T>
@Data
public class Result<T> implements Serializable {
private Integer code;
private String msg;
private T data;
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.code = 1;
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.code = 1;
result.data = data;
return result;
}
public static <T> Result<T> error(String msg) {
Result<T> result = new Result<>();
result.code = 0;
result.msg = msg;
return result;
}
}
1.2.4 Service 层
在 EmployeeService 中声明新增方法:
void save(EmployeeDTO employeeDTO);
1.2.5 Service 实现类
在 EmployeeServiceImpl 中实现 save 方法:
@Override
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);
//密码加密
String password = DigestUtils.md5DigestAsHex(DEFAULT_PASSWORD.getBytes());
employee.setPassword(password);
employee.setStatus(ENABLE);
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//记录操作人与更新人id
// 记录操作人功能
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
BaseContext.removeCurrentId();
employeeMapper.insert(employee);
}
状态常量定义于
StatusConstant.java:
public class StatusConstant {
public static final Integer ENABLE = 1;
public static final Integer DISABLE = 0;
}
1.2.6 Mapper 层
在 EmployeeMapper 中定义插入方法:
@Insert("insert into employee values (default,#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})")
void insert(Employee employee);
需在
application.yml中开启驼峰命名映射:
mybatis:
configuration:
map-underscore-to-camel-case: true
1.3 接口测试
提示“新增失败”,因为未传入 Token:
使用员工登录接口获取合法 Token:
将 JWT 添加到全局请求头中:
二、全局异常捕获
当尝试新增 username=zhumama7 的员工,而数据库中已存在该用户名时,系统将抛出如下异常:
我们这里先完善全局异常捕获功能
2.1 异常处理逻辑实现
进入 qi-server 模块的 com.qi.handler 包,编辑 GlobalExceptionHandler.java:
/*
* 捕获SQL异常
* */
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
String message = ex.getMessage();
log.error("异常信息:{}", message);
if (message.contains("Duplicate entry")) {
String[] split = message.split(" ");
String msg = split[2] + MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
} else {
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
2.2 常量定义
进入 qi-common 模块,在 MessageConstant.java 添加常量:
public class MessageConstant {
public static final String ALREADY_EXISTS = "已存在";
public static final String UNKNOWN_ERROR = "发生未知错误";
}
2.3 异常测试
再次提交用户名已存在的数据:
三、JWT令牌
员工登录成功后,系统会生成包含用户 ID 的 JWT 令牌,并将其返回给前端。
为了获取当前登录用户的 ID,所以我们要完成登录功能中完成jwt令牌的传递
/**
* 登录
*
* @param employeeLoginDTO
* @return
*/
@PostMapping("/login")
@ApiOperation(value = "员工登录")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
//.........
//登录成功后,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
//............
return Result.success(employeeLoginVO);
}
这样后续请求中,前端会携带JWT令牌,通过JWT令牌可以解析出当前登录员工id:
/**
* jwt令牌校验的拦截器
*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//..............
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
log.info("当前员工id:", empId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
什么是 ThreadLocal?
在新增员工时,需要设置该记录的:
createUser:创建人 IDupdateUser:修改人 ID
而请求对象只在 Controller 层可访问,如果要在 Service 层使用,就必须借助中间机制传递。
那么就使用到了ThreadLocal,ThreadLocal 不是线程,而是线程的“局部变量存储工具”。它为每个线程提供了一个独立的变量副本,实现了线程隔离。
常用方法:
// 设置值
ThreadLocal<T>.set(T value)
// 获取值
ThreadLocal<T>.get()
// 移除值(防止内存泄漏)
ThreadLocal<T>.remove()
ThreadLocal流程图:
初始工程中已经封装了 ThreadLocal 操作的工具类:
qi-common模块
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}
在拦截器中解析出当前登录员工id,并放入线程局部变量中:
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//.............................
//2、校验令牌
try {
//.................
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
log.info("当前员工id:", empId);
/////将用户id存储到ThreadLocal////////
BaseContext.setCurrentId(empId);
////////////////////////////////////
//3、通过,放行
return true;
} catch (Exception ex) {
//......................
}
}
}
在Service中获取线程局部变量中的值:
public void save(EmployeeDTO employeeDTO) {
//记录操作人与更新人id
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.insert(employee);
}
四、 分页查询
4.1 需求分析
4.1.1 产品原型
当系统中员工数量较多时,如果在一个页面中全部展示会导致页面混乱、加载缓慢,不便于用户操作。因此,采用分页展示是更合理的做法。本系统在分页查询的基础上,支持按“员工姓名”进行模糊查询。
功能设计要求如下:
- 按页码分页展示员工信息;
- 每页显示 10 条记录;
- 支持根据员工姓名进行模糊查询。
4.1.2 接口设计
接口地址:/admin/employee/page
请求方式:GET
请求数据类型:application/x-www-form-urlencoded
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| name | query | false | string | ||
| page | query | false | integer(int32) | ||
| pageSize | query | false | integer(int32) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | PageResult | PageResult | |
| records | array | object | |
| total | integer(int64) | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {
"records": [],
"total": 0
},
"msg": ""
}
4.2 代码实现
4.2.1 设计 DTO
在 qi-pojo 模块中定义 EmployeePageQueryDTO:
@Data
public class EmployeePageQueryDTO implements Serializable {
private String name; // 员工姓名
private int page; // 页码
private int pageSize; // 每页记录数
}
4.2.2 分页结果封装类 PageResult
分页返回结构统一封装为 PageResult,定义于 sky-common 模块:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
private long total; // 总记录数
private List records; // 当前页数据
}
4.2.4 Controller 层
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
log.info("员工分页查询,参数为:{}", employeePageQueryDTO);
PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}
4.2.5 Service 层
PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
4.2.6 Service 实现类
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
// 开始分页
PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
long total = page.getTotal();
List<Employee> records = page.getResult();
return new PageResult(total, records);
}
⚠️ 注意:分页功能依赖 MyBatis 插件
PageHelper,其底层基于拦截器机制实现。
Maven 依赖(已在初始工程中添加):
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper}</version>
</dependency>
4.2.7 Mapper 层
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
SQL映射:EmployeeMapper.xml
<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>
4.3 功能测试
4.3.1 接口文档测试
4.3.2 前后端联调演示
五、日期格式化处理
在接口开发中,后端返回的时间字段通常会以时间戳格式展示,为了美化时间格式,我们需要对返回的时间字段进行统一格式化处理。
5.1 方式一:注解式格式化(不推荐)
通过在时间字段上添加注解 @JsonFormat 实现格式化:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
缺点:
- 每个时间字段都需要单独添加注解;
- 代码重复,维护成本高;
- 无法统一全局生效。
5.2 方式二(推荐):全局配置统一格式
通过扩展 Spring MVC 的消息转换器,可以实现对所有时间字段的统一格式化处理,在配置类中进行统一设置。
5.2.1 扩展消息转换器
在 WebMvcConfiguration 类中重写 extendMessageConverters 方法,设置自定义的对象映射器 JacksonObjectMapper:
/**
* 扩展 Spring MVC 框架的消息转换器
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
// 创建消息转换器
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 设置对象转换器(Java对象转JSON)
converter.setObjectMapper(new JacksonObjectMapper());
// 优先使用自定义的消息转换器
converters.add(0, converter);
}
5.2.2 定义 JacksonObjectMapper
在 qi-common 模块中定义 JacksonObjectMapper 类,实现时间字段的统一格式设置:
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
5.2.3 测试
原效果:
统一格式化配置完成后,调用接口返回的时间字段格式将变为:
这样就无需对每个字段添加注解,格式清晰统一,便于前端展示
记得多提交git哦,有助于后续复盘和备份
六、员工账号启用与禁用
6.1 需求分析
在员工管理页面中,可以对员工账号进行启用或禁用操作:
- 账号禁用:无法登录系统;
- 账号启用:可以正常登录;
业务规则:
- 仅允许对“启用”状态的员工账号执行“禁用”操作;
- 仅允许对“禁用”状态的员工账号执行“启用”操作;
- 被禁用的账号不能登录系统;
- 本质上是一次更新操作。
6.1.1 产品原型
6.1.2 接口设计
接口地址:/admin/employee/status/{status}
请求方式:POST
请求数据类型:application/json
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| status | status | path | true | integer(int32) | |
| id | id | query | false | integer(int64) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
6.2 功能实现
6.2.1 Controller 层
@PostMapping("/status/{status}")
@ApiOperation("修改员工状态")
public Result startOrStop(@PathVariable Integer status, Long id){
log.info("启用禁用员工参数:status:{},id:{}", status, id);
employeeService.alterStatus(status,id);
return Result.success();
}
6.2.2 Service 层
void alterStatus(Integer status, Long id);
6.2.3 Service 实现类
@Override
public void alterStatus(Integer status, Long id) {
Employee employee = new Employee();
employee.setStatus(status);
employee.setId(id);
}
6.2.4 Mapper 层
void update(Employee employee);
<update id="update">
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>
6.3 联调测试
- 启用/禁用员工账号的交互流程演示:
七、编辑员工功能
7.1 需求分析
在员工列表页面点击“编辑”按钮,可跳转至员工信息编辑页。页面将回显原始员工数据,用户修改后点击“保存”即可完成编辑。
7.1.1 产品原型
7.1.2 接口设计
接口地址:/admin/employee
请求方式:PUT
请求数据类型:application/json
请求示例:
{
"id": 0,
"idNumber": "",
"name": "",
"phone": "",
"sex": "",
"username": ""
}
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| employeeDTO | employeeDTO | body | true | EmployeeDTO | EmployeeDTO |
| id | false | integer(int64) | |||
| idNumber | false | string | |||
| name | false | string | |||
| phone | false | string | |||
| sex | false | string | |||
| username | false | string |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
7.2 功能实现
该功能分为两个步骤:
- 根据员工 ID 查询信息;
- 修改并提交员工信息。
7.2.1 根据 ID 查询员工信息
1)Controller 层
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<Employee> getById(@PathVariable Long id){
Employee employee = employeeService.getById(id);
return Result.success(employee);
}
2)Service 层
Employee getById(Long id);
3)Service 实现类
@Override
public Employee getById(Long id) {
Employee employee = employeeMapper.getById(id);
return employee;
}
4)Mapper 层
@Select("select * from employee where id = #{id}")
Employee getById(Long id);
7.2.2 修改员工信息
1)Controller 层
@PutMapping
@ApiOperation("编辑员工信息")
public Result update(@RequestBody EmployeeDTO employeeDTO){
log.info("编辑员工信息:{}", employeeDTO);
employeeService.update(employeeDTO);
return Result.success();
}
2)Service 层
void update(EmployeeDTO employeeDTO);
3)Service 实现类
@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);
}
注:之前已实现
employeeMapper.update(employee)方法,直接复用
7.3 联调测试
查询回写
八、分类模块
8.1 需求分析
后台管理系统中支持对分类信息进行管理,主要分为两类:
- 菜品分类
- 套餐分类
主要功能如下:
- 新增菜品分类:在新增菜品时需选择所属分类,移动端展示也基于分类进行。
- 分页查询:当分类数量较多时,采用分页方式展示,提高可读性与管理效率。
- 删除分类:支持根据 ID 删除分类。但若分类已被菜品或套餐引用,则不允许删除。
- 修改分类:可在管理列表中点击“修改”,弹窗回显分类信息并编辑。
- 启用/禁用分类:通过按钮切换分类状态。
- 根据类型查询分类:支持通过下拉选择菜品分类或套餐分类,从数据库动态加载数据。
8.1.1 产品原型
业务规则:
- 分类名称必须唯一
- 分类类型包括:1 表示菜品分类,2 表示套餐分类
- 新增分类的状态默认为“禁用”
8.1.2 接口设计
分类模块涉及 6 个接口:
| 功能 | 接口说明 |
|---|---|
| 新增分类 | 新增一条分类记录 |
| 分类分页查询 | 支持按名称和类型分页查询 |
| 根据 ID 删除分类 | 删除前需判断是否关联数据 |
| 修改分类 | 支持根据 ID 修改分类 |
| 启用/禁用分类 | 切换分类状态 |
| 类型分类查询 | 查询指定类型下的所有分类 |
1)新增分类
接口地址:/admin/dish
请求方式:POST
请求数据类型:application/json
请求示例:
{
"categoryId": 0,
"description": "",
"flavors": [
{
"dishId": 0,
"id": 0,
"name": "",
"value": ""
}
],
"id": 0,
"image": "",
"name": "",
"price": 0,
"status": 0
}
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| dishDTO | dishDTO | body | true | DishDTO | DishDTO |
| categoryId | false | integer(int64) | |||
| description | false | string | |||
| flavors | false | array | DishFlavor | ||
| dishId | false | integer | |||
| id | false | integer | |||
| name | false | string | |||
| value | false | string | |||
| id | false | integer(int64) | |||
| image | false | string | |||
| name | false | string | |||
| price | false | number | |||
| status | false | integer(int32) |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
2)分页查询分类
接口地址:/admin/dish/page
请求方式:GET
请求数据类型:application/x-www-form-urlencoded
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| categoryId | query | false | integer(int32) | ||
| name | query | false | string | ||
| page | query | false | integer(int32) | ||
| pageSize | query | false | integer(int32) | ||
| status | query | false | integer(int32) |
响应状态:
| 状态码 | 说明 | schema |
|---|---|---|
| 200 | OK | Result«PageResult» |
| 401 | Unauthorized | |
| 403 | Forbidden | |
| 404 | Not Found |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | PageResult | PageResult | |
| records | array | object | |
| total | integer(int64) | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {
"records": [],
"total": 0
},
"msg": ""
}
3)删除分类
接口地址:/admin/dish
请求方式:DELETE
请求数据类型:application/x-www-form-urlencoded
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| ids | ids | query | true | array | integer |
响应状态:
| 状态码 | 说明 | schema |
|---|---|---|
| 200 | OK | Result |
| 204 | No Content | |
| 401 | Unauthorized | |
| 403 | Forbidden |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
4)修改分类
接口地址:/admin/dish
请求方式:PUT
请求数据类型:application/json
请求示例:
{
"categoryId": 0,
"description": "",
"flavors": [
{
"dishId": 0,
"id": 0,
"name": "",
"value": ""
}
],
"id": 0,
"image": "",
"name": "",
"price": 0,
"status": 0
}
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| dishDTO | dishDTO | body | true | DishDTO | DishDTO |
| categoryId | false | integer(int64) | |||
| description | false | string | |||
| flavors | false | array | DishFlavor | ||
| dishId | false | integer | |||
| id | false | integer | |||
| name | false | string | |||
| value | false | string | |||
| id | false | integer(int64) | |||
| image | false | string | |||
| name | false | string | |||
| price | false | number | |||
| status | false | integer(int32) |
响应状态:
| 状态码 | 说明 | schema |
|---|---|---|
| 200 | OK | Result |
| 201 | Created | |
| 401 | Unauthorized | |
| 403 | Forbidden | |
| 404 | Not Found |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
5)启用/禁用分类
接口地址:/admin/dish/status/{status}
请求方式:POST
请求数据类型:application/json
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| id | id | query | true | integer(int64) | |
| status | status | path | true | integer(int32) |
响应状态:
| 状态码 | 说明 | schema |
|---|---|---|
| 200 | OK | Result |
| 201 | Created | |
| 401 | Unauthorized | |
| 403 | Forbidden | |
| 404 | Not Found |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | object | ||
| msg | string |
响应示例:
{
"code": 0,
"data": {},
"msg": ""
}
6)根据类型查询分类
接口地址:/admin/dish/list
请求方式:GET
请求数据类型:application/x-www-form-urlencoded
请求参数:
| 参数名称 | 参数说明 | 请求类型 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|---|
| categoryId | categoryId | query | false | string |
响应状态:
| 状态码 | 说明 | schema |
|---|---|---|
| 200 | OK | Result«List«DishVO»» |
| 401 | Unauthorized | |
| 403 | Forbidden | |
| 404 | Not Found |
响应参数:
| 参数名称 | 参数说明 | 类型 | schema |
|---|---|---|---|
| code | integer(int32) | integer(int32) | |
| data | array | DishVO | |
| categoryId | integer(int64) | ||
| categoryName | string | ||
| description | string | ||
| flavors | array | DishFlavor | |
| dishId | integer | ||
| id | integer | ||
| name | string | ||
| value | string | ||
| id | integer(int64) | ||
| image | string | ||
| name | string | ||
| price | number | ||
| status | integer(int32) | ||
| updateTime | string(date-time) | ||
| msg | string |
响应示例:
{
"code": 0,
"data": [
{
"categoryId": 0,
"categoryName": "",
"description": "",
"flavors": [
{
"dishId": 0,
"id": 0,
"name": "",
"value": ""
}
],
"id": 0,
"image": "",
"name": "",
"price": 0,
"status": 0,
"updateTime": ""
}
],
"msg": ""
}
8.2 功能实现
8.2.1 Mapper 层
DishMapper.java
@Select("select count(id) from dish where category_id = #{categoryId}")
Integer countByCategoryId(Long categoryId);
SetmealMapper.java
@Select("select count(id) from setmeal where category_id = #{categoryId}")
Integer countByCategoryId(Long categoryId);
CategoryMapper.java
@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
" values (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
void insert(Category category);
Page<Category> pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);
@Delete("delete from category where id = #{id}")
void deleteById(Long id);
void update(Category category);
List<Category> list(Integer type);
CategoryMapper.xml
<select id="pageQuery" resultType="com.sky.entity.Category">
select * from category
<where>
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
<if test="type != null">
and type = #{type}
</if>
</where>
order by sort asc, create_time desc
</select>
<update id="update" parameterType="Category">
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>
<select id="list" resultType="Category">
select * from category
where status = 1
<if test="type != null">and type = #{type}</if>
order by sort asc, create_time desc
</select>
8.2.2 Service 层
CategoryService.java
void save(CategoryDTO categoryDTO);
PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);
void deleteById(Long id);
void update(CategoryDTO categoryDTO);
void startOrStop(Integer status, Long id);
List<Category> list(Integer type);
8.2.3 Service 实现类
/**
* 新增分类
* @param categoryDTO
*/
public void save(CategoryDTO categoryDTO) {
Category category = new Category();
//属性拷贝
BeanUtils.copyProperties(categoryDTO, category);
//分类状态默认为禁用状态0
category.setStatus(StatusConstant.DISABLE);
//设置创建时间、修改时间、创建人、修改人
category.setCreateTime(LocalDateTime.now());
category.setUpdateTime(LocalDateTime.now());
category.setCreateUser(BaseContext.getCurrentId());
category.setUpdateUser(BaseContext.getCurrentId());
categoryMapper.insert(category);
}
/**
* 分页查询
* @param categoryPageQueryDTO
* @return
*/
public PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO) {
PageHelper.startPage(categoryPageQueryDTO.getPage(),categoryPageQueryDTO.getPageSize());
//下一条sql进行分页,自动加入limit关键字分页
Page<Category> page = categoryMapper.pageQuery(categoryPageQueryDTO);
return new PageResult(page.getTotal(), page.getResult());
}
/**
* 根据id删除分类
* @param id
*/
public void deleteById(Long id) {
//查询当前分类是否关联了菜品,如果关联了就抛出业务异常
Integer count = dishMapper.countByCategoryId(id);
if(count > 0){
//当前分类下有菜品,不能删除
throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_DISH);
}
//查询当前分类是否关联了套餐,如果关联了就抛出业务异常
count = setmealMapper.countByCategoryId(id);
if(count > 0){
//当前分类下有菜品,不能删除
throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_SETMEAL);
}
//删除分类数据
categoryMapper.deleteById(id);
}
/**
* 修改分类
* @param categoryDTO
*/
public void update(CategoryDTO categoryDTO) {
Category category = new Category();
BeanUtils.copyProperties(categoryDTO,category);
// //设置修改时间、修改人
category.setUpdateTime(LocalDateTime.now());
category.setUpdateUser(BaseContext.getCurrentId());
categoryMapper.update(category);
}
/**
* 启用、禁用分类
* @param status
* @param id
*/
public void startOrStop(Integer status, Long id) {
Category category = Category.builder()
.id(id)
.status(status)
// .updateTime(LocalDateTime.now())
// .updateUser(BaseContext.getCurrentId())
.build();
categoryMapper.update(category);
}
/**
* 根据类型查询分类
* @param type
* @return
*/
public List<Category> list(Integer type) {
return categoryMapper.list(type);
}
8.2.4 Controller 层
CategoryController.java
@RestController
@RequestMapping("/admin/category")
@Api(tags = "分类相关接口")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping
@ApiOperation("新增分类")
public Result<String> save(@RequestBody CategoryDTO categoryDTO) {
log.info("新增分类:{}", categoryDTO);
categoryService.save(categoryDTO);
return Result.success();
}
@GetMapping("/page")
@ApiOperation("分页查询分类")
public Result<PageResult> page(CategoryPageQueryDTO categoryPageQueryDTO) {
log.info("分页查询分类:{}", categoryPageQueryDTO);
PageResult pageResult = categoryService.pageQuery(categoryPageQueryDTO);
return Result.success(pageResult);
}
@DeleteMapping
@ApiOperation("根据ID删除分类")
public Result<String> deleteById(Long id) {
log.info("删除分类:{}", id);
categoryService.deleteById(id);
return Result.success();
}
@PutMapping
@ApiOperation("修改分类")
public Result<String> update(@RequestBody CategoryDTO categoryDTO) {
categoryService.update(categoryDTO);
return Result.success();
}
@PostMapping("/status/{status}")
@ApiOperation("启用/禁用分类")
public Result<String> startOrStop(@PathVariable("status") Integer status, Long id) {
categoryService.startOrStop(status, id);
return Result.success();
}
@GetMapping("/list")
@ApiOperation("根据类型查询分类")
public Result<List<Category>> list(Integer type) {
List<Category> list = categoryService.list(type);
return Result.success(list);
}
}
关于员工管理和分类管理模块就基本实现了,下一章小柒味来 - 菜品管理模块开发
欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1701220998@qq.com