開發模式&環境搭建
開發模式:
前后端分離開發
前端程序員寫前端頁面,后端程序員寫后端的接口,前端工程發送請求來訪問后臺,后臺處理完請求后要給前端相應對應的數據。
還需要一套標準來約束即接口文檔,在接口文檔中會對每一個接口的訪問路徑,請求方式以及請求參數還有響應數據進行明確的說明。
一般這樣的接口文檔就需要提供好,有了接口文檔就有了開發的標準。前端程序員參照接口文檔進行開發,后端程序員也參考同一份接口文檔開發,最終開發好的項目就不會出現接口出錯的情況了。
環境搭建
準備數據庫表
創建springboot工程,引入對應的依賴(web,MyBatis,MySQL驅動)
在配置文件中引入MyBatis的配置信息
創建包結構,并準備實體類
注冊接口
用戶
-
注冊
查看用戶表及其實體類:
在數據庫表中user_pic應該為圖片,但為什么數據類型為varchar,因為將來將圖片放在第三方服務器上,這里存放的是一個URL地址。
開發接口流程:
首先我們要明確一下需求,需要知道該功能將來用戶是如何使用的,
接下來需要去閱讀接口文檔明確該接口的輸入以及輸出
接著要進行思路分析,分析將來如何寫代碼邏輯,再接著就是開發寫代碼,代碼寫完之后需要測試接口的正確性
在寫注冊接口時,依然是按照三層架構的方式書寫:
Controller層:
@Controller
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("/register")public Result register(String username,String password){//查詢用戶User user = userService.findByUsername(username);if(user != null){return Result.error("用戶已存在");}else {userService.Register(username,password);return Result.success();}}
}
Service層:
//根據用戶名查詢用戶User findByUsername(String username);
//注冊void Register(User user);
實現類:
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User findByUsername(String username) {return userMapper.findByUsername(username);}@Overridepublic void Register(String username,String password) {//加密password = MD5Util.MD5Lower(password);userMapper.addUser(username,password);}
}
Mapper層:
@Mapper
public interface UserMapper {@Select("select * from user where username = #{username}")User findByUsername(String username);@Insert("insert into user(username,password,create_time,update_time) values(#{username},#{password},now(),now())")void addUser(String username, String password);
}
執行:發現錯誤,無法掃描到mapperBean對象,檢查得知,沒有添加MyBatis-spring的starter,導入之后可正常啟動。
接口測試:
檢查數據庫數據
測試成功。
注冊接口參數校驗
我們在接口文檔中對username和password有明確的說明,這兩個參數必須是5-16位的非空字符,那接口就必須保證,如果前端傳遞的參數不符合規則,就不能夠完成注冊。而在我們之前的代碼中并沒有對username與password的校驗,就會導致無論什么樣的數據都會被無差別傳入數據庫。
所以我們需要對參數做校驗
?@RestController@RequestMapping("/user")public class UserController {@Resourceprivate UserService userService;@PostMapping("/register")public Result register(String username,String password){//檢驗用戶名與密碼必須滿足5到16位且不能為空if(username.length() < 5 || username.length() > 16 || password.length() < 5 || password.length() > 16){return Result.error("用戶名或密碼長度不符合要求");}else {User user = userService.findByUsername(username);if(user != null){return Result.error("用戶已存在");}else {userService.Register(username,password);return Result.success();}}?}}
接口測試得:
證明代碼執行成功。
但是該業務代碼太過繁瑣,且在只有兩個參數的情況下,就已經如此,在訪問量高的情況下,更不用想。
解決方案:
Spring Validation
spring 提供的一個參數校驗框架,是用預定義的注解完成參數校驗。
案例要求:使用spring Validation 對注冊接口的參數進行合法性校驗。
使用步驟:
-
引入Spring Validation 起步依賴
-
在參數前面添加@Pattern注解
-
在Controller類上添加@Validated
代碼展示:
pom.xml
?<!-- ? ? ? ?validation依賴--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
UserController
?
@RestController@RequestMapping("/user")@Validatedpublic class UserController {@Resourceprivate UserService userService;@PostMapping("/register")public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password){//檢驗用戶名與密碼必須滿足5到16位且不能為空if(username.length() < 5 || username.length() > 16 || password.length() < 5 || password.length() > 16){return Result.error("用戶名或密碼長度不符合要求");}else {User user = userService.findByUsername(username);if(user != null){return Result.error("用戶已存在");}else {userService.Register(username,password);return Result.success();}}?}}
測試接口結果:
發現500異常,在idea內部也報錯
使用全局異常處理器進行參數校驗失敗異常處理,
代碼展示:
?// 全局異常處理類@RestControllerAdvicepublic class GlobalExceptionHandler {// 捕獲所有異常@ExceptionHandler(Exception.class)// 返回值類型為Result,因為全局異常處理類返回值類型為Resultpublic Result handleException(Exception e){e.printStackTrace();// 三元運算符 :如果e.getMessage()不為空,則返回e.getMessage(),否則返回"服務器異常"return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage() : "服務器異常");}}
??
效果展示:
小結:
Spring Validation
-
導入 validation 起步依賴
-
在參數上添加@Pattern注解,指定校驗規則
-
在Controller類上添加@Validated注解
-
在全局異常處理器中處理參數校驗注解失敗的異常
登錄
登錄需求:
用戶在登錄界面輸入用戶名還有密碼,點擊登錄按鈕,訪問后臺的登錄接口,如果登錄成功,要跳轉到首頁,登錄失敗則會給出對應的提示。
根據接口文檔
分析實現思路
首先在UserController中添加方法。
?@PostMapping("/login")public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password){//根據用戶名查詢用戶User user = userService.findByUsername(username);//判斷用戶是否存在if (user == null) {return Result.error("用戶不存在");}// 判斷密碼是否正確if (MD5Util.MD5Lower(password).equals(user.getPassword())){return Result.success("登錄成功 JWT 令牌");}return Result.error("密碼錯誤");?}}
測試成功
登錄認證
在實際項目中,會有許多接口,在提供服務之前需要對用戶的登錄狀態進行檢查,檢查的過程被稱為登錄認證。
而在當前項目中并沒有登錄認證,因此我們要為項目添加登錄驗證。
如果要實現登錄認證,要借助于令牌的技術,即我們在登錄頁面登錄成功之后,需要生成一個JWT令牌,再將令牌響應給瀏覽器,瀏覽器在訪問其它接口或資源是都需要攜帶該令牌,則服務器能夠得到該令牌,還需要去驗證令牌的合法性,如果令牌合法,則正常提供服務,如果不是,則不提供服務。
令牌就是一段字符串
-
承載業務數據,減少后續請求查詢數據庫的次數(系統需要得知本次操作是哪個用戶操作的,方便以后回收,如果每次都需要到數據庫去查詢用戶信息,則會極大影響性能,因此可以將用戶信息放在令牌中,而瀏覽器每次訪問接口都會攜帶令牌,就可以直接從令牌中獲取,由此減少了數據庫的訪問次數,提高了性能)
-
防止篡改,保證信息的合法性與有效性。
而在實際開發中,我們所用到的常用令牌規范為JWT令牌規范
JWT
簡介
全稱:JSON Web Token(官網地址:JSON Web Tokens - jwt.io)
定義了一種簡潔的、自包含的格式,用于通信雙方以JSON數據格式安全的傳輸信息。
組成:
-
第一部分:Header(頭),記錄令牌類型、簽名算法等。例如
{"alg":"HS256","type":"JWT"}
,算法用于防篡改 -
第二部分:Payload(有效載荷),攜帶一些自定義信息,默認信息。例如
{"id":"1","username":"Tom"}
。而外在表示為一段無規律的64個可打印字符
原因:借助base64編碼方式(Base64:是一種基于64個可打印字符(A-Z a-z 0-9 + /)來表示二進制數據的編碼方式)來完成,將json字符串變為64個可打印字符,
優點:提高token的實用性:假如JSON字符串中含有空格、中文等特殊字符,cookie就不能支持,因此在JWT中將JS字符串通過base64編碼轉換。
注意事項:Base64僅僅是編碼方式,不是加密方式,且編碼方式是公開的,因此安全性不高,所以在Payload中不要存放私密數據。
(Base64特點:通用,在任意場景下都被支持)
-
第三部分:Signature(簽名),防止token被篡改,確保安全性。將header,payload,借助于密鑰以及加密算法通過加密得來的。
加密算法是通過第一部分的alg
指定,而密鑰可以在程序中單獨的配置。
主要作用:防篡改,保證token的安全性,即使篡改了前兩部分,也無法篡改第三部分,因為是加密后的字符串,之后JWT解析token令牌時,會通過解密第三部分數字簽名從而得到頭部與載荷,在拿解密的內容與用戶傳遞的內容作對比,如果不一樣,則說明數據被篡改過,就會禁止訪問。
注意事項:JWT就是生成token的一種規則,包含了token的生成,校驗,是一種約定俗成。
生成令牌
JWT是令牌的規范,我們可以自己手動書寫,也可以借助生成JWT令牌的工具。
生成步驟:
導入依賴:
<!-- JWT依賴--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version></dependency>
生成令牌(大多數實際開發直接使用工具包生成即可,在這里手動編譯是加強記憶):
public class JwtTest {@Testpublic void test() {Map<String, Object> claims = new HashMap<>();claims.put("userId",1);claims.put("username","lyc");//生成JWT的代碼String token = JWT.create().withClaim("user", claims)//添加載荷.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))//設置過期時間.sign(Algorithm.HMAC256("lyc"));//指定簽名算法System.out.println(token);}
}
驗證令牌:
@Test
public void testParse(){//定義字符串,模擬從數據庫中取出的JWTString token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +".eyJ1c2VyIjp7InVzZXJJZCI6MSwidXNlcm5hbWUiOiJseWMifSwiZXhwIjoxNzQ4MzAwMDI4fQ" +".f5LzYorp0U4RBiKt7DzBgYDm_SRanjHPpb1zCMYJszQ";JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("lyc")).build();DecodedJWT decodedJWT = jwtVerifier.verify(token);//解析JWT,生成一個DecodedJWT對象decodedJWT.getClaims().forEach((k,v)->{System.out.println(k + ":" + v.asMap());});}
-
如果篡改了頭部與載荷,就會拋出異常
-
如果修改了密鑰,就會拋出異常
-
如果過期了,就會拋出異常
注意事項:
-
JWT校驗使用的簽名密鑰,必須和生成JWT令牌時使用的密鑰是配套的。
-
如果JWT令牌解析校驗時報錯,則說明令牌被篡改或失效,令牌非法。
登錄驗證實現
操作流程:在USerController中生成JWT令牌,在其他接口中去驗證該令牌
先編譯一個JWT工具類
??
public class JWTUtil {private static final String SIGNATURE = "lyc";/*** 生成token* @param map //傳入payload* @return 返回token*/public static String getToken(Map<String,Object> map){JWTCreator.Builder builder = JWT.create();builder.withClaim("claims",map);Calendar instance = Calendar.getInstance();instance.add(Calendar.HOUR,12);builder.withExpiresAt(instance.getTime());return builder.sign(Algorithm.HMAC256(SIGNATURE));}//接收token,驗證token,返回業務數據public static Map<String,Object> verifyToken(String token){DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);return verify.getClaims().get("claims").asMap();}??}
在userController中的login接口里,新建JWT令牌,
@PostMapping("/login")
public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password){//根據用戶名查詢用戶User user = userService.findByUsername(username);//判斷用戶是否存在if (user == null) {return Result.error("用戶不存在");}// 判斷密碼是否正確if (MD5Util.MD5Lower(password).equals(user.getPassword())){// 生成JWT令牌Map<String, Object> claims = new HashMap<>();claims.put("username", username);claims.put("password", password);String token = JWTUtil.getToken(claims);return Result.success(token);}return Result.error("密碼錯誤");}
測試,看接口是否能接收到token
在ArticleController中驗證令牌
代碼如下:
@RestController
@RequestMapping("/article")
public class ArticleController {@RequestMapping("/list")public Result<String> list(@RequestHeader(name="Authorization") String token, HttpServletResponse response){try {Map<String, Object> claims = JWTUtil.verifyToken(token);return Result.success("文章列表");} catch (Exception e) {response.setStatus(401);return Result.error("未登錄");}}
}
測試結果
在我們實際項目中會有很多的接口需要驗證用戶的登錄狀態,我們不能在每一個接口中書寫業務代碼,所以我們應該使用攔截器來進行驗證令牌,讓所有請求都經過攔截器,在攔截器里統一完成令牌驗證,如果生效則通行,失效則攔截。
代碼如下:
JWTInterceptor:
?public class JWTInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//驗證令牌String token = request.getHeader("Authorization");if (token == null || token.isEmpty()) {response.setStatus(401);return false;}try {Map<String, Object> claims = JWTUtil.verifyToken(token);return true;} catch (Exception e) {response.setStatus(401);return false;}}}
MvcConfig(將攔截器注冊進IOC容器中):
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new JWTInterceptor()).excludePathPatterns("/user/login","/user/register");}
}
測試結果:
不攜帶token
攜帶token
注意事項:
-
使用攔截器統一驗證令牌
-
登錄與注冊接口需要放行
獲取用戶詳細信息
明確需求:當用戶登陸成功后需要跳轉到首頁,在首頁頂部導航欄需要展示用戶的昵稱,頭像等,以及個人中心得基本資料,更換頭像,重置密碼等都需要先查詢數據庫來獲取用戶的詳細信息,再去更新。
根據接口文檔分析實現思路
我們還是需要在UserController中聲明userInfo方法。在接口文檔中并沒有攜帶參數,但是瀏覽器中攜帶的token里有用戶的username,因此我們可以取出username,在根據用戶名查詢用戶的詳細信息。
代碼展示:
@GetMapping("/userInfo")
public Result<User> userInfo(@RequestHeader(name = "Authorization") String token){//根據用戶名獲取當前用戶信息Map<String, Object> claims = JWTUtil.verifyToken(token);String username = (String)claims.get("username");User user = userService.findByUsername(username);return Result.success(user);
}
效果展示:
發現問題:在返回的數據中將用戶的密碼也返回,在接口文檔中并沒有返回密碼。
解決方案:在實體類上的密碼屬性添加@JsonIgnore,讓springmvc把當前對象轉換成json字符串時,忽略password,最終的json字符串中就沒有password了。
注意事項:一定要檢查導入的依賴為com.fasterxml.jackson.annotation.JsonIgnore
測試效果:
發現問題:在創建數據庫表示規定createTime以及updateTime非空,而在獲取的信息中卻為空。
原因:因為在實體類中的createTime與updateTime命名為駝峰命名,而數據庫中的命名為下劃線命名,這樣就導致MyBatis無法自動識別,需要在配置文件中設置
mybatis:configuration:map-underscore-to-camel-case: true # 開啟駝峰映射
這時開啟駝峰命名與下劃線命名的相互轉換。
測試結果:
優化獲取用戶信息接口
問題引入:
在userInfo接口中解析前端的接收頭的業務代碼在攔截器中已經寫過一遍,不應該在重復造輪子,我們可以復用在攔截器里解析到的結果。
需要知識:ThreadLocal
ThreadLocal(線程本地存儲)是Java中用于解決線程間數據共享問題的機制。
作用:提供線程局部變量,為每個線程提供獨立的數據副本,防止因共享資源造成的競態條件。
競態條件:
多個線程或者進程沒有正確的同步的訪問共享資源的時候,其執行結果可能依賴于一些不可控的執行順序導致一些不可預測的錯誤行為。簡單來講,就是并發安全性問題。
-
ThreadLocal中用來存取數據(set()/get())
-
TreadLocal可以做到線程隔離。
代碼展示:
?@Testpublic void threadLocalTest1() {// 創建ThreadLocal對象ThreadLocal threadLocal = new ThreadLocal();//開啟兩個線程new Thread(()->{threadLocal.set("thread1");System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());},"thread1").start();new Thread(()->{threadLocal.set("thread2");System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());},"thread2").start();}}
如何實現?
首先定義ThreadLocal對象,定義之后再去在對應線程中設置value(set()方法),并且將value保存在每個線程中的獨立存儲數據的空間ThreadLocalMap
,ThreadLocal對象會去維護一個ThreadLocalMap對象。
對于項目的作用:
假如項目中的Mapper層,Service層,Controller層都需要傳入username參數,如果還有其他業務代碼要用到username,還需要去傳參,那有沒有一種方式,不需要聲明,也可以使用參數。此時就可以使用ThreadLocal來進行優化,可以維護一個全局的ThreadLocal對象,用來存儲這類經常使用的數據,這時就可以在請求到達攔截器之后,調用Threadlocal對象的set()方法存儲變量,在請求到達接口之后,可以直接定義ThreadLocal對象,在調用get方法調用參數即可。且不會造成相同參數名混淆的情況,因為客戶端在訪問服務端時,服務端會為用戶單獨開啟一條線程用來提供服務,而ThreadLocal為每一個線程都提供了獨立的存儲空間,并且做到了數據隔離。同時做到了統一線程數據共享,不同線程數據隔離。
作用總結:減少參數的傳遞,同一個線程之間共享數據,不同線程之間數據隔離。
代碼優化:
新建ThreadLocal工具類:
?package com.lyc.utils;// 線程工具類public class ThreadUtil {//提供ThreadLocal對象private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();//根據建獲取值//泛型 可以直接強轉public static <T> T get(){return (T) THREAD_LOCAL.get();}//存儲鍵值對public static void set(Object obj){THREAD_LOCAL.set(obj);}//清除Thread Local 防止內存泄露public static void remove(){THREAD_LOCAL.remove();}}
再在攔截器中將參數存入ThreadLocal中。
最后在UserController中調用get方法獲取user參數。
@GetMapping("/userInfo")public Result<User> userInfo(/*@RequestHeader(name = "Authorization") String token*/){//根據用戶名獲取當前用戶信息Map<String, Object> claims = ThreadUtil.get();String username = (String)claims.get("username");User user = userService.findByUsername(username);return Result.success(user);}
}
測試結果:
注意事項:ThreadLocal的生命周期與線程的生命周期一致,因此不使用時就應該將ThreadLocal對象移除,防止內存泄漏
所以在攔截器中重寫afterCompletion方法,該方法是在請求結束后執行,在方法中移除ThreadLocal對象。
?@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清除ThreadLocal中存儲的數據ThreadUtil.remove();}
更新用戶基本信息
明確需求:當用戶在個人中心點擊基本資料后,當前頁面會展示用戶的詳細信息,用戶可以再次修改信息并點擊提交修改按鈕,從而讓訪問后臺的接口更新當前用戶的信息,
注意事項:當前用戶的登錄名稱無法修改。
根據接口文檔分析實現思路
首先在UserController中聲明一個新的方法,使用put請求方式提交,所以需要添加注解@PutMapping,瀏覽器向服務端請求時攜帶的信息是用戶想要修改的信息,并放在了請求體中以json格式攜帶,因此可以將這些數據封裝在user對象接收,為了讓框架能夠自動把請求體里面的JSON數據給轉換成一個實體類對象,因此使用@RequestBody注解。
注意事項:在Mapper層中要注意,SQL修改語句中要將updateTime一并修改,且不能修改username。
代碼展示:
USerController:
?@PutMapping("/update")public Result update(@RequestBody User user){userService.update(user);return Result.success();}
UserServiceImpl:
?@Overridepublic void update(User user) {//獲取當前時間user.setUpdateTime(LocalDateTime.now());userMapper.update(user);?}
測試結果,發現錯誤:
org.springframework.web.servlet.resource.NoResourceFoundException: No static resource user/update.
表示 Spring MVC 在嘗試訪問靜態資源 /user/update
時沒有找到對應的文件。
如果 Spring Boot 錯誤地認為 /user/update
是靜態資源,所以關閉靜態資源處理
在配置文件中 配置spring.web.resources.add-mappings=false。
?web:resources:add-mappings: false
再次測試:
檢查數據庫:
優化:參數校驗
在前面只是將user屬性更新,但是并沒有對屬性進行校驗,
首先ID必須傳遞,不能為null,nickname必須傳遞,不能為null,且必須是1-10位非空字符,以及email也是必須傳遞,不能為null,需要滿足郵箱的格式。
可以繼續使用Spring Validation,如果要對實體參數進行校驗,首先要在實體類的成員變量上添加validation提供的注解,對指定的屬性值完成校驗。
使用注解詳情:
注解 | 作用 |
---|---|
@NotNull | 值不能為null |
@NotEmpty | 值不能為null,且內容不為空 |
滿足郵箱格式 |
代碼展示:
?public class User {@NotNullprivate Integer id;// 主鍵IDprivate String username;// 用戶名@JsonIgnore// 讓springmvc把當前對象轉換成json字符串時,忽略password,最終的json字符串中就沒有password了。private String password;// 密碼@NotEmpty@Pattern(regexp = "^\\S{1,10}$")private String nickname;// 昵稱@NotEmpty@Emailprivate String email;// 郵箱private String userPic;// 用戶頭像地址private LocalDateTime createTime;// 創建時間private LocalDateTime updateTime;// 修改時間?}
其次需要在Controller層的方法參數上聲明@Validated注解
? ? ?@PutMapping("/update")public Result update(@RequestBody @Validated User user){userService.update(user);return Result.success();}}
測試結果:
查看數據庫:
當不添加ID時:
小結:實體參數校驗:
-
實體類的成員變量上添加注解(@NotNUll,@NotEmpty,@Email等)
-
接口方法的實體參數上添加@Validated注解
更新用戶頭像
明確需求:當用戶在個人中心點擊更換頭像,然后會在頁面的主區域展示當前用戶的頭像,此時用戶可以點擊選擇頭像,選擇一張本地的圖片,再次點擊上傳頭像按鈕,訪問后臺的接口完成頭像更新。
根據接口文檔分析實現思路:
請求方式為patch(因為更新用戶頭像僅僅是更新用戶信息里的局部)
請求參數需要一個URL,頭像地址。
首先需要在UserController中新聲明一個方法,在該方式中需要聲明@PatchMapping注解,且需要從前端中拿到請求體中的參數,需要使用@RequestParam注解。
還需要在Service層以及Mapper層都需要修改。
注意事項:修改SQL語句時依舊要修改updateTime。
代碼實現:
UserController
?@PatchMapping("/updateAvatar")public Result updateAvatar(@RequestParam("avatar") String avatarUrl){userService.updateAvatar(avatarUrl);return Result.success();}
serviceImpl
?@Overridepublic void updateAvatar(String avatarUrl) {Map<String,Object> map = ThreadUtil.get();String username = (String) map.get("username");System.out.println("username = " + username);Integer id = userMapper.findByUsername(username).getId();userMapper.updateAvatar(avatarUrl,id);}
注意事項:這里是在登錄時并沒有存儲user的ID,因此只能拿到username,在通過username拿到user,在通過user拿到userid。
測試結果:
查看數據庫:
優化:參數校驗
此時無論前端傳入任何值都會直接傳入,但是用戶頭像要求為URL格式,因此我們可以調用validation提供的參數校驗注解@URL,直接放在參數上面即可。
代碼展示:
?@PatchMapping("/updateAvatar")public Result updateAvatar(@RequestParam("avatar") @URL String avatarUrl){userService.updateAvatar(avatarUrl);return Result.success();}
測試結果:
更新用戶密碼
明確需求:當用戶在個人中心點擊重置密碼時,會在刷新一個表單,在該表單用戶需要填寫三項內容,原密碼、新密碼、確認新密碼,如果沒問題,點擊修改密碼,最終需要訪問后臺的接口完成密碼的更新。
根據接口文檔分析實現思路
請求方式依舊為patch(更新用戶密碼僅僅是更新用戶對象中的一個字段)。
首先在UserController新聲明一個方法,用于完成密碼的更新,在該方法上需要聲明一個map類型的參數用于接手前端提交的JSON參數
注意:在之前更新用戶基本信息的時候,也接收過JSON參數,當時聲明了一個user實體對象來接受,這是因為當時傳遞的JSON中的鍵名剛好和user實體類的屬性名一樣,但此時接受的JSON中的鍵名以實體類屬性名不一致,需要使用map類型的參數來接受,MVC框架會自動的將JSON數據轉換成map。
依舊是要修改Service層以及Mapper層的業務代碼。
代碼展示:
UserController:
?@PatchMapping("/updatePwd")public Result updatePwd(@RequestBody Map<String,String> params){//校驗參數String OldPwd = params.get("old_pwd");String NewPwd = params.get("new_pwd");String RePwd = params.get("re_pwd");//校驗三個參數是否都被傳入if (!StringUtils.hasLength(OldPwd) || !StringUtils.hasLength(NewPwd) || !StringUtils.hasLength(RePwd)){return Result.error("參數不能為空");}//校驗原密碼是否正確Map<String, Object> claims = ThreadUtil.get();String username = (String)claims.get("username");User user = userService.findByUsername(username);System.out.println(user.getPassword());System.out.println(OldPwd);System.out.println(MD5Util.MD5Lower("123456"));System.out.println(MD5Util.MD5Lower(OldPwd));if (!MD5Util.MD5Lower(OldPwd).equals(user.getPassword())){return Result.error("原密碼錯誤");}if (!NewPwd.equals(RePwd)){return Result.error("新密碼不一致");}userService.updatePwd(NewPwd);//調用service層修改密碼return Result.success();}
UserServiceImpl:
?@Overridepublic void updatePwd(String pwd) {Map<String,Object> map = ThreadUtil.get();String username = (String) map.get("username");System.out.println("username = " + username);Integer id = userMapper.findByUsername(username).getId();userMapper.updatePwd(MD5Util.MD5Lower(pwd),id);}
測試結果:
查看數據庫:
注意事項:由于不可知原因,在測試接口時,參數前面被添加了逗號,因此在校驗原密碼時記得加上逗號。
至此關于用戶模塊的接口開發全部完成。