新增員工
DTO
將前端傳遞的參數列表通過對應的實體類接收
當前端提交的數據和實體類中對應的屬性差別較大時,使用DTO來封裝數據
@Data
public class EmployeeDTO implements Serializable {private Long id;private String username;private String name;private String phone;private String sex;private String idNumber;}
記得要implements Serializable,實現接口后,對象就具備了可序列化的能力
controller
/** * 新增員工 */
@PostMapping
@ApiOperation("新增員工")
public Result save(@RequestBody EmployeeDTO employeeDTO)
{ log.info("新增員工:{}",employeeDTO); employeeService.save(employeeDTO); return Result.success();
}
Result定義了后端的返回結果格式
Result
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; } }
Result是一個通用的后端統一返回結果封裝類,用于向前端返回統一格式的響應數據
泛型參數表示返回的數據類型,可以是任何對象
屬性:
code 相應狀態碼,1表示成功,0或其他表示失敗
msg:描述錯誤信息,成功則為空
data:封裝返回給前端的具體數據,類型為泛型T
方法:
success()
只返回code=1
success(T object):靜態方法,返回包含具體數據的成功響應(code=1,data=object)
error(String msg):返回錯誤信息和失敗狀態碼
public static <T> Result<T>
是一個Java泛型方法的聲明,前一個表明了泛型方法的類型參數聲明部分
Result表示該方法返回一個Result類型的對象
static表示這是一個類方法,可通過類名調用
@Service
@Override
public void save(EmployeeDTO employeeDTO) { Employee employee = new Employee(); BeanUtils.copyProperties(employeeDTO,employee); employee.setStatus(StatusConstant.ENABLE); employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()); employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); employee.setCreateUser(10L); employee.setCreateUser(10L);employeeMapper.insert(employee);
}
注意一下BeanUtils.copyProperties()方法,以后用的很多
用于將一個 JavaBean 對象(源對象)的屬性值復制到另一個 JavaBean 對象(目標對象)中。這里它將 EmployeeDTO
對象 employeeDTO
的屬性值復制到 Employee
對象 employee
中。通常,源對象和目標對象需有相同或兼容的屬性名及類型,這樣才能正確復制屬性值,比如 EmployeeDTO
中的 name
屬性若與 Employee
中的 name
屬性類型匹配,就會將 employeeDTO
的 name
值復制到 employee
的 name
屬性中 。
mapper
@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中已經開啟了駝峰命名,所以二者可以對應
mybatis:configuration:#開啟駝峰命名map-underscore-to-camel-case: true
問題一 重名
使用全局異常處理器來解決問題
@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); }
}
@ExceptionHandler
是一個注解,該方法用于處理特定類型的異常。比如在上述代碼中,標記的方法 exceptionHandler
專門處理 SQLIntegrityConstraintViolationException
類型的異常。當程序執行過程中拋出此類型異常時,就會執行被 @ExceptionHandler
標記的方法,根據異常信息進行相應處理,比如提取異常信息中的用戶名,返回特定的錯誤提示給前端,告知用戶用戶名已存在等。
問題二:
新增員工時,我們需要獲得當前登錄員工的id
使用JWT 令牌并相應給前端
當前端指令請求時,它所攜帶的JWT令牌可以解析出對應員工的登錄id;
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; }
}
在攔截器中已經解析出了員工的id
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token); Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
第一行調用JwtUtil工具類的parseJWT方法,使用配置文件中的管理員密鑰對token令牌進行解析,將解析后的對象(包括id)存儲在claim對象中
通過JwtClaimsContant.EMP_ID這個常量作為鍵,獲取對應的用戶ID,得到字符串之后再轉換為Long類型
ThreadLocal
ThreadLocal是為每一個線程提供的一個單獨的存儲空間,具有線程隔離的效果,其他線程無法訪問
常用方法
public void set(T value)設置
public T get()獲取
public void remove()
解決流程
在初始工程中已經封裝了對應類
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();
}
在一個多線程的 Web 應用中,不同的請求線程可能需要各自獨立的用戶 ID 標識。可以在某個請求線程開始處理業務時,調用setCurrentId
方法設置該線程的用戶 ID,在處理業務過程中通過getCurrentId
方法隨時獲取該線程的用戶 ID,處理完成后調用removeCurrentId
方法清理資源。
在攔截器中存儲empid
try {//.................Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("當前員工id:", empId);/將用戶id存儲到ThreadLocalBaseContext.setCurrentId(empId);//3、通過,放行return true;
在service中獲取局部變量的值
public void save(EmployeeDTO employeeDTO) {//.............................//設置當前記錄創建人id和修改人idemployee.setCreateUser(BaseContext.getCurrentId());employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.insert(employee);}
員工分頁查詢
注意:
請求參數類型為Query,在路徑后拼接/admin/employee/page?name=zhangsan
返回數據中records數組使用Employee實體類對屬性進行了封裝
Controller
@GetMapping("/page")@ApiOperation("員工分頁查詢")public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){log.info("員工分頁查詢,參數為:{}", employeePageQueryDTO);PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);//后續定義return Result.success(pageResult);}
方法返回值類型為Result<PageResult>
,Result
是自定義的用于封裝響應結果的類,PageResult
則可能是包含分頁數據的類。
PageResult是一個專用的數據載體
long total 總記錄數
List records 當前頁實際查詢到的數據集合
Result
是泛型的具體類型實例,data字段的類型是PageResult
service
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) { PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize()); Page<Employee>page=employeeMapper.pageQuery(employeePageQueryDTO); long total=page.getTotal(); List<Employee> records=page.getResult(); return new PageResult(total,records);
}
mappe層
<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>
查詢名字中間包含name的,不需要limit,分頁由pagehelper插件進行處理
操作時間字段問題
在WebMvcConfiguration中擴展SpringMVC消息轉換器
Spring Boot 默認使用 MappingJackson2HttpMessageConverter
處理 JSON 序列化 / 反序列化,但 Jackson 對日期類型的默認序列化規則會導致格式異常:
- 若實體類中是
Date
、LocalDateTime
等日期類型,Jackson 默認可能將其序列化為 時間戳 或 拆分的數組(如[年, 月, 日, 時, 分, 秒]
,與前端期望的格式(如2022524112024
)不匹配。 - 將自定義的
MappingJackson2HttpMessageConverter
放到轉換器列表的第一位,確保優先使用(覆蓋默認的 Jackson 轉換器)。
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { log.info("擴展消息轉換器..."); //創建一個消息轉換器對象 MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); //需要為消息轉換器設置一個對象轉換器,對象轉換器可以將Java對象序列化為json數據 converter.setObjectMapper(new JacksonObjectMapper()); //將自己的消息轉化器加入容器內 converters.add(0, converter);
}
啟用禁用員工賬號
Controller
@PostMapping("/status/{status}")
@ApiOperation("啟用禁用員工賬號")
public Result startOrStop(@PathVariable Integer status,@RequestParam("id") Long id // 顯式聲明id來自請求參數
)
{ log.info("啟用禁用員工賬號"); employeeService.startOrStop(status,id); return Result.success();
}
注意講義中當前方法中 Long id
參數未顯式聲明參數來源。在 Spring MVC 中,未標注@PathVariable
/@RequestBody
等注解的參數默認會嘗試從請求參數(@RequestParam
)獲取,但如果請求中未傳遞id
參數,會直接拋出MissingServletRequestParameterException
(400 錯誤)。建議修改
編輯員工
回顯員工信息
controller
/** * 根據id查詢員工信息 */
@GetMapping("/{id}")
@ApiOperation("根據id查詢員工信息")
public Result<Employee> getById(@PathVariable Long id) { Employee employee = employeeService.getById(id); return Result.success(employee);
}
service層
@Override
public Employee getById(Long id) { Employee employee = employeeMapper.getById(id); employee.setPassword("****"); return employee;
}
密碼主動修改很細節
修改信息
service
注意這里不能用builder(),因為builder只能是創建對象的時候使用
public void update(EmployeeDTO employeeDTO) { Employee employee = new Employee(); BeanUtils.copyProperties(employeeDTO, employee); employee.setUpdateTime(LocalDateTime.now()); employee.setUpdateUser(BaseContext.getCurrentId()); employeeMapper.update(employee); }
在上一個功能中已經實現了可泛用的mapper,這里不需要再寫了
導入代碼
CategoryMapper.xml文件中注意到一個點
select 語句中使用where if test時需要"and",表示&
在update語句中set里不需要寫表示| ,有就滿足
<select id="pageQuery" resultType="com.sky.entity.Category"> select * from category <where> <if test="name != null and name != ''"> and name like concat('%',#{name},'%') </if> <if test="type != null"> and type = #{type} </if> </where> order by sort asc , create_time desc</select> <update id="update" parameterType="Category"> update category <set> <if test="type != null"> type = #{type}, </if> <if test="name != null"> name = #{name}, </if> <if test="sort != null"> sort = #{sort}, </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.DTO:
用來接受前端提交的數據以及不同層之間傳遞
當與實體類差別較大時使用
2.Result
通用后端統一返回結果封裝類,向前端返回統一格式的響應數據
3.public static Result
是一個Java泛型方法的聲明,前一個表明了泛型方法的類型參數聲明部分
Result表示該方法返回一個Result類型的對象
static表示這是一個類方法,可通過類名調用
4.BeanUtils.copyProperties()
一般將DTO賦值給對應的對象
5.重名
ExceptionHandler注解,當程序拋出對應異常時,會執行被@ExceptionHandler標記的方法
6.JWT令牌
通過JwTUtil.parseJWT可以解析令牌
7.ThreadLocal
僅本線程可用存儲空間
set get remove
8.分頁查詢
PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
第一個參數employeePageQueryDTO.getPage()
獲取到的是要查詢的頁碼,第二個參數employeePageQueryDTO.getPageSize()
獲取到的是每頁顯示的記錄數
方法返回值類型為Result
PageResult是專門的數據載體,包括List records,即當前頁查詢到的數據集合
records
9.操作時間字段
更換自定義的轉換器覆蓋原有
10.啟用禁用員工賬號
未表明數據來源會默認從請求參數(@RequestParam)獲取,即帶有查詢參數(Query Parameters)的 URL 鏈接?xx=xx
11.xml文件 if條件
在select時要保證全滿足,即&,所以if 后要加"and"
在update時滿足一個修改一個,即 |,所以If 后不用加