课程内容

  • 新增员工
  • 员工分页查询
  • 启用禁用员工账号
  • 编辑员工
  • 导入分类模块功能代码

功能实现: 员工管理 菜品分类管理


开发前准备

  • 需求分析与设计
    • 根据产品原型进行分析
    • 需要添加以下信息
      • 账号
      • 员工姓名
      • 手机号
      • 性别
      • 身份证号
    • 注意事项
      • 账号必须唯一
      • 手机号是合法的11位手机号
      • 身份证号为合法的18位数字
      • 密码暂时为123456
  • 接口设计
    • 根据提供资料
    • 请求路径 /admin/employee
    • 请求方式 post
    • 请求参数以及返回数据
    • 管理端使用/damin前缀
    • 用户端用/user前嘴
  • 数据表设计
字段名 数据类型 说明 备注
id bigint 主键 自增
name varchar(32) 姓名
username varchar(32) 用户名 唯一
password varchar(64) 密码
phone varchar(11) 手机号
sex varchar(2) 性别
id_number varchar(18) 身份证号
status Int 账号状态 1正常 0锁定
create_time Datetime 创建时间
update_time datetime 最后修改时间
create_user bigint 创建人id
update_user bigint 最后修改人id

代码开发

  • 根据新增员工接口设计对应的DTO
  • 因为前端提交的数据与实体类对应的属性差别比较大
  • 建议使用DTO来封转数据
  • 进入sky-pojo模块,在com.sky.dto包下,已定义EmployeeDTO
package com.sky.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class EmployeeDTO implements Serializable {
    private Long id;
    private String username;
    private String name;
    private String phone;
    private String sex;
    private String idNumber;
}
  • Controller层

EmployeeController中创建新增员工方法

进入到sky-server模块中,在com.sky.controller.admin包下,在EmployeeController中创建新增员工方法,接收前端提交的参数。

	/**
     * 新增员工
     * @param employeeDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增员工")
    public Result save(@RequestBody EmployeeDTO employeeDTO){
        log.info("新增员工:{}",employeeDTO);
        employeeService.save(employeeDTO);//该方法后续步骤会定义
        return Result.success();
    }

**注:**Result类定义了后端统一返回结果格式。

进入sky-common模块,在com.sky.result包下定义了Result.java

package com.sky.result;

import lombok.Data;

import java.io.Serializable;

/**
 * 后端统一返回结果
 * @param <T>
 */
@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}

#### 1.2.2 Controller层

 **EmployeeController中创建新增员工方法**

进入到sky-server模块中,在com.sky.controller.admin包下,在EmployeeController中创建新增员工方法,接收前端提交的参数。

