当前位置: 首页 > news >正文

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

目录

  • <<回到导览
    • 1. 环境搭建
      • 1.1.项目结构介绍
      • 1.2.数据库环境搭建
      • 1.3.Nginx简介
      • 1.4.完善登录功能
      • 1.5.导入接口文档
      • 1.6.Swagger
    • 2.员工模块
      • 2.1.新增员工
      • 2.2.员工分页查询
      • 2.3.启用禁用账号
      • 2.4.编辑员工信息
      • 2.5.公共字段填充
    • 3.菜品分类模块
      • 3.1.前言
      • 3.2.分类分页查询
      • 3.3.修改菜品分类
      • 3.4.启用禁用分类
      • 3.5.新增分类
      • 3.6.根据id删除分类
      • 3.7.根据类型查询分类
    • 4.菜品模块
      • 4.1.菜品分页查询
      • 4.2.根据id查询菜品
      • 4.3.修改菜品
      • 4.4.菜品起售停售
      • 4.5.文件上传
      • 4.6.新增菜品
      • 4.7.批量删除菜品
    • 5.Redis
      • 5.1.数据类型
      • 5.2.常用命令
      • 5.3.java中操作Redis
      • 5.4.店铺模块
    • 6.HttpClient

<<回到导览

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);// 设置密码,默认密码123456employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));// 设置当前记录的创建、修改时间employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());// 设置当前记录创建人id和修改人id// TODO 后期需要改为当前登录用户的idemployee.setCreateUser(10L);employee.setUpdateUser(10L);employeeMapper.insert(employee);
    }
    
  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令牌中解析出idLong 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());// 设置当前记录修改人idemployee.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());// 设置当前记录修改人idemployee.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 insertOperationType 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 {@Autowiredprivate 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 {@Autowiredprivate CategoryMapper categoryMapper;// 分类分页查询@Overridepublic 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 categorywhere 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.comaccess-key-id: LTAI5tKr2ZB866rBJea9ucuJaccess-key-secret: gLx3lSngXHXv5SUyBW2c29XhHL5XQqbucket-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 {@Autowiredprivate 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: localhostport: 6379# password: 这里我没有设置密码,所有没有写database: 0
    
  3. 编写配置类,创建RedisTemplate对象

    // sky-server/src/main/java/com/sky/config/RedisConfiguration.java
    @Configuration
    @Slf4j
    public class RedisConfiguration {@Beanpublic 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";@Autowiredprivate 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";@Autowiredprivate 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;
    }
    

    分组完成

    在这里插入图片描述

6.HttpClient

HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP协议最新的版本和建议。

maven坐标

<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version>
</dependency>

但是在我们的项目组不用导入,因为我吗之前导入了阿里OSS的maven坐标,这个坐标依赖了httpclientjar包

  • 发送Get方式请求

    先启动主程序和Redis服务

    // 发送Get方式请求
    @Test
    public void testGet() throws IOException {// 1.创建httpclient对象CloseableHttpClient httpclient = HttpClients.createDefault();// 2.创建请求对象HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");// 3.发送请求,接受响应结果CloseableHttpResponse response = httpclient.execute(httpGet);// 获取服务端返回的状态码int statusCode = response.getStatusLine().getStatusCode();System.out.println("服务端返回的状态码" + statusCode);   // 服务端返回的状态码200// 获取服务端返回的数据HttpEntity entity = response.getEntity();String body = EntityUtils.toString(entity);System.out.println("服务端返回的数据" + body);  // 服务端返回的数据{"code":1,"msg":null,"data":1}// 关闭资源response.close();
    }
    
  • 发送Post方式请求

    @Test
    public void testPost() throws IOException {// 1.创建httpclient对象CloseableHttpClient httpclient = HttpClients.createDefault();// 2.创建请求对象HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");// ---设置请求参数JSONObject jsonObject = new JSONObject();jsonObject.put("username", "admin");jsonObject.put("password", "123456");StringEntity requestEntity = new StringEntity(jsonObject.toString());// 指定请求编码方式requestEntity.setContentEncoding("UTF-8");// 数据格式requestEntity.setContentType("application/json");httpPost.setEntity(requestEntity);// 3.发送请求,接受响应结果CloseableHttpResponse response = httpclient.execute(httpPost);// 获取服务端返回的状态码int statusCode = response.getStatusLine().getStatusCode();System.out.println("服务端返回的状态码" + statusCode);   // 服务端返回的状态码200// 获取服务端返回的数据HttpEntity responseEntity = response.getEntity();String body = EntityUtils.toString(responseEntity);System.out.println("服务端返回的数据" + body);  // // 服务端返回的数据{"code":1,"msg":null,"data":{"id":1,"userName":"admin","name":"管理员","token":"..."}}// 关闭资源response.close();
    }
    

    但是,但是?但是!在初始项目中,已经封装好了工具类(sky-common/src/main/java/com/sky/utils/HttpClientUtil.java)

相关文章:

  • ChromeDriver进程泄漏问题分析与最佳实践解决方案
  • unity 鼠标更换指定图标
  • 关于嵌入式系统的知识课堂(一)
  • 【1000以内具有12个以上因子的整数并输出它的因子】2021-12-27
  • FFplay 音视频同步机制解析:以音频为基准的时间校准与动态帧调整策略
  • JVM调优实战
  • Qt之Qfile类
  • 用HBuilder运行小程序到微信开发者工具
  • 【​​HTTPS基础概念与原理​】​​HTTPS vs HTTP:为什么现代网站必须用HTTPS?
  • [目标检测] YOLO系列算法讲解
  • Manus逆向工程:AI智能体的“思考”与“行动”
  • Nginx 反向代理 静态文件404异常处理
  • 【SSL证书系列】客户端如何验证https网站服务器发的证书是否由受信任的根证书签发机构签发
  • MySQL知识点总结(持续更新)
  • 企业数字化转型背景下的企业知识管理挑战与经验杂谈
  • GTM4.1-CCM
  • ARM杂谈——临界段保护恢复的中断状态可靠吗
  • 【解析:新能源汽车芯片主要玩家及技术发展】
  • 码题集——魔数、A的B次方、网球比赛、三角形、点与线段的关系
  • 数据清洗的艺术:如何为AI模型准备高质量数据集?
  • 商人运作亿元“茅台酒庞氏骗局”,俩客户自认受害人不服“从犯”判决提申诉
  • 欧元区财长会讨论国际形势及应对美国关税政策
  • 上海浦东机场1号、2号航站楼均推出国内出发安检24小时服务
  • 深一度|在亚马尔的天才面前,姆巴佩戴上“帽子”又如何
  • 重庆大学通报本科生发14篇SCI论文:涉事学生及其父亲被处理
  • 洗冤录|县令遇豪强:黄榦处理的一起地产纠纷案