【苍穹外卖-管理端部分-学习笔记】

<<回到导览

1. 环境搭建

1.1.项目结构介绍

项目整体结构介绍

名称说明
sky-take-outmaven父工程,统一管理依赖版本,聚合其他子模块
sky-common子模块,存放公共类,例如:工具类、常量类、异常类等
sky-pojo子模块,存放实体类、VO、DTO等
sky-server子模块,后端服务,存放配置文件、Controller、Service、Mapper等

在这里插入图片描述

sky-pojo模块介绍

说明
Entity实体,通常和数据库中的表对应
DTO数据传输对象,通常用于程序中各层之间传递数据
VO视图对象,为前端展示数据提供的对象
POJO普通Java对象,只有属性和对应的getter和setter

在这里插入图片描述

1.2.数据库环境搭建

  • sql文件

    表名含义
    employee员工表
    category分类表
    dish菜品表
    dish flavor菜品口味表
    setmeal套餐表
    setmeal_dish套餐菜品关系表
    user用户表
    address book地址表
    shopping_cart购物车表
    orders订单表
    order_detail订单明细表

1.3.Nginx简介

  • 反向代理:将前端发送的动态请求由nginx转发到后端服务器

    优点:

    1. 提高访问数据(缓存)
    2. 进行负载均衡(大量请求按指定方式分配给集群中的每台服务器,如轮询)
    3. 保证后端服务安全(后端服务不能直接通过前端访问)
  • 反向代理配置方式

    在这里插入图片描述

  • 负载均衡配置方式

    在这里插入图片描述

1.4.完善登录功能

要求:将密码通过MD5加密方式对明文密码加密后储存,提高安全性

注意:MD5只能单向加密,即正常情况下,只能加密,不能解密

小技巧:发现要做的模块,现在不着急做(及生成代办注释),可以用TODO注释如:

// TODO 后期需要进行md5加密,然后再进行比对

这样我们可以通过窗口看到代办注释

在这里插入图片描述

对密码进行MD5加密(EmployeeServiceImpl.java)

// 对明文密码进行MD5加密
password = DigestUtils.md5DigestAsHex(password.getBytes());

我们将数据库中的密码改写成MD5加密后的,例如密码为123456,MD5加密后为e10adc3949ba59abbe56e057f20f883e

1.5.导入接口文档

  • 前后端分离开发流程

    在这里插入图片描述

接下来我们用apifox导入api

  1. 创建项目

    在这里插入图片描述

  2. 导入

    在这里插入图片描述

在这里插入图片描述

1.6.Swagger

  • 使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。

    官网:https://swagger.io/

    Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。

  • 使用方式

    1. 导入Knife4j坐标

      <!-- Knife4j坐标 -->
      <dependency>
          <groupld>com.github.xiaoymin</groupld>
          <artifactld>knife4j-spring-boot-starter</artifactld>
          <version>3.0.2</version>
      </dependency>
      
    2. 在配置类中加入knife4j相关配置(sky-server/src/main/java/com/sky/config)

      // 通过knife4j生成接口文档
      @Bean
      public Docket docket() {
          ApiInfo apiInfo = new ApiInfoBuilder()
              .title("苍穹外卖项目接口文档")
              .version("2.0")
              .description("苍穹外卖项目接口文档")
              .build();
          Docket docket = new Docket(DocumentationType.SWAGGER_2)
              .apiInfo(apiInfo)
              .select()
              .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
              .paths(PathSelectors.any())
              .build();
          return docket;
      }
      
    3. 启动服务后,可以在浏览器输入http://localhost:8080/doc.html,查看生成的接口文档,并进行测试

      在这里插入图片描述

  • Swagger常用注解

    注解说明
    @Api用在类上,例如Controller,表示对类的说明
    @ApiModel用在类上,例如entity、DTO、VO
    @ApiModelProperty用在属性上,描述属性信息
    @ApiOperation用在方法上,例如Controller的方法,说明方法的用途、作用
    @RestController
    @RequestMapping("/admin/employee")
    @Slf4j
    // 1. @Api,对类的说明
    @Api(tags = "员工相关接口")
    public class EmployeeController {
        // ...
    }
    
    @PostMapping("/login")
    // 4. @ApiOperation,用在方法上,说明方法的用途、作用
    @ApiOperation(value = "员工登录")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        // ...
    }
    
    @Data
    // 2. @ApiModel,用在entity、DTO、VO类上
    @ApiModel(description = "员工登录时传递的数据模型")
    public class EmployeeLoginDTO implements Serializable {
        // 3. @ApiModelProperty, 用在属性上,描述属性信息
        @ApiModelProperty("用户名")
        private String username;
    	// ...
    }
    

    添加完毕后,重启程序,文档会发生相应变化
    在这里插入图片描述

2.员工模块

2.1.新增员工