```java
	/**
     * 新增员工
     * @param employeeDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增员工")
    public Result save(@RequestBody EmployeeDTO employeeDTO){
        log.info("新增员工:{}",employeeDTO);
        employeeService.save(employeeDTO);//该方法后续步骤会定义
        return Result.success();
    }

**注:Result类定义了后端统一返回结果格式。

进入sky-common模块,在com.sky.result包下定义了Result.java

package com.sky.result;

import lombok.Data;

import java.io.Serializable;

/**
 * 后端统一返回结果
 * @param <T>
 */
@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}
  • Service层接口
	/**
     * 新增员工
     * @param employeeDTO
     */
    void save(EmployeeDTO employeeDTO);
  • Service层实现类
    在EmployeeServiceImpl中实现新增员工方法
    com.sky.server.impl.EmployeeServiceImpl中创建方法
	/**
     * 新增员工
     *
     * @param employeeDTO
     */
    public void save(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        //对象属性拷贝
        BeanUtils.copyProperties(employeeDTO, employee);

        //设置账号的状态,默认正常状态 1表示正常 0表示锁定
        employee.setStatus(StatusConstant.ENABLE);
        //设置密码,默认密码123456employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
        //设置当前记录的创建时间和修改时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        //设置当前记录创建人id和修改人id
        employee.setCreateUser(10L);//目前写个假数据,后期修改
        employee.setUpdateUser(10L);
        employeeMapper.insert(employee);//后续步骤定义
    }

在sky-common模块com.sky.constants包下已定义StatusConstant.java

package com.sky.constant;
/**
 * 状态常量,启用或者禁用
 */
public class StatusConstant {
    //启用
    public static final Integer ENABLE = 1;
    //禁用
    public static final Integer DISABLE = 0;
}
  • Mapper层
  • 在EmployeeMapper中声明insert方法
	/**
     * 插入员工数据
     * @param employee
     */
    @Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user,status) " +
            "values " +
            "(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})")
    void insert(Employee employee);

在application.yml中已开启驼峰命名,故id_number和idNumber可对应。

mybatis:
  configuration:
    #开启驼峰命名
    map-underscore-to-camel-case: true

接口测试

访问Swaggger接口文档
json数据:

{
  "id": 0,
  "idNumber": "111222333444555666",
  "name": "xiaozhi",
  "phone": "13812344321",
  "sex": "1",
  "username": "小智"
}

响应码:401 报错

  • debug日志:
    • 通过断点调试
    • 请求头中不包含令牌
    • 调用原先准备好的登录接口 获取到令牌
    • 在文档管理中找到全局参数设置
    • 新增token参数

代码完善

程序存在的问题:

  • 录入员工名存在的异常未处理
    • 通过全局异常处理器来处理
    • 进入到sky-server模块,com.sky.hander包下,GlobalExceptionHandler.java添加方法
	/**
     * 处理SQL异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        //Duplicate entry 'zhangsan' for key 'employee.idx_username'
        String message = ex.getMessage();
        if(message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String username = split[2];
            String msg = username + MessageConstant.ALREADY_EXISTS;
            return Result.error(msg);
        }else{
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }
	进入到sky-common模块,在MessageConstant.java添加
public static final String ALREADY_EXISTS = "已存在";
  • 新增员工时 创建人 修改人id为固定值
    • 需要获取到当前登录员工的id
    • 在员工登录成功后会生成JWT令牌给前端
    • 使用ThreadLocal实现
      • ThreadLocal是TThread的局部变量
      • 为每个线程单独提供一份存储空间 具有线程隔离的作用
      • 只有在线程内才能获取到对应的值 线程外不能访问
    • 原理补充
      • 在拦截器 server controller中内添加代码`System.out.println(“当前线程的Id:”+ Thread.currentThread().getId());
      • 查看到每次请求在不同位置都是同一个线程
    • 常用方法
      • void set(T value) 设置当前线程局部变量的值
      • T get() 返回当前线程所对应的线程局部变量的值
      • Void remove() 移除当前线程局部变量的值
    • 使用步骤
      • 在拦截器添加代码`BaseContext.setCurrentId(empId);
      • 取出当前线程id
      • 在server中添加代码`BaseContext.getCurrentId();

员工分页查询

需求分析设计

  • 每页10条数据分页展示
  • 可以通过员工姓名查询
  • 根据号码展示员工信息
  • 请求参数类型为Query 不是json
  • 直接在路径后拼接
  • /admin/employee/page?name=zhangsan
  • 返回数据中的records数据中使用Employee实体类封转

代码开发

  • 设计DTO类
  • 根据请求参数进行封装 在pojo模块中
package com.sky.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class EmployeePageQueryDTO implements Serializable {
    //员工姓名
    private String name;
    //页码
    private int page;
    //每页显示记录数
    private int pageSize;
}
  • 封装PageResult对象作为分页查询统一返回对象
public class PageResult implements Serializable {

    private long total; //总记录数

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

}
  • 在controller层总添加分页查询方法
  • 在server层声明并在实现类中实现
  • 基于pageHelper分页插件
@Override  
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {  
    //使用pageHelper进行分页查询  
    //开始分页查询  
    PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());  
    Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);  
    long total = page.getTotal();  
    List<Employee> records = page.getResult();  
    //封装分页结果  
    PageResult pageResult = new PageResult(total , records);  
    return pageResult;  
}
  • 考虑到需要实现动态sql 用xml配置实现
<select id = "pageQuery" resultType="com.sky.entity.Employee">  
    SELECT * FROM employee  
    <where>  
        <if test="name != null and name != ''">  
            name LIKE CONCAT('%', #{name}, '%')  
        </if>  
    </where>  
    order by create_time desc  
</select>

代码完善

  • 经过前后端联调后发现问题
  • 最好操作时间格式不清晰
  • 处理方式
    • 在属性上加入注解 对日期进行格式化
    • 在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());
        //将自己的消息转化器加入容器中
        converters.add(0,converter);
    }

启用禁用员工账号

需求分析设计

在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 “禁用”,如果员工账号状态为已禁用,则按钮显示为"启用"。

  • 请求方式 POST
  • 请求路径 /admin/employee/status/{status}
  • 路径参数携带状态值
  • 同时传递id 明确操作的用户
  • 必须返回code状态

代码开发

  • Controller层
//启用禁用员工账号  
@PostMapping("/status/{status}")   
@ApiOperation("启用禁用员工账号")  
public Result startOrstop(Long id, @PathVariable Integer status) {  
    //通过pathVariable获取路径参数  
    log.info("修改员工状态: id={}, status={}", id, status);  
    employeeService.startOrStop(id, status);  
    return Result.success();  
}
  • 在server层声明并在实现类中实现
/**  
 * 启用或禁用员工  
 *  
 * @param id     员工id  
 * @param status 状态  
 */  
@Override  
public void startOrStop(Long id, Integer status) {  
    Employee employee = Employee.builder()  
            .status(status)  
            .id(id)  
            .build();  
    employeeMapper.update(employee);  
}
  • 基于xml编写sql语句
<update id="update" parameterType="com.sky.entity.Employee">  
    UPDATE employee  
    <set>  
        <if test="name != null and name != ''">name = #{name}, </if>  
        <if test="username != null and username != ''">username = #{username},</if>  
        <if test="password != null and password != ''">password = #{password},</if>  
        <if test="phone != null and phone != ''">phone = #{phone},</if>  
        <if test="sex != null and sex != ''">sex = #{sex},</if>  
        <if test="idNumber != null and idNumber != ''">id_number = #{idNumber},</if>  
        <if test="status != null">status = #{status},</if>  
        <if test="updateTime != null">update_time = #{updateTime},</if>  
        <if test="updateUser != null and updateUser != ''">update_user = #{updateUser},</if>  
    </set>  
</update>

编辑员工

需求分析与设计

在员工管理列表页面点击 “编辑” 按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击 “保存” 按钮完成编辑操作。

  • 涉及两个接口
    • 根据id查询员工信息
    • 编辑员工信息

代码开发

  • 回显员工信息功能
    • controller层
    • 在EmployeeController 中创建 getById 方法:
	/**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询员工信息")
    public Result<Employee> getById(@PathVariable Long id){
        Employee employee = employeeService.getById(id);
        return Result.success(employee);
    }
  • 实现…
 	/**
     * 根据id查询员工
     *
     * @param id
     * @return
     */
    public Employee getById(Long id) {
        Employee employee = employeeMapper.getById(id);
        employee.setPassword("****");
        return employee;
    }
  • mapper层中声明
  • 较简单 就不用xml了
	/**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    @Select("select * from employee where id = #{id}")
    Employee getById(Long id);
  • 修改员工信息功能
    • 在 EmployeeController 中创建 update 方法:
    /**
     * 编辑员工信息
     * @param employeeDTO
     * @return
     */
    @PutMapping
    @ApiOperation("编辑员工信息")
    public Result update(@RequestBody EmployeeDTO employeeDTO){
        log.info("编辑员工信息:{}", employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();
    }
    
- 实现
```java
 	/**
     * 编辑员工信息
     *
     * @param employeeDTO
     */
    public void update(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO, employee);

        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.update(employee);
    }
  • 在实现启用禁用员工账号功能时,已实现employeeMapper.update(employee),在此不需写Mapper层代码。
  • debug日志
    • 完成上述操作后在前后端联调时出现问题
    • 我的update方法没有做用户名唯一性校验,导致当用户名和其他员工重复时,数据库唯一索引冲突或业务层报“用户名已存在”。
    • 在xml文件中添加`where id = #{id}解决

导入分类模块功能代码

  • 直接导入现成的代码即可
  • 分类管理模块功能

新增菜品分类:当我们在后台系统中添加菜品时需要选择一个菜品分类,在移动端也会按照菜品分类来展示对应的菜品。

菜品分类分页查询:*系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

根据id删除菜品分类:在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。

修改菜品分类:在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作。

启用禁用菜品分类:在分类管理列表页面,可以对某个分类进行启用或者禁用操作。

分类类型查询:当点击分类类型下拉框时,从数据库中查询所有的菜品分类数据进行展示。

category表结构:

字段名 数据类型 说明 备注
id bigint 主键 自增
name varchar(32) 分类名称 唯一
type int 分类类型 1菜品分类 2套餐分类
sort int 排序字段 用于分类数据的排序
status int 状态 1启用 0禁用
create_time datetime 创建时间
update_time datetime 最后修改时间
create_user bigint 创建人id
update_user bigint 最后修改人id

想温柔的对待这个世界