開發背景:
????????用戶提交表單后,插入到對應數據庫表的字段中去,因需要保存是哪一個用戶提交的,所以需要拿到主表的user_id,更新功能為記錄提交時間,短時間不得再次提交
????????在對一個已有角色權限分配,登錄進行基礎業務開發的時候,在原有用戶表的基礎上添加了一個字段來記錄時間,前端提交表單后,在后端多表聯查時僅需要主鍵和這個字段的值(并未寫sql),調用了MP層的save(密碼重置的問題)方法,后續又調用了Update方法(解決角色權限問題,烏龍,因為當時未注意調用的是UserService層寫的同名方法,在這耽誤了不少的時間)
業務開發思路
這一業務需求并不困難,基本思路為:用戶提交表單 --> 先拿到登錄信息的user_id ?--> 根據user_id查找到主表用戶的新字段
-->如果字段為空,則保存本地時間 -->保存user_id插入表單信息到對應的表中
-->如果字段不為空,則和當前時間作比較
? ? ? ? ? ? ? ?--> 當前時間小于字段時間,則提示用戶處于凍結時間無法操作 --> 并拋出異常
? ? ? ? ? ? ? ?--> 當前時間大于字段時間,則重置凍結時間為當前時間+5分鐘 --> 保存user_id插入表單信息到對應的表中
業務實際開發過程
1.主表添加凍結字段記錄凍結時間
2.用戶提交表單后,調用接口
2.1 先拿到登錄信息的user_id(光拿user_id或拿Userinfo(包含user_id))
當時考慮的是后續說不定會用到Userinfo里面的字段,所以選擇的第二種
2.1.1 只拿user_id
???????Controller層繼承了一個AbstractController(系統自寫)
???????在AbstractController中用到的方法
protected SysUserEntity getUser() {return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();}protected Long getUserId() {return getUser().getUserId();}
代碼在第二種方式有解釋,因為是聲明為protected,所以咱們可以直接在Controller層調用
2.1.2?拿Userinfo(包含user_id)
Controller層
????????創建VO對象調用service層getLoginUserInfo方法
UserInfoVO userInfoVO = sysUserService.getLoginUserInfo();
在service層中的實現:
????????創建實體對象:? SecurityUtils是Shiro框架提供的一個工具類,.getSubject()方法返回的是線程綁定的Subject實例,包含了用戶的大部分信息(用戶名,密碼,授權,狀態等等),.getPrincipal()是獲取主體的主要身份信息,然后將返回的Subjcet對象強轉為SysUserEntity對象
? ? ? ? 判斷登錄用戶是否為空:調用了Spring框架中的Asset進行條件判斷。它確保了從安全上下文中獲取到的當前登錄用戶(loginUser)不為空。如果為空,則拋出一個帶有自定義錯誤消息
? ? ? ? 創建VO層對象接收基本信息: 調用Mapper的getUserInfoById(),在dao層寫的多表聯查sql語句
? ? ? ? 填充用戶對應的角色信息,因為在user表中并沒有直接綁定用戶的role,而是通過關聯表查詢,但這個也不是拿到對應的role_id,而是名稱,返回VO對象
public UserInfoVO getLoginUserInfo() {SysUserEntity loginUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal();Assert.notNull(loginUser,"獲取當前登錄人員信息失敗!");UserInfoVO userInfoVO = this.baseMapper.getUserInfoById(loginUser.getUserId());// 獲取用戶對應的角色信息userInfoVO.setRoleNames(sysUserRoleService.queryRoleNameList(loginUser.getUserId().toString()));return userInfoVO;}
2.2 根據User_id查找凍結時間并判斷? 問題代碼(密碼失效問題)(權限失效問題)
密碼失效
最開始的方法比較草率,導致出了問題,根據user_id先拿到user對象的所有信息,然后set時間,然后調用方法直接保存
SysUserEntity user = sysUserService.getById(getUserId());long nowTimestamp = new Date().getTime();long freezeTimestamp = nowTimestamp + 5 * 60 * 1000;Date freezeUntilDate = new Date(freezeTimestamp);user.setFreezeUntil(freezeUntilDate);System.out.println(user);sysUserService.update(user);
問題出在拿到的user對象將所有的數據取了出來,然而密碼是加密的形式,再進行保存便出現了原始密碼并不可用的問題,而且其他數據也并不需要,拿整個對象的數據就多此一舉了
權限失效
sysUserService.update(user);
其實問題也是出在了這里,當時將密碼重置的問題解決后,在第二天發現權限消失了,又回來看這個代碼,當時因為在User的實體類中定義了roleIdList的list集合,但是在數據庫主表中的并未設置這個字段,而且注解設置的也是false不存在,然后在后面修正的代碼測試時拿到的數據也是為空,我就在想會不會是其他的地方用到了這個字段,例如登錄的時候對其進行了操作?
然后為了驗證是不是這一個方法出了問題,有且僅有可能是這里出了問題,所以又用Warpper去寫了一個方法嘗試,問題便解決了,但還是想知道為什么會有什么問題
UpdateWrapper<SysUserEntity> updateWrapper = new UpdateWrapper<>();updateWrapper.eq("user_id", userInfoVO.getUserId()).set("freeze_until", newFreezeUntilDate)
sysUserService.update(user, updateWrapper);
MabatisPlus根據后面的代碼也不會對空的字段進行操作,然后就去翻了翻對權限分配的一些Service層的方法,結果發現在Service層有個同名的Update方法,在Controller層調用的也是這一層的方法
@TableField(exist=false)private List<Long> roleIdList;
Service層的實現
這是在User修改個人信息的時候調用到的方法
檢查密碼:對傳入的對象中的密碼字段進行校驗,如果為空,則將密碼設為null,這樣在更新時不會修改用戶的密碼。如果不為空,則使用Sha256Hash進行加密(結合了用戶鹽值user.getSalt()),并將加密后的密碼字符串(十六進制形式)重新賦給用戶對象的密碼字段。 Sha256Hash也是Shiro下的方法
更新用戶的基本信息:調用mabtisplus的updateByid方法,根據實體類非空屬性更新對應id的數據
因為在修改用戶的界面可以修改用戶的角色屬性,即更改權限,所以在調用saveOrUpdate方法時,后端傳回RoleIdList是null值,
public void update(SysUserEntity user) {if(StringUtils.isBlank(user.getPassword())){user.setPassword(null);}else{user.setPassword(new Sha256Hash(user.getPassword(), user.getSalt()).toHex());}this.updateById(user);//檢查角色是否越權
// checkRole(user);//保存用戶與角色關系sysUserRoleService.saveOrUpdate(user.getUserId(), user.getRoleIdList());}
在進到aveOrUpdate方法后,我們就可以發現因為null值,所以我在數據庫當中的權限也沒有了,到這里后便發現了為什么權限消失的問題
public void saveOrUpdate(Long userId, List<Long> roleIdList) {//先刪除用戶與角色關系this.removeByMap(new MapUtils().put("user_id", userId));if(roleIdList == null || roleIdList.size() == 0){return ;}
?修改后的代碼:
1.創建user對象:在bug找到后其實就用service層的對象就ok的。創建了一個UpdateWrapper對象,用于更新數據庫中的數據。 再通過Service層的getOne方法通過QueryWrapper對象查詢數據庫中user_id為用戶登錄id的記錄,只返回user_id,以及凍結時間的字段并只返回一條記錄
2.比較時間:先獲取當前時間戳nowTImestamp(用于做比較)? -->檢查凍結時間是否為空?
-->如果為空,計算新的凍結時間點freezeTimestamp,即當前時間加5分鐘(以毫秒為單位),再將時間戳轉換為Date對象 freezeUntilDate(為了返回給數據庫字段) ,構建UpdateWrapper,調用eq()方法根據用戶ID,set()方法更新其在數據庫中的'freeze_until'字段為新的凍結結束日期
-->時間不為空,先獲取用戶現有的凍結時間時間戳,再與當地時間進行比較
? ? ? ? -->大于當地時間戳則拋出異常,提示無法操作
? ? ? ? -->小于當地時間,獲取當地時間+5分鐘的時間戳對象,并將時間對象調用eq()方法根據用戶ID,set()方法更新其在數據庫中的'freeze_until'字段為新的凍結結束日期
//1.UpdateWrapper<SysUserEntity> updateWrapper = new UpdateWrapper<>();//2.SysUserEntity user = sysUserService.getOne(new QueryWrapper<SysUserEntity>().eq("user_id", userInfoVO.getUserId()).select("user_id", "freeze_until").last("limit 1"));//3.long nowTimestamp = new Date().getTime();if (user.getFreezeUntil() == null) {long freezeTimestamp = nowTimestamp + 5 * 60 * 1000;Date freezeUntilDate = new Date(freezeTimestamp);//user.setFreezeUntil(freezeUntilDate);updateWrapper.eq("user_id", userInfoVO.getUserId()).set("freeze_until", freezeUntilDate);} else {long freezeUntilTimestamp = user.getFreezeUntil().getTime();if (nowTimestamp < freezeUntilTimestamp) {throw new IllegalStateException("當前時間小于凍結結束時間,無法操作");} else {// 當前時間已經超過凍結期,則重置凍結時間為當前時間+5分鐘long newFreezeTimestamp = nowTimestamp + 5 * 60 * 1000;Date newFreezeUntilDate = new Date(newFreezeTimestamp);// user.setFreezeUntil(newFreezeUntilDate);updateWrapper.eq("user_id", userInfoVO.getUserId()).set("freeze_until", newFreezeUntilDate);}}
2.2 保存對象
1.調用service層的update函數,將用戶信息和更新條件封裝到updateWrapper中,然后更新用戶信息到數據庫中。 2.將userInfoVO.getUserId()賦值給holiday對象的userId屬性。3.調用iHolidayService.save(holiday)函數,將holiday對象保存到數據庫中。4.返回R.ok()表示操作成功。
sysUserService.update(user, updateWrapper);holiday.setUserId(userInfoVO.getUserId());iHolidayService.save(holiday);return R.ok();
完整代碼
controller層
public R save(@RequestBody Holiday holiday) {getUser();getUserId();//可以拿主表登錄人員的數據UserInfoVO userInfoVO = sysUserService.getLoginUserInfo();//1.UpdateWrapper<SysUserEntity> updateWrapper = new UpdateWrapper<>();//2.SysUserEntity user = sysUserService.getOne(new QueryWrapper<SysUserEntity>().eq("user_id", userInfoVO.getUserId()).select("user_id", "freeze_until").last("limit 1"));//3.long nowTimestamp = new Date().getTime();if (user.getFreezeUntil() == null) {long freezeTimestamp = nowTimestamp + 5 * 60 * 1000;Date freezeUntilDate = new Date(freezeTimestamp);//user.setFreezeUntil(freezeUntilDate);updateWrapper.eq("user_id", userInfoVO.getUserId()).set("freeze_until", freezeUntilDate);} else {// 獲取用戶現有的凍結結束時間戳long freezeUntilTimestamp = user.getFreezeUntil().getTime();if (nowTimestamp < freezeUntilTimestamp) {throw new IllegalStateException("當前時間小于凍結結束時間,無法操作");} else {// 當前時間已經超過凍結期,則重置凍結時間為當前時間+5分鐘long newFreezeTimestamp = nowTimestamp + 5 * 60 * 1000;Date newFreezeUntilDate = new Date(newFreezeTimestamp);// user.setFreezeUntil(newFreezeUntilDate);updateWrapper.eq("user_id", userInfoVO.getUserId()).set("freeze_until", newFreezeUntilDate);}}//holiday.setUserId(getUserId());//更新用戶凍結時間//sysUserService.update(user);sysUserService.update(user, updateWrapper);holiday.setUserId(userInfoVO.getUserId());iHolidayService.save(holiday);return R.ok();}