目录
1. 新增员工
1.1 需求分析和设计
1.2 代码开发
①定义DTO类:(在sky-pojo里)
②EmployeeController中创建新增员工方法save()
③EmployeeService里声明save方法(alt+enter)
④EmployeeServiceImpl中实现save方法
⑤在EmployeeMapper中声明insert方法,插入数据库
1.3 功能测试
1.4 代码完善
1.5 代码提交
1.6 遇到的问题
2. 员工的分页查询
2.1 需求分析和设计
2.2 代码开发
①定义DTO类
②EmployeeController中创建员工分页查询方法page()
③EmployeeService里声明pageQuery方法
④ EmployeeServiceImpl中实现pageQuery方法
⑤在EmployeeMapper中声明pageQuery方法,插入数据库
2.3 功能测试
2.4 代码完善
2.5 代码提交
3. 启用禁用员工账号
3.1 需求分析和设计编辑
3.2 代码开发
3.3 功能测试
4. 编辑员工
4.1 需求分析和设计
4.2 代码开发
4.3 功能测试-成功
5. 导入分类模块功能
5.1 需求分析和设计
5.2 代码导入
5.3 功能测试
1. 新增员工
步骤:需求分析和设计(产品原型→接口设计→数据库设计)→代码开发
1.1 需求分析和设计
产品原型:
当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。
请求方式:post,由于需要提交员工信息,post可以携带json数据
请求参数:json数据
返回数据:响应码,数据,携带错误信息等..
接口设计:
其实就是把页面中录入的员工数据打包成json格式插入emplyee表。
数据库设计(emplyee表)
红框中就是页面输入的数据
1.2 代码开发
当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据。
①定义DTO类:(在sky-pojo里)
@Data
public class EmployeeDTO implements Serializable {
private Long id;
private String username;
private String name;
private String phone;
private String sex;
private String idNumber;
}
②EmployeeController中创建新增员工方法save()
Springboot是面向注解开发,所以注意添加如下注解
③EmployeeService里声明save方法(alt+enter)
/**后面直接回车补全注释
.var快捷创建一个对象
/**
* 新增员工
* @param employeeDTO
*/
void save(EmployeeDTO employeeDTO);
④EmployeeServiceImpl中实现save方法
使用了对象属性拷贝的方式进行了强制对象转换。
具体来说,这里使用了Spring框架中的BeanUtils类的静态方法copyProperties来将EmployeeDTO对象的属性值复制到Employee对象中。这种方式可以视作一种强制对象转换,因为它将两个不同类的对象之间的属性进行了映射和复制,从而实现了数据的传递和转换。
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
//对象属性拷贝,除了拷贝的属性相同,其他的属性都要自己设置
BeanUtils.copyProperties(employeeDTO, employee);
//设置账号的状态,默认正常状态 1表示正常 0表示锁定(放在常量类里方便维护可复用)
employee.setStatus(StatusConstant.ENABLE);
//设置密码,默认密码123456(md5加密后再存储)
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);//后续步骤定义
}
⑤在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);
表的字段和属性是一一对应,表的属性采用驼峰命名法(在server的配置文件application.yml中)
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.sky.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
1.3 功能测试
两种方法:接口文档(主要)、前后端联调(需要前端开发好)
我们上期讲到,接口文档是由Swagger规范生成,由注解来修饰具有更好可读性的文档。
首先开启启动类SkyAppication.java
然后打开localhost:8081/doc.html——接口文档,开始调试,返回401
原来是存在jwt令牌校验的拦截器
所以我们先到登录页面获取令牌
添加为全局参数
然后发送,这个时候遇到了一个500问题 ,反复确定insert SQL语句没有错误后,修改发送请求与参考视频里的一致,重新调试,成功添加
前后端联调:登录一直在加载,最终报了一个错(504),怀疑是因为连接着接口文档或者没有往下运行到登录
所以加载不出来,重新debug,成功登陆界面,添加数据成功!
1.4 代码完善
1、要捕获username不唯一的异常
debug停在获取jwt令牌的一行,原因是2小时以后原来的token失效,重新获取
在server的handler文件中添加exceptionhandler:
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
//Duplicate entry 'zhangsan' for key 'employee.idx_username'
//我们希望当重复时,提示xx重复
String message = ex.getMessage();
if(message.contains("Duplicate entry")){
String[] sp = message.split(" ");
String username = sp[2];
//String msg = username + "已存在";
String msg = username + MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
}else {
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
修改之后,异常被捕获,作出响应
2、创建员工save方法里的创建人和修改人是固定写死的,即
//TODO 后期需要修改为当前登录用户的id
employee.setCreateUser(10L);//目前写个假数据,后期修改
employee.setUpdateUser(10L);
我们需要的user_id就在拦截的token里面,需要进行解析
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
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;
}
问题是解析得到了empId后,我们要如何传递给service的save方法
● ThreadLocal
并不是一个线程(Thread),他是一个线程的局部变量
为每个线程提供单独一份存储空间,起到线程隔离的作用,只有线程内才能获取对应的值
那么我们客户端发出的每次请求是否对应一个线程呢?在不同的地方添加语句
System.out.println("当前线程的id:" + Thread.currentThread().getId());
可以看到都对应线程:44
重新请求对应线程46,说明:一次请求确实对应一个线程。所以可以把id存入ThreadLocal,再在server里取出来.
ThreadLocal常用方法:set(T value), get(), remove()
我们使用ThreadLocal时往往会进行一个简单的封装,包装成一个工具类common-BaseContext
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();
}
}
添加两句,interceptor(拦截器)里
BaseContext.setCurrentId(empId);
serviceimpl实现类里
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
debug时可以右键-evalueate expression得到里面的值为empId
1.5 代码提交
commit + push即可
1.6 遇到的问题
1、没有自动补全SQL语句功能
net start MYSQL80后可以测试数据库连接
这篇文章非常有用:IDEA设置MYSQL语句自动提示补全
最后发现是因为安装的社区版本很多功能都没有,所以重新安装Utimate版本
2、Cannot resolve symbol 'SQLIntegrityConstraintViolationException'
import java.sql.SQLIntegrityConstraintViolationException;(导入这个包)
2. 员工的分页查询
2.1 需求分析和设计
分析产品原型——
请求方式:get方式请求,
查询参数:页码,每页记录数,员工姓名(不同于增加员工查询参数是json格式,这里使用Query)
后端相应数据:响应码,错误信息,总的数据数total,和这一页展示的员工数据集合records
接口设计:
2.2 代码开发
根据接口设置对应的DTO
后面所有的分页查询都封装成PageResult对象,再+code+msg封装成统一的result
具体来说,Result<Object>接口传入PageResult对象
①定义DTO类
@Data
public class EmployeePageQueryDTO implements Serializable {
//员工姓名
private String name;
//页码
private int page;
//每页显示记录数
private int pageSize;
}
②EmployeeController中创建员工分页查询方法page()
路径等于@RequestMapping+下面的路径
@RequestMapping("/admin/employee")
@PostMapping("/login")
@PostMapping("/logout")
@GetMapping("/page")
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
Log.info("员工分页查询,参数为:{}", employeePageQueryDTO);
PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO); //service存储分页查询的方法
return Result.success(pageResult);
}
③EmployeeService里声明pageQuery方法
/**
* 分页查询方法
* @param employeePageQueryDTO
* @return
*/
PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
④ EmployeeServiceImpl中实现pageQuery方法
一般来说通过从数据库中导入分页数据
select * from employee limit 0,10(limit关键字查询第0-10条)
mybatis框架提供插件pagehelper,简化分页操作 (在pom.xml中增加配置)
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
page和pageSize已知,直接用pagehelper
/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
//select * from employee limit 0,10(limit关键字查询第0-10条)
//实现原理:将page封装后存入threadlocal,在分页查询前取出来,动态拼接实现SQL
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO); //返回pagehelper格式的对象
long total = page.getTotal();
List<Employee> records = page.getResult();
return new PageResult(total, records);
}
⑤在EmployeeMapper中声明pageQuery方法,插入数据库
由于是动态SQL,要使用动态标签,注解不太方便,
所以我们将SQL写入映射配置文件,在server-resources-mapper-EmployeeMapper.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.EmployeeMapper">
</mapper>
ctrl+点击EmployeeMapper可以进入EmployeeMapper.java文件说明映射已经建立好了
同时为了确保xml文件被扫描到,在application.xml中增加配置
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.sky.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
下载mybatisx插件:
红色头绳的表示映射配置文件,蓝色头绳的表示mapper接口。在mapper接口点击红色头绳的小鸟图标会自动跳转到对应的映射配置文件,在映射配置文件中点击蓝色头绳的小鸟图标会自动跳转到对应的mapper接口。也可以在mapper接口中定义方法,自动生成映射配置文件中的 statement
alt+enter-select自动生成配置文件,在自动生成的基础上编写动态SQL
<mapper namespace="com.sky.mapper.EmployeeMapper">
<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>
</mapper>
2.3 功能测试
①接口文档:
返回401,说明token又过期了,在application.yml中
返回两条json(报了500看了一下是因为xml文件里加中文注释)
②前后端联调
功能一:分页查询,前端已经显示了
功能二:模糊查询调用name like语句
2.4 代码完善
刚刚在测试时已经发现了问题,红框里的最后操作时间不是规范格式
第一种方式需要一个一个地处理,第二种方式则可以统一处理
通过server-config-WebMvcConfiguration里重写Spring框架里自带的一个方法extendHandlerExceptionResolvers,扩展消息转换器((对后端返回给前端的数据做统一处理)
通过实现WebMvcConfigurer接口,你可以按需选择性地覆盖接口中的方法,只实现你需要的配置,而不需要处理所有配置。
/**
* 扩展Spring MVC框架的消息转换器
* @param exceptionResolvers
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters){
log.info("扩展消息转换器...");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器,将java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器converter加入容器converters里,并优先使用
converters.add(0, converter);
}
2.5 代码提交
3. 启用禁用员工账号
3.1 需求分析和设计
POST请求是因为启用禁用 本质是修改
一般来说,状态用路径参数传,id用请求参数传
3.2 代码开发
EmployeeController中创建方法
/**
* 启用禁用员工账号
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status, Long id){
log.info("启用禁用员工账号,参数为:{},{}", status, id);
employeeService.startOrStop(status, id);
return Result.success();
}
EmployeeService里声明
/**
* 启用禁用员工账号
* @param status
* @param id
*/
void startOrStop(Integer status, Long id);
EmployeeServiceImpl中实现
public void startOrStop(Integer status, Long id) {
//根据id改status,动态更新所以传实体类比较合适
//update employee set status =? where id = ?
Employee employee = new Employee();
employee.setStatus(status);
employee.setId(id);
employeeMapper.update(employee);
}
也可以通过@Builder构建器获得一个构件器对象
public void startOrStop(Integer status, Long id) {
//根据id改status,动态更新所以传实体类比较合适
//update employee set status =? where id = ?
Employee employee = Employee.builder()
.status(status)
.id(id)
.build();
employeeMapper.update(employee);
}
在EmployeeMapper中
/**
* 根据主键动态修改属性
* @param employee
*/
void update(Employee employee);
既然是动态的,SQL语句也要放在映射配置文件里写
<update id="update">
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>
3.3 功能测试
修改成功!
前后端联调也成功!
最后记得提交!
4. 编辑员工
4.1 需求分析和设计
点击修改,就会跳转修改界面
接口设计:
涉及到两个接口:一个是根据id查询员工信息(GET),一个是修改员工信息(PUT)
还有提交用POST,删除用DELETE
4.2 代码开发
①根据id查询员工信息比较简单,这里只放具体实现了
/**
* 根据id查询员工信息
* @param id
* @return
*/
@Select("select * from employee where id = #{id}")
Employee getById(Long id);
前后端联调成功,并且可以从开发者工具-网络看到响应格式
② 编辑员工信息
利用mapper之前定义好的update语句更新员工信息
/**
* 编辑员工信息
* @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);
}
4.3 功能测试-成功
5. 导入分类模块功能
5.1 需求分析和设计
设计包括6个接口,和员工管理类似
5.2 代码导入
mapper-xml-service-imple-controller倒序导入,这样不会报错
maven编译一遍无误
5.3 功能测试
两天终于敲完了 Day2,进度有点慢啊T^T