新增員工
需求分析和設計
產品原型:
接口設計:
本項目約定:
? ? ? ? 管理端發出的請求,統一使用 /admin 作為前綴
? ? ? ? 用戶端發出的請求,統一使用 /user 作為前綴
數據庫表設計:
代碼開發
根據新增員工接口設計對應的 DTO:
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;}
@PostMapping@ApiOperation("新增員工")public Result save(@RequestBody EmployeeDTO employeeDTO){log.info("新增了員工: {}", employeeDTO);employeeService.save(employeeDTO);return Result.success();}
@Overridepublic void save(EmployeeDTO employeeDTO) {Employee employee = new Employee();//對象屬性拷貝BeanUtils.copyProperties(employeeDTO,employee);//設置賬號狀態,默認正常狀態,1表示正常,0表示鎖定employee.setStatus(StatusConstant.ENABLE);//設置加密后的密碼employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));//設置當前記錄的創建時間employee.setUpdateTime(LocalDateTime.now());employee.setCreateTime(LocalDateTime.now());//設置當前記錄的創建人id和修改人id//TODO 后期更改為當前登錄用戶的 idemployee.setCreateUser(10L);employee.setUpdateUser(10L);log.info("新增了員工: {}", employee);employeeMapper.save(employee);}
@Insert("insert into sky_take_out.employee (name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user) " +"values (#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")void save(Employee employee);
}
功能測試
功能測試方法:
通過接口文檔測試
? ? ? ? 通過前后端聯調測試
注意:由于開發階段前端和后端是并行開發的,后端完成某個功能后,此時前端對應的功能可能還沒有開發完成,導致無法進行前后端聯調測試,所以在開發階段,后端測試主要以接口文檔測試為主。
代碼完善
程序存在的問題:
錄入的用戶名已存在,拋出異常后沒有處理
? ? ? ? 新增員工時,創建人 id 和 修改人 id 設置了固定值
先在全局異常處理類中完成第一個問題
@ExceptionHandlerpublic Result exceptionHandler(BaseException ex){log.error("異常信息:{}", ex.getMessage());return Result.error(ex.getMessage());}@ExceptionHandlerpublic 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);}}
}
這里用到了方法重載
針對第二個問題,需要通過某種方式動態獲取當前登錄員工的 id
當登錄成功時,我們的 id 就已經在 claims 里面了,所以如果我們想反向拿出來也是可以辦到的
package com.sky.interceptor;import com.sky.constant.JwtClaimsConstant;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** jwt令牌校驗的攔截器*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProperties jwtProperties;/*** 校驗jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判斷當前攔截到的是Controller的方法還是其他資源if (!(handler instanceof HandlerMethod)) {//當前攔截到的不是動態方法,直接放行return true;}//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;}}
}
注意第48行,就是我們需要的 empId,那么現在的問題就是怎么將這個 id 傳給我們的 service
的 save 方法。這里需要用到一個重要的知識 ThreadLocal
ThreadLocal 并不是一個 Thread,而是 Thread 的局部變量。
ThreadLocal 為每一個線程提供單獨一份存儲空間,具有線程隔離效果,只有在線程內才能獲取到對應的值,線程外則不能訪問
? ? ? ??
我們需要一個工具類用來調用 ThreadLocal 方法
package com.sky.context;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();}}
然后在校驗令牌時取出 empId,,并將他放入 threadlocal
最后要用的時候拿出來
員工分頁查詢
需求分析和設計
產品原型
接口設計
代碼開發
和前面新增員工一樣,我們需要一個與接口設計相對應的 DTO:
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;}
@GetMapping("/page")@ApiOperation("分頁查詢")public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){log.info("分頁查詢:{}", employeePageQueryDTO);PageResult pageResult = employeeService.page(employeePageQueryDTO);return Result.success(pageResult);}
@Overridepublic PageResult page(EmployeePageQueryDTO employeePageQueryDTO) {PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());Page<Employee> page = employeeMapper.pagequery(employeePageQueryDTO);return new PageResult(page.getTotal(), page.getResult());}
拓展:PageHelper 的底層時 ThreadLocal 實現的
<select id="pagequery" resultType="com.sky.entity.Employee">select * from sky_take_out.employee<where><if test="name != null and name != ' '">and name like concat('%', #{name}, '%')</if></where></select>
功能測試
這里會發現日期這邊看起來不舒服,接下來完善代碼解決這個問題
代碼完善
package com.sky.json;import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;/*** 對象映射器:基于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);}
}
解決方式:
? ? ? ? 方式一:在屬性上加入注解,對日期進行格式化??
? ? ? ? 方式二:在 WebMvcConfiguration 中拓展 Spring MVC 的消息轉換器,統一對日期類型進行? ? ? ? ? ? ? ? ? ? ? ? ?格式化處理
建議使用第二種,這個方法是固定的,是我們這個配置類繼承的父類里面就有的
啟用禁用員工賬號
需求分析和設計
產品原型
接口設計
代碼開發
@PostMapping("/status/{status}")@ApiOperation("啟用或禁用員工")public Result start_or_stop(@PathVariable Integer status, Long id){log.info("啟用或禁用員工:{},{}", status, id);employeeService.start_or_stop(status, id);return Result.success();}
@Overridepublic void start_or_stop(Integer status, Long id) {Employee employee = Employee.builder().status(status).id(id).build();employeeMapper.update(employee);}
<update id="update">UPDATE sky_take_out.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">update_user = #{updateUser}</if></set>WHERE id = #{id}</update>
功能測試
編輯員工
需求分析和設計
產品原型
接口設計
注意這里需要兩個接口
1:根據 id 查詢員工信息,也就是查詢回顯
2:編輯員工信息
代碼開發
首先是查詢回顯
@GetMapping("/{id}")@ApiOperation("根據 id 查詢員工")public Result<Employee> queryById(@PathVariable Long id){log.info("查詢的員工的 id 為:{}", id);Employee employee = employeeService.queryById(id);return Result.success(employee);}
@Overridepublic Employee queryById(Long id) {Employee employee = employeeMapper.queryById(id);return employee;}
<select id="queryById" resultType="com.sky.entity.Employee">select * from sky_take_out.employee where id = #{id}</select>
然后是更新員工
@PutMapping@ApiOperation("修改員工數據")public Result update(@RequestBody EmployeeDTO employeeDTO){log.info("修改員工:{}", employeeDTO);employeeService.update(employeeDTO);return Result.success();}
@Overridepublic void update(EmployeeDTO employeeDTO) {Employee employee = new Employee();BeanUtils.copyProperties(employeeDTO,employee);employee.setUpdateUser(BaseContext.getCurrentId());employee.setUpdateTime(LocalDateTime.now());employeeMapper.update(employee);}
<update id="update">UPDATE sky_take_out.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">update_user = #{updateUser}</if></set>WHERE id = #{id}</update>
功能測試
導入分類模塊功能代碼
需求分析和設計
產品原型:
接口設計
代碼導入
因為這里和之前員工管理的部分基本一樣,所以直接導入即可,注意最好從后往前導入,也就是從Mapper 層開始導入,這樣會減少報錯,導入完之后記得手動編譯