欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

spring-boot-plus V1.4.0發(fā)布 集成用戶角色權(quán)限部門管理(推薦)

 更新時間:2019年11月05日 09:11:32   作者:spring-boot-plus  
這篇文章主要介紹了spring-boot-plus V1.4.0發(fā)布 集成用戶角色權(quán)限部門管理,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值需要的朋友可以參考下

RBAC用戶角色權(quán)限

用戶角色權(quán)限部門管理核心接口介紹

Shiro權(quán)限配置

👉 Shiro權(quán)限配置

數(shù)據(jù)庫模型圖

👉 spring-boot-plus初始化SQL下載

獲取驗證碼

  • 可配置是否啟用驗證碼
  • 默認(rèn)未啟用
  • 如已啟用驗證碼校驗,登陸時,需傳入verifyToken和code

驗證碼演示

spring-boot-plus:
 # 是否啟用ansi控制臺輸出有顏色的字體
 enable-ansi: true
 # 是否啟用驗證碼
 enable-verify-code: true

enable-verify-code 設(shè)置為 true 啟用驗證碼驗證

兩種方式獲取驗證碼

驗證碼后臺保存在Redis中,過期時間默認(rèn)為5分鐘

方式一:

輸出圖片流到瀏覽器,驗證碼token輸出到響應(yīng)頭

http://localhost:8888/verificationCode/getImage

Response Headers
HTTP/1.1 200
verifyToken: 6515b4b798ce49e68b1e40f98ff8eb19

方式二:

獲取Base64編碼圖片和驗證碼token

http://localhost:8888/verificationCode/getBase64Image

{
 "code": 200,
 "msg": "操作成功",
 "success": true,
 "data": {
 "image": "data:image/png;base64,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",
 "verifyToken": "42ba8abde7bc47b2b1397b4d6676956a"
 },
 "time": "2019-11-01 22:40:37"
}

系統(tǒng)用戶登陸

POST請求,Content-Type: application/json

http://127.0.0.1:8888/login

請求參數(shù)

{
 "code": "驗證碼",
 "password": "123456",
 "username": "admin",
 "verifyToken": "驗證碼token"
}

注意

  • 如果沒有啟用驗證碼登陸,則只需傳入usernamepassword
  • 前端應(yīng)將密碼加密后進(jìn)行傳輸

登陸成功

  • 返回登陸用戶信息:部門/角色/權(quán)限
  • 返回用戶token
{
 "code": 200,
 "msg": "登陸成功",
 "success": true,
 "data": {
 "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJpc3MiOiJzcHJpbmctYm9vdC1wbHVzIiwiZXhwIjoxNTcyNjIzMDE5LCJpYXQiOjE1NzI2MTk0MTksImp0aSI6IjdlZmVlM2UwMjc2MTRiYTc5M2I2YmYwZmE4NTgzYmUwIiwidXNlcm5hbWUiOiJhZG1pbiJ9.O3w7CNRqw_Miwp8MDzPND6w490c9Q7yFlKpFJK9ubSU",
 "loginSysUserVo": {
  "id": "1",
  "username": "admin",
  "nickname": "管理員",
  "gender": 1,
  "state": 1,
  "departmentId": "1",
  "departmentName": "管理部",
  "roleId": "1",
  "roleName": "管理員",
  "roleCode": "admin",
  "permissionCodes": [
  "sys:permission:codes",
  "system:management",
  "sys:department:update",
  "sys:department:page",
  "sys:role:management",
  "sys:permission:add",
  "sys:user:add",
  "sys:role:page",
  "sys:permission:page",
  "sys:department:delete",
  "sys:permission:management",
  "sys:user:delete",
  "sys:department:management",
  "sys:user:page",
  "sys:user:update",
  "sys:user:update:password",
  "sys:user:update:head",
  "sys:role:add",
  "sys:permission:menu:tree",
  "sys:department:info",
  "sys:permission:all:menu:list",
  "sys:permission:info",
  "sys:role:info",
  "sys:permission:all:menu:tree",
  "sys:permission:update",
  "sys:permission:menu:list",
  "sys:role:update",
  "sys:user:info",
  "sys:user:management",
  "sys:role:delete",
  "sys:permission:delete"
  ]
 }
 },
 "time": "2019-11-01 22:43:39"
}
  • token默認(rèn)失效時間為1個小時
  • 設(shè)置JWT Token失效時間
############################ JWT start #############################
 jwt:
 # 默認(rèn)過期時間1小時,單位:秒
 expire-second: 3600
  • 后臺使用Redis緩存登陸用戶信息
  • redis key

login:user:admin

其它需要授權(quán)訪問的接口,請求頭需攜帶token

部門樹形列表

部門可以設(shè)置為N級,后臺使用遞歸將部門列表轉(zhuǎn)換為樹形列表

SysDepartmentServiceImpl

@Override
 public List<SysDepartmentTreeVo> getAllDepartmentTree() {
  List<SysDepartment> sysDepartmentList = getAllDepartmentList();
  if (CollectionUtils.isEmpty(sysDepartmentList)) {
   throw new IllegalArgumentException("SysDepartment列表不能為空");
  }
  List<SysDepartmentTreeVo> list = SysDepartmentConvert.INSTANCE.listToTreeVoList(sysDepartmentList);
  List<SysDepartmentTreeVo> treeVos = new ArrayList<>();
  for (SysDepartmentTreeVo treeVo : list) {
   if (treeVo.getParentId() == null) {
    treeVos.add(findChildren(treeVo, list));
   }
  }
  return treeVos;
 }

 /**
  * 遞歸獲取樹形結(jié)果列表
  *
  * @param tree
  * @param list
  * @return
  */
 public SysDepartmentTreeVo findChildren(SysDepartmentTreeVo tree, List<SysDepartmentTreeVo> list) {
  for (SysDepartmentTreeVo vo : list) {
   if (tree.getId().equals(vo.getParentId())) {
    if (tree.getChildren() == null) {
     tree.setChildren(new ArrayList<>());
    }
    tree.getChildren().add(findChildren(vo, list));
   }
  }
  return tree;
 }

前端JSON結(jié)構(gòu)

http://127.0.0.1:8888/sysDepartment/getAllDepartmentTree

角色管理

設(shè)置角色權(quán)限

  • 核心代碼,刪除角色權(quán)限,新增角色權(quán)限
  • 求集合的差集
  • SysRolePermissionServiceImpl
@Transactional(rollbackFor = Exception.class)
 @Override
 public boolean updateSysRole(UpdateSysRoleParam updateSysRoleParam) throws Exception {
  Long roleId = updateSysRoleParam.getId();
  List<Long> permissionIds = updateSysRoleParam.getPermissionIds();
  // 校驗角色是否存在
  SysRole sysRole = getById(roleId);
  if (sysRole == null) {
   throw new BusinessException("該角色不存在");
  }
  // 校驗權(quán)限列表是否存在
  if (!sysPermissionService.isExistsByPermissionIds(permissionIds)) {
   throw new BusinessException("權(quán)限列表id匹配失敗");
  }

  // 修改角色
  sysRole.setName(updateSysRoleParam.getName())
    .setType(updateSysRoleParam.getType())
    .setRemark(updateSysRoleParam.getRemark())
    .setState(updateSysRoleParam.getState())
    .setUpdateTime(new Date());
  boolean updateResult = updateById(sysRole);
  if (!updateResult) {
   throw new DaoException("修改系統(tǒng)角色失敗");
  }

  // 獲取之前的權(quán)限id集合
  List<Long> beforeList = sysRolePermissionService.getPermissionIdsByRoleId(roleId);
  // 差集計算
  // before:1,2,3,4,5,6
  // after: 1,2,3,4,7,8
  // 刪除5,6 新增7,8
  // 此處真實刪除,去掉deleted字段的@TableLogic注解
  Set<Long> beforeSet = new HashSet<>(beforeList);
  Set<Long> afterSet = new HashSet<>(permissionIds);
  SetUtils.SetView deleteSet = SetUtils.difference(beforeSet, afterSet);
  SetUtils.SetView addSet = SetUtils.difference(afterSet, beforeSet);
  log.debug("deleteSet = " + deleteSet);
  log.debug("addSet = " + addSet);

  // 刪除權(quán)限關(guān)聯(lián)
  UpdateWrapper updateWrapper = new UpdateWrapper();
  updateWrapper.eq("role_id",roleId);
  updateWrapper.in("permission_id",deleteSet);
  boolean deleteResult = sysRolePermissionService.remove(updateWrapper);
  if (!deleteResult) {
   throw new DaoException("刪除角色權(quán)限關(guān)系失敗");
  }
  // 新增權(quán)限關(guān)聯(lián)
  boolean addResult = sysRolePermissionService.saveSysRolePermissionBatch(roleId, addSet);
  if (!addResult) {
   throw new DaoException("新增角色權(quán)限關(guān)系失敗");
  }
  return true;
 }

權(quán)限管理

權(quán)限樹形列表

  • 用戶設(shè)置角色權(quán)限時,選擇
  • 權(quán)限菜單權(quán)限分為菜單和功能權(quán)限
  • 后臺獲取三層權(quán)限樹
@Override
 public List<SysPermissionTreeVo> getAllMenuTree() throws Exception {
  List<SysPermission> list = getAllMenuList();
  // 轉(zhuǎn)換成樹形菜單
  List<SysPermissionTreeVo> treeVos = convertSysPermissionTreeVoList(list);
  return treeVos;
 }

 @Override
 public List<SysPermissionTreeVo> convertSysPermissionTreeVoList(List<SysPermission> list) {
  if (CollectionUtils.isEmpty(list)) {
   throw new IllegalArgumentException("SysPermission列表不能為空");
  }
  // 按level分組獲取map
  Map<Integer, List<SysPermission>> map = list.stream().collect(Collectors.groupingBy(SysPermission::getLevel));
  List<SysPermissionTreeVo> treeVos = new ArrayList<>();
  // 循環(huán)獲取三級菜單樹形集合
  for (SysPermission one : map.get(LevelEnum.ONE.getKey())) {
   SysPermissionTreeVo oneVo = SysPermissionConvert.INSTANCE.permissionToTreeVo(one);
   Long oneParentId = oneVo.getParentId();
   if (oneParentId == null || oneParentId == 0) {
    treeVos.add(oneVo);
   }
   List<SysPermission> twoList = map.get(LevelEnum.TWO.getKey());
   if (CollectionUtils.isNotEmpty(twoList)) {
    for (SysPermission two : twoList) {
     SysPermissionTreeVo twoVo = SysPermissionConvert.INSTANCE.permissionToTreeVo(two);
     if (two.getParentId().equals(one.getId())) {
      oneVo.getChildren().add(twoVo);
     }
     List<SysPermission> threeList = map.get(LevelEnum.THREE.getKey());
     if (CollectionUtils.isNotEmpty(threeList)) {
      for (SysPermission three : threeList) {
       if (three.getParentId().equals(two.getId())) {
        SysPermissionTreeVo threeVo = SysPermissionConvert.INSTANCE.permissionToTreeVo(three);
        twoVo.getChildren().add(threeVo);
       }
      }
     }
    }
   }

  }
  return treeVos;
 }

前端JSON格式

http://127.0.0.1:8888/sysPermission/getAllMenuTree

權(quán)限編碼列表

返回當(dāng)前用戶所有的權(quán)限編碼,方便前端展示導(dǎo)航菜單和功能按鈕

http://127.0.0.1:8888/sysPermission/getPermissionCodesByUserId/1

{
 "code": 200,
 "msg": "操作成功",
 "success": true,
 "data": [
 "system:management",
 "system:management",
 "sys:user:management",
 "sys:user:management",
 "sys:role:management",
 "sys:permission:management",
 "sys:department:management",
 "sys:user:add",
 "sys:user:add",
 "sys:user:update",
 "sys:user:update",
 "sys:user:delete",
 "sys:user:delete",
 "sys:user:info",
 "sys:user:info",
 "sys:user:page",
 "sys:user:page",
 "sys:user:update:password",
 "sys:user:update:head",
 "sys:role:add",
 "sys:role:update",
 "sys:role:delete",
 "sys:role:info",
 "sys:role:page",
 "sys:permission:add",
 "sys:permission:update",
 "sys:permission:delete",
 "sys:permission:info",
 "sys:permission:page",
 "sys:permission:all:menu:list",
 "sys:permission:all:menu:tree",
 "sys:permission:menu:list",
 "sys:permission:menu:tree",
 "sys:permission:codes",
 "sys:department:update",
 "sys:department:delete",
 "sys:department:info",
 "sys:department:page"
 ],
 "time": "2019-11-02 00:32:17"
}

注意

使用Shiro注解@RequiresPermissions進(jìn)行controller方法權(quán)限過濾

@RequiresPermissions("sys:department:add")

生成代碼時,可配置生成RequiresPermissions注解

 // 是否生成Shiro RequiresPermissions注解
  codeGenerator.setRequiresPermissions(true);

生成或新增的controller方法,需要進(jìn)行權(quán)限管理,需要到sys_permission表新增權(quán)限編碼記錄,并給相應(yīng)角色賦予權(quán)限

總結(jié)

以上所述是小編給大家介紹的spring-boot-plus V1.4.0發(fā)布 集成用戶角色權(quán)限部門管理,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!

相關(guān)文章

  • Spring Boot中數(shù)據(jù)庫操作Druid和HikariDataSource的詳細(xì)過程

    Spring Boot中數(shù)據(jù)庫操作Druid和HikariDataSource的詳細(xì)過程

    這篇文章主要介紹了Spring Boot中數(shù)據(jù)庫操作Druid和HikariDataSource的詳細(xì)過程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • Java Hashtable機制深入了解

    Java Hashtable機制深入了解

    HashTable是jdk 1.0中引入的產(chǎn)物,基本上現(xiàn)在很少使用了,但是會在面試中經(jīng)常被問到。本文就來帶大家一起深入了解一下Hashtable,需要的可以參考一下
    2022-09-09
  • Mybatis返回單個實體或者返回List的實現(xiàn)

    Mybatis返回單個實體或者返回List的實現(xiàn)

    這篇文章主要介紹了Mybatis返回單個實體或者返回List的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 詳解Java高并發(fā)編程之AtomicReference

    詳解Java高并發(fā)編程之AtomicReference

    此篇文章主要介紹了AtomicReference的出現(xiàn)背景,AtomicReference的使用場景,以及介紹了AtomicReference的源碼,重點方法的源碼分析
    2021-06-06
  • Springboot使用RestTemplate調(diào)用第三方接口的操作代碼

    Springboot使用RestTemplate調(diào)用第三方接口的操作代碼

    這篇文章主要介紹了Springboot使用RestTemplate調(diào)用第三方接口,我只演示了最常使用的請求方式get、post的簡單使用方法,當(dāng)然RestTemplate的功能還有很多,感興趣的朋友可以參考RestTemplate源碼
    2022-12-12
  • Java實現(xiàn)一個簡單的長輪詢的示例代碼

    Java實現(xiàn)一個簡單的長輪詢的示例代碼

    長輪詢是與服務(wù)器保持即時通信的最簡單的方式,它不使用任何特定的協(xié)議,例如 WebSocket ,所以也不依賴于瀏覽器版本等外部條件的兼容性。本文將用Java實現(xiàn)一個簡單的長輪詢,需要的可以參考一下
    2022-08-08
  • 詳解如何讓Spring MVC顯示自定義的404 Not Found頁面

    詳解如何讓Spring MVC顯示自定義的404 Not Found頁面

    這篇文章主要介紹了詳解如何讓Spring MVC顯示自定義的404 Not Found頁面,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • Struts2學(xué)習(xí)筆記(1)-入門教程

    Struts2學(xué)習(xí)筆記(1)-入門教程

    本文是一個Struts2的簡單入門教程,比較簡單,希望能給大家做一個參考。
    2016-06-06
  • 一次 Java 服務(wù)性能優(yōu)化實例詳解

    一次 Java 服務(wù)性能優(yōu)化實例詳解

    這篇文章主要介紹了一次 Java 服務(wù)性能優(yōu)化實例詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-07-07
  • redis做服務(wù)間通信工具的項目示例

    redis做服務(wù)間通信工具的項目示例

    Redis是一種高效的服務(wù)間通信工具,它以鍵值對的形式存儲數(shù)據(jù),并支持多種數(shù)據(jù)類型和豐富的操作,本文主要介紹了redis做服務(wù)間通信工具的項目示例,感興趣的可以了解一下
    2023-08-08

最新評論