外卖点餐系统02

苍穹外卖day02,主要开发员工模块代码,包含新增员工、员工分页查询、启用禁用员工账号和编辑员工接口。

外卖点餐系统02

新增员工

需求分析和设计

产品原型

0

接口设计

0

数据库设计(employee表)

0

代码开发

根据新增员工接口设计对应的DTO
用DTO封装数据而不是直接使用实体类,使用DTO可以方便在参数类上添加注解,有助于数据校验,实体类封装数据不需要的变量是对性能的浪费

/**
 * 新增员工
 * @param employeeDTO
 * @return
 */
@PostMapping                // 该接口是post
@ApiOperation("新增员工")     // Swagger接口文档注解, @ApiOperation用在方法上,例如Controller的方法,说明方法的用途、作用

public Result save(@RequestBody EmployeeDTO employeeDTO) {   
// @RequestBody 注解用于将 HTTP 请求体中的 JSON 数据转换为 Java 对象
// @RequestBody 是 Spring Framework 中用于处理 HTTP 请求体的注解。
// 通常用于 RESTful Web 服务的控制器方法中,以便将请求体内容自动反序列化为 Java 对象

    log.info("新增员工:{}", employeeDTO);   // 增加日志方便调试
                                            // 使用 SLF4J(Simple Logging Facade for Java)或者 Log4j 等日志框架时,使用 {} 占位符来格式化日志信息
                                            // SLF4J 会自动将后面的参数替换到占位符位置。
    employeeService.save(employeeDTO);
    return Result.success();
}

employeeService.save()方法

/**
 * 新增员工
 * @param employeeDTO
 */
@Override
public void save(EmployeeDTO employeeDTO) {
    // 转DTO,传入Mapping层需要是实体类
    Employee employee = new Employee();

    // employee.setUsername(employeeDTO.getUsername());         // 直接设置属性过于繁琐

    // 这里使用了 对象属性拷贝 ,Spring提供了BeanUtils工具类, 使用该工具类要确保属性名称保持一致
    BeanUtils.copyProperties(employeeDTO, employee);

    // employee实体类中属性比DTO中更多,所以需要继续将剩下属性填充
    // 设置账号状态,默认正常状态 1表示正常 0表示锁定
    employee.setStatus(StatusConstant.ENABLE);

    // 设置密码,默认密码123456
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));

    // 设置创建时间,更新时间
    employee.setCreateTime(LocalDateTime.now());
    employee.setUpdateTime(LocalDateTime.now());

    // 设置d当前记录创建人id,修改人id
    // TODO 创建人和修改人应该从前端传过来,动态获取,后期改变为当前用户id
    employee.setCreateUser(10L);
    employee.setUpdateUser(10L);

    employeeMapper.insert(employee);
}

功能测试

  • 通过接口文档测试
  • 前后端联调测试

由于前后端是并行开发,往往后端开发某个功能后,前端对应功能未完成,导致无法进行前后端联调测试,所以在开发阶段,后端测试主要以接口文档测试为主。

token处理

  • 调用员工登录接口获得一个合法的JWT令牌
  • 将合法的JWT令牌添加到全局参数中

0

代码完善

程序存在问题:

  • 录入的用户名已经存在,抛出异常之后没有处理 - 数据库中用户名字段设置成unique
  • 新增员工时,creatUser和updateUser设置为了固定值
  1. 解决异常,通过全局异常器处理
0
  1. 针对第二个问题,可以通过JWT Token动态获取当前登陆员工id

JWT Token登陆流程

0

拦截请求验证JWT Token环节可以反向解析Token,获取当前登陆用户id,因为在生成JWT Token环节是一个已经登陆的状态,token中包含登陆id信息。

employeeController.java

0

JwtTokenAdminInterceptor.java

0

解析出员工登陆id后,如何传递给Service的save方法?

ThreadLocal 并不是一个Thread,而是Thread的局部变量。

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

注意客户端发送的每次请求,后端的Tomcat服务器都会分配一个单独的线程来处理请求

所以我们可以在请求验证JWT Token环节将id存储到ThreadLocal中,然后在service中的save读取id

ThreadLocal常用方法:

public void set(T value)   //设置当前线程的线程局部变量的值
public T get()   //返回当前线程所对应的线程局部变量的值
public void remove()       // 移除当前线程的线程局部变量

我们在实际中使用ThreadLocal会进行一个简单的封装,包装成一个工具类,使用起来会更方便,初始工程中sky-common部分的 com.sky.context

员工分页查询

需求分析

代码开发

  • 根据分页查询接口设计对应的DTO
@Data
public class EmployeePageQueryDTO implements Serializable {
    //员工姓名
    private String name;
    //页码
    private int page;
    //每页显示记录数
    private int pageSize;
}
  • 后端所有分页查询,统一封装成PageResult对象
/**
 * 封装分页查询结果
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {

    private long total; //总记录数

    private List records; //当前页数据集合

}

员工信息分页查询后端返回的对象类型为Result<PageResult>
data里存放的就是PageResult对象


首先在EmployeeControl.java中增加员工分页方法

/**
 * 员工分页查询
 * @param employeePageQueryDTO
 * @return
 */
@GetMapping("/page")     // 接口是GET方式请求
@ApiOperation("员工分页查询")     // Swagger接口文档注解
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {  //前端请求数据格式是Query
    log.info("员工分页查询,参数为:{}", employeePageQueryDTO);
    PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
    return Result.success(pageResult);
}

然后在EmployeeService.java声明pageQuery接口和EmployeeServiceImpl.java中实现pageQuery接口

/**
 * 员工分页查询
 * @param employeePageQueryDTO
 * @return
 */
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
    // select * from employee limit 0, 10
    // 开始分页查询,使用pagehelper插件,这里能使用PageHelper插件是因为在项目描述文件pom.xml中添加了pagehelper依赖
    PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());       // 基于MyBatis拦截器实现

    // 在pageQuery()中似乎没有实现分页的SQL语句,而这里又没有将PageHelper的返回结果传递给pageQuery()方法,那么分页查询的SQL语句是如何实现的?
    // 是因为,PageHelper底层是通过ThreadLocal实现的,在pageQuery中实现数据库语句之前会通过ThreadLocal将实现分页的语句拼接起来
    Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);

    long total = page.getTotal();
    List<Employee> records = page.getResult();

    return new PageResult(total, records);
}

代码完善

日期显示是数组形式,格式不对

解决方法

  • 方法一:在属性上加入注解,对日期进行格式化
  • 方法二:在WebMvcConfiguration中扩展Spring MVC的消息转换器,统一对日期进行格式化处理
/**
 * 扩展spring MVC框架的消息转换器
 * @param converters
 */
//对后端返回前端的数据统一
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    log.info("开始拓展消息转换器...");
    // 创建一个消息转化器对象
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    // 需要为消息转换器设置一个对象转换器,对象转换器将Java对象序列化为json对象
    converter.setObjectMapper(new JacksonObjectMapper());
    // 将写好的对象转换器放入Spring MVC框架的容器 converters中
    converters.add(0, converter);
}

三、启用禁用员工账号

需求分析

业务规则:

  • 可以对状态为“启用” 的员工账号进行“禁用”操作
  • 可以对状态为“禁用”的员工账号进行“启用”操作
  • 状态为“禁用”的员工账号不能登录系统

代码开发

/**
 * 启用禁用员工账号
 * @param id
 * @param status
 * @return
 */
@PostMapping("/status/{status}")        // 请求方式是POST,请求路径 /admin/employee/status/{status}
@ApiOperation("启用禁用员工账号")
// 两个参数,分别为路径参数status和地址栏传参id
public Result startOrStop(@PathVariable("status") Integer status, long id) {            // 查询功能,返回范型,非查询功能返回result即可
    log.info("启用禁用员工账号: {},{}", status, id);
    employeeService.startOrStop(status, id);
    return Result.success();
}

employServiceImp.java

    /**
     * 启用禁用员工账号
     * @param status
     * @param id
     */
    public void startOrStop(Integer status, long id) {
        // update employee set status = ? where id =?
        // 在这里希望设置成动态SQL修改语句,根据传进来的参数不同可以修改不同的数据
//        Employee employee = new Employee();
//        employee.setId(id);
//        employee.setStatus(status);

        // Employee实体添加了  @Builder 构建器注解,也可以通过Employee.builder()创建对象
        Employee employee = Employee.builder()
                        .status(status)
                        .id(id)
                        .build();

        employeeMapper.update(employee);
    }

EmployMapper.xml

<update id="update" parameterType="Employee">    <!--这里可以不带路径指定参数,是因为在application.yml配置文件中指明了mapper配置路径和映射路径 -->
    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="status != null"> status = #{status},</if>
        <if test="updateTime != null"> update_time = #{updateTime},</if>
        <if test="updateUser != null"> update_user = #{updateUser},</if>       <!-- MySQL语句不区分大小写-->
    </set>
    where id = #{id}
</update>

总结

day02主要是根据需求设计接口,然后通过接口文档开发代码即可,大部分内容还是通俗易懂。

Read more

如何设计秒杀系统

什么是秒杀设计 秒杀设计 是一种针对高并发、大流量场景的系统设计,通常应用于电商活动中(如限时抢购、促销等),用户在非常短的时间内大量涌入系统,抢购有限的商品或优惠。这种场景下,系统需要能够承受巨大的瞬时并发请求,同时保证数据的一致性和业务的正确性。 秒杀技术分析 秒杀系统贯穿活动、商品和下单三个领域,涵盖了页面静态化、接口限流、Redis预减库存、异步下单和接口动态化等技术。 需要迎接的挑战有: 1. 高并发和压力测试:秒杀活动会带来巨大的流量,服务器和数据库的并发处理能力是关键。 2. 保证数据一致性:抢购涉及到商品库存的实时减少,需要保证在高并发场景下库存数据的准确性和一致性 3. 防止超卖和重复购买:确保同一商品不会被重复购买,同时避免超卖,即使是在极端的高并发情况下 4. 分布式锁和限流:使用分布式锁来保护关键资源,限制用户访问频率以免系统崩溃 5. 性能优化:包括代码层面的优化、数据库的优化、缓存的使用等,以提高系统性能和响应速度。 页面静态化 秒杀页面静态化是将动态生成的秒杀页面转换为静态HTML页面,从而提高页面响应速度和系统性能

By Yucan Huang

分布式系统下雪花算法生成全局唯一ID

雪花算法 在分布式系统中,为了避免多个节点生成相同的唯一标识符id,我们通常使用一种全局唯一ID生成策略,而Snowflake Algorithm就是广泛使用的解决方案之一。 雪花算法简介 雪花算法生成的 ID 是一个 64 位的二进制整数,具有以下组成部分: 部分 字节长度 描述 符号位 1bit 固定为0,因为生成的ID是整数 时间戳 41bits 表示时间戳,单位是毫秒,可以存储大约68年的时间 机器ID 10bits 用于标识不同的机器(节点)。10位可以将其分数据中心ID(5位)和机器ID(5位),共支持1024台机器同时生成ID 序列号 12bits 同一毫秒内的序列号,用来区分在同一台机器、同一时间戳下生成的多个ID,最大支持同一毫秒生成4096个唯一ID 代码实现 package com.can.springbootmessage; public class SnowflakeIdGenerator { // 起始时间戳,Long是64位,

By Yucan Huang

责任链设计模式

什么是责任链模式 项目有个请求,需要有对应的服务来处理,然后这个请求可能需要被很多个层级权限的服务来处理。我们将这些处理该请求的服务放在一条链上,链从前往后,是层级更高的服务,第一个服务处理不了,传递到链上的下一个服务,直到这个请求被处理成功。 责任链模式(Chain of Responsibility)是一种处理请求的模式,它让多个处理器都有机会处理该请求,直到其中某个处理器成功处理该请求,责任链模式把多个处理器串成链,然后让请求在链上传递。 1. 如何把多个处理器串成链,然后让请求在链上传递 客户端发送请求,处理类去处理它的请求,所以有个处理方法(handleRequest),处理类要连接在一起,所以**处理类要有一个方法(成员变量nextHandler)**指向下一个处理类。 我们抽象出一个公共的父类,然后去定义不同的处理类,这些处理类通过nextHandler连接起来。 2. 代码演示 package interview.pattern; public class ChainRespPattern { Handl

By Yucan Huang