员工管理 新增员工 产品原型
接口设计
【注意】本项目约定:
管理端发出的请求,统一使用/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
代码开发 1.根据新增员工接口设计对应的DTO。
1 2 3 4 5 6 7 8 9 10 11 package com.sky.dto;@Data public class EmployeeDTO implements Serializable { private Long id; private String username; private String name; private String phone; private String sex; private String idNumber; }
(1)注意:当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据。
使用DTO的好处:假设你数据库中定义了User类,包含用户名、密码、邮箱、手机号等等;当用户登录时一般只需要输入用户名和密码,那么传入服务端的用户名和密码就可以在controller层封装到UserDto实体类中。DTO解决了在客户端和服务器端之间传递大量数据的问题,但是客户端往往需要更细粒度的数据访问。
DTO数据传输对象详解_dto撖寡情-CSDN博客
Spring Boot中的数据传输对象(DTO)_springboot dto-CSDN博客
(2)序列化Serializable:
Serializable是什么,为什么要实现Serializable接口?-CSDN博客
2.后端统一返回结果Result。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.sky.result;@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 <T>(); result.code = 1 ; return result; } public static <T> Result<T> success (T object) { Result<T> result = new Result <T>(); result.data = object; result.code = 1 ; return result; } public static <T> Result<T> error (String msg) { Result result = new Result (); result.msg = msg; result.code = 0 ; return result; } }
3.在EmployeeController中创建新增员工方法,接收前端提交的参数。
1 2 3 4 5 6 7 8 @PostMapping @ApiOperation("新增员工") public Result save (@RequestBody EmployeeDTO employeeDTO) { log.info("新增员工:{}" , employeeDTO); employeeService.save(employeeDTO); return Result.success(); }
4.在EmployeeService接口中声明新增员工方法。
1 2 void save (EmployeeDTO employeeDTO) ;
5.在EmployeeServiceImpl中实现新增员工方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override public void save (EmployeeDTO employeeDTO) { Employee employee = new Employee (); BeanUtils.copyProperties(employeeDTO, employee); employee.setStatus(StatusConstant.ENABLE); employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes())); employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); employee.setCreateUser(BaseContext.getCurrentId()); employee.setUpdateUser(BaseContext.getCurrentId()); employeeMapper.insert(employee); }
BeanUtils.copyProperties:Spring BeanUtils:灵活高效的JavaBean操作助手-CSDN博客
6.在EmployeeMapper中声明insert方法。
1 2 3 4 @Insert("insert into employee (name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user)" + "VALUES (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{status}, #{createTime},#{updateTime},#{createUser}, #{updateUser})") void insert (Employee employee) ;
处理已存在用户名异常 通过全局异常处理器来处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.sky.handler;@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler public Result exceptionHandler (SQLIntegrityConstraintViolationException ex) { 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); }else { return Result.error(MessageConstant.UNKNOWN_ERROR); } } }
设置创建人id和修改人id 需要动态获取当前登录员工的id。
员工登录成功后会生成JWT令牌并响应给前端:
1 2 3 4 5 6 7 8 Map<String, Object> claims = new HashMap <>(); claims.put(JwtClaimsConstant.EMP_ID, employee.getId());String token = JwtUtil.createJWT( jwtProperties.getAdminSecretKey(), jwtProperties.getAdminTtl(), claims);
后续请求中,前端会携带JWT令牌,通过JWT令牌可以解析出当前登录员工id:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 String token = request.getHeader(jwtProperties.getAdminTokenName());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); return true ; } catch (Exception ex) { response.setStatus(401 ); return false ; }
ThreadLocal ThreadLocal并不是一个Thread,而是Thread的局部变量 。
ThreadLocal为每个线程提供单独一份存储空间 ,具有线程隔离 的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:
public void set(T value):设置当前线程的线程局部变量的值。
public T get():返回当前线程所对应的线程局部变量的值。
public void remove():移除当前线程的线程局部变量。
注意:客户端发送的每次请求 ,后端的Tomcat服务器都会分配一个单独的线程来处理请求 。
参考链接:
史上最全ThreadLocal详解(一)-CSDN博客
Java 中的 ThreadLocal 是如何实现线程资源隔离的? - 面试鸭 - 程序员求职面试刷题神器
Java四大引用:
Java:强引用,软引用,弱引用和虚引用_强弱引用-CSDN博客
初始工程中已经封装了ThreadLocal操作的工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.sky.context;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,并放入线程局部变量中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true ; } String token = request.getHeader(jwtProperties.getAdminTokenName()); 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); BaseContext.setCurrentId(empId); return true ; } catch (Exception ex) { response.setStatus(401 ); return false ; } }
在Service中获取线程局部变量中的值。
功能测试 1.通过接口文档测试。
由于JWT令牌校验失败,导致EmployeeController的save方法没有被调用。
解决办法:调用员工登录接口获得一个合法的JWT令牌,将合法的JWT令牌添加到全局参数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.sky.interceptor;@Component @Slf4j public class JwtTokenAdminInterceptor implements HandlerInterceptor { @Autowired private JwtProperties jwtProperties; public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true ; } String token = request.getHeader(jwtProperties.getAdminTokenName()); 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); return true ; } catch (Exception ex) { response.setStatus(401 ); return false ; } } }
2.通过前后端联调测试。
注意:由于开发阶段前端和后端是并行开发的,后端完成某个功能后,此时前端对应的功能可能还没有开发完成,导致无法进行前后端联调测试。所以在开发阶段,后端测试主要以接口文档测试为主。
员工分页查询 产品原型
接口设计
代码开发 1.根据分页查询接口设计对应的DTO。
1 2 3 4 5 6 7 8 package com.sky.dto;@Data public class EmployeePageQueryDTO implements Serializable { private String name; private int page; private int pageSize; }
2.后面所有的分页查询,统一都封装成PageResult对象。
1 2 3 4 5 6 7 8 9 10 package com.sky.result;@Data @AllArgsConstructor @NoArgsConstructor public class PageResult implements Serializable { private long total; private List records; }
3.员工信息分页查询后端返回的对象类型为:Result<PageResult>。根据接口定义创建分页查询方法。
1 2 3 4 5 6 7 8 @GetMapping("/page") @ApiOperation("员工分页查询") public Result<PageResult> page (EmployeePageQueryDTO employeePageQueryDTO) { log.info("员工分页查询,参数为:{}" , employeePageQueryDTO); PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO); return Result.success(pageResult); }
4.在EmployeeService接口中声明pageQuery方法。
1 2 PageResult pageQuery (EmployeePageQueryDTO employeePageQueryDTO) ;
5.在EmployeeServiceImpl中实现pageQuery方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 @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来简化分页代码的开发。底层基于mybatis的拦截器实现。
【PageHelp原理】 PageHelp底层是基于ThreadLocal实现的,通过把page(包含pageNum和pageSize)存储到存储空间,在进行分页查询之前,通过ThreadLocal把pageNum和pageSize取出,然后在SQL语句查询时动态的把limit关键字拼进去。
6.在EmployeeMapper中声明pageQuery方法。
1 2 Page<Employee> pageQuery (EmployeePageQueryDTO employeePageQueryDTO) ;
7.在EmployeeMapper.xml(路径:sky-server/src/main/resources/mapper/EmployeeMapper.xml)中编写SQL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.sky.mapper.EmployeeMapper" > <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 > </mapper >
操作时间字段显示错误 解决方式:
方式一:在属性上加入注解,对日期进行格式化。
1 2 3 4 5 6 7 8 9 10 package com.sky.entity;public class Employee implements Serializable { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime; }
方式二:在WebMvcConfiguration中扩展Spring MVC的消息转换器,统一对日期类型进行格式化处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.sky.config;@Configuration @Slf4j public class WebMvcConfiguration extends WebMvcConfigurationSupport { @Override protected void extendMessageConverters (List<HttpMessageConverter<?>> converters) { log.info("扩展消息转换器" ); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter (); converter.setObjectMapper(new JacksonObjectMapper ()); converters.add(0 , converter); } }
【补充】
1.@DateTimeFormat和@JsonFormat。
参考链接:
@JsonFormat 和 @DateTimeFormat 时间格式化注解详解(不看血亏)_jsonformat注解-CSDN博客
Spring @DateTimeFormat日期格式化时注解浅析分享_datetimeformatter注解用法-CSDN博客
总结:@DateTimeFormat只是规定了前端传给后端的时间格式,但是@JsonFormat才能控制前端如何显示时间。
2.消息转换器
参考链接:
一步到位 SpringBoot 序列化与消息转换器 (你需要的这里都有)_objectmapper和messageconverter的关系与区别-CSDN博客
Spring MVC 消息转换器_springmvc消息转换器-CSDN博客
WebMvcConfigurer和WebMvcConfigurationSupport(MVC配置)-CSDN博客
重学SpringBoot3-WebMvcAutoConfiguration类-CSDN博客
@DateTimeFormat:当从requestParam中获取string参数并需要转化为Date类型时,会根据此注解的参数pattern的格式进行转化。
@JsonFormat:当从请求体中获取json字符序列,需要反序列化为对象时,时间类型会按照这个注解的属性内容进行处理。
这两个注解需要加在实体类的对应字段上即可。
3.自定义序列化
参考链接:
【自定义序列化器】⭐️通过继承JsonSerializer和实现WebMvcConfigurer类完成自定义序列化_webmvcconfigurer 自定义jackson-CSDN博客
SpringBoot中Jackson实现自定义序列化和反序列化总结_jackson 自定义反序列化-CSDN博客
实现自定义序列化和反序列化控制的5种方式-腾讯云开发者社区-腾讯云
启用禁用员工账号 产品原型
业务规则:
可以对状态为“启用” 的员工账号进行“禁用”操作。
可以对状态为“禁用”的员工账号进行“启用”操作。
状态为“禁用”的员工账号不能登录系统。
接口设计
代码开发 1.根据接口设计中的请求参数形式对应的在EmployeeController中创建启用禁用员工账号的方法。
1 2 3 4 5 6 7 8 @PostMapping("/status/{status}") @ApiOperation("启用禁用员工账号") public Result startOrStop (@PathVariable Integer status, Long id) { log.info("启用禁用员工账号:{},{}" , status, id); employeeService.startOrStop(status, id); return Result.success(); }
2.在EmployeeService接口中声明启用禁用员工账号的业务方法。
1 2 void startOrStop (Integer status, Long id) ;
3.在EmployeeServiceImpl中实现启用禁用员工账号的业务方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public void startOrStop (Integer status, Long id) { Employee employee = Employee.builder() .status(status) .id(id) .build(); employeeMapper.update(employee); }
4.在EmployeeMapper接口中声明update方法。
1 2 void update (Employee employee) ;
5.在EmployeeMapper.xml中编写SQL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <update id ="update" parameterType ="Employee" > -- parameterType可以省略。可以只写Employee,不用写上包名。 -- 因为配置文件设置mybatis: type-aliases-package: com.sky.entity 整体扫描了这个包, -- Employee就在这个包里面,就统一扫到为这些实体创建了别名,所以可以只写别名Employee,不用写完整的包名 update employee <set > <if test ="username != null" > username = #{username},</if > <if test ="name != null" > name = #{name},</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 >
编辑员工 产品原型
接口设计 编辑员工功能涉及到两个接口:
1.根据id查询员工信息。
2.编辑员工信息。
代码开发 1.在EmployeeController中创建getById方法。
1 2 3 4 5 6 7 8 @GetMapping("/{id}") @ApiOperation("根据id查询员工信息") public Result<Employee> getById (@PathVariable Long id) { log.info("根据id查询员工信息:{}" , id); Employee employee = employeeService.getById(id); return Result.success(employee); }
2.在EmployeeService接口中声明getById方法:
1 2 Employee getById (Long id) ;
3.在EmployeeServiceImpl中实现getById方法:
1 2 3 4 5 6 7 @Override public Employee getById (Long id) { Employee employee = employeeMapper.getById(id); employee.setPassword("****" ); return employee; }
4.在EmployeeMapper接口中声明getById方法:
1 2 3 @Select("select * from employee where id = #{id}") Employee getById (Long id) ;
5.在EmployeeController中创建update方法:
1 2 3 4 5 6 7 8 @PutMapping @ApiOperation("编辑员工信息") public Result update (@RequestBody EmployeeDTO employeeDTO) { log.info("编辑员工信息:{}" , employeeDTO); employeeService.update(employeeDTO); return Result.success(); }
6.在EmployeeService接口中声明update方法:
1 2 void update (EmployeeDTO employeeDTO) ;
7.在EmployeeServiceImpl中实现update方法:
1 2 3 4 5 6 7 8 9 @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); }
分类模块 产品原型
业务规则
分类名称必须是唯一的。
分类按照类型可以分为菜品分类和套餐分类。
新添加的分类状态默认为“禁用”。
接口设计
新增分类
分类分页查询
根据id删除分类
修改分类
启用禁用分类
根据类型查询分类
数据库设计(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
代码开发 1.CategoryController。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 package com.sky.controller.admin;@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("删除分类") 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); } }
2.CategoryMapper。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.sky.mapper;@Mapper public interface CategoryMapper { @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) ; }
3.DishMapper。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.sky.mapper;@Mapper public interface DishMapper { @Select("select count(id) from dish where category_id = #{categoryId}") Integer countByCategoryId (Long categoryId) ; }
4.SetmealMapper。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.sky.mapper;@Mapper public interface SetmealMapper { @Select("select count(id) from setmeal where category_id = #{categoryId}") Integer countByCategoryId (Long id) ; }
5.CategoryService。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.sky.service;public interface CategoryService { 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) ; }
6.CategoryServiceImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 package com.sky.service.impl;@Service @Slf4j public class CategoryServiceImpl implements CategoryService { @Autowired private CategoryMapper categoryMapper; @Autowired private DishMapper dishMapper; @Autowired private SetmealMapper setmealMapper; public void save (CategoryDTO categoryDTO) { Category category = new Category (); BeanUtils.copyProperties(categoryDTO, category); category.setStatus(StatusConstant.DISABLE); category.setCreateTime(LocalDateTime.now()); category.setUpdateTime(LocalDateTime.now()); category.setCreateUser(BaseContext.getCurrentId()); category.setUpdateUser(BaseContext.getCurrentId()); categoryMapper.insert(category); } public PageResult pageQuery (CategoryPageQueryDTO categoryPageQueryDTO) { PageHelper.startPage(categoryPageQueryDTO.getPage(),categoryPageQueryDTO.getPageSize()); Page<Category> page = categoryMapper.pageQuery(categoryPageQueryDTO); return new PageResult (page.getTotal(), page.getResult()); } 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); } public void update (CategoryDTO categoryDTO) { Category category = new Category (); BeanUtils.copyProperties(categoryDTO,category); category.setUpdateTime(LocalDateTime.now()); category.setUpdateUser(BaseContext.getCurrentId()); categoryMapper.update(category); } 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); } public List<Category> list (Integer type) { return categoryMapper.list(type); } }
7.CategoryMapper.xml。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.sky.mapper.CategoryMapper" > <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 > </mapper >
菜品管理 公共字段自动填充 问题分析 业务表中的公共字段:
序号
字段名
含义
数据类型
1
create_time
创建时间
datetime
2
create_user
创建人id
bigint
3
update_time
修改时间
datetime
4
update_user
修改人id
bigint
公共代码:
1 2 3 4 5 category.setCreateTime(LocalDateTime.now()); category.setUpdateTime(LocalDateTime.now()); category.setCreateUser(BaseContext.getCurrentId()); category.setUpdateUser(BaseContext.getCurrentId());
实现思路
序号
字段名
含义
数据类型
操作类型
1
create_time
创建时间
datetime
insert
2
create_user
创建人id
bigint
insert
3
update_time
修改时间
datetime
insert、update
4
update_user
修改人id
bigint
insert、update
自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法。
自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值。
在Mapper的方法上加入AutoFill注解。
技术点:枚举、注解、AOP、反射。
代码开发 1.自定义注解AutoFill。
1 2 3 4 5 6 7 8 9 package com.sky.annotation;@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { OperationType value () ; }
2.自定义切面AutoFillAspect,完善自定义切面AutoFillAspect的autoFill方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 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) { log.info("开始进行公共字段自动填充" ); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); AutoFill autoFill = methodSignature.getMethod().getAnnotation(AutoFill.class); 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 { Method setCreateTime = entity.getClass().getMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); Method setCreateUser = entity.getClass().getMethod(AutoFillConstant.SET_CREATE_USER, Long.class); Method setUpdateTime = entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); setCreateTime.invoke(entity, now); setCreateUser.invoke(entity, currentId); setUpdateTime.invoke(entity, now); setUpdateUser.invoke(entity, currentId); }catch (Exception e) { e.printStackTrace(); } }else if (operationType == OperationType.UPDATE) { try { Method setUpdateTime = entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); setUpdateTime.invoke(entity, now); setUpdateUser.invoke(entity, currentId); }catch (Exception e) { e.printStackTrace(); } } } }
3.在Mapper接口的方法上加入AutoFill注解。(CategoryMapper.java和EmployeeMapper.java的插入和更新操作都需要添加AutoFill注解)
1 2 3 4 5 6 7 8 9 @Insert("insert into employee (name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user)" + "VALUES (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{status}, #{createTime},#{updateTime},#{createUser}, #{updateUser})") @AutoFill(value = OperationType.INSERT) void insert (Employee employee) ;@AutoFill(value = OperationType.UPDATE) void update (Employee employee) ;
4.将业务层为公共字段赋值的代码注释掉。
新增菜品 产品原型
业务规则:
菜品名称必须是唯一的。
菜品必须属于某个分类下,不能单独存在。
新增菜品时可以根据情况选择菜品的口味。
每个菜品必须对应一张图片。
接口设计 根据类型查询分类(已完成)
文件上传
新增菜品
数据库设计 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)
口味值
代码开发 文件上传接口 application-dev.yml
1 2 3 4 5 6 sky: alioss: endpoint: oss-cn-hangzhou.aliyuncs.com access-key-id: access-key-secret: bucket-name: srr-web-tlias
application.yml
1 2 3 4 5 6 sky: alioss: endpoint: ${sky.alioss.endpoint} access-key-id: ${sky.alioss.access-key-id} access-key-secret: ${sky.alioss.access-key-secret} bucket-name: ${sky.alioss.bucket-name}
OssConfiguration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.sky.config;@Configuration @Slf4j public class OssConfiguration { @Bean @ConditionalOnMissingBean public AliOssUtil aliOssUtil (AliOssProperties aliOssProperties) { log.info("开始创建阿里云文件上传工具类对象:{}" , aliOssProperties); return new AliOssUtil (aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret(), aliOssProperties.getBucketName()); } }
CommonController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.sky.controller.admin;@RestController @RequestMapping("/admin/common") @Api(tags = "通用接口") @Slf4j public class CommonController { @Autowired private AliOssUtil aliOssUtil; @PostMapping("/upload") @ApiOperation("文件上传") public Result<String> upload (MultipartFile file) { log.info("文件上传:{}" , file); try { String originalFilename = file.getOriginalFilename(); String extension = originalFilename.substring(originalFilename.lastIndexOf("." )); String objectName = UUID.randomUUID().toString() + extension; String filePath = aliOssUtil.upload(file.getBytes(), objectName); return Result.success(filePath); }catch (Exception e){ log.error("文件上传失败:{}" , e); } return Result.error(MessageConstant.UPLOAD_FAILED); } }
新增菜品接口 1.根据新增菜品接口设计对应的DTO:DishDTO。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.sky.dto;@Data public class DishDTO implements Serializable { private Long id; private String name; private Long categoryId; private BigDecimal price; private String image; private String description; private Integer status; private List<DishFlavor> flavors = new ArrayList <>(); }
2.DishController。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.sky.controller.admin;@RestController @RequestMapping("/admin/dish") @Api("菜品相关接口") @Slf4j public class DishController { @Autowired private DishService dishService; @PostMapping @ApiOperation("新增菜品") public Result save (@RequestBody DishDTO dishDTO) { log.info("新增菜品:{}" ,dishDTO); dishService.saveWithFlavor(dishDTO); return Result.success(); } }
3.DishService。
1 2 3 4 5 6 7 package com.sky.service;@Service public interface DishService { public void saveWithFlavor (DishDTO dishDTO) ; }
4.DishServiceImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.sky.service.impl;@Service @EnableTransactionManagement @Slf4j public class DishServiceImpl implements DishService { @Autowired private DishMapper dishMapper; @Autowired private DishFlavorMapper dishFlavorMapper; @Override @Transactional public void saveWithFlavor (DishDTO dishDTO) { Dish dish = new Dish (); BeanUtils.copyProperties(dishDTO, dish); dishMapper.insert(dish); Long dishId = dish.getId(); List<DishFlavor> flavors = dishDTO.getFlavors(); if (flavors != null && flavors.size() > 0 ){ flavors.forEach(dishFlavor -> { dishFlavor.setDishId(dishId); }); dishFlavorMapper.insertBatch(flavors); } } }
5.DishMapper。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.sky.mapper;@Mapper public interface DishMapper { @Select("select count(id) from dish where category_id = #{categoryId}") Integer countByCategoryId (Long categoryId) ; @AutoFill(value = OperationType.INSERT) void insert (Dish dish) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.sky.mapper.DishMapper" > <insert id ="insert" useGeneratedKeys ="true" keyProperty ="id" > insert into dish (status, name, category_id, price, image, description, create_time, update_time, create_user,update_user) values (#{status}, #{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime},#{createUser}, #{updateUser}) </insert > </mapper >
6.DishFlavorMapper。
1 2 3 4 5 6 7 package com.sky.mapper;@Mapper public interface DishFlavorMapper { void insertBatch (List<DishFlavor> flavors) ; }
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.sky.mapper.DishFlavorMapper" > <insert id ="insertBatch" > insert into dish_flavor (dish_id, name, value) values <foreach collection ="flavors" item ="dishFlavor" separator ="," > (#{dishFlavor.dishId},#{dishFlavor.name},#{dishFlavor.value}) </foreach > </insert > </mapper >
菜品分页查询 产品原型
业务规则:
根据页码展示菜品信息。
每页展示10条数据。
分页查询时可以根据需要输入菜品名称、菜品分类、菜品状态进行查询。
接口设计
代码开发 1.根据菜品分页查询接口定义设计对应的DTO。
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.sky.dto;@Data public class DishPageQueryDTO implements Serializable { private int page; private int pageSize; private String name; private Integer categoryId; private Integer status; }
2.根据菜品分页查询接口定义设计对应的VO。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.sky.vo;@Data @Builder @NoArgsConstructor @AllArgsConstructor public class DishVO implements Serializable { private Long id; private String name; private Long categoryId; private BigDecimal price; private String image; private String description; private Integer status; private LocalDateTime updateTime; private String categoryName; private List<DishFlavor> flavors = new ArrayList <>(); }
3.根据接口定义创建DishController的page分页查询方法。
1 2 3 4 5 6 7 8 @GetMapping("/page") @ApiOperation("菜品分页查询") public Result<PageResult> page (DishPageQueryDTO dishPageQueryDTO) { log.info("菜品分页查询:{}" ,dishPageQueryDTO); PageResult pageResult = dishService.pageQuery(dishPageQueryDTO); return Result.success(pageResult); }
4.在DishService中扩展分页查询方法。
1 2 PageResult pageQuery (DishPageQueryDTO dishPageQueryDTO) ;
5.在DishServiceImpl中实现分页查询方法。
1 2 3 4 5 6 7 @Override public PageResult pageQuery (DishPageQueryDTO dishPageQueryDTO) { PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize()); Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO); return new PageResult (page.getTotal(), page.getResult()); }
6.在DishMapper接口中声明pageQuery方法。
1 2 Page<DishVO> pageQuery (DishPageQueryDTO dishPageQueryDTO) ;
7.在DishMapper.xml中编写SQL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="pageQuery" resultType ="com.sky.vo.DishVO" > select d.*, c.name categoryName from dish d left outer join category c on d.category_id = c.id <where > <if test ="name != null" > and d.name like concat('%',#{name},'%') </if > <if test ="categoryId != null" > and d.category_id = #{categoryId} </if > <if test ="status != null" > and d.status = #{status} </if > </where > order by d.create_time desc</select >
删除菜品 产品原型
业务规则:
可以一次删除一个菜品,也可以批量删除菜品。
起售中的菜品不能删除。
被套餐关联的菜品不能删除。
删除菜品后,关联的口味数据也需要删除掉。
接口设计
数据库设计
代码开发 1.根据删除菜品的接口定义在DishController中创建方法。
1 2 3 4 5 6 7 8 @DeleteMapping @ApiOperation("菜品批量删除") public Result delete (@RequestParam List<Long> ids) { log.info("菜品批量删除:{}" , ids); dishService.deleteBatch(ids); return Result.success(); }
2.在DishService接口中声明deleteBatch方法。
1 2 void deleteBatch (List<Long> ids) ;
3.在DishServiceImpl中实现deleteBatch方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Transactional @Override public void deleteBatch (List<Long> ids) { for (Long id : ids) { Dish dish = dishMapper.getById(id); if (dish.getStatus() == StatusConstant.ENABLE){ throw new DeletionNotAllowedException (MessageConstant.DISH_ON_SALE); } } List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids); if (setmealIds != null && setmealIds.size() > 0 ){ throw new DeletionNotAllowedException (MessageConstant.DISH_BE_RELATED_BY_SETMEAL); } dishMapper.deleteByIds(ids); dishFlavorMapper.deleteByDishIds(ids); }
4.在DishMapper中声明getById方法,并配置SQL。在DishMapper中声明deleteById方法并配置SQL。
1 2 3 4 5 6 7 8 9 10 @Select("select * from dish where id = #{id}") Dish getById (Long id) ;@Delete("delete from dish where id = #{id}") void deleteById (Long id) ;void deleteByIds (List<Long> ids) ;
1 2 3 4 5 6 <delete id ="deleteByIds" > delete from dish where id in <foreach collection ="ids" open ="(" close =")" separator ="," item ="id" > #{id} </foreach > </delete >
5.创建SetmealDishMapper,声明getSetmealIdsByDishIds方法,并在xml文件中编写SQL。在DishFlavorMapper中声明deleteByDishId方法并配置SQL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.sky.mapper;@Mapper public interface DishFlavorMapper { void insertBatch (List<DishFlavor> flavors) ; @Delete("delete from dish_flavor where dish_id = #{dishId}") void deleteByDishId (Long id) ; void deleteByDishIds (List<Long> dishIds) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.sky.mapper.DishFlavorMapper" > <insert id ="insertBatch" > insert into dish_flavor (dish_id, name, value) values <foreach collection ="flavors" item ="dishFlavor" separator ="," > (#{dishFlavor.dishId},#{dishFlavor.name},#{dishFlavor.value}) </foreach > </insert > <delete id ="deleteByDishIds" > delete from dish_flavor where dish_id in <foreach collection ="dishIds" open ="(" close =")" separator ="," item ="dishId" > #{dishId} </foreach > </delete > </mapper >
修改菜品 产品原型
接口设计 根据id查询菜品
根据类型查询分类(已实现) 文件上传(已实现) 修改菜品
代码开发 根据id查询菜品接口开发 1.DishController。
1 2 3 4 5 6 7 8 @GetMapping("/{id}") @ApiOperation("根据id查询菜品") public Result<DishVO> getById (@PathVariable Long id) { log.info("根据id查询菜品:{}" , id); DishVO dishVO = dishService.getByIdWithFlavor(id); return Result.success(dishVO); }
2.DishService。
1 2 DishVO getByIdWithFlavor (Long id) ;
3.DishServiceImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public DishVO getByIdWithFlavor (Long id) { Dish dish = dishMapper.getById(id); List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id); DishVO dishVO = new DishVO (); BeanUtils.copyProperties(dish, dishVO); dishVO.setFlavors(dishFlavors); return dishVO; }
4.DishFlavorMapper。
1 2 3 @Select("select * from dish_flavor where dish_id = #{dishId}") List<DishFlavor> getByDishId (Long dishId) ;
修改菜品接口开发 1.DishController。
1 2 3 4 5 6 7 8 @PutMapping() @ApiOperation("修改菜品") public Result update (@RequestBody DishDTO dishDTO) { log.info("修改菜品:{}" ,dishDTO); dishService.updateWithFlavor(dishDTO); return Result.success(); }
2.DishService。
1 2 void updateWithFlavor (DishDTO dishDTO) ;
3.DishServiceImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override public void updateWithFlavor (DishDTO dishDTO) { Dish dish = new Dish (); BeanUtils.copyProperties(dishDTO, dish); dishMapper.update(dish); dishFlavorMapper.deleteByDishId(dishDTO.getId()); List<DishFlavor> flavors = dishDTO.getFlavors(); if (flavors != null && flavors.size() > 0 ){ flavors.forEach(dishFlavor -> { dishFlavor.setDishId(dishDTO.getId()); }); dishFlavorMapper.insertBatch(flavors); } }
4.DishMapper。
1 2 3 @AutoFill(value = OperationType.UPDATE) void update (Dish dish) ;
5.DishMapper.xml。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <update id ="update" > update dish <set > <if test ="name != null" > name = #{name}, </if > <if test ="categoryId != null" > category_id = #{categoryId}, </if > <if test ="price != null" > price = #{price}, </if > <if test ="image != null" > image = #{image}, </if > <if test ="description != null" > description = #{description}, </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 >
菜品起售停售 接口设计
代码开发 1.DishController。
1 2 3 4 5 6 7 8 @PostMapping("status/{status}") @ApiOperation("菜品起售停售") public Result startOrStop (@PathVariable Integer status, Long id) { log.info("菜品起售停售:{},{}" ,status, id); dishService.startOrStop(status, id); return Result.success(); }
2.DishService。
1 2 void startOrStop (Integer status, Long id) ;
3.DishServiceImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override @Transactional public void startOrStop (Integer status, Long id) { Dish dish = Dish.builder().id(id).status(status).build(); dishMapper.update(dish); if (status == StatusConstant.DISABLE){ List<Long> dishIds = new ArrayList <>(); dishIds.add(id); List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(dishIds); if (setmealIds != null && setmealIds.size() > 0 ){ for (Long setmealId : setmealIds){ Setmeal setmeal = Setmeal.builder().id(setmealId).status(StatusConstant.DISABLE).build(); setmealMapper.update(setmeal); } } } }
4.SetmealMapper。
1 2 3 @AutoFill(value = OperationType.UPDATE) void update (Setmeal setmeal) ;
5.SetmealMapper.xml。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.sky.mapper.SetmealMapper" > <update id ="update" parameterType ="Setmeal" > update setmeal <set > <if test ="name != null" > name = #{name}, </if > <if test ="categoryId != null" > category_id = #{categoryId}, </if > <if test ="price != null" > price = #{price}, </if > <if test ="status != null" > status = #{status}, </if > <if test ="description != null" > description = #{description}, </if > <if test ="image != null" > image = #{image}, </if > <if test ="updateTime != null" > update_time = #{updateTime}, </if > <if test ="updateUser != null" > update_user = #{updateUser} </if > </set > where id = #{id} </update > </mapper >
套餐管理 新增套餐 产品原型
业务规则:
套餐名称唯一。
套餐必须属于某个分类。
套餐必须包含菜品。
名称、分类、价格、图片为必填项。
添加菜品窗口需要根据分类类型来展示菜品。
新增的套餐默认为停售状态。
接口设计
根据类型查询分类(已完成)。
根据分类id查询菜品。
图片上传(已完成)。
新增套餐。
数据库设计 setmeal表为套餐表,用于存储套餐的信息。具体表结构如下:
字段名
数据类型
说明
备注
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
setmeal_dish表为套餐菜品关系表,用于存储套餐和菜品的关联关系。具体表结构如下:
字段名
数据类型
说明
备注
id
bigint
主键
自增
setmeal_id
bigint
套餐id
逻辑外键
dish_id
bigint
菜品id
逻辑外键
name
varchar(32)
菜品名称
冗余字段
price
decimal(10,2)
菜品单价
冗余字段
copies
int
菜品份数
代码实现 1.DishController。
1 2 3 4 5 6 7 8 @GetMapping("/list") @ApiOperation("根据分类id查询菜品") public Result<List<Dish>> list (Long categoryId) { log.info("根据分类id查询菜品:{}" ,categoryId); List<Dish> list = dishService.list(categoryId); return Result.success(list); }
2.DishService。
1 2 List<Dish> list (Long categoryId) ;
3.DishServiceImpl。
1 2 3 4 5 6 7 @Override public List<Dish> list (Long categoryId) { Dish dish = Dish.builder().categoryId(categoryId).status(StatusConstant.ENABLE).build(); List<Dish> list = dishMapper.list(dish); return list; }
4.DishMapper。
1 2 List<Dish> list (Dish dish) ;
5.DishMapper.xml。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="list" resultType ="Dish" parameterType ="Dish" > select * from dish <where > <if test ="name != null" > and name like concat('%',#{name},'%') </if > <if test ="categoryId != null" > and category_id = #{categoryId} </if > <if test ="status != null" > and status = #{status} </if > </where > order by create_time desc</select >
6.SetmealController。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.sky.controller.admin;@RestController @RequestMapping("/admin/setmeal") @Api(tags = "套餐相关接口") @Slf4j public class SetmealController { @Autowired private SetmealService setmealService; @PostMapping @ApiOperation("新增套餐") public Result save (@RequestBody SetmealDTO setmealDTO) { log.info("新增套餐:{}" ,setmealDTO); setmealService.saveWithDish(setmealDTO); return Result.success(); } }
7.SetmealService。
1 2 3 4 5 6 package com.sky.service;public interface SetmealService { void saveWithDish (SetmealDTO setmealDTO) ; }
8.SetmealServiceImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.sky.service.impl;@Service @Slf4j public class SetmealServiceImpl implements SetmealService { @Autowired private SetmealMapper setmealMapper; @Autowired private SetmealDishMapper setmealDishMapper; @Override public void saveWithDish (SetmealDTO setmealDTO) { Setmeal setmeal = new Setmeal (); BeanUtils.copyProperties(setmealDTO, setmeal); setmealMapper.insert(setmeal); Long setmealId = setmeal.getId(); List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes(); setmealDishes.forEach(setmealDish -> { setmealDish.setSetmealId(setmealId); }); setmealDishMapper.insertBatch(setmealDishes); } }
9.SetmealMapper。
1 2 3 @AutoFill(value = OperationType.INSERT) void insert (Setmeal setmeal) ;
10.SetmealMapper.xml。
1 2 3 4 5 6 <insert id ="insert" parameterType ="Setmeal" useGeneratedKeys ="true" keyProperty ="id" > insert into setmeal (category_id, name, price, status, description, image, create_time, update_time, create_user, update_user) values (#{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})</insert >
11.SetmealDishMapper。
1 2 void insertBatch (List<SetmealDish> setmealDishes) ;
12.SetmealDishMapper.xml。
1 2 3 4 5 6 7 8 <insert id ="insertBatch" parameterType ="list" > insert into setmeal_dish (setmeal_id, dish_id, name, price, copies) values <foreach collection ="setmealDishes" item ="sd" separator ="," > (#{sd.setmealId}, #{sd.dishId}, #{sd.name}, #{sd.price}, #{sd.copies}) </foreach > </insert >
套餐分页查询 产品原型
业务规则:
根据页码进行分页展示。
每页展示10条数据。
可以根据需要,按照套餐名称、分类、售卖状态进行查询。
接口设计
代码实现 1.SetmealController。
1 2 3 4 5 6 7 8 @GetMapping("/page") @ApiOperation("分页查询") public Result<PageResult> page (SetmealPageQueryDTO setmealPageQueryDTO) { log.info("分页查询:{}" ,setmealPageQueryDTO); PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO); return Result.success(pageResult); }
2.SetmealService。
1 2 PageResult pageQuery (SetmealPageQueryDTO setmealPageQueryDTO) ;
3.SetmealServiceImpl。
1 2 3 4 5 6 7 @Override public PageResult pageQuery (SetmealPageQueryDTO setmealPageQueryDTO) { PageHelper.startPage(setmealPageQueryDTO.getPage(), setmealPageQueryDTO.getPageSize()); Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO); return new PageResult (page.getTotal(), page.getResult()); }
4.SetmealMapper。
1 2 Page<SetmealVO> pageQuery (SetmealPageQueryDTO setmealPageQueryDTO) ;
5.SetmealMapper.xml。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <select id ="pageQuery" resultType ="com.sky.vo.SetmealVO" > select s.*, c.name categoryName from setmeal s left join category c on s.category_id = c.id <where > <if test ="name != null" > and s.name like concat('%', #{name}, '%') </if > <if test ="status != null" > and s.status = #{status} </if > <if test ="categoryId != null" > and s.category_id = #{categoryId} </if > </where > order by s.create_time desc</select >
删除套餐 产品原型
业务规则:
可以一次删除一个套餐,也可以批量删除套餐。
起售中的套餐不能删除。
接口设计
代码实现 1.SetmealController。
1 2 3 4 5 6 7 8 @DeleteMapping @ApiOperation("批量删除套餐") public Result delete (@RequestParam List<Long> ids) { log.info("批量删除套餐:{}" , ids); setmealService.deleteBatch(ids); return Result.success(); }
2.SetmealService。
1 2 void deleteBatch (List<Long> ids) ;
3.SetmealServiceImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public void deleteBatch (List<Long> ids) { ids.forEach(id -> { Setmeal setmeal = setmealMapper.getById(id); if (StatusConstant.ENABLE == setmeal.getStatus()){ throw new DeletionNotAllowedException (MessageConstant.SETMEAL_ON_SALE); } }); ids.forEach(setmealId -> { setmealMapper.deleteById(setmealId); setmealDishMapper.deleteBySetmealId(setmealId); }); }
4.SetmealMapper。
1 2 3 4 5 6 7 @Select("select * from setmeal where id = #{id}") Setmeal getById (Long id) ;@Delete("delete from setmeal where id = #{setmealId}") void deleteById (Long setmealId) ;
5.SetmealDishMapper
1 2 3 @Delete("delete from setmeal_dish where setmeal_id = #{setmealId}") void deleteBySetmealId (Long setmealId) ;
修改套餐 产品原型
接口设计
根据id查询套餐。
根据类型查询分类(已完成)。
根据分类id查询菜品(已完成)。
图片上传(已完成)。
修改套餐。
代码实现 1.SetmealController。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @GetMapping("/{id}") @ApiOperation("根据id查询套餐") public Result<SetmealVO> getById (@PathVariable Long id) { log.info("根据id查询套餐:{}" , id); SetmealVO setmealVO = setmealService.getByIdWithDish(id); return Result.success(setmealVO); }@PutMapping @ApiOperation("修改套餐") public Result update (@RequestBody SetmealDTO setmealDTO) { log.info("修改套餐:{}" ,setmealDTO); setmealService.update(setmealDTO); return Result.success(); }
2.SetmealService。
1 2 3 4 5 SetmealVO getByIdWithDish (Long id) ;void update (SetmealDTO setmealDTO) ;
3.SetmealServiceImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Override public SetmealVO getByIdWithDish (Long id) { Setmeal setmeal = setmealMapper.getById(id); List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id); SetmealVO setmealVO = new SetmealVO (); BeanUtils.copyProperties(setmeal, setmealVO); setmealVO.setSetmealDishes(setmealDishes); return setmealVO; }@Override public void update (SetmealDTO setmealDTO) { Setmeal setmeal = new Setmeal (); BeanUtils.copyProperties(setmealDTO, setmeal); setmealMapper.update(setmeal); Long setmealId = setmeal.getId(); setmealDishMapper.deleteBySetmealId(setmealId); List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes(); setmealDishes.forEach(setmealDish -> { setmealDish.setSetmealId(setmealId); }); setmealDishMapper.insertBatch(setmealDishes); }
4.SetmealDishMapper。
1 2 3 @Select("select * from setmeal_dish where setmeal_id = #{setmealId}") List<SetmealDish> getBySetmealId (Long setmealId) ;
起售停售套餐 产品原型
业务规则:
可以对状态为起售的套餐进行停售操作,可以对状态为停售的套餐进行起售操作。
起售的套餐可以展示在用户端,停售的套餐不能展示在用户端。
起售套餐时,如果套餐内包含停售的菜品,则不能起售。
接口设计
代码实现 1.SetmealController。
1 2 3 4 5 6 7 8 @PostMapping("/status/{status}") @ApiOperation("套餐起售停售") public Result startOrStop (@PathVariable Integer status, Long id) { log.info("套餐起售停售:{},{}" ,status, id); setmealService.startOrStop(status, id); return Result.success(); }
2.SetmealService。
1 2 void startOrStop (Integer status, Long id) ;
3.SetmealServiceImpl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public void startOrStop (Integer status, Long id) { if (status == StatusConstant.ENABLE){ List<Dish> dishList = dishMapper.getBySetmealId(id); if (dishList != null && dishList.size() > 0 ){ dishList.forEach(dish -> { if (StatusConstant.DISABLE == dish.getStatus()){ throw new SetmealEnableFailedException (MessageConstant.SETMEAL_ENABLE_FAILED); } }); } } Setmeal setmeal = Setmeal.builder().id(id).status(status).build(); setmealMapper.update(setmeal); }
4.DishMapper。
1 2 3 4 @Select("select d.* from dish d left join setmeal_dish s " + "on d.id = s.dish_id where s.setmeal_id = #{setmealId}") List<Dish> getBySetmealId (Long setmealId) ;
Redis Redis入门 Redis简介 Redis是一个基于内存的key-value结构数据库。
优点:基于内存 存储,读写性能高。适合存储热点数据(热点商品、资讯、新闻)。企业应用广泛。
官网:https://redis.io
中文网:https://www.redis.net.cn/
Redis下载与安装 Redis安装包分为Windows版和Linux版:
Redis的Windows版属于绿色软件,直接解压即可使用,解压后目录结构如下:
Redis服务启动与停止 1.服务启动命令:redis-server.exe redis.windows.conf。
2.Redis服务默认端口号为6379,通过快捷键Ctrl + C即可停止Redis服务。
3.客户端连接命令:redis-cli.exe。
4.通过redis-cli.exe命令默认连接的是本地的redis服务,并且使用默认6379端口。也可以通过指定如下参数连接:
-h ip地址
-p 端口号
-a 密码(如果需要)
5.设置Redis服务密码,修改redis.windows.conf:requirepass 123456。
注意:
修改密码后需要重启Redis服务才能生效。
Redis配置文件中#表示注释。
6.Redis客户端图形工具:Another Redis Desktop Manager。
Redis数据类型 5种常用数据类型介绍 Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:
字符串string
哈希hash
列表list
集合set
有序集合sorted set / zset
各种数据类型的特点
字符串(string):普通字符串,Redis中最简单的数据类型。
哈希(hash):也叫散列,类似于Java中的HashMap结构。
列表(list):按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList。
集合(set):无序集合,没有重复元素,类似于Java中的HashSet。
有序集合(sorted set / zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素。
Redis常用命令 字符串操作命令
命令
含义
SET key value
设置指定key的值
GET key
获取指定key的值
SETEX key seconds value
设置指定key的值,并将 key 的过期时间设为 seconds 秒
SETNX key value
只有在 key 不存在时设置 key 的值
1 2 3 4 5 6 7 8 9 10 > set name surourou OK > set age 23 OK > setex token 60 whfiewrhfoiwhfoilwejrfoilw OK > setnx name srr 0 > setnx nickname mm 1
哈希操作命令 Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
命令
含义
HSET key field value
将哈希表 key 中的字段 field 的值设为 value
HGET key field
获取存储在哈希表中指定字段的值
HDEL key field
删除存储在哈希表中的指定字段
HKEYS key
获取哈希表中所有字段
HVALS key
获取哈希表中所有值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 > hset student name surourou1 > hset student age 23 1 > hget student name surourou > hkeys student name age > hvals student surourou23 > hdel student age1 > hkeys student name
列表操作命令 Redis列表是简单的字符串列表,按照插入顺序排序。
命令
含义
LPUSH key value1 [value2]
将一个或多个值插入到列表头部(左边)
LRANGE key start stop
获取列表指定范围内的元素
RPOP key
移除并获取列表最后一个元素(右边)
LLEN key
获取列表长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 > lpush arr 1 2 3 3 > rpush 4 5 6 2 > rpush arr 4 5 6 6 > lrange arr 0 -1 3 2 1 4 5 6 > rpop arr6 > llen arr5
集合操作命令 Redis的set 是string类型的无序集合。集合成员是唯一的,集合中不能出现重复的数据。
命令
含义
SADD key member1 [member2]
向集合添加一个或多个成员
SMEMBERS key
返回集合中的所有成员
SCARD key
获取集合的成员数
SINTER key1 [key2]
返回给定所有集合的交集
SUNION key1 [key2]
返回所有给定集合的并集
SREM key member1 [member2]
删除集合中一个或多个成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 > sadd set1 1 2 1 2 > sadd set2 1 3 8 3 > smembers set11 2 > scard set12 > sinter set1 set21 > sunion set1 set21 2 3 8 > srem set2 3 1 > smembers set21 8
有序集合操作命令 Redis有序集合是string类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数。(默认根据score升序排序。)
命令
含义
ZADD key score1 member1 [score2 member2]
向有序集合添加一个或多个成员
ZRANGE key start stop [WITHSCORES]
通过索引区间返回有序集合中指定区间内的成员
ZINCRBY key increment member
有序集合中对指定成员的分数加上增量 increment
ZREM key member [member …]
移除有序集合中的一个或多个成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 > zadd zset1 1 srr 2 surourou 3 surrou3 > zrange zset1 0 -1 srr surourou surrou > zincrby zset1 8 surourou10 > zrange zset1 0 -1 srr surrou surourou > zrem zset1 surrou1 > zrange zset1 0 -1 srr surourou
通用命令 Redis的通用命令是不分数据类型的,都可以使用的命令。
命令
含义
KEYS pattern
查找所有符合给定模式( pattern)的 key
EXISTS key
检查给定 key 是否存在
TYPE key
返回 key 所储存的值的类型
DEL key
该命令用于在 key 存在是删除 key
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 > keys * set1 arr age name student zset1 set2 nickname > exists arr1 > type name string > type zset1 zset > del nickname1 > keys a* arr age > keys * set1 arr age name student zset1 set2
在Java中操作Redis Redis的Java客户端 Redis的Java客户端很多,常用的几种:
Jedis
Lettuce
Spring Data Redis
Spring Data Redis是Spring的一部分,对Redis底层开发包进行了高度封装。在Spring项目中,可以使用Spring Data Redis来简化操作。
Spring Data Redis使用方式 操作步骤:
1.导入Spring Data Redis的maven坐标。
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
2.配置Redis数据源。
application-dev.yml文件。
1 2 3 4 5 6 sky: redis: host: localhost port: 6379 password: database: 1
application.yml文件。
1 2 3 4 5 6 spring: redis: host: ${sky.redis.host} port: ${sky.redis.port} password: ${sky.redis.password} database: ${sky.redis.database}
3.编写配置类,创建RedisTemplate对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.sky.config;@Configuration @Slf4j public class RedisConfiguration { @Bean public RedisTemplate redisTemplate (RedisConnectionFactory redisConnectionFactory) { log.info("开始创建redis模板对象" ); RedisTemplate redisTemplate = new RedisTemplate (); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer ()); return redisTemplate; } }
4.通过RedisTemplate对象操作Redis。
RedisTemplate针对大量api进行了归类封装,将同一数据类型的操作封装为对应的Operation接口,具体分类如下:
ValueOperations:string数据操作。
SetOperations:set类型数据操作。
ZSetOperations:zset类型数据操作。
HashOperations:hash类型的数据操作。
ListOperations:list类型的数据操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 package com.sky.test;@SpringBootTest public class SpringDataRedisTest { @Autowired private RedisTemplate redisTemplate; @Test public void testRedisTemplate () { System.out.println(redisTemplate); ValueOperations valueOperations = redisTemplate.opsForValue(); HashOperations hashOperations = redisTemplate.opsForHash(); ListOperations listOperations = redisTemplate.opsForList(); SetOperations setOperations = redisTemplate.opsForSet(); ZSetOperations zSetOperations = redisTemplate.opsForZSet(); } @Test public void testString () { redisTemplate.opsForValue().set("city" ,"广东" ); String city = (String) redisTemplate.opsForValue().get("city" ); System.out.println(city); redisTemplate.opsForValue().set("code" , "123456" , 3 , TimeUnit.MINUTES); redisTemplate.opsForValue().setIfAbsent("lock" , 1 ); redisTemplate.opsForValue().setIfAbsent("lock" , 2 ); } @Test public void testHash () { HashOperations hashOperations = redisTemplate.opsForHash(); hashOperations.put("100" , "name" , "srr" ); hashOperations.put("100" , "age" , "23" ); String name = (String) hashOperations.get("100" , "name" ); System.out.println(name); Set keys = hashOperations.keys("100" ); System.out.println(keys); List values = hashOperations.values("100" ); System.out.println(values); hashOperations.delete("100" , "age" ); } @Test public void testList () { ListOperations listOperations = redisTemplate.opsForList(); listOperations.leftPushAll("mylist" , "a" , "b" , "c" ); listOperations.rightPush("mylist" , "d" ); List mylist = listOperations.range("mylist" , 0 , -1 ); System.out.println(mylist); listOperations.rightPop("mylist" ); Long size = listOperations.size("mylist" ); System.out.println(size); } @Test public void testSet () { SetOperations setOperations = redisTemplate.opsForSet(); setOperations.add("set1" , "a" , "b" , "c" , "a" , "d" ); setOperations.add("set2" , "a" , "b" , "b" , "x" , "y" ); Set members = setOperations.members("set1" ); System.out.println(members); Long size = setOperations.size("set1" ); System.out.println(size); Set intersect = setOperations.intersect("set1" , "set2" ); System.out.println(intersect); Set union = setOperations.union("set1" , "set2" ); System.out.println(union); setOperations.remove("set1" , "a" , "c" ); } @Test public void testZSet () { ZSetOperations zSetOperations = redisTemplate.opsForZSet(); zSetOperations.add("zset1" , "a" , 10 ); zSetOperations.add("zset2" , "b" , 20 ); zSetOperations.add("zset3" , "c" , 8 ); Set zset = zSetOperations.range("zset1" , 0 , -1 ); System.out.println(zset); zSetOperations.incrementScore("zset1" , "c" , 10 ); zSetOperations.remove("zset1" , "b" ); } @Test public void testCommon () { Set keys = redisTemplate.keys("*" ); System.out.println(keys); Boolean name = redisTemplate.hasKey("name" ); Boolean set1 = redisTemplate.hasKey("set1" ); for (Object key : keys){ DataType type = redisTemplate.type(key); System.out.println(type); } redisTemplate.delete("mylist" ); } }
店铺营业状态设置 产品原型
接口设计 设置营业状态。
管理端查询营业状态。
用户端查询营业状态。
本项目约定:
管理端发出的请求,统一使用/admin作为前缀。
用户端发出的请求,统一使用/user作为前缀。
营业状态数据存储方式:基于Redis的字符串来进行存储。约定:1表示营业,0表示打烊。
代码开发 1.管理端ShopController。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.sky.controller.admin;@RestController("adminShopController") @RequestMapping("/admin/shop") @Api(tags = "店铺相关接口") @Slf4j public class ShopController { @Autowired private RedisTemplate redisTemplate; public static final String KEY = "SHOP_STATUS" ; @PutMapping("/{status}") @ApiOperation("设置店铺的营业状态") public Result setStatus (@PathVariable Integer status) { log.info("设置店铺的营业状态:{}" , status == 1 ? "营业中" : "打烊中" ); redisTemplate.opsForValue().set(KEY, status); return Result.success(); } @GetMapping("/status") @ApiOperation("获取店铺的营业状态") public Result<Integer> getStatus () { Integer status = (Integer) redisTemplate.opsForValue().get(KEY); log.info("获取店铺的营业状态:{}" , status == 1 ? "营业中" : "打烊中" ); return Result.success(status); } }
2.用户端ShopController。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.sky.controller.user;@RestController("userShopController") @RequestMapping("/user/shop") @Api(tags = "店铺相关接口") @Slf4j public class ShopController { @Autowired private RedisTemplate redisTemplate; public static final String KEY = "SHOP_STATUS" ; @GetMapping("/status") @ApiOperation("获取店铺的营业状态") public Result<Integer> getStatus () { Integer status = (Integer) redisTemplate.opsForValue().get(KEY); log.info("获取店铺的营业状态:{}" , status == 1 ? "营业中" : "打烊中" ); return Result.success(status); } }
注意:因为管理端和用户端都命名为ShopController,在创建Bean时都会使用默认的名字shopController发生冲突,出现报错。
报错:Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'shopController' for bean class [com.sky.controller.user.ShopController] conflicts with existing, non-compatible bean definition of same name and class [com.sky.controller.admin.ShopController]
解决:分别给两个ShopController命名为:@RestController("adminShopController")和@RestController("userShopController")。