代码:

  1. Controller

    @ApiOperation("新增员工")
    @PostMapping
    public Result save(@RequestBody EmployeeDTO employeeDTO) {
        log.info("新增员工:{}", employeeDTO);
        employeeService.save(employeeDTO);
        return Result.success();
    }
    
  2. Service

    public interface EmployeeService {
        // 新增员工
        void save(EmployeeDTO employeeDTO);
    }
    
    // impl
    // 新增员工
    @Override
    public void save(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
    
        // 对象属性拷贝
        BeanUtils.copyProperties(employeeDTO, employee);
    
        // 设置账号状态,默认正常状态
        employee.setStatus(StatusConstant.ENABLE);
    
        // 设置密码,默认密码123456
      employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
    
        // 设置当前记录的创建、修改时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
    
        // 设置当前记录创建人id和修改人id
        // TODO 后期需要改为当前登录用户的id
        employee.setCreateUser(10L);
        employee.setUpdateUser(10L);
    
        employeeMapper.insert(employee);
    }
    
  3. Mapper

    // 插入员工数据
    @Insert( "insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user)" +
            "values" +
            "(#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    void insert(Employee employee);
    

调试:

  1. 发送登录请求,获取token
    在这里插入图片描述

  2. 配置全局参数

    在这里插入图片描述

    删掉员工登录选修卡,可以看见token被成功添加到请求头

    在这里插入图片描述

  3. 测试新增员工接口

    在这里插入图片描述

    数据库成功添加员工信息

    在这里插入图片描述

    前后端联调成功

    在这里插入图片描述

在这里插入图片描述

功能完善:

  • 录入的用户名已经存在,抛出异常后未处理

    我们可以看到的是,报错类型是SQLIntegrityConstraintViolationException,

    在这里插入图片描述

    捕获这个sql异常并处理

    /* sql异常 */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        String message = ex.getMessage();
        if(message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String username = split[2];
            return Result.error(username + MessageConstant.ALREADY_EXIT);
        }else{
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }
    

    在这里插入图片描述

  • 创建人id和修改人id设置为固定值

    员工登录成功后,生成JWT令牌并相应给前端

    // controller/admin/EmployeeController.java
    //登录成功后,生成jwt令牌
    Map<String, Object> claims = new HashMap<>();
    claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
    String token = JwtUtil.createJWT(
        jwtProperties.getAdminSecretKey(),
        jwtProperties.getAdminTtl(),
        claims);
    

    在校验令牌时,拦截器进行拦截并将令牌中的id解析出来

    // interceptor/JwtTokenAdminInterceptor.java
    //2、校验令牌
    try {
        log.info("jwt校验:{}", token);
        Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
        // 从JWT令牌中解析出id
        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将id传输到我们需要使用id的service实现类中

    ThreadLocal并不是Thread而是Thread的局部变量

    ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。

    客户端发送的每一次请求都是一个单独的线程

    ThreadLocal常用方法说明
    public void set(T value)设置当前线程的线程局部变量的值
    public T get()返回当前线程的线程局部变量的值
    public void remove()移除当前线程的线程局部变量的值

    在外面使用ThreadLocal时,常常会封装成一个工具类

    // sky-common/src/main/java/com/sky/context/BaseContext.java
    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();
        }
    }
    

    调用ThreadLocal完善功能

    //2、校验令牌
    try {
        Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
        // 存入
        BaseContext.setCurrentId(empId);
    } catch (Exception ex) {
        // ...
    }
    
    // 设置当前记录创建人id和修改人id
    employee.setCreateUser(BaseContext.getCurrentId());
    employee.setUpdateUser(BaseContext.getCurrentId());
    

2.2.员工分页查询

业务规则:

  • 根据页码显示员工信息
  • 每页展示10条数据
  • 分页查询时可以根据需要,输入员工姓名进行查询

代码:

  1. Controller

    // 员工分页查询
    @ApiOperation("员工分页查询")
    @GetMapping("/page")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {
        log.info("员工分页查询,参数为{}", employeePageQueryDTO);
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }
    
  2. Service

    // 员工分页查询
    PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
    
    // 员工分页查询
    @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);
    }
    
  3. Mapper

    // 分页查询
    Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
    
    <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>
    

2.3.启用禁用账号

业务规则:

  • 启用/禁用的相互切换
  • 状态为禁用的员工账号不能登录系统

代码:

  1. Controller

    // 启用禁用员工账号
    @ApiOperation("启用/禁用员工账号")
    @PostMapping("/status/{status}")
    public Result startOrStop(@PathVariable Integer status, long id) {
        log.info("启用/禁用员工账号:{},{}", status, id);
        employeeService.startOrStop(status, id);
        return Result.success();
    }
    
  2. Service

    // 禁用或启用员工
    void startOrStop(Integer status, long id);
    
    // 禁用或启用员工
    @Override
    public void startOrStop(Integer status, long id) {
        // Employee employee = new Employee();
        // employee.setId(id);
        // employee.setStatus(status);
        Employee employee = Employee.builder()
            .status(status)
            .id(id)
            .build();
        // 设置更新时间
        employee.setUpdateTime(LocalDateTime.now());
        
        // 设置当前记录修改人id
        employee.setUpdateUser(BaseContext.getCurrentId());
        
        employeeMapper.update(employee);
    }
    
  3. Mapper

    // 根据主键动态修改属性
    void update(Employee employee);
    
    <update id="update" parameterType="Employee">
        update employee
        <set>
            <if test="name != null">name = #{name},</if>
            <if test="username != null">username = #{username},</if>
            <if test="password != null">password = #{password},</if>
            <if test="phone != null">phone = #{phone},</if>
            <if test="sex != null">sex = #{sex},</if>
            <if test="idNumber != null">id_Number = #{idNumber},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateUser != null">update_User = #{updateUser},</if>
            <if test="status != null">status = #{status},</if>
        </set>
        where id = #{id}
    </update>
    

2.4.编辑员工信息

业务规则:

  • 根据id查询员工信息
  • 编辑员工信息

代码:

  1. Controller

    // 根据id查询员工
    @ApiOperation("根据id查询员工")
    @GetMapping("/{id}")
    public Result<Employee> getById(@PathVariable Long id) {
        log.info("根据id={}, 查询员工",id);
        Employee employee = employeeService.getById(id);
        return Result.success(employee);
    }
    
    // 编辑员工信息
    @ApiOperation("编辑员工信息")
    @PutMapping
    public Result update(@RequestBody EmployeeDTO employeeDTO) {
        log.info("编辑员工信息:{}",employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();
    }
    
  2. Service

    // 根据id查询员工
    Employee getById(long id);
    // 更新员工信息
    void update(EmployeeDTO employeeDTO);
    
    // 根据id查询员工
    @Override
    public Employee getById(long id) {
        Employee employee = employeeMapper.getById(id);
        employee.setPassword("****");
        return employee;
    }
    
    @Override
    public void update(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        // 对象属性拷贝
        BeanUtils.copyProperties(employeeDTO, employee);
        
        // 设置更新时间
        employee.setUpdateTime(LocalDateTime.now());
        
        // 设置当前记录修改人id
        employee.setUpdateUser(BaseContext.getCurrentId());
        
        employeeMapper.update(employee);
    }
    
  3. Mapper

    // 根据id查询员工
    @Select("select * from employee where id = #{id}")
    Employee getById(Long id);
    

2.5.公共字段填充

在上面业务表中存在一些公共字段,在更新、插入数据库时需要手动填充,造成代码冗余,我们可以通过自动填充公共字段来解决这个问题

字段名含义数据类型操作类型
create_time创建时间datetimeinsert
create_user创建人idbigintinsert
update_time修改时间datetimeinsert、update
update_user修改人idbigintinsert、update

实现思路:

  • 自定义注解 AutoFill,用于标识需要进行公共字段填充的方法

    // sky-server/src/main/java/com/sky/annotation/AutoFill.java
    // 自定义注解,用于标识某个方法需要进行功能字段自动填充
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AutoFill {
        // 数据库操作类型:update insert
        OperationType value();
    }
    
  • 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

    // 自定义切面,实现公共字段自动填充处理逻辑
    @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("开始进行公共字段自动填充...");
            // 1.获取到当前被拦截的方法上的数据库操作类型
            MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 方法签名对象
            AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);  // 获得方法上的注解
            OperationType operationType = autoFill.value(); // 获得数据库操作类型
    
            // 2.获取到当前被拦截的方法的参数--实体对象
            Object[] args = joinPoint.getArgs();
            if(args == null || args.length == 0){
                return;
            }
            Object entity = args[0];
    
            // 3.准备赋值的数据
            LocalDateTime now = LocalDateTime.now();
            Long currentId = BaseContext.getCurrentId();
            
            // 4.根据当前不同的操作类型,为对应的属性通过反射赋值
            if(operationType == OperationType.INSERT){
                // 为4个公共字段赋值
                try {
                    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 (Exception e) {
                    throw new RuntimeException(e);
                }
            }else if(operationType == OperationType.UPDATE){
                // 为2个公共字段赋值
                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 (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
  • 在Mapper 的方法上加入 AutoFl 注解

    // 插入员工数据
    @AutoFill(value = OperationType.INSERT)
    @Insert( "insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user)" +
            "values" +
            "(#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    void insert(Employee employee);
    
    // 根据主键动态修改属性
    @AutoFill(value = OperationType.UPDATE)
    void update(Employee employee);
    

    我们再删除Server实现类中手动填充字段的代码,重启程序,更新时间会随着操作变化,则自动填充设置成功

3.菜品分类模块

3.1.前言

虽然这个模块视频中的老师是导入已经写好的代码,但是我还是按照文档手写了一遍,也是收获满满。由于时间原因,前面的代码都是跟着老师敲的,而我又资质愚钝,效果并不是很好。在独立地写完这个模块时,也是花了很多时间去试错(比如分页查询部分,代码一切正常,但是就是没有返回结果,控制台也不报错,排错了很久才发现是Controller中没有返回,也是被自己蠢笑了QwQ)。

在写代码中我也总结出了自己的一些小经验:

  1. 在选择将查询语句是否写入xml文件中,一般来说,插入和删除一般不会写入xml文件,而更新和查询会。
    因为插入和查询语句很简单,一般是根据id来进行操作,而不会判断值是否为空。

  2. 在最开始的时候我并不理解很什么是DTO和VO的作用,后来也渐渐明白,DTO就是把前端在发送请求所携带的数据时封装的对象,VO是后端给前端发送的对象,因为前端并不会使用数据库的全部数据,Entity是和数据库中的表的字段对应的对象,POJO则是普通的java对象,DTO、VO、Entity都属于POJO
    在这里插入图片描述

3.2.分类分页查询

  1. Controller

    @RestController
    @RequestMapping("/admin/category")
    @Slf4j
    @Api(tags = "菜品分类相关接口")
    public class CategoryController {
        @Autowired
        private CategoryService categoryService;
        
        // 分类分页查询
        @GetMapping("/page")
        @ApiOperation("菜品分类分页查询")
        public Result<PageResult> page(CategoryPageQueryDTO categoryPageQueryDTO) {
            log.info("菜品类分页查询,参数为{}", categoryPageQueryDTO);
            PageResult pageResult = categoryService.pageQuery(categoryPageQueryDTO);
            return Result.success(pageResult);
        }
    }
    
  2. Service

    public interface CategoryService {
        // 分类分页查询
        PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);
    }
    
    // impl
    @Service
    public class CategoryServiceImpl implements CategoryService {
        @Autowired
        private CategoryMapper categoryMapper;
    
        // 分类分页查询
        @Override
        public PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO) {
            PageHelper.startPage(categoryPageQueryDTO.getPage(), categoryPageQueryDTO.getPageSize());
            Page<Category> page = categoryMapper.pageQuery(categoryPageQueryDTO);
    
            long total = page.getTotal();
            List<Category> records = page.getResult();
            return new PageResult(total, records);
        }
    }
    
  3. Mapper

    @Mapper
    public interface CategoryMapper {
        // 分类分页查询
        Page<Category> pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);
    }
    
    <!-- xml -->
    <?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 != ''">
                        and type = #{type}
                    </if>
                </where>
            order by create_time desc
        </select>
    </mapper>
    

3.3.修改菜品分类

  1. Controller

    // 修改分类
    @PutMapping
    @ApiOperation("修改分类")
    public Result update(@RequestBody CategoryDTO categoryDTO) {
        log.info("修改分类:{}", categoryDTO);
        categoryService.update(categoryDTO);
        return Result.success();
    }
    
  2. Service

    // 修改分类
    void update(CategoryDTO categoryDTO);
    
    // 修改分类
    @Override
    public void update(CategoryDTO categoryDTO) {
        Category category = new Category();
        // 对象属性拷贝
        BeanUtils.copyProperties(categoryDTO, category);
        categoryMapper.update(category);
    }
    
  3. Mapper

    // 修改分类
    @AutoFill(value = OperationType.UPDATE)
    void update(Category category);
    
    <!--  xml  -->
    <!--  修改分类  -->
    <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>
    

3.4.启用禁用分类

  1. Controller

    // 启用、禁用分类
    @PostMapping("/status/{status}")
    @ApiOperation("启用/禁用分类")
    public Result startOrStop(@PathVariable Integer status, long id) {
        log.info("禁用/启用菜品分类:{},{}", id, status);
        categoryService.startOrStop(id, status);
        return Result.success();
    }
    
  2. Service

    // 启用、禁用分类
    void startOrStop(long id, Integer status);
    
    // 启用、禁用分类
    @Override
    public void startOrStop(long id, Integer status) {
        Category category = Category.builder()
            .id(id)
            .status(status)
            .build();
        categoryMapper.update(category);
    }
    

3.5.新增分类

  1. Controller

    // 新增分类
    @PostMapping
    @ApiOperation("新增分类")
    public Result save(@RequestBody CategoryDTO categoryDTO) {
        log.info("新增分类:{}", categoryDTO);
        categoryService.save(categoryDTO);
        return Result.success();
    }
    
  2. Service

    // 新增分类
    void save(CategoryDTO categoryDTO);
    
    // 新增分类
    @Override
    public void save(CategoryDTO categoryDTO) {
        Category category = new Category();
        BeanUtils.copyProperties(categoryDTO, category);
        categoryMapper.save(category);
    }
    
  3. Mapper

    // 新增分类
    @AutoFill(value = OperationType.INSERT)
    @Insert("insert into category (type, name, sort, status, create_time, create_user, update_time, update_user) " +
            "value (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{createUser}, #{updateTime}, #{updateUser})")
    void save(Category category);
    

3.6.根据id删除分类

  1. Controller

    // 根据id删除分类
    @DeleteMapping
    @ApiOperation("根据id删除分类")
    public Result delete(long id){
        log.info("根据id={}删除菜品", id);
        categoryService.deleteById(id);
        return Result.success();
    }
    
  2. Service

    // 根据id删除分类
    void deleteById(long id);
    
    // 新增分类
    @Override
    public void save(CategoryDTO categoryDTO) {
        Category category = new Category();
        BeanUtils.copyProperties(categoryDTO, category);
        categoryMapper.save(category);
    }
    
  3. Mapper

    // 根据id删除分类
    @Delete("delete from category where id = #{id}")
    void delete(long id);
    

3.7.根据类型查询分类

  1. Controller

    // 根据类型查询分类
    @GetMapping("/list")
    @ApiOperation("根据类型查询分类")
    public Result<List<Category>> list(String type) {
        log.info("根据类型type={}查询分类", type);
        List<Category> list = categoryService.list(type);
        return Result.success(list);
    }
    
  2. Service

    // 根据类型查询
    List<Category> list(String type);
    
    // impl
    // 根据类型查询
    @Override
    public List<Category> list(String type) {
        return categoryMapper.list(type);
    }
    
  3. Mapper

    // 根据类型查询
    List<Category> list(String type);
    
    <!--  根据类型查询  -->
    <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>
    

4.菜品模块

4.1.菜品分页查询

  1. Controller

    // 菜品分页查询
    @GetMapping("page")
    @ApiOperation("菜品分页查询")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){
        log.info("菜品分页查询:{}",dishPageQueryDTO);
        PageResult page = dishService.page(dishPageQueryDTO);
        return Result.success(page);
    }
    
  2. Service

    // 菜品分页查询
    PageResult page(DishPageQueryDTO dishPageQueryDTO);
    
    // impl
    // 菜品分页查询
    @Override
    public PageResult page(DishPageQueryDTO dishPageQueryDTO) {
        PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
        Page<Dish> page = dishMapper.page(dishPageQueryDTO);
        long total = page.getTotal();
        List<Dish> records = page.getResult();
        return new PageResult(total, records);
    }
    
  3. Mappper

    // 分类分页查询
    Page<Dish> page(DishPageQueryDTO dishPageQueryDTO);
    
    <!--  xml  -->
    <select id="page" resultType="com.sky.entity.Dish">
        select * from dish
        <where>
            <if test="name != null"> name like concat('%', #{name}, '%' );</if>
            <if test="categoryId != null">category_id = #{categoryId};</if>
            <if test="status != null">status = #{status};</if>
        </where>
    </select>
    

4.2.根据id查询菜品

  1. Controller

    // 根据id查询菜品
    @GetMapping("/{id}")
    @ApiOperation("根据id查询菜品")
    public Result<DishVO> getById(@PathVariable Long id){
        log.info("根据id查询菜品:{}",id);
        DishVO list = dishService.getById(id);
        return Result.success(list);
    }
    
  2. Service

    // 根据id查询菜品
    DishVO getById(Long id);
    
    // impl
    // 根据id查询菜品
    @Override
    public DishVO getById(Long id) {
        return dishMapper.getById(id);
    }
    
  3. Mappper

    // 根据id查询菜品
    @Select("select * from dish where id = #{id}")
    DishVO getById(Long categoryId);
    

4.3.修改菜品

  1. Controller

    // 修改菜品
    @PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO){
        log.info("修改菜品:{}",dishDTO);
        dishService.update(dishDTO);
        return Result.success();
    }
    
  2. Service

    // 修改菜品
    void update(DishDTO dishDTO);
    
    // impl
    // 修改菜品
    @Override
    public void update(DishDTO dishDTO) {
        Dish dish = Dish.builder()
            .id(dishDTO.getId())
            .name(dishDTO.getName())
            .price(dishDTO.getPrice())
            .image(dishDTO.getImage())
            .description(dishDTO.getDescription())
            .status(dishDTO.getStatus())
            .categoryId(dishDTO.getCategoryId())
            .build();
        dishMapper.update(dish);
    }
    
  3. Mappper

    // 修改菜品
    @AutoFill(OperationType.UPDATE)
    void update(Dish dish);
    
    <!--  xml  -->
    <!-- 修改菜品 -->
    <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>
    </update>
    

4.4.菜品起售停售

  1. Controller

    // 菜品起售停售
    @PostMapping("status/{status}")
    @ApiOperation("菜品起售停售")
    public Result startOrStop(@PathVariable Integer status, Long id){
        log.info("菜品起售停售:{}, {}", status, id);
        dishService.startOrStop(id, status);
        return Result.success();
    }
    
  2. Service

    // 菜品起售停售
    void startOrStop(Long id, Integer status);
    
    // impl
    // 菜品起售停售
    @Override
    public void startOrStop(Long id, Integer status) {
        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();
                    setmealDishMapper.update(setmeal);
                }
            }
        }
    }
    
  3. Mapper

    // sky-server/src/main/java/com/sky/mapper/SetmealDishMapper.java
    // 根据id修改套餐
    @AutoFill(OperationType.UPDATE)
    void update(Setmeal setmeal);
    
    <!-- sky-server/src/main/resources/mapper/SetmealDishMapper.xml -->
    <!-- 根据id修改套餐 -->
    <update id="update">
        update setmeal
        <set>
            <if test="name != null">name = #{name}</if>
            <if test="name != null">category_id = #{categoryId}</if>
            <if test="name != null">price = #{price}</if>
            <if test="name != null">status = #{status}</if>
            <if test="name != null">description = #{description}</if>
            <if test="name != null">image = #{image}</if>
            <if test="name != null">update_time = #{updateTime}</if>
            <if test="name != null">update_user = #{updateUser}</if>
        </set>
    </update>
    

4.5.文件上传

  1. 配置oss

    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}
    

    配置属性类

    // sky-common/src/main/java/com/sky/properties/AliOssProperties.java
    @Component
    @ConfigurationProperties(prefix = "sky.alioss")
    @Data
    public class AliOssProperties {
        private String endpoint;
        private String accessKeyId;
        private String accessKeySecret;
        private String bucketName;
    }
    
    # sky-server/src/main/resources/application-dev.yml
    sky:
      alioss:
        endpoint: oss-cn-beijing.aliyuncs.com
        access-key-id: LTAI5tKr2ZB866rBJea9ucuJ
        access-key-secret: gLx3lSngXHXv5SUyBW2c29XhHL5XQq
        bucket-name: sky-jiaqi
    
  2. Controller

    在项目的sky-common/src/main/java/com/sky/utils/AliOssUtil.java中,已经写好了请求oss的工具类,我们在yml文件中配置好bucket仓库,然后调用公里类中的upload方法上传文件即可

    // sky-server/src/main/java/com/sky/controller/admin/CommonController.java
    // 通用接口
    @RestController
    @Slf4j
    @RequestMapping("/admin/common")
    @Api(tags = "通用接口")
    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 (IOException e) {
                log.error("文件上传失败:{}", e);
            }
            return Result.error(MessageConstant.UPLOAD_FAILED);
        }
    }
    

4.6.新增菜品

业务规则:

  • 菜品名称必须统一
  • 菜品必须属于某个分类下,不能单独存在
  • 新增菜品时可以根据1情况选择菜品的口味
  • 每个菜品必须对应一张图片
  1. Controller

    // 新增菜品
    @PostMapping()
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }
    
  2. Service

    // 修改菜品
    void update(DishDTO dishDTO);
    
    // impl
    // 新增菜品
    @Override
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        // 向菜品表插入一条数据
        dishMapper.insert(dish);
    
        // 获取insert语句生成的主键值
        Long dishId = dish.getId();
    
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0) {
            flavors.forEach(flavor -> {
                flavor.setDishId(dishId);
            });
            // 向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
        }
    }
    
  3. Mappper

    // 向菜品表插入一条数据
    @AutoFill(OperationType.INSERT)
    void insert(Dish dish);
    
    <!--  xml  -->
    <!-- 向菜品表中插入一条数据 -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into dish
        (name, category_id, price, image, description, create_time, update_time, create_user, update_user, status)
        values
        (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status});
    </insert>
    
    @Mapper
    public interface DishFlavorMapper {
        // 向口味表插入n条数据
        void insertBatch(List<DishFlavor> flavors);
    }
    
    <!--  xml  -->
    <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>
    

4.7.批量删除菜品

业务规则:

  • 可以一次删除一个菜品,也可以批量删除菜品
  • 起售中的菜品不能删除
  • 被套餐关联的菜品不能删除
  • 删除菜品后,关联的口味数据也需要删除掉

代码

  1. Controller

    // 菜品批量删除
    @DeleteMapping
    @ApiOperation("菜品批量删除")
    public Result delete(@RequestParam List<Long> ids) {
        log.info("菜品批量删除:{}", ids);
        dishService.deleteBatch(ids);
        return Result.success();
    }
    
  2. Service

    // 菜品批量删除
    void deleteBatch(List<Long> ids);
    
    // impl
    // 菜品批量删除
    @Override
    @Transactional
    public void deleteBatch(List<Long> ids) {
        // 是否存在起售中的菜品
        for (Long id : ids) {
            Dish dish = dishMapper.getById(id);
            if(Objects.equals(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.CATEGORY_BE_RELATED_BY_DISH);
        }
    
        // 删除菜品表中的菜品数据
        for (Long id : ids) {
            dishMapper.deleteById(id);
            // 删除菜品关联的口味数据
            dishFlavorMapper.deleteByDishId(id);
        }
    }
    
  3. Mappper

    • 根据菜品id查询套餐id

      // sky-server/src/main/java/com/sky/mapper/SetmealDishMapper.java
      // 根据菜品id查询套餐id
      List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
      
      <!-- sky-server/src/main/resources/mapper/SetmealDishMapper.xml -->
      <!--  xml  -->
      <!--  根据菜品id查询套餐id  -->
      <select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
          select setmeal_id from setmeal_dish where dish_id in
          <foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
              #{dishId}
          </foreach>
      </select>
      
    • 删除

      // 根据id删除菜品表数据
      @Delete("delete from dish where id = #{id}")
      void deleteById(Long id);
      
      // 删除菜品关联的口味数据
      @Delete("delete from dish_flavor where dish_id = #{dish_id}")
      void deleteByDishId(Long dishId);
      

5.Redis

Redis是一个基于内存的 key-value 结构数据库。

  • 基于内存存储,读写性能高
  • 适合存储热点数据 (热点商品、资讯 新闻)
  • 企业应用广泛

下载地址:

  • Windows版下载地址:https://github.com/microsoftarchive/redis/releases
  • Linux版下载地址:https://download.redis.io/releasesl
  • 解压后目录

    在这里插入图片描述

  • 启动redis服务(默认端口号为6379)

    在安装的目录下运行redis-server.exe redis.windows.conf

    在这里插入图片描述

    可以修改密码(也可以不设置)

    在这里插入图片描述

    在后面对Redis的操作,我们大多采用软件Another Redis Desktop Manager来完成

5.1.数据类型

  • 字符串(string):普通字符串,Redis中最简单的数据类型
  • 哈希(hash):也叫散列,类似于Java中的HashMap结构
  • 列表(list):按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList
  • 集合(set):无序集合,没有重复元素,类似于Java中的HashSet
  • 有序集合(sorted set / zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素

在这里插入图片描述

5.2.常用命令

  • 字符串

    命令说明
    SET key value设置指定key的值
    GET key获取指定key的值
    SETEX key seconds value设置指定key的值,并将 key 的过期时间设为 seconds 秒
    SETNX key value只有在 key 不存在时设置 key 的值
  • 哈希

    Redis hash 是一个string类型的 field 和 value 的映射表,hash特别适合用于存储对象

    key相对于对象名,field相当于属性名,value相当于属性值

    命令说明
    HSET key field value将哈希表 key 中的字段field 的值设为 value
    HGET key field获取存储在哈希表中指定字段的值
    HDEL key field删除存储在哈希表中的指定字段
    HKEYS key获取哈希表中所有字段
    HVALS key获取哈希表中所有值
  • 列表

    Redis 列表是简单的字符串列表,按照插入顺序排序

    命令说明
    LPUSH key value1 [value2]将一个或多个值插入到列表头部
    LRANGE key start stop获取列表指定范围内的元素(从0开始)
    RPOP key移除并获取列表最后一个元素
    LLEN key获取列表长度
  • 集合

    Redis set 是string类型的无序集合。集合成员是唯一的,集合中不能出现重复的数据

    命令说明
    SADD key member1 [member2]向集合添加一个或多个成员
    SMEMBERS key返回集合中的所有成员
    SCARD key获取集合的成员数
    SINTER key1 [key2]返回给定所有集合的交集
    SUNION key1 [key2]返回所有给定集合的并集
    SREM key member1 [member2]删除集合中一个或多个成员
  • 有序集合

    Redis有序集合是string类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数。

    命令说明
    ZADD key score1 member1 [score2 member2]向有序集合添加一个或多个成员
    ZRANGE key start stop [WITHSCORES]通过索引区间返回有序集合中指定区间内的成员
    ZINCRBY key increment member有序集合中对指定成员的分数加上增量 increment
    ZREM key member [member …]移除有序集合中的一个或多个成员
  • 通用命令

    命令说明
    KEYS pattern查找所有符合给定模式( pattern)的 key,通配符为*
    EXISTS key检查给定 key 是否存在
    TYPE key返回 key 所储存的值的类型
    DEL key该命令用于在 key 存在时删除 key

5.3.java中操作Redis

常用的redis的java客户端:

  • Jedis
  • Lettuce
  • Spring Data Redis(推荐)

操作步骤

  1. 导入Spring Data Redis 的maven坐标(初始项目已经导入)

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 配置Redis数据源()

    database配置项,在启动redis服务后,默认创建16个数据库(0~15),默认为0、

    # application.yml
    # 该配置项与datasource平级
    redis:
      host: ${sky.redis.host}
      port: ${sky.redis.port}
      # password: 这里我没有设置密码,所有没有写
      database: ${sky.redis.database}
    
    # application-dev.yml
    # 该配置项与datasource平级
    redis:
        host: localhost
        port: 6379
        # password: 这里我没有设置密码,所有没有写
        database: 0
    
  3. 编写配置类,创建RedisTemplate对象

    // sky-server/src/main/java/com/sky/config/RedisConfiguration.java
    @Configuration
    @Slf4j
    public class RedisConfiguration {
        @Bean
        public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            log.info("开始创建redis模版对象");
            RedisTemplate redisTemplate = new RedisTemplate();
            // 设置redis的连接工程对象
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            // 设置redis key的序列化器
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            return redisTemplate;
        }
    }
    
  4. 通过RedisTemplate对象操作Redis

    在对数据进行操作时,可以将对象先复制,减少代码冗余

    @Test
    public void test() {
        // 操作字符串的对象
        ValueOperations valueOperations = redisTemplate.opsForValue();
        // 操作哈希的对象
        HashOperations hashOperations = redisTemplate.opsForHash();
        // 操作列表的对象
        ListOperations listOperations = redisTemplate.opsForList();
        // 操作集合的对象
        SetOperations setOperations = redisTemplate.opsForSet();
        // 操作有序集合的对象
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    }
    

    对字符串类型操作

    @Test
    public void testString() {
        // set(添加)
        redisTemplate.opsForValue().set("key1", "value1");
        // get(获取)
        String city = (String) redisTemplate.opsForValue().get("key1");
        System.out.println(city);
        // setex(限时字符串)
        redisTemplate.opsForValue().set("key2", "111", 60, TimeUnit.SECONDS);
        // setnx(没有就添加字符串)
        redisTemplate.opsForValue().setIfAbsent("lock", "1");
        // 因为上面已经设置了lock,所有这里不会设置成功
        redisTemplate.opsForValue().setIfAbsent("lock", "2");
    }
    

    对hash类型操作

    // 操作hash类型的数据
    @Test
    public void testHsh() {
        HashOperations hashOperations = redisTemplate.opsForHash();
        // hset(添加)
        hashOperations.put("ob", "name", "Tom");
        hashOperations.put("ob", "age", "18");
        // hget(获取)
        String name = (String)hashOperations.get("ob", "name");
        System.out.println(name);
        // hvals(获取该键的所有values)
        List values = hashOperations.values("ob");
        System.out.println(values);
        // hdel(删除)
        hashOperations.delete("ob", "name");
    }
    

    对列表类型操作

    @Test
    public void testList() {
        ListOperations listOperations = redisTemplate.opsForList();
        // lpush(添加元素)
        listOperations.leftPush("lst", "1");
        listOperations.leftPushAll("lst", "2", "3", "4");
        // lrange(范围获取)
        List list = listOperations.range("lst", 0, -1);
        System.out.println(list);   //[4, 3, 2, 1]
        // rpop(右弹出一个元素)
        Object i1 = listOperations.rightPop("lst");
        System.out.println(i1);     // 1
        // llen(列表元素个数)
        Long len = listOperations.size("lst");
        System.out.println(len);    // 3
    }
    

    对集合进行操作

    @Test
    public void testSet() {
        SetOperations setOperations = redisTemplate.opsForSet();
        // sadd(添加)
        setOperations.add("set1", "a", "b", "c");
        setOperations.add("set2", "d", "e", "f");
        // smembers(获取集合元素)
        Set members = setOperations.members("set1");
        System.out.println(members);    // [b, c, a]
        // scard(元素个数)
        Long size = setOperations.size("set1");
        System.out.println(size);   // 3
        // sinter(求交集)
        Set intersect = setOperations.intersect("set1", "set2");
        System.out.println(intersect);  // []
        // sunion(求并集)
        Set union = setOperations.union("set1", "set2");
        System.out.println(union);  // [b, a, c, d, f, e]
        // srem(移除元素)
        setOperations.remove("set1", "a");
    }
    

    对有序集合进行操作

    @Test
    public void testZSet() {
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        // zadd(添加)
        zSetOperations.add("zset1", "a", 1);
        zSetOperations.add("zset1", "b", 2);
        zSetOperations.add("zset1", "c", 3);
        // zincrby(赋权重)
        zSetOperations.incrementScore("zset1", "a", 3);
        zSetOperations.incrementScore("zset1", "b", 2);
        zSetOperations.incrementScore("zset1", "c", 1);
        // zrange(范围获取)
        Set orderset = zSetOperations.range("zset1", 0, -1);
        // 最先打印有序列表权重最大的元素
        System.out.println(orderset);   // [a, b, c]
        // zrem(删除)
        zSetOperations.remove("zset1", "a");
    }
    

    通用命令操作

    @Test
    public void testCommon() {
        // keys(获取key)
        Set keys = redisTemplate.keys("*");
        System.out.println(keys);   // [ob, name, set1, obj, lock, set2, key1, zset1, lst]
        // exists(查询key是否存在)
        redisTemplate.hasKey("name");
        redisTemplate.hasKey("set1");
        // type(查看key的类型)
        for (Object key : keys) {
            DataType type = redisTemplate.type(key);
            System.out.println(type);   // HASH STRING...
        }
        // del(删除)
        redisTemplate.delete("name");
    }
    

5.4.店铺模块

业务规则:

  • 管理端设置、查询营业状态

    @RestController("adminShopController")
    @RequestMapping("/admin/shop")
    @Api(tags = "店铺相关接口")
    @Slf4j
    public class ShopController {
        public static final String KEY = "shop_status";
        @Autowired
        private RedisTemplate redisTemplate;
    
        // 设置店铺营业状态
        @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 shopStatus = (Integer) redisTemplate.opsForValue().get(KEY);
            log.info("获取到店铺的营业状态为:{}", shopStatus == 1? "营业中":"打烊中");
            return Result.success(shopStatus);
        }
    }
    
  • 用户端查询营业状态

    @RestController("userShopController")
    @RequestMapping("/user/shop")
    @Api(tags = "店铺相关接口")
    @Slf4j
    public class ShopController {
        public static final String KEY = "shop_status";
        @Autowired
        private RedisTemplate redisTemplate;
    
        // 查询店铺状态
        @GetMapping("/status")
        @ApiOperation("获取店铺营业状态")
        public Result<Integer> getStatus() {
            Integer shopStatus = (Integer) redisTemplate.opsForValue().get(KEY);
            log.info("获取到店铺的营业状态为:{}", shopStatus == 1? "营业中":"打烊中");
            return Result.success(shopStatus);
        }
    }
    
  • 我们可以通过修改WebMvcConfiguration.config配置文件,将接口分为管理端和用户端

    // 将docket拆分为docket1、docket2两个文档,并进行分组
    @Bean
    public Docket docket1() {
        log.info("准备生成接口文档...");
        ApiInfo apiInfo = new ApiInfoBuilder()
            // 省略...
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
            .groupName("管理端接口")
            // 省略...
            return docket;
    }
    
    @Bean
    public Docket docket2() {
        log.info("准备生成接口文档...");
        ApiInfo apiInfo = new ApiInfoBuilder()
            // 省略...
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
            .groupName("用户端接口")
            // 省略...
            return docket;
    }
    

    分组完成

    在这里插入图片描述

5.5 关于Redis启动错误

报错:Creating Server TCP listening socket 127.0.0.1:6379: bind: No error

  1. D:\APP\Redis\Redis-x64-3.2.100>redis-cli.exe
  2. 127.0.0.1:6379> shutdown
  3. not connected> exit
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值