工程搭建

  • 创建SpringBoot工程,并引入Web开发起步依赖,mybasits,mysql驱动,lombok

developer tools: lombok

Web:Spring Web

SQL:Mybatis Framework,MySQL Driver

image-20251113134722714

创建之后保留这三个文件就可以了

把resources中的文件都删掉,新建application.yml文件,在此文件中连接数据库

yml配置文件形式

image-20251113135842773

关于数据库

在不同主机名(如localhost)下都是一个个电脑,而数据库就是在这电脑里,通过idea连接的就是此数据库(因为之前不明白数据库是怎么工作的),只要属性都对上就可以正常工作

  • 创建数据库表,并在application.yml中配置数据库的基本信息

可以像这么写

1
2
3
4
5
datasource:
driver-class-name: ${sky.datasource.driver-class-name}
url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}
username: ${sky.datasource.username}
password: ${sky.datasource.password}

image-20251113143335565

其中mapper的xml配置可以像这么配置

寻找在recources的mapper目录下的所有xml

1
2
3
4
5
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
  • 准备基础代码结构,并引入实体类及统一的相应结果分装类Result

DTO接收数据

1
2
3
4
5
6
7
8
9
10
11
12
// 前端发送 POST 请求,Body 是 JSON:
// {"username": "admin", "password": "123456"}

@PostMapping("/login")
public Result login(@RequestBody EmployeeLoginDTO dto) {
// 1. HTTP 请求进来,SpringMVC 拦截到
// 2. @RequestBody 告诉 Spring:把请求 Body 的 JSON 转成 Java 对象
// 3. Spring 调用 Jackson 库,自动匹配 JSON 的 key 和 DTO 的字段名
// 4. 自动创建 EmployeeLoginDTO 对象并填充数据:
// dto.username = "admin"
// dto.password = "123456"
}

通过``@RequestBody`把前端返回的数据变为java语言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@PostMapping("/login")
public Result<EmployeeVO> login(@RequestBody EmployeeLoginDTO dto) {
// 1. Service 层查出 Employee 实体
Employee entity = employeeService.login(dto);

// 2. 手动把 Entity 转成 VO(隐藏敏感字段)
EmployeeVO vo = new EmployeeVO();
vo.setId(entity.getId());
vo.setName(entity.getName()); // 不返回 password!

// 3. 把 VO 装进 Result
Result<EmployeeVO> result = Result.success(vo);

// 4. SpringMVC 把 Result 对象转成 JSON 字符串
// 5. 通过 HTTP 响应返回给前端
return result; // 最终前端收到:{"code":1,"msg":null,"data":{"id":1,"name":"admin"}}
}

通过return Result.success(employeeLoginVO);把VO装进result返回

新增员工

image-20251114105141508

员工的DTO按前端的来设置

如:

1
2
3
4
5
6
7
8
9
10
11
private Long id;

private String username;

private String name;

private String phone;

private String sex;

private String idNumber;

controller

1
2
3
4
5
6
7
8
9
10
/*
新增员工
*/
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}",employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}

获取dto,用@RequestBody变为java语言,在用sava方法传给Service

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
新增员工
*/
@Override
public void save(EmployeeDTO employeeDTO) {
//在sava里,把employee里没涉及到的属性进行封装
Employee employee = new Employee();
//一键拷贝
BeanUtils.copyProperties(employeeDTO,employee);
//把dto里没有的属性加进去
//密码进行md5加密
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
//创建,更新时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//设置创建人和修改人id
// TODO 后期改换为当前登录用户的id
employee.setCreateUser(10L);
employee.setUpdateUser(10L);

employeeMapper.insert(employee);

}

一键拷贝只能拷dto和employee共有的,其余的要手动打

再用insert方法传给mapper

mapper

1
2
3
4
5
6
/*
新增员工
*/
@Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user) VALUE " +
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser})")
void insert(Employee employee)

用驼峰来传递

代码完善

目前有两个问题:

  • 录入的用户名已存在,抛出异常后没有处理
  • 新增员工时,创建人id和修改人id设置为固定值

问题一

在handler全局异常处理器中

原语句:java.sql.SQLIntegrityConstraintViolationException: Duplicate entry ‘慈超栋’ for key ‘employee.idx_username’

1
2
3
4
5
6
7
8
9
10
11
12
13
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
//Duplicate entry '慈超栋' 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 + "已存在";
return Result.error(msg);
} else {
return Result.error("未知错误");
}
}

问题二

客户端发送的每一个请求都是一个单独的线程,因此可以用ThreadLocal在这个线程内,通过拦截器获取id并把id设置到线程中来获取

image-20251114134557756

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}

分页查询

先分析一下

image-20251114165946062

Result中的data里面有total和records,records里有员工的信息,因此可以把data单独封装,以便方便

1
2
3
4
5
6
7
8
9
10
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {

private long total; //总记录数

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

}

image-20251114171711913

controller

1
2
3
4
5
6
@GetMapping("/page")
public Result<PageResult> pageQuery(EmployeePageQueryDTO employeePageQueryDTO){//因为返回类型是QueryPageResult,
log.info("分页查询:{}",employeePageQueryDTO);
PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}

因为是分页查询,所以返回的Result的泛型就要为PageResult(看图,相当于Result包含PageResult)

最后返回PageResult

service

1
2
3
4
5
6
7
8
9
10
11
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
//通过PageHelper进行分页
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
//PageHelper规定要用Page<T>
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
//因为要返回PageResult,所以要配置其他变量(因为这是service层啊,这玩意就是干这个的)
long total = page.getTotal();
List<Employee> records = page.getResult();
return new PageResult(total,records);
}

通过PageHelper插件计算页码

最后返回PageResult

mapper

1
2
3
4
5
6
7
8
9
<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>

其中like是模糊查询,concat(‘%’,#{name},’%’)是拼接字符串,会查询任何包含name的字符串

image-20251114182917495

image-20251114182907361

但是操作时间有问题,需要改一下

在config中改

1
2
3
4
5
6
7
8
9
10
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器");
//创建一个消息转化器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转化器设置一个对象转换器,对象转换器可以将Java对象转为json
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器加入容器中(把自己的排在序号0上)
converters.add(0,converter);
}

JacksonObjectMapper()到时候找ai生成就行了,比较固定(懒

禁用&启用员工账号

原理就是通过更新

controller

1
2
3
4
5
6
@PostMapping("/status/{status")//status表示状态,来判断启用禁用
public Result startOrStop(@PathVariable Integer status, Long id){
log.info("启用禁用员工,{},{}",status,id);
employeeService.startOrStop(status,id);
return Result.success();
}

路径参数就加上@PathVariable

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void startOrStop(Integer status, Long id) {
//但是如果只是传这两个参数的话,mapper的update语句局限性太大了、
//因此可以在这里新建一个对象来传递
//这样的话之后还想用update的话也可以复用

//这里的build是因为dto里有@Builder
Employee employee = Employee.builder()
.status(status)
.id(id)
.build();

employeeMapper.update(employee);
}

mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
<update id="update">
update employee
<set>
<if test="username != null">username = #{username},</if>
<if test="name != null">name = #{name},</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>
</set>
where id = #{id}
</update>

编辑员工

两个接口

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

查询员工

controller

1
2
3
4
5
6
@GetMapping("/{id}")
public Result<Employee> getById(@PathVariable Long id){
log.info("根据id查询员工,{}",id);
Employee employee = employeeService.getById(id);
return Result.success(employee);
}

service

1
2
3
4
5
6
@Override
public Employee getById(Long id) {
Employee employee = employeeMapper.getById(id);
employee.setPassword("****");
return employee;
}

把密码隐藏掉

mapper

1
2
@Select("select * from employee where id = #{id}")
Employee getById(Long id);

11.15

分类管理模块的功能已写好,就是按类型分类有问题,其他的跟员工部分没什么区别