目录
新增员工——需求分析与设计
产品原型
接口设计:
数据库设计:
新增员工——代码开发
DTO是传输数据的实体类,而不是某一个表格的实体类。
在Controller层中
EmployeeController中
/*
*
*
* 新增员工
* */
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}",employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}
在Service层中
定义一个新的接口方法
/**
* 新增员工业务方法
* @param employeeDTO
*/
void save(EmployeeDTO employeeDTO);
接口实现类新增方法,此处创建人和修改人id后面再解决。
@Override
public void save(EmployeeDTO employeeDTO) {
Employee employee=new Employee();
//两个实体有很多属性名都一样,不一个一个set的话,可以通过对象的属性拷贝
BeanUtils.copyProperties(employeeDTO,employee);
//设置账号状态,默认正常状态1,0锁定,为了方便后期维护,使用一个常量类
employee.setStatus(StatusConstant.ENABLE);
//设置密码,默认密码123456
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);
}
在Mapper层中
/**
* 插入员工数据
* @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);
功能测试
接口文档测试:
在调试界面设置好参数,点击发送 后返回一个401,没有成功返回,这里是被拦截器拦截了。
会校验jwt令牌 ,这里因为没有令牌,所以token为空,返回的是401
首先要获取一个令牌,在登录接口正常获取即可。
获取到token之后在文档管理——>全局参数设置里面配置一个token属性。
这里name=token是因为后端项目配置文件里面配置的一个属性就是token,项目会自动取出这个token
再次发送调试成功返回
前后端联调测试:
数据库成功多出两条数据
新增员工——代码完善
第一个问题
将插入重复员工时报的错误异常放进全局异常处理器进行处理
SQLIntegrityConstraintViolationException
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
// Duplicate entry 'yhy' for key 'employee.idx_username'
String message=ex.getMessage();
if(message.contains("Duplicate entry")){
String[] s = message.split(" ");
String username = s[2];
String msg=username+ MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
}else{
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
再次测试
第二个问题
总的就是获取token中间的字段进行解析之后就可以获取登录用户ID了
客户端发起的每一次请求都是一次单独的线程。
这里项目已经提前封装好了ThreadLocal的常用方法
在拦截器当中将取出的ID存进暂存区
BaseContext.setCurrentId(empId);
在服务层代码中取出
//设置当前记录创建人id和修改人id
// TODO 后期改为当前登录用户的id
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
完成一个小功能的代码记得及时存储推送到云端。
员工分页查询
需求分析与设计
产品原型如下
接口设计
代码开发
设计DTO,传输数据格式。
分页查询的响应数据因为有两个对象,所以再封装一次。然后返回一个Result<PageResult>对象。
此处使用到一个mybatis提供的分页查询的插件,依赖如下。
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper}</version>
</dependency>
在Controller层中
此处是Query参数,所以不需要@Requestbody注解返回的PageResult对象要封装层Result风格。
/**
* 员工分页查询
* @param employeePageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult>page( EmployeePageQueryDTO employeePageQueryDTO){
log.info("员工分页查询参数为{}",employeePageQueryDTO);
PageResult pageResult=employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}
在Service层中
这里用到了PageHeloer插件,要封装成pageResult对象还要将查询结果里面的数据取出来。
此处startPage方法和pageQuery方法看似没有交集,但是在底层是通过ThreadLocal对象共享了一部分数据。后面开始分页查询之前就已经通过ThreadLocal取出了页码和每页记录数,然后动态的拼接上limit关键字。
/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
//select * from employee limit 0,10
//开始分页查询,传了个页码和每页的记录数
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
//此处Page对象是PageHelper提供的。
Page<Employee> page=employeeMapper.pageQuery(employeePageQueryDTO);
long total = page.getTotal();
List<Employee> records = page.getResult();
return new PageResult(total,records);
}
在Mapper层中
这条查询用的不是注解开发,用的是xml配置文件开发,所以还需要在Resources目录下准备对应的xml文件。
/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
在配置文件中已经配置好了这个xml开发
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.sky.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
在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">
<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>
功能测试
swagger接口文档测试
控制台输出
前后端联调测试
因为返回的日期数据用的是数据格式,所以前端的时间格式看着很别扭.
代码完善
方式一:
加上注解
再次测试看见数据格式有了变化。
第二种方式
准备一个对象转换器 ,主要进行序列化和反序列化操作
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
在配置类中
/**
* 扩展springMVC框架的消息转换器,可以
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器,对象转换器可以将java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器加入容器中,并设定优先级为0,最高优先级
converters.add(0,converter);
}
接口文档测试:
前后端联调测试
都没有问题了。
最后还要记得推送云端。
启用,禁用员工账号——需求分析与设计
接口设计
需要一个路径参数和一个Query参数
启用,禁用员工账号——代码开发和功能测试
在Controller层中
针对查询类的操作需要返回data数据,则要加上泛型,非查询类的就不需要了。
/**
* 路径参数通过PathVariable注解获取,Query参数只要name保持一致即可
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable("status") Integer status,Long id){
log.info("启用禁用员工账号{},{}",status,id);
employeeService.startOrStop(status,id);
return Result.success();
}
在Service层中
/**
* 启用禁用员工账号
* @param status
* @param id
*/
@Override
public void startOrStop(Integer status, Long id) {
//update employee set status = ? where id = ?
// Employee employee = new Employee();
// employee.setStatus(status);
// employee.setId(id);
//两种编程风格
Employee employee = Employee.builder().status(status).id(id).build();
employeeMapper.update(employee);
}
在Mapper层中
这里用的是动态SQL写法,需要xml文件
/**
* 根据主键动态修改属性
* @param employee
*/
void update(Employee 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="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>
功能测试
接口文档测试
数据库数据修改成功
前后端联调测试
成功修改。
编辑员工——需求分析与设计
编辑界面需要点击修改然后查询回显员工信息然后根据需要进行修改再保存更新数据库。
产品原型:
接口设计
一个接口是查询的接口,一个接口是更新的接口。
编辑员工——代码开发
可以看见id是通过路径参数传参。
在Controller层中
/**
* 根据id查询员工信息
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<Employee> getById(@PathVariable Long id){
Employee employee=employeeService.getBYId(id);
return Result.success(employee);
}
/**
* 编辑员工信息
* @param employeeDTO
* @return
*/
@PutMapping
@ApiOperation("编辑员工信息")
public Result update(@RequestBody EmployeeDTO employeeDTO)
{
log.info("编辑员工信息{}",employeeDTO);
employeeService.update(employeeDTO);
return Result.success();
}
在Service层中
/**
* 根据id查询员工
* @param id
* @return
*/
@Override
public Employee getBYId(Long id) {
Employee employee= employeeMapper.getById(id);
employee.setPassword("****");//防止密码泄露
return employee;
}
/**
* 编辑员工信息
* @param employeeDTO
*/
@Override
public void update(EmployeeDTO employeeDTO) {
Employee employee=new Employee();
//对象属性拷贝,这样就可以使用上面启用禁用那里的update语句
BeanUtils.copyProperties(employeeDTO,employee);
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.update(employee);
}
在Mapper层中
这里因为update方法在启用禁用部分已经写过了,所以这里不再写一次了
/**
* 根据id查询员工信息
* @param id
* @return
*/
@Select("select * from employee where id =#{id}")
Employee getById(Long id);
功能测试
查询回显成功
前后端联调测试
记得推送代码
导入分类管理功能代码
产品原型
数据库设计表
导入分类管理模块的代码之后成功进行前后端联调测试
记得git push