searchTokenSessionId(String keyword, int start, int s
* 此方法不会直接将此账号id踢下线,如需封禁后立即掉线,请追加调用 StpUtil.logout(id)
*
* @param loginId 指定账号id
- * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
+ * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
*/
public static void disable(Object loginId, long time) {
stpLogic.disable(loginId, time);
@@ -939,7 +937,7 @@ public static void untieDisable(Object loginId) {
*
* @param loginId 指定账号id
* @param service 指定服务
- * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
+ * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
*/
public static void disable(Object loginId, String service, long time) {
stpLogic.disable(loginId, service, time);
@@ -959,7 +957,7 @@ public static boolean isDisable(Object loginId, String service) {
/**
* 校验:指定账号 指定服务 是否已被封禁,如果被封禁则抛出异常
*
- * @param loginId 账号id
+ * @param loginId 账号id
* @param services 指定服务,可以指定多个
*/
public static void checkDisable(Object loginId, String... services) {
@@ -980,7 +978,7 @@ public static long getDisableTime(Object loginId, String service) {
/**
* 解封:指定账号、指定服务
*
- * @param loginId 账号id
+ * @param loginId 账号id
* @param services 指定服务,可以指定多个
*/
public static void untieDisable(Object loginId, String... services) {
@@ -994,8 +992,8 @@ public static void untieDisable(Object loginId, String... services) {
* 封禁:指定账号,并指定封禁等级
*
* @param loginId 指定账号id
- * @param level 指定封禁等级
- * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
+ * @param level 指定封禁等级
+ * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
*/
public static void disableLevel(Object loginId, int level, long time) {
stpLogic.disableLevel(loginId, level, time);
@@ -1006,8 +1004,8 @@ public static void disableLevel(Object loginId, int level, long time) {
*
* @param loginId 指定账号id
* @param service 指定封禁服务
- * @param level 指定封禁等级
- * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
+ * @param level 指定封禁等级
+ * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
*/
public static void disableLevel(Object loginId, String service, int level, long time) {
stpLogic.disableLevel(loginId, service, level, time);
@@ -1017,7 +1015,7 @@ public static void disableLevel(Object loginId, String service, int level, long
* 判断:指定账号是否已被封禁到指定等级
*
* @param loginId 指定账号id
- * @param level 指定封禁等级
+ * @param level 指定封禁等级
* @return /
*/
public static boolean isDisableLevel(Object loginId, int level) {
@@ -1029,7 +1027,7 @@ public static boolean isDisableLevel(Object loginId, int level) {
*
* @param loginId 指定账号id
* @param service 指定封禁服务
- * @param level 指定封禁等级
+ * @param level 指定封禁等级
* @return /
*/
public static boolean isDisableLevel(Object loginId, String service, int level) {
@@ -1040,7 +1038,7 @@ public static boolean isDisableLevel(Object loginId, String service, int level)
* 校验:指定账号是否已被封禁到指定等级(如果已经达到,则抛出异常)
*
* @param loginId 指定账号id
- * @param level 封禁等级 (只有 封禁等级 ≥ 此值 才会抛出异常)
+ * @param level 封禁等级 (只有 封禁等级 ≥ 此值 才会抛出异常)
*/
public static void checkDisableLevel(Object loginId, int level) {
stpLogic.checkDisableLevel(loginId, level);
@@ -1051,7 +1049,7 @@ public static void checkDisableLevel(Object loginId, int level) {
*
* @param loginId 指定账号id
* @param service 指定封禁服务
- * @param level 封禁等级 (只有 封禁等级 ≥ 此值 才会抛出异常)
+ * @param level 封禁等级 (只有 封禁等级 ≥ 此值 才会抛出异常)
*/
public static void checkDisableLevel(Object loginId, String service, int level) {
stpLogic.checkDisableLevel(loginId, service, level);
@@ -1109,7 +1107,7 @@ public static boolean isSwitch() {
/**
* 在一个 lambda 代码段里,临时切换身份为指定账号id,lambda 结束后自动恢复
*
- * @param loginId 指定账号id
+ * @param loginId 指定账号id
* @param function 要执行的方法
*/
public static void switchTo(Object loginId, SaFunction function) {
@@ -1131,7 +1129,7 @@ public static void openSafe(long safeTime) {
/**
* 在当前会话 开启二级认证
*
- * @param service 业务标识
+ * @param service 业务标识
* @param safeTime 维持时间 (单位: 秒)
*/
public static void openSafe(String service, long safeTime) {
@@ -1161,7 +1159,7 @@ public static boolean isSafe(String service) {
* 判断:指定 token 是否处于二级认证时间内
*
* @param tokenValue Token 值
- * @param service 业务标识
+ * @param service 业务标识
* @return true=二级认证已通过, false=尚未进行二级认证或认证已超时
*/
public static boolean isSafe(String tokenValue, String service) {
diff --git a/src/main/java/cc/uncarbon/module/adminapi/web/auth/AdminAuthController.java b/src/main/java/cc/uncarbon/module/adminapi/web/auth/AdminAuthController.java
index 92846fe..3882a33 100644
--- a/src/main/java/cc/uncarbon/module/adminapi/web/auth/AdminAuthController.java
+++ b/src/main/java/cc/uncarbon/module/adminapi/web/auth/AdminAuthController.java
@@ -11,26 +11,23 @@
import cc.uncarbon.helper.CaptchaHelper;
import cc.uncarbon.helper.RolePermissionCacheHelper;
import cc.uncarbon.module.adminapi.constant.AdminApiConstant;
+import cc.uncarbon.module.adminapi.model.interior.AdminCaptchaContainer;
+import cc.uncarbon.module.adminapi.model.response.AdminCaptchaVO;
+import cc.uncarbon.module.adminapi.util.AdminStpUtil;
import cc.uncarbon.module.sys.annotation.SysLog;
import cc.uncarbon.module.sys.constant.SysConstant;
-import cc.uncarbon.module.sys.enums.SysErrorEnum;
import cc.uncarbon.module.sys.model.request.SysUserLoginDTO;
import cc.uncarbon.module.sys.model.response.SysUserLoginBO;
import cc.uncarbon.module.sys.model.response.SysUserLoginVO;
import cc.uncarbon.module.sys.service.SysUserService;
-import cc.uncarbon.module.adminapi.util.AdminStpUtil;
import cn.dev33.satoken.annotation.SaCheckLogin;
-import cn.hutool.captcha.AbstractCaptcha;
import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
-import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
-import java.io.IOException;
@Api(value = "后台管理-鉴权接口", tags = {"后台管理-鉴权接口"})
@@ -55,6 +52,9 @@ public class AdminAuthController {
@ApiOperation(value = "登录")
@PostMapping(value = "/auth/login")
public ApiResult login(@RequestBody @Valid SysUserLoginDTO dto) {
+ // 登录验证码核验;前端项目搜索关键词「 Helio: 登录验证码」
+ // AdminApiErrorEnum.CAPTCHA_VALIDATE_FAILED.assertTrue(captchaHelper.validate(dto.getCaptchaId(), dto.getCaptchaAnswer()))
+
// RPC调用, 失败抛异常, 成功返回用户信息
SysUserLoginBO userInfo = sysUserService.adminLogin(dto);
@@ -99,24 +99,12 @@ public ApiResult logout() {
return ApiResult.success();
}
- @ApiOperation(value = "验证码图片")
- @ApiImplicitParam(name = "uuid", value = "验证码图片UUID", required = true)
+ @ApiOperation(value = "获取验证码")
@GetMapping(value = "/auth/captcha")
- public void captcha(HttpServletResponse response, String uuid) throws IOException {
- /*
- 由前端定义 UUID 其实并不算太好的办法,但是够简单
- 更复杂而安全的做法是:由后端生成一个 UUID,通过响应头返回给前端(这对前端有一定的技能技术要求)
- */
- // uuid 为空则抛出异常
- SysErrorEnum.UUID_CANNOT_BE_BLANK.assertNotBlank(uuid);
-
+ public ApiResult captcha() {
// 核验方法:captchaHelper.validate
- AbstractCaptcha captcha = captchaHelper.generate(uuid);
-
- // 写入响应流
- response.setHeader("Cache-Control", "no-store, no-cache");
- response.setContentType("image/png");
- captcha.write(response.getOutputStream());
+ AdminCaptchaContainer captchaContainer = captchaHelper.generate();
+ return ApiResult.data(new AdminCaptchaVO(captchaContainer));
}
}
diff --git a/src/main/java/cc/uncarbon/module/adminapi/web/common/AdminSelectOptionsController.java b/src/main/java/cc/uncarbon/module/adminapi/web/common/AdminSelectOptionsController.java
index 017de60..2de94af 100644
--- a/src/main/java/cc/uncarbon/module/adminapi/web/common/AdminSelectOptionsController.java
+++ b/src/main/java/cc/uncarbon/module/adminapi/web/common/AdminSelectOptionsController.java
@@ -1,16 +1,15 @@
package cc.uncarbon.module.adminapi.web.common;
-import cc.uncarbon.framework.core.enums.GenderEnum;
-import cc.uncarbon.framework.core.enums.YesOrNoEnum;
import cc.uncarbon.framework.web.model.response.ApiResult;
import cc.uncarbon.module.adminapi.constant.AdminApiConstant;
import cc.uncarbon.module.adminapi.model.response.SelectOptionItemVO;
+import cc.uncarbon.module.adminapi.util.AdminStpUtil;
import cc.uncarbon.module.sys.model.response.SysDeptBO;
+import cc.uncarbon.module.sys.model.response.SysRoleBO;
import cc.uncarbon.module.sys.service.SysDeptService;
-import cc.uncarbon.module.adminapi.util.AdminStpUtil;
+import cc.uncarbon.module.sys.service.SysRoleService;
import cn.dev33.satoken.annotation.SaCheckLogin;
-import cn.dev33.satoken.annotation.SaIgnore;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
@@ -30,47 +29,29 @@
@Slf4j
public class AdminSelectOptionsController {
+ private final SysRoleService sysRoleService;
private final SysDeptService sysDeptService;
/*
这里统一存放所有用于后台管理的下拉框数据源接口
- 避免多人协作时,不知道原来是否已经有了,或者写在某个边边角角里
- 造成重复开发
- */
-
- // 👇该注解标记的接口不需要登录
- @SaIgnore
- @ApiOperation(value = "(演示接口;可删除)性别下拉框")
- @GetMapping(value = "/select-options/genders")
- public ApiResult> genders(YesOrNoEnum demo) {
- if (demo == YesOrNoEnum.YES) {
- // demo=YES时,不输出「未知」、多输出demoString1
- List ret = SelectOptionItemVO.listOf(GenderEnum.class, item -> item != GenderEnum.UNKNOWN);
- for (SelectOptionItemVO item : ret) {
- item.setDemoString1("文本字段1-" + item.getValue());
- }
- return ApiResult.data(ret);
- }
- // 默认返回
- return ApiResult.data(SelectOptionItemVO.listOf(GenderEnum.class));
+ 避免多人协作时,不知道原来是否已经有了,或者写在某个边边角角里,造成重复开发
+ */
+
+ @ApiOperation(value = "后台角色下拉框")
+ @GetMapping(value = "/select-options/roles")
+ public ApiResult> roles() {
+ return ApiResult.data(
+ SelectOptionItemVO.listOf(sysRoleService.adminSelectOptions(), SysRoleBO::getId, SysRoleBO::getTitle)
+ );
}
- // 👇该注解标记的接口不需要登录
- @SaIgnore
- @ApiOperation(value = "(演示接口;可删除)部门下拉框")
+ @ApiOperation(value = "部门下拉框(前端负责转为树状数据)")
@GetMapping(value = "/select-options/depts")
- public ApiResult> depts(YesOrNoEnum demo) {
- if (demo == YesOrNoEnum.YES) {
- // demo=YES时,多输出上级ID、多输出demoString1
- List ret = SelectOptionItemVO.listOf(sysDeptService.adminList(), SysDeptBO::getId, SysDeptBO::getTitle, SysDeptBO::getParentId);
- for (SelectOptionItemVO item : ret) {
- item.setDemoString1("文本字段1-" + item.getId());
- }
- return ApiResult.data(ret);
- }
- // 默认返回
- return ApiResult.data(SelectOptionItemVO.listOf(sysDeptService.adminList(), SysDeptBO::getId, SysDeptBO::getTitle));
+ public ApiResult> depts() {
+ return ApiResult.data(
+ SelectOptionItemVO.listOf(sysDeptService.adminSelectOptions(true), SysDeptBO::getId, SysDeptBO::getTitle, SysDeptBO::getParentId)
+ );
}
}
diff --git a/src/main/java/cc/uncarbon/module/adminapi/web/oss/AdminOssUploadDownloadController.java b/src/main/java/cc/uncarbon/module/adminapi/web/oss/AdminOssUploadDownloadController.java
index fba8ea3..6703549 100644
--- a/src/main/java/cc/uncarbon/module/adminapi/web/oss/AdminOssUploadDownloadController.java
+++ b/src/main/java/cc/uncarbon/module/adminapi/web/oss/AdminOssUploadDownloadController.java
@@ -68,8 +68,7 @@ public ApiResult upload(
attr
.setOriginalFilename(file.getOriginalFilename())
.setContentType(file.getContentType())
- .setMd5(md5)
- ;
+ .setMd5(md5);
bo = ossUploadDownloadFacade.upload(file.getBytes(), attr);
}
diff --git a/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysDataDictController.java b/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysDataDictController.java
index 9fb641a..150ea9e 100644
--- a/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysDataDictController.java
+++ b/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysDataDictController.java
@@ -36,7 +36,7 @@
@Slf4j
public class AdminSysDataDictController {
- private static final String PERMISSION_PREFIX = "SysDataDict:" ;
+ private static final String PERMISSION_PREFIX = "SysDataDict:";
private final SysDataDictService sysDataDictService;
diff --git a/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysRoleController.java b/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysRoleController.java
index 37b3069..c2c2e26 100644
--- a/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysRoleController.java
+++ b/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysRoleController.java
@@ -65,6 +65,7 @@ public ApiResult getById(@PathVariable Long id) {
@ApiOperation(value = "新增")
@PostMapping(value = "/sys/roles")
public ApiResult insert(@RequestBody @Valid AdminInsertOrUpdateSysRoleDTO dto) {
+ dto.setTenantId(null);
sysRoleService.adminInsert(dto);
return ApiResult.success();
@@ -75,7 +76,9 @@ public ApiResult insert(@RequestBody @Valid AdminInsertOrUpdateSysRoleDTO
@ApiOperation(value = "编辑")
@PutMapping(value = "/sys/roles/{id}")
public ApiResult update(@PathVariable Long id, @RequestBody @Valid AdminInsertOrUpdateSysRoleDTO dto) {
- dto.setId(id);
+ dto
+ .setTenantId(null)
+ .setId(id);
sysRoleService.adminUpdate(dto);
return ApiResult.success();
diff --git a/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysTenantController.java b/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysTenantController.java
index 290384b..0a6a3ea 100644
--- a/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysTenantController.java
+++ b/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysTenantController.java
@@ -6,6 +6,8 @@
import cc.uncarbon.framework.web.model.request.IdsDTO;
import cc.uncarbon.framework.web.model.response.ApiResult;
import cc.uncarbon.module.adminapi.constant.AdminApiConstant;
+import cc.uncarbon.module.adminapi.event.KickOutSysUsersEvent;
+import cc.uncarbon.module.adminapi.util.AdminStpUtil;
import cc.uncarbon.module.sys.annotation.SysLog;
import cc.uncarbon.module.sys.constant.SysConstant;
import cc.uncarbon.module.sys.facade.SysTenantFacade;
@@ -13,10 +15,11 @@
import cc.uncarbon.module.sys.model.request.AdminListSysTenantDTO;
import cc.uncarbon.module.sys.model.request.AdminUpdateSysTenantDTO;
import cc.uncarbon.module.sys.model.response.SysTenantBO;
+import cc.uncarbon.module.sys.model.response.SysTenantKickOutUsersBO;
import cc.uncarbon.module.sys.service.SysTenantService;
-import cc.uncarbon.module.adminapi.util.AdminStpUtil;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.extra.spring.SpringUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
@@ -75,7 +78,12 @@ public ApiResult insert(@RequestBody @Valid AdminInsertSysTenantDTO dto) {
@PutMapping(value = "/sys/tenants/{id}")
public ApiResult update(@PathVariable Long id, @RequestBody @Valid AdminUpdateSysTenantDTO dto) {
dto.setId(id);
- sysTenantService.adminUpdate(dto);
+ SysTenantKickOutUsersBO needKickOutUsers = sysTenantFacade.adminUpdate(dto);
+
+ // 强制登出所有租户用户
+ SpringUtil.publishEvent(new KickOutSysUsersEvent(
+ new KickOutSysUsersEvent.EventData(needKickOutUsers.getSysUserIds())
+ ));
return ApiResult.success();
}
@@ -85,7 +93,12 @@ public ApiResult update(@PathVariable Long id, @RequestBody @Valid AdminUp
@ApiOperation(value = "删除")
@DeleteMapping(value = "/sys/tenants")
public ApiResult delete(@RequestBody @Valid IdsDTO dto) {
- sysTenantService.adminDelete(dto.getIds());
+ SysTenantKickOutUsersBO needKickOutUsers = sysTenantFacade.adminDelete(dto.getIds());
+
+ // 强制登出所有租户用户
+ SpringUtil.publishEvent(new KickOutSysUsersEvent(
+ new KickOutSysUsersEvent.EventData(needKickOutUsers.getSysUserIds())
+ ));
return ApiResult.success();
}
diff --git a/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysUserController.java b/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysUserController.java
index bf1502f..a2466ab 100644
--- a/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysUserController.java
+++ b/src/main/java/cc/uncarbon/module/adminapi/web/sys/AdminSysUserController.java
@@ -9,6 +9,7 @@
import cc.uncarbon.module.adminapi.constant.AdminApiConstant;
import cc.uncarbon.module.sys.annotation.SysLog;
import cc.uncarbon.module.sys.constant.SysConstant;
+import cc.uncarbon.module.sys.enums.SysUserStatusEnum;
import cc.uncarbon.module.sys.model.request.*;
import cc.uncarbon.module.sys.model.response.SysUserBO;
import cc.uncarbon.module.sys.model.response.VbenAdminUserInfoVO;
@@ -75,6 +76,11 @@ public ApiResult update(@PathVariable Long id, @RequestBody @Valid AdminIn
dto.setId(id);
sysUserService.adminUpdate(dto);
+ // 新状态是禁用,连带踢出登录
+ if (dto.getStatus() == SysUserStatusEnum.BANNED) {
+ kickOut(dto.getId());
+ }
+
return ApiResult.success();
}
@@ -85,6 +91,9 @@ public ApiResult update(@PathVariable Long id, @RequestBody @Valid AdminIn
public ApiResult delete(@RequestBody @Valid IdsDTO dto) {
sysUserService.adminDelete(dto.getIds());
+ // 连带踢出登录
+ dto.getIds().forEach(this::kickOut);
+
return ApiResult.success();
}
diff --git a/src/main/java/cc/uncarbon/module/appapi/constant/AppApiConstant.java b/src/main/java/cc/uncarbon/module/appapi/constant/AppApiConstant.java
index 3794040..1288d92 100644
--- a/src/main/java/cc/uncarbon/module/appapi/constant/AppApiConstant.java
+++ b/src/main/java/cc/uncarbon/module/appapi/constant/AppApiConstant.java
@@ -4,7 +4,7 @@
/**
* C端接口常量
*/
-public class AppApiConstant {
+public final class AppApiConstant {
private AppApiConstant() {
}
diff --git a/src/main/java/cc/uncarbon/module/oss/constant/OssConstant.java b/src/main/java/cc/uncarbon/module/oss/constant/OssConstant.java
index 5dd0942..4ae8f0d 100644
--- a/src/main/java/cc/uncarbon/module/oss/constant/OssConstant.java
+++ b/src/main/java/cc/uncarbon/module/oss/constant/OssConstant.java
@@ -1,6 +1,6 @@
package cc.uncarbon.module.oss.constant;
-public class OssConstant {
+public final class OssConstant {
private OssConstant() {
}
diff --git a/src/main/java/cc/uncarbon/module/oss/enums/OssErrorEnum.java b/src/main/java/cc/uncarbon/module/oss/enums/OssErrorEnum.java
index f2a166c..f6d3685 100644
--- a/src/main/java/cc/uncarbon/module/oss/enums/OssErrorEnum.java
+++ b/src/main/java/cc/uncarbon/module/oss/enums/OssErrorEnum.java
@@ -7,7 +7,7 @@
/**
- * OSS异常枚举类
+ * oss模块错误枚举类
*/
@AllArgsConstructor
@Getter
diff --git a/src/main/java/cc/uncarbon/module/oss/mapper/OssFileInfoMapper.java b/src/main/java/cc/uncarbon/module/oss/mapper/OssFileInfoMapper.java
index 2ae68fa..aff1171 100644
--- a/src/main/java/cc/uncarbon/module/oss/mapper/OssFileInfoMapper.java
+++ b/src/main/java/cc/uncarbon/module/oss/mapper/OssFileInfoMapper.java
@@ -1,7 +1,7 @@
package cc.uncarbon.module.oss.mapper;
-import cc.uncarbon.framework.crud.mapper.HelioBaseMapper;
import cc.uncarbon.module.oss.entity.OssFileInfoEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@@ -9,6 +9,6 @@
* 上传文件信息
*/
@Mapper
-public interface OssFileInfoMapper extends HelioBaseMapper {
+public interface OssFileInfoMapper extends BaseMapper {
}
diff --git a/src/main/java/cc/uncarbon/module/oss/model/request/AdminInsertOrUpdateOssFileInfoDTO.java b/src/main/java/cc/uncarbon/module/oss/model/request/AdminInsertOrUpdateOssFileInfoDTO.java
deleted file mode 100644
index cec39dc..0000000
--- a/src/main/java/cc/uncarbon/module/oss/model/request/AdminInsertOrUpdateOssFileInfoDTO.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package cc.uncarbon.module.oss.model.request;
-
-import cc.uncarbon.framework.core.enums.EnabledStatusEnum;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-import lombok.experimental.SuperBuilder;
-
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-import java.io.Serializable;
-
-
-/**
- * 后台管理-新增/编辑上传文件信息 DTO
- */
-@Accessors(chain = true)
-@SuperBuilder
-@AllArgsConstructor
-@NoArgsConstructor
-@Data
-public class AdminInsertOrUpdateOssFileInfoDTO implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- @ApiModelProperty(value = "主键ID", hidden = true, notes = "仅更新时使用")
- private Long id;
-
- @ApiModelProperty(value = "前置路径", required = true)
- @NotBlank(message = "前置路径不能为空")
- private String path;
-
- @ApiModelProperty(value = "文件名", required = true)
- @NotBlank(message = "文件名不能为空")
- private String filename;
-
- @ApiModelProperty(value = "扩展名", required = true)
- @NotBlank(message = "扩展名不能为空")
- private String extendName;
-
- @ApiModelProperty(value = "文件大小", required = true)
- @NotNull(message = "文件大小不能为空")
- private Long filesize;
-
- @ApiModelProperty(value = "MD5", required = true)
- @NotBlank(message = "MD5不能为空")
- private String md5;
-
- @ApiModelProperty(value = "状态", required = true)
- @NotNull(message = "状态不能为空")
- private EnabledStatusEnum status;
-
- @ApiModelProperty(value = "类别编号")
- private Integer classified;
-
-}
diff --git a/src/main/java/cc/uncarbon/module/oss/service/OssFileInfoService.java b/src/main/java/cc/uncarbon/module/oss/service/OssFileInfoService.java
index 9aaf342..6667e13 100644
--- a/src/main/java/cc/uncarbon/module/oss/service/OssFileInfoService.java
+++ b/src/main/java/cc/uncarbon/module/oss/service/OssFileInfoService.java
@@ -101,7 +101,7 @@ public void adminDelete(Collection ids) {
log.info("[后台管理-删除上传文件信息] >> ids={}", ids);
// 1. 删除原始文件
- List entityList = ossFileInfoMapper.selectByIds(ids);
+ List entityList = ossFileInfoMapper.selectBatchIds(ids);
for (OssFileInfoEntity entity : entityList) {
fileStorageService.delete(toFileInfo(entity));
}
@@ -244,8 +244,7 @@ private PageResult entityPage2BOPage(Page enti
.setCurrent(entityPage.getCurrent())
.setSize(entityPage.getSize())
.setTotal(entityPage.getTotal())
- .setRecords(this.entityList2BOs(entityPage.getRecords()))
- ;
+ .setRecords(this.entityList2BOs(entityPage.getRecords()));
}
}
diff --git a/src/main/java/cc/uncarbon/module/sys/constant/SysConstant.java b/src/main/java/cc/uncarbon/module/sys/constant/SysConstant.java
index 989ff59..333266d 100644
--- a/src/main/java/cc/uncarbon/module/sys/constant/SysConstant.java
+++ b/src/main/java/cc/uncarbon/module/sys/constant/SysConstant.java
@@ -4,7 +4,7 @@
/**
* 系统管理常量
*/
-public class SysConstant {
+public final class SysConstant {
private SysConstant() {
}
@@ -30,4 +30,15 @@ private SysConstant() {
*/
public static final Long SUPER_ADMIN_ROLE_ID = 1L;
+ /**
+ * 超级管理员角色值(固定)
+ */
+ public static final String SUPER_ADMIN_ROLE_VALUE = "SuperAdmin";
+
+ /**
+ * 租户管理员角色值
+ * 为了外显美观没有在前面增加Tenant字样
+ */
+ public static final String TENANT_ADMIN_ROLE_VALUE = "Admin";
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/entity/SysDataDictEntity.java b/src/main/java/cc/uncarbon/module/sys/entity/SysDataDictEntity.java
index fc2529b..ddb9c58 100644
--- a/src/main/java/cc/uncarbon/module/sys/entity/SysDataDictEntity.java
+++ b/src/main/java/cc/uncarbon/module/sys/entity/SysDataDictEntity.java
@@ -36,11 +36,11 @@ public class SysDataDictEntity extends HelioBaseEntity {
@TableField(value = "pascal_case_key")
private String pascalCaseKey;
- @ApiModelProperty(value = "键值")
+ @ApiModelProperty(value = "数据值")
@TableField(value = "value")
private String value;
- @ApiModelProperty(value = "参数描述")
+ @ApiModelProperty(value = "描述")
@TableField(value = "description")
private String description;
diff --git a/src/main/java/cc/uncarbon/module/sys/entity/SysRoleEntity.java b/src/main/java/cc/uncarbon/module/sys/entity/SysRoleEntity.java
index 95cc8d2..414a654 100644
--- a/src/main/java/cc/uncarbon/module/sys/entity/SysRoleEntity.java
+++ b/src/main/java/cc/uncarbon/module/sys/entity/SysRoleEntity.java
@@ -1,6 +1,7 @@
package cc.uncarbon.module.sys.entity;
import cc.uncarbon.framework.crud.entity.HelioBaseEntity;
+import cc.uncarbon.module.sys.constant.SysConstant;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
@@ -24,12 +25,26 @@
@TableName(value = "sys_role")
public class SysRoleEntity extends HelioBaseEntity {
- @ApiModelProperty(value = "名称")
+ @ApiModelProperty(value = "角色名")
@TableField(value = "title")
private String title;
- @ApiModelProperty(value = "值")
+ @ApiModelProperty(value = "角色编码")
@TableField(value = "value")
private String value;
+ /**
+ * 角色实例可被视为超级管理员
+ */
+ public boolean isSuperAdmin() {
+ return SysConstant.SUPER_ADMIN_ROLE_ID.equals(getId()) || SysConstant.SUPER_ADMIN_ROLE_VALUE.equalsIgnoreCase(getValue());
+ }
+
+ /**
+ * 角色实例可被视为租户管理员
+ */
+ public boolean isTenantAdmin() {
+ return SysConstant.TENANT_ADMIN_ROLE_VALUE.equalsIgnoreCase(getValue());
+ }
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/enums/SysErrorEnum.java b/src/main/java/cc/uncarbon/module/sys/enums/SysErrorEnum.java
index 24f1e40..62a2f8e 100644
--- a/src/main/java/cc/uncarbon/module/sys/enums/SysErrorEnum.java
+++ b/src/main/java/cc/uncarbon/module/sys/enums/SysErrorEnum.java
@@ -6,7 +6,7 @@
/**
- * SYS异常枚举类
+ * sys模块错误枚举类
*/
@AllArgsConstructor
@Getter
@@ -28,7 +28,40 @@ public enum SysErrorEnum implements HelioBaseEnum {
NO_MENU_AVAILABLE_FOR_CURRENT_ROLE(400, "当前角色没有可用菜单"),
- UUID_CANNOT_BE_BLANK(400, "UUID不能为空"),
+ /*
+ 以下8个枚举用于后台角色管理的越权检查
+ */
+
+ ROLE_VALUE_CANNOT_BE(403, "角色值 {} 不能用于新增或编辑,请选用其他值"),
+
+ CANNOT_DELETE_SUPER_ADMIN_ROLE(403, "不能删除超级管理员角色"),
+
+ CANNOT_DELETE_TENANT_ADMIN_ROLE(403, "为减少脏数据,不建议直接删除租户管理员角色,需通过【删除租户】关联删除"),
+
+ CANNOT_DELETE_SELF_ROLE(403, "不能删除自身角色"),
+
+ CANNOT_BIND_MENUS_FOR_SUPER_ADMIN_ROLE(403, "不能为超级管理员角色绑定菜单"),
+
+ CANNOT_BIND_MENUS_FOR_SELF(403, "不能为自身角色绑定菜单"),
+
+ BEYOND_AUTHORITY_BIND_MENUS(401, "不得超越自身菜单权限"),
+
+ CANNOT_BIND_MENUS_FOR_TENANT_ADMIN_ROLE(403, "无权为租户管理员绑定菜单"),
+
+ /*
+ 以下4个枚举用于后台用户管理的越权检查
+ */
+ CANNOT_OPERATE_SELF_USER(403, "不能对自身进行此操作"),
+
+ CANNOT_OPERATE_THIS_USER(403, "不能该用户进行此操作"),
+
+ CANNOT_UNBIND_SELF_TENANT_ADMIN_ROLE(403, "自身的管理员角色不能被取消"),
+
+ BEYOND_AUTHORITY_BIND_ROLES(401, "不得超越自身角色权限"),
+
+ CANNOT_DELETE_PRIVILEGED_TENANT(403, "不能删除超级租户"),
+
+ NEED_DELETE_EXISTING_TENANT_ADMIN_ROLE(500, "租户ID {} 对应的租户管理员角色已存在,请使用超级管理员账号删除"),
;
private final Integer value;
diff --git a/src/main/java/cc/uncarbon/module/sys/extension/impl/DefaultSysLogAspectExtension.java b/src/main/java/cc/uncarbon/module/sys/extension/impl/DefaultSysLogAspectExtension.java
index 4986bfa..b357810 100644
--- a/src/main/java/cc/uncarbon/module/sys/extension/impl/DefaultSysLogAspectExtension.java
+++ b/src/main/java/cc/uncarbon/module/sys/extension/impl/DefaultSysLogAspectExtension.java
@@ -6,6 +6,7 @@
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
@@ -31,6 +32,8 @@ public IPLocationBO queryIPLocation(String ip) {
.form("ip", ip)
.form("json", Boolean.TRUE.toString())
.charset(CharsetUtil.CHARSET_GBK)
+ // since 1.11.0,加个UA避免被当成恶意请求,造成查IP失败
+ .header(Header.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36")
.timeout(5000);
try (HttpResponse httpResponse = httpRequest.execute()) {
String repStr = httpResponse.body();
diff --git a/src/main/java/cc/uncarbon/module/sys/facade/SysTenantFacade.java b/src/main/java/cc/uncarbon/module/sys/facade/SysTenantFacade.java
index 3db17e0..8875384 100644
--- a/src/main/java/cc/uncarbon/module/sys/facade/SysTenantFacade.java
+++ b/src/main/java/cc/uncarbon/module/sys/facade/SysTenantFacade.java
@@ -1,9 +1,13 @@
package cc.uncarbon.module.sys.facade;
import cc.uncarbon.module.sys.model.request.AdminInsertSysTenantDTO;
+import cc.uncarbon.module.sys.model.request.AdminUpdateSysTenantDTO;
+import cc.uncarbon.module.sys.model.response.SysTenantKickOutUsersBO;
+
+import java.util.Collection;
/**
- * 系统租户防腐层,用于解决循环依赖
+ * 系统租户解耦层,用于解决循环依赖
*/
public interface SysTenantFacade {
@@ -12,4 +16,14 @@ public interface SysTenantFacade {
*/
Long adminInsert(AdminInsertSysTenantDTO dto);
+ /**
+ * 后台管理-编辑
+ */
+ SysTenantKickOutUsersBO adminUpdate(AdminUpdateSysTenantDTO dto);
+
+ /**
+ * 后台管理-删除
+ */
+ SysTenantKickOutUsersBO adminDelete(Collection ids);
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/facade/impl/SysTenantFacadeImpl.java b/src/main/java/cc/uncarbon/module/sys/facade/impl/SysTenantFacadeImpl.java
index 7959be8..8c83169 100644
--- a/src/main/java/cc/uncarbon/module/sys/facade/impl/SysTenantFacadeImpl.java
+++ b/src/main/java/cc/uncarbon/module/sys/facade/impl/SysTenantFacadeImpl.java
@@ -1,21 +1,34 @@
package cc.uncarbon.module.sys.facade.impl;
+import cc.uncarbon.framework.core.constant.HelioConstant;
+import cc.uncarbon.framework.core.enums.EnabledStatusEnum;
+import cc.uncarbon.framework.core.function.StreamFunction;
+import cc.uncarbon.module.sys.constant.SysConstant;
import cc.uncarbon.module.sys.entity.SysTenantEntity;
+import cc.uncarbon.module.sys.enums.SysErrorEnum;
+import cc.uncarbon.module.sys.enums.SysUserStatusEnum;
import cc.uncarbon.module.sys.facade.SysTenantFacade;
import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysRoleDTO;
import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysUserDTO;
import cc.uncarbon.module.sys.model.request.AdminInsertSysTenantDTO;
+import cc.uncarbon.module.sys.model.request.AdminUpdateSysTenantDTO;
+import cc.uncarbon.module.sys.model.response.SysTenantBO;
+import cc.uncarbon.module.sys.model.response.SysTenantKickOutUsersBO;
import cc.uncarbon.module.sys.service.SysRoleService;
import cc.uncarbon.module.sys.service.SysTenantService;
import cc.uncarbon.module.sys.service.SysUserRoleRelationService;
import cc.uncarbon.module.sys.service.SysUserService;
+import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.util.*;
+import java.util.stream.Collectors;
+
/**
- * 系统租户防腐层,用于解决循环依赖
+ * 系统租户Facade接口实现类
*/
@RequiredArgsConstructor
@Service
@@ -50,38 +63,92 @@ public Long adminInsert(AdminInsertSysTenantDTO dto) {
Long newRoleId = sysRoleService.adminInsert(
AdminInsertOrUpdateSysRoleDTO.builder()
.tenantId(newTenantId)
- .title(dto.getTenantName() + "管理员")
- .value("Admin")
+ .title(dto.getTenantName() + "主管理员")
+ .value(SysConstant.TENANT_ADMIN_ROLE_VALUE)
.build()
);
- /*
- 3. 创建一个新用户
- */
+ // 3. 创建一个新用户
Long newUserId = sysUserService.adminInsert(
AdminInsertOrUpdateSysUserDTO.builder()
.tenantId(newTenantId)
.username(dto.getTenantAdminUsername())
.passwordOfNewUser(dto.getTenantAdminPassword())
- .nickname(dto.getTenantName() + "管理员")
+ .nickname(dto.getTenantName() + "主管理员")
.email(dto.getTenantAdminEmail())
.phoneNo(dto.getTenantAdminPhoneNo())
+ // 默认为正常状态
+ .status(SysUserStatusEnum.ENABLED)
.build()
);
- /*
- 4. 将新用户绑定至新角色上
- */
+ // 4. 将新用户绑定至新角色上
sysUserRoleRelationService.adminInsert(newTenantId, newUserId, newRoleId);
- /*
- 5. 把管理员账号更新进库
- */
+ // 5. 把管理员账号更新进库
SysTenantEntity update = new SysTenantEntity().setTenantAdminUserId(newUserId);
update.setId(newTenantEntityId);
sysTenantService.adminUpdate(update);
-
return newTenantId;
}
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public SysTenantKickOutUsersBO adminUpdate(AdminUpdateSysTenantDTO dto) {
+ sysTenantService.adminUpdate(dto);
+
+ if (dto.getStatus() == EnabledStatusEnum.DISABLED) {
+ // 新状态是禁用,查出需要强制登出的用户
+ Long tenantId = CollUtil.getFirst(determineTenantIdsByPrimaryKeys(Collections.singleton(dto.getId())).values());
+ if (Objects.nonNull(tenantId)) {
+ List tenantSysUserIds = sysUserService.listUserIdsByTenantId(tenantId, Collections.singleton(EnabledStatusEnum.ENABLED));
+ return new SysTenantKickOutUsersBO(tenantSysUserIds);
+ }
+ }
+ return new SysTenantKickOutUsersBO();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public SysTenantKickOutUsersBO adminDelete(Collection ids) {
+ Collection tenantIds = determineTenantIdsByPrimaryKeys(ids).values();
+ if (CollUtil.isNotEmpty(tenantIds)) {
+ // 不能删除「超级租户」(租户ID=0)
+ SysErrorEnum.CANNOT_DELETE_PRIVILEGED_TENANT.assertNotContains(tenantIds, HelioConstant.Tenant.DEFAULT_PRIVILEGED_TENANT_ID);
+
+ // 删除租户管理员角色、租户
+ sysRoleService.adminDeleteTenantRoles(tenantIds, Collections.singleton(SysConstant.TENANT_ADMIN_ROLE_VALUE));
+ sysTenantService.adminDelete(ids);
+
+ // 查出需要强制登出的用户
+ List tenantSysUserIds = tenantIds.stream()
+ .map(tenantId -> sysUserService.listUserIdsByTenantId(tenantId, Collections.singleton(EnabledStatusEnum.ENABLED)))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ return new SysTenantKickOutUsersBO(tenantSysUserIds);
+ }
+ return new SysTenantKickOutUsersBO();
+ }
+
+ /*
+ ----------------------------------------------------------------
+ 私有方法 private methods
+ ----------------------------------------------------------------
+ */
+
+ /**
+ * 根据租户主键ID,确定租户IDs
+ * @return map[主键ID, 租户ID]
+ */
+ private Map determineTenantIdsByPrimaryKeys(Collection ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyMap();
+ }
+ List sysTenantInfos = sysTenantService.listByIds(ids, false);
+ if (CollUtil.isEmpty(sysTenantInfos)) {
+ return Collections.emptyMap();
+ }
+ return sysTenantInfos.stream().collect(Collectors.toMap(SysTenantBO::getId, SysTenantBO::getTenantId, StreamFunction.ignoredThrowingMerger()));
+ }
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/mapper/SysDeptMapper.java b/src/main/java/cc/uncarbon/module/sys/mapper/SysDeptMapper.java
index fb7e24b..57ad0f1 100644
--- a/src/main/java/cc/uncarbon/module/sys/mapper/SysDeptMapper.java
+++ b/src/main/java/cc/uncarbon/module/sys/mapper/SysDeptMapper.java
@@ -1,13 +1,23 @@
package cc.uncarbon.module.sys.mapper;
import cc.uncarbon.module.sys.entity.SysDeptEntity;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
/**
* 部门
*/
@Mapper
public interface SysDeptMapper extends BaseMapper {
-
+
+ /**
+ * 列举已排序好的所有部门列表
+ */
+ default List sortedList() {
+ return selectList(new LambdaQueryWrapper().orderByAsc(SysDeptEntity::getSort));
+ }
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/mapper/SysRoleMapper.java b/src/main/java/cc/uncarbon/module/sys/mapper/SysRoleMapper.java
index a92d001..0993314 100644
--- a/src/main/java/cc/uncarbon/module/sys/mapper/SysRoleMapper.java
+++ b/src/main/java/cc/uncarbon/module/sys/mapper/SysRoleMapper.java
@@ -9,5 +9,5 @@
*/
@Mapper
public interface SysRoleMapper extends BaseMapper {
-
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/mapper/SysUserMapper.java b/src/main/java/cc/uncarbon/module/sys/mapper/SysUserMapper.java
index 43bac02..0411f44 100644
--- a/src/main/java/cc/uncarbon/module/sys/mapper/SysUserMapper.java
+++ b/src/main/java/cc/uncarbon/module/sys/mapper/SysUserMapper.java
@@ -1,12 +1,19 @@
package cc.uncarbon.module.sys.mapper;
+import cc.uncarbon.framework.core.enums.EnabledStatusEnum;
import cc.uncarbon.module.sys.entity.SysUserEntity;
import cc.uncarbon.module.sys.model.response.SysUserBaseInfoBO;
+import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
/**
* 后台用户
*/
@@ -29,4 +36,18 @@ public interface SysUserMapper extends BaseMapper {
@InterceptorIgnore(tenantLine = "true")
SysUserBaseInfoBO getBaseInfoByUserId(@Param(value = "userId") Long userId);
+ /**
+ * 查询所有用户IDs
+ * @param statusEnums 仅保留符合指定状态的,可以为null
+ */
+ default List selectIds(Collection statusEnums) {
+ return selectList(
+ new LambdaQueryWrapper()
+ // 只取主键ID
+ .select(SysUserEntity::getId)
+ // 状态
+ .in(CollUtil.isNotEmpty(statusEnums), SysUserEntity::getStatus, statusEnums)
+ ).stream().map(SysUserEntity::getId).collect(Collectors.toList());
+ }
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/mapper/xml/SysUserMapper.xml b/src/main/java/cc/uncarbon/module/sys/mapper/xml/SysUserMapper.xml
index 3bccc5e..55069e4 100644
--- a/src/main/java/cc/uncarbon/module/sys/mapper/xml/SysUserMapper.xml
+++ b/src/main/java/cc/uncarbon/module/sys/mapper/xml/SysUserMapper.xml
@@ -5,7 +5,8 @@
@@ -17,7 +18,8 @@
, email
, phone_no
FROM sys_user
- WHERE id = #{userId}
+ WHERE del_flag = 0
+ AND id = #{userId}
LIMIT 1
diff --git a/src/main/java/cc/uncarbon/module/sys/model/interior/UserDeptContainer.java b/src/main/java/cc/uncarbon/module/sys/model/interior/UserDeptContainer.java
new file mode 100644
index 0000000..3e9847e
--- /dev/null
+++ b/src/main/java/cc/uncarbon/module/sys/model/interior/UserDeptContainer.java
@@ -0,0 +1,82 @@
+package cc.uncarbon.module.sys.model.interior;
+
+import cc.uncarbon.module.sys.entity.SysDeptEntity;
+import cn.hutool.core.collection.CollUtil;
+import lombok.Getter;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 用户关联部门容器
+ */
+@Getter
+public class UserDeptContainer {
+
+ /**
+ * 直接关联的部门IDs
+ * 一般只有0或1个元素
+ */
+ private final List relatedDeptIds;
+
+ /**
+ * 直接关联的部门实例集合
+ * 一般只有0或1个元素
+ */
+ private final List relatedDepts;
+
+ /**
+ * 可见的部门IDs
+ */
+ private List visibleDeptIds;
+
+ /**
+ * 可见的部门实例集合
+ */
+ private List visibleDepts;
+
+
+ public UserDeptContainer(List relatedDeptIds, List relatedDepts) {
+ this.relatedDeptIds = relatedDeptIds;
+ this.relatedDepts = relatedDepts;
+ this.visibleDeptIds = relatedDeptIds;
+ this.visibleDepts = relatedDepts;
+ }
+
+ /**
+ * 用户是否有实际关联的部门
+ */
+ public boolean hasRelatedDepts() {
+ return CollUtil.isNotEmpty(relatedDeptIds) && CollUtil.isNotEmpty(relatedDepts);
+ }
+
+ /**
+ * 用户主要关联的部门,默认取第一个元素
+ * @return null or 部门实例
+ */
+ public SysDeptEntity primaryRelatedDept() {
+ return CollUtil.getFirst(relatedDepts);
+ }
+
+ /**
+ * 用户是否有实际可见的部门
+ * 也可视为部门的数据权限范围
+ */
+ public boolean hasVisibleDepts() {
+ return CollUtil.isNotEmpty(visibleDeptIds) && CollUtil.isNotEmpty(visibleDepts);
+ }
+
+ /**
+ * 更新可见的部门
+ */
+ public void updateVisibleDepts(List visibleDepts) {
+ if (CollUtil.isEmpty(visibleDepts)) {
+ this.visibleDeptIds = Collections.emptyList();
+ this.visibleDepts = Collections.emptyList();
+ } else {
+ this.visibleDeptIds = visibleDepts.stream().map(SysDeptEntity::getId).collect(Collectors.toList());
+ this.visibleDepts = visibleDepts;
+ }
+ }
+}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/interior/UserRoleContainer.java b/src/main/java/cc/uncarbon/module/sys/model/interior/UserRoleContainer.java
new file mode 100644
index 0000000..b02958e
--- /dev/null
+++ b/src/main/java/cc/uncarbon/module/sys/model/interior/UserRoleContainer.java
@@ -0,0 +1,48 @@
+package cc.uncarbon.module.sys.model.interior;
+
+import cc.uncarbon.module.sys.entity.SysRoleEntity;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 用户关联角色容器
+ */
+@Getter
+public class UserRoleContainer {
+
+ /**
+ * 直接关联的角色IDs
+ */
+ private final Set relatedRoleIds;
+
+ /**
+ * 直接关联的角色实例集合
+ */
+ private final List relatedRoles;
+
+ /**
+ * 为超级管理员
+ */
+ private final boolean superAdmin;
+
+ /**
+ * 为租户管理员
+ */
+ private final boolean tenantAdmin;
+
+ /**
+ * 非级管理员or租户管理员
+ */
+ private final boolean notAnyAdmin;
+
+
+ public UserRoleContainer(Set relatedRoleIds, List relatedRoles) {
+ this.relatedRoleIds = relatedRoleIds;
+ this.relatedRoles = relatedRoles;
+ this.superAdmin = relatedRoles.stream().anyMatch(SysRoleEntity::isSuperAdmin);
+ this.tenantAdmin = relatedRoles.stream().anyMatch(SysRoleEntity::isTenantAdmin);
+ this.notAnyAdmin = !this.superAdmin && !this.tenantAdmin;
+ }
+}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDataDictDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDataDictDTO.java
index 146f484..a77b5f6 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDataDictDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDataDictDTO.java
@@ -8,6 +8,7 @@
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
import java.io.Serializable;
@@ -25,32 +26,40 @@ public class AdminInsertOrUpdateSysDataDictDTO implements Serializable {
private Long id;
@ApiModelProperty(value = "驼峰式键名", required = true)
+ @Size(max = 100, message = "【驼峰式键名】最长100位")
@NotBlank(message = "驼峰式键名不能为空")
private String camelCaseKey;
@ApiModelProperty(value = "下划线式键名", required = true)
+ @Size(max = 100, message = "【下划线式键名】最长100位")
@NotBlank(message = "下划线式键名不能为空")
private String underCaseKey;
@ApiModelProperty(value = "帕斯卡式键名", required = true)
+ @Size(max = 100, message = "【帕斯卡式键名】最长100位")
@NotBlank(message = "帕斯卡式键名不能为空")
private String pascalCaseKey;
- @ApiModelProperty(value = "键值", required = true)
- @NotBlank(message = "键值不能为空")
+ @ApiModelProperty(value = "数据值", required = true)
+ @Size(max = 255, message = "【数据值】最长255位")
+ @NotBlank(message = "数据值不能为空")
private String value;
- @ApiModelProperty(value = "参数描述", required = true)
- @NotBlank(message = "参数描述不能为空")
+ @ApiModelProperty(value = "描述", required = true)
+ @Size(max = 255, message = "【描述】最长255位")
+ @NotBlank(message = "描述不能为空")
private String description;
@ApiModelProperty(value = "单位")
+ @Size(max = 30, message = "【单位】最长30位")
private String unit;
@ApiModelProperty(value = "取值范围")
+ @Size(max = 255, message = "【取值范围】最长255位")
private String valueRange;
@ApiModelProperty(value = "别称键名")
+ @Size(max = 100, message = "【别称键名】最长100位")
private String aliasKey;
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDeptDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDeptDTO.java
index 2190c2f..d1a6fe4 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDeptDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDeptDTO.java
@@ -9,6 +9,7 @@
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
import java.io.Serializable;
@@ -25,11 +26,9 @@ public class AdminInsertOrUpdateSysDeptDTO implements Serializable {
@ApiModelProperty(value = "主键ID", hidden = true, notes = "仅更新时使用")
private Long id;
- @ApiModelProperty(value = "所属租户ID", hidden = true, notes = "仅新增时使用")
- private Long tenantId;
-
- @ApiModelProperty(value = "名称", required = true)
- @NotBlank(message = "名称不能为空")
+ @ApiModelProperty(value = "部门名称", required = true)
+ @Size(max = 50, message = "【部门名称】最长50位")
+ @NotBlank(message = "部门名称不能为空")
private String title;
@ApiModelProperty(value = "上级ID(无上级节点设置为0)")
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysMenuDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysMenuDTO.java
index 1e22057..575c735 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysMenuDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysMenuDTO.java
@@ -11,6 +11,7 @@
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
import java.io.Serializable;
@@ -27,11 +28,9 @@ public class AdminInsertOrUpdateSysMenuDTO implements Serializable {
@ApiModelProperty(value = "主键ID", hidden = true, notes = "仅更新时使用")
private Long id;
- @ApiModelProperty(value = "所属租户ID", hidden = true, notes = "仅新增时使用")
- private Long tenantId;
-
- @ApiModelProperty(value = "名称", required = true)
- @NotBlank(message = "名称不能为空")
+ @ApiModelProperty(value = "菜单名称", required = true)
+ @Size(max = 50, message = "【菜单名称】最长50位")
+ @NotBlank(message = "菜单名称不能为空")
private String title;
@ApiModelProperty(value = "上级菜单ID(无上级节点设置为0)")
@@ -42,12 +41,15 @@ public class AdminInsertOrUpdateSysMenuDTO implements Serializable {
private SysMenuTypeEnum type;
@ApiModelProperty(value = "组件")
+ @Size(max = 50, message = "【组件】最长50位")
private String component;
@ApiModelProperty(value = "权限标识")
+ @Size(max = 255, message = "【权限标识】最长255位")
private String permission;
@ApiModelProperty(value = "图标")
+ @Size(max = 255, message = "【图标】最长255位")
private String icon;
@ApiModelProperty(value = "排序")
@@ -57,6 +59,7 @@ public class AdminInsertOrUpdateSysMenuDTO implements Serializable {
private EnabledStatusEnum status;
@ApiModelProperty(value = "外链地址")
+ @Size(max = 255, message = "【外链地址】最长255位")
private String externalLink;
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysParamDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysParamDTO.java
index d0d476f..60a7613 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysParamDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysParamDTO.java
@@ -8,6 +8,7 @@
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
import java.io.Serializable;
@@ -25,14 +26,17 @@ public class AdminInsertOrUpdateSysParamDTO implements Serializable {
private Long id;
@ApiModelProperty(value = "键名", required = true)
+ @Size(max = 50, message = "【键名】最长50位")
@NotBlank(message = "键名不能为空")
private String name;
@ApiModelProperty(value = "键值", required = true)
+ @Size(max = 255, message = "【键值】最长255位")
@NotBlank(message = "键值不能为空")
private String value;
@ApiModelProperty(value = "描述", required = true)
+ @Size(max = 255, message = "【描述】最长255位")
@NotBlank(message = "描述不能为空")
private String description;
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysRoleDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysRoleDTO.java
index 8cb009e..92378ac 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysRoleDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysRoleDTO.java
@@ -1,14 +1,18 @@
package cc.uncarbon.module.sys.model.request;
+import cc.uncarbon.module.sys.constant.SysConstant;
import io.swagger.annotations.ApiModelProperty;
-import java.io.Serializable;
-import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Objects;
+
/**
* 后台管理-新增/编辑后台角色
@@ -26,12 +30,21 @@ public class AdminInsertOrUpdateSysRoleDTO implements Serializable {
@ApiModelProperty(value = "所属租户ID", hidden = true, notes = "仅新增时使用")
private Long tenantId;
- @ApiModelProperty(value = "名称", required = true)
- @NotBlank(message = "名称不能为空")
+ @ApiModelProperty(value = "角色名", required = true)
+ @Size(max = 50, message = "【角色名】最长50位")
+ @NotBlank(message = "角色名不能为空")
private String title;
- @ApiModelProperty(value = "值", required = true)
- @NotBlank(message = "值不能为空")
+ @ApiModelProperty(value = "角色编码", required = true)
+ @Size(max = 100, message = "【角色编码】最长100位")
+ @NotBlank(message = "角色编码不能为空")
private String value;
+ /**
+ * 是否用于创建新租户管理员角色
+ */
+ public boolean creatingNewTenantAdmin() {
+ return Objects.nonNull(tenantId) && SysConstant.TENANT_ADMIN_ROLE_VALUE.equalsIgnoreCase(value);
+ }
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysUserDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysUserDTO.java
index 0ae4615..1005094 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysUserDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysUserDTO.java
@@ -2,7 +2,9 @@
import cc.uncarbon.framework.core.constant.HelioConstant;
import cc.uncarbon.framework.core.enums.GenderEnum;
+import cc.uncarbon.framework.core.exception.BusinessException;
import cc.uncarbon.module.sys.enums.SysUserStatusEnum;
+import cn.hutool.core.text.CharSequenceUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -13,7 +15,9 @@
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
import java.io.Serializable;
+import java.util.Objects;
/**
@@ -33,6 +37,7 @@ public class AdminInsertOrUpdateSysUserDTO implements Serializable {
private Long tenantId;
@ApiModelProperty(value = "账号", required = true)
+ @Size(min = 6, max = 16, message = "【账号】最短6位,最长16位")
@NotBlank(message = "账号不能为空")
private String username;
@@ -40,6 +45,7 @@ public class AdminInsertOrUpdateSysUserDTO implements Serializable {
private String passwordOfNewUser;
@ApiModelProperty(value = "昵称", required = true)
+ @Size(max = 100, message = "【昵称】最长100位")
@NotBlank(message = "昵称不能为空")
private String nickname;
@@ -53,15 +59,29 @@ public class AdminInsertOrUpdateSysUserDTO implements Serializable {
@ApiModelProperty(value = "邮箱", required = true)
@Pattern(message = "邮箱格式有误", regexp = HelioConstant.Regex.EMAIL)
+ @Size(max = 255, message = "【邮箱】最长255位")
@NotBlank(message = "邮箱不能为空")
private String email;
@ApiModelProperty(value = "手机号", required = true)
@Pattern(message = "手机号格式有误", regexp = HelioConstant.Regex.CHINA_MAINLAND_PHONE_NO)
+ @Size(max = 20, message = "【手机号】最长20位")
@NotBlank(message = "手机号不能为空")
private String phoneNo;
@ApiModelProperty(value = "所属部门ID")
private Long deptId;
+
+ public void validate() {
+ boolean isUpdate = Objects.nonNull(id);
+ if (!isUpdate) {
+ // 新增
+ int passwordOfNewUserLen = CharSequenceUtil.length(passwordOfNewUser);
+ if (passwordOfNewUserLen < 8 || passwordOfNewUserLen > 20) {
+ throw new BusinessException("【密码】最短8位,最长20位");
+ }
+ }
+ }
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertSysTenantDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertSysTenantDTO.java
index 1896564..f1aa07f 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertSysTenantDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertSysTenantDTO.java
@@ -8,8 +8,7 @@
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.Pattern;
+import javax.validation.constraints.*;
/**
@@ -22,21 +21,30 @@
@Data
public class AdminInsertSysTenantDTO extends AdminUpdateSysTenantDTO {
+ @ApiModelProperty(value = "租户ID(纯数字)", required = true)
+ @Positive(message = "租户ID须为正整数")
+ @NotNull(message = "租户ID不能为空")
+ private Long tenantId;
+
@ApiModelProperty(value = "管理员账号", required = true)
+ @Size(min = 6, max = 16, message = "【管理员账号】最短6位,最长16位")
@NotBlank(message = "管理员账号不能为空")
private String tenantAdminUsername;
@ApiModelProperty(value = "管理员密码", required = true)
+ @Size(min = 8, max = 20, message = "【管理员密码】最短8位,最长20位")
@NotBlank(message = "管理员密码不能为空")
private String tenantAdminPassword;
@ApiModelProperty(value = "管理员邮箱", required = true)
- @Pattern(message = "邮箱格式不正确", regexp = HelioConstant.Regex.EMAIL)
+ @Pattern(message = "管理员邮箱格式不正确", regexp = HelioConstant.Regex.EMAIL)
+ @Size(max = 255, message = "【管理员邮箱】最长255位")
@NotBlank(message = "管理员邮箱不能为空")
private String tenantAdminEmail;
@ApiModelProperty(value = "管理员手机号", required = true)
- @Pattern(message = "手机号格式不正确", regexp = HelioConstant.Regex.CHINA_MAINLAND_PHONE_NO)
+ @Pattern(message = "管理员手机号格式不正确", regexp = HelioConstant.Regex.CHINA_MAINLAND_PHONE_NO)
+ @Size(max = 20, message = "【管理员手机号】最长20位")
@NotBlank(message = "管理员手机号不能为空")
private String tenantAdminPhoneNo;
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysDataDictDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysDataDictDTO.java
index cb8a81b..698b266 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysDataDictDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysDataDictDTO.java
@@ -20,7 +20,7 @@
@Data
public class AdminListSysDataDictDTO implements Serializable {
- @ApiModelProperty(value = "参数描述(关键词)")
+ @ApiModelProperty(value = "描述(关键词)")
private String description;
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysRoleDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysRoleDTO.java
index d7c2219..1f98fc9 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysRoleDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysRoleDTO.java
@@ -20,10 +20,10 @@
@Data
public class AdminListSysRoleDTO implements Serializable {
- @ApiModelProperty(value = "名称(关键词)")
+ @ApiModelProperty(value = "角色名(关键词)")
private String title;
- @ApiModelProperty(value = "值(关键词)")
+ @ApiModelProperty(value = "角色编码(关键词)")
private String value;
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysUserDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysUserDTO.java
index f761e18..bb18976 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysUserDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminListSysUserDTO.java
@@ -1,5 +1,6 @@
package cc.uncarbon.module.sys.model.request;
+import cc.uncarbon.module.sys.constant.SysConstant;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -8,6 +9,7 @@
import lombok.experimental.Accessors;
import java.io.Serializable;
+import java.util.Objects;
/**
@@ -23,4 +25,14 @@ public class AdminListSysUserDTO implements Serializable {
@ApiModelProperty(value = "手机号(关键词)")
private String phoneNo;
+ @ApiModelProperty(value = "手动选择的部门ID")
+ private Long selectedDeptId;
+
+ /**
+ * 是否需要根据【手动选择的部门】筛选用户
+ */
+ public boolean needFilterBySelectedDeptId() {
+ return Objects.nonNull(selectedDeptId) && selectedDeptId > SysConstant.ROOT_PARENT_ID;
+ }
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminResetSysUserPasswordDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminResetSysUserPasswordDTO.java
index 27722c7..b313103 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminResetSysUserPasswordDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminResetSysUserPasswordDTO.java
@@ -23,11 +23,11 @@
public class AdminResetSysUserPasswordDTO implements Serializable {
@ApiModelProperty(value = "随机新密码", required = true)
- @Size(min = 16, max = 64, message = "随机新密码须为16-64位")
+ @Size(min = 16, max = 64, message = "【随机新密码】最短16位,最长64位")
@NotBlank(message = "随机新密码不能为空")
private String randomPassword;
- @ApiModelProperty(value = "用户ID", hidden = true)
+ @ApiModelProperty(value = "用户ID", hidden = true)
private Long userId;
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateCurrentSysUserPasswordDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateCurrentSysUserPasswordDTO.java
index 74c1000..7a150ac 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateCurrentSysUserPasswordDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateCurrentSysUserPasswordDTO.java
@@ -27,12 +27,12 @@ public class AdminUpdateCurrentSysUserPasswordDTO implements Serializable {
private String oldPassword;
@ApiModelProperty(value = "新密码", required = true)
- @Size(min = 8, max = 20, message = "密码须为8-20位")
+ @Size(min = 8, max = 20, message = "【密码】长度须在 8 至 20 位之间")
@NotBlank(message = "密码不能为空")
private String newPassword;
@ApiModelProperty(value = "确认新密码", required = true)
- @Size(min = 8, max = 20, message = "确认密码须为8-20位")
+ @Size(min = 8, max = 20, message = "【确认密码】长度须在 8 至 20 位之间")
@NotBlank(message = "确认密码不能为空")
private String confirmNewPassword;
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateSysTenantDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateSysTenantDTO.java
index ebc09d8..28cc7b2 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateSysTenantDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateSysTenantDTO.java
@@ -10,6 +10,7 @@
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
import java.io.Serializable;
@@ -27,18 +28,16 @@ public class AdminUpdateSysTenantDTO implements Serializable {
private Long id;
@ApiModelProperty(value = "租户名", required = true)
+ @Size(max = 50, message = "【租户名】最长50位")
@NotBlank(message = "租户名不能为空")
private String tenantName;
- @ApiModelProperty(value = "租户ID(纯数字)", required = true)
- @NotNull(message = "租户ID不能为空")
- private Long tenantId;
-
@ApiModelProperty(value = "状态", required = true)
@NotNull(message = "状态不能为空")
private EnabledStatusEnum status;
@ApiModelProperty(value = "备注")
+ @Size(max = 255, message = "【备注】最长255位")
private String remark;
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/request/SysUserLoginDTO.java b/src/main/java/cc/uncarbon/module/sys/model/request/SysUserLoginDTO.java
index 64a3ce1..79e2467 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/request/SysUserLoginDTO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/request/SysUserLoginDTO.java
@@ -24,12 +24,12 @@
public class SysUserLoginDTO implements Serializable {
@ApiModelProperty(value = "账号", required = true)
- @Size(min = 5, max = 16, message = "【账号】长度须在 5 至 16 位之间")
+ @Size(min = 5, max = 16, message = "【账号】最短5位,最长16位")
@NotBlank(message = "账号不能为空")
private String username;
@ApiModelProperty(value = "密码", required = true)
- @Size(min = 5, max = 64, message = "【密码】长度须在 5 至 16 位之间")
+ @Size(min = 5, max = 20, message = "【密码】最短5位,最长20位")
@NotBlank(message = "密码不能为空")
private String password;
@@ -40,10 +40,10 @@ public class SysUserLoginDTO implements Serializable {
@ApiModelProperty(value = "租户ID(可选,启用多租户后有效)")
private Long tenantId;
- @ApiModelProperty(value = "验证码图片UUID(可选,需自行对接业务逻辑)")
- private String captchaUUID;
+ @ApiModelProperty(value = "验证码唯一标识(可选)")
+ private String captchaId;
- @ApiModelProperty(value = "验证码答案(可选,需自行对接业务逻辑)")
+ @ApiModelProperty(value = "验证码答案(可选)")
private String captchaAnswer;
}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictBO.java b/src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictBO.java
index bc40186..468ddf6 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictBO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictBO.java
@@ -46,10 +46,10 @@ public class SysDataDictBO implements Serializable {
@ApiModelProperty(value = "帕斯卡式键名")
private String pascalCaseKey;
- @ApiModelProperty(value = "键值")
+ @ApiModelProperty(value = "数据值")
private String value;
- @ApiModelProperty(value = "参数描述")
+ @ApiModelProperty(value = "描述")
private String description;
@ApiModelProperty(value = "单位")
diff --git a/src/main/java/cc/uncarbon/module/sys/model/response/SysRoleBO.java b/src/main/java/cc/uncarbon/module/sys/model/response/SysRoleBO.java
index bc61f96..7b00073 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/response/SysRoleBO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/response/SysRoleBO.java
@@ -38,10 +38,10 @@ public class SysRoleBO implements Serializable {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = HelioConstant.Jackson.DATE_TIME_FORMAT)
private LocalDateTime updatedAt;
- @ApiModelProperty(value = "名称")
+ @ApiModelProperty(value = "角色名")
private String title;
- @ApiModelProperty(value = "值")
+ @ApiModelProperty(value = "角色编码")
private String value;
@ApiModelProperty(value = "可见菜单Ids")
diff --git a/src/main/java/cc/uncarbon/module/sys/model/response/SysTenantKickOutUsersBO.java b/src/main/java/cc/uncarbon/module/sys/model/response/SysTenantKickOutUsersBO.java
new file mode 100644
index 0000000..4cbeffb
--- /dev/null
+++ b/src/main/java/cc/uncarbon/module/sys/model/response/SysTenantKickOutUsersBO.java
@@ -0,0 +1,26 @@
+package cc.uncarbon.module.sys.model.response;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 系统租户-需强制登出用户 BO
+ * 同一时间大量登出,会操作大量Redis键,可能存在缓存雪崩的风险
+ */
+@Getter
+public class SysTenantKickOutUsersBO {
+
+ @ApiModelProperty(value = "后台用户IDs")
+ private final List sysUserIds;
+
+ public SysTenantKickOutUsersBO() {
+ this.sysUserIds = Collections.emptyList();
+ }
+
+ public SysTenantKickOutUsersBO(List sysUserIds) {
+ this.sysUserIds = sysUserIds;
+ }
+}
diff --git a/src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginBO.java b/src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginBO.java
index 4421513..cf0a166 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginBO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginBO.java
@@ -2,16 +2,17 @@
import cc.uncarbon.framework.core.context.TenantContext;
import io.swagger.annotations.ApiModelProperty;
-import java.io.Serializable;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
/**
* 登录后返回的字段
diff --git a/src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginVO.java b/src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginVO.java
index e868652..8a0d494 100644
--- a/src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginVO.java
+++ b/src/main/java/cc/uncarbon/module/sys/model/response/SysUserLoginVO.java
@@ -1,14 +1,15 @@
package cc.uncarbon.module.sys.model.response;
import io.swagger.annotations.ApiModelProperty;
-import java.io.Serializable;
-import java.util.Collection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
+import java.io.Serializable;
+import java.util.Collection;
+
/**
* 登录后返回的字段
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysDataDictService.java b/src/main/java/cc/uncarbon/module/sys/service/SysDataDictService.java
index 0a07ed8..c839797 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysDataDictService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysDataDictService.java
@@ -176,8 +176,7 @@ private PageResult entityPage2BOPage(Page enti
.setCurrent(entityPage.getCurrent())
.setSize(entityPage.getSize())
.setTotal(entityPage.getTotal())
- .setRecords(this.entityList2BOs(entityPage.getRecords()))
- ;
+ .setRecords(this.entityList2BOs(entityPage.getRecords()));
}
/**
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysDeptService.java b/src/main/java/cc/uncarbon/module/sys/service/SysDeptService.java
index 7b046d9..83a4ca3 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysDeptService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysDeptService.java
@@ -1,11 +1,14 @@
package cc.uncarbon.module.sys.service;
import cc.uncarbon.framework.core.constant.HelioConstant;
+import cc.uncarbon.framework.core.context.UserContextHolder;
import cc.uncarbon.framework.core.exception.BusinessException;
import cc.uncarbon.module.sys.constant.SysConstant;
import cc.uncarbon.module.sys.entity.SysDeptEntity;
import cc.uncarbon.module.sys.enums.SysErrorEnum;
import cc.uncarbon.module.sys.mapper.SysDeptMapper;
+import cc.uncarbon.module.sys.model.interior.UserDeptContainer;
+import cc.uncarbon.module.sys.model.interior.UserRoleContainer;
import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysDeptDTO;
import cc.uncarbon.module.sys.model.response.SysDeptBO;
import cn.hutool.core.bean.BeanUtil;
@@ -17,10 +20,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
/**
@@ -33,20 +34,14 @@ public class SysDeptService {
private final SysDeptMapper sysDeptMapper;
private final SysUserDeptRelationService sysUserDeptRelationService;
+ private final SysRoleService sysRoleService;
/**
* 后台管理-列表
*/
public List adminList() {
- List entityList = sysDeptMapper.selectList(
- new QueryWrapper()
- .lambda()
- // 排序
- .orderByAsc(SysDeptEntity::getSort)
- );
-
- return this.entityList2BOs(entityList);
+ return entityList2BOs(sysDeptMapper.sortedList());
}
/**
@@ -62,7 +57,7 @@ public SysDeptBO getOneById(Long id) {
/**
* 根据 ID 取详情
*
- * @param id 主键ID
+ * @param id 主键ID
* @param throwIfInvalidId 是否在 ID 无效时抛出异常
* @return null or BO
*/
@@ -124,19 +119,51 @@ public void adminDelete(Collection ids) {
}
/**
- * 取所属部门简易信息
- *
- * @param userId 用户ID
+ * 后台管理-下拉框数据
+ * @param inferiorsOnly 只能看到本部门及以下
*/
- public SysDeptBO getPlainDeptByUserId(Long userId) {
- List deptIds = sysUserDeptRelationService.getUserDeptIds(userId);
- if (CollUtil.isEmpty(deptIds)) {
- return null;
+ public List adminSelectOptions(boolean inferiorsOnly) {
+ if (inferiorsOnly) {
+ UserRoleContainer currentUser = sysRoleService.getCurrentUserRoleContainer();
+ if (currentUser.isNotAnyAdmin()) {
+ // 非管理员才会限制,只能看到本部门及以下
+ UserDeptContainer deptContainer = getCurrentUserDeptContainer(true);
+ return entityList2BOs(deptContainer.getVisibleDepts());
+ }
}
- SysDeptEntity entity = sysDeptMapper.selectById(CollUtil.getFirst(deptIds));
- return this.entity2BO(entity);
+ // 能看所有
+ return adminList();
}
+ /**
+ * 取当前用户关联部门信息
+ * 仅内部使用
+ * @param queryVisibleDept 是否要进一步查询可见部门
+ */
+ protected UserDeptContainer getCurrentUserDeptContainer(boolean queryVisibleDept) {
+ return getSpecifiedUserDeptContainer(UserContextHolder.getUserId(), queryVisibleDept);
+ }
+
+ /**
+ * 取指定用户关联部门信息
+ * 仅内部使用
+ * @param queryVisibleDept 是否要进一步查询可见部门
+ */
+ protected UserDeptContainer getSpecifiedUserDeptContainer(Long specifiedUserId, boolean queryVisibleDept) {
+ List currentUserDeptIds = sysUserDeptRelationService.getUserDeptIds(specifiedUserId);
+ List currentUserDepts = Collections.emptyList();
+ if (CollUtil.isNotEmpty(currentUserDeptIds)) {
+ currentUserDepts = sysDeptMapper.selectBatchIds(currentUserDeptIds);
+ }
+ UserDeptContainer container = new UserDeptContainer(currentUserDeptIds, currentUserDepts);
+
+ if (queryVisibleDept && container.hasRelatedDepts()) {
+ List allDepts = sysDeptMapper.sortedList();
+ List inferiors = determineAllInferiors(allDepts, container.primaryRelatedDept());
+ container.updateVisibleDepts(inferiors);
+ }
+ return container;
+ }
/*
----------------------------------------------------------------
@@ -207,4 +234,34 @@ private void checkExistence(AdminInsertOrUpdateSysDeptDTO dto) {
throw new BusinessException(400, "已存在相同部门,请重新输入");
}
}
+
+ /**
+ * 找出本部门及所有下级部门
+ *
+ * @param start 本部门
+ * @return 本部门 + 所有下级部门
+ */
+ private List determineAllInferiors(List entityList, SysDeptEntity start) {
+ // 结果集合
+ List ret = new ArrayList<>(entityList.size());
+ Deque deque = new ArrayDeque<>();
+
+ // 转map提高效率
+ Map> groupByParentId = entityList.stream().collect(Collectors.groupingBy(SysDeptEntity::getParentId));
+
+ // 起点
+ ret.add(start);
+ deque.add(start);
+
+ // 循环填充下级部门实例
+ while (CollUtil.isNotEmpty(deque)) {
+ SysDeptEntity parent = deque.pop();
+ List children = groupByParentId.get(parent.getId());
+ if (CollUtil.isNotEmpty(children)) {
+ ret.addAll(children);
+ deque.addAll(children);
+ }
+ }
+ return ret;
+ }
}
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysLogService.java b/src/main/java/cc/uncarbon/module/sys/service/SysLogService.java
index cefe760..3be9cbb 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysLogService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysLogService.java
@@ -173,8 +173,7 @@ private PageResult entityPage2BOPage(Page entityPage) {
.setCurrent(entityPage.getCurrent())
.setSize(entityPage.getSize())
.setTotal(entityPage.getTotal())
- .setRecords(this.entityList2BOs(entityPage.getRecords()))
- ;
+ .setRecords(this.entityList2BOs(entityPage.getRecords()));
}
}
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysMenuService.java b/src/main/java/cc/uncarbon/module/sys/service/SysMenuService.java
index e5b9bc8..441d3d5 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysMenuService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysMenuService.java
@@ -174,8 +174,7 @@ public Map> getRoleIdPermissionMap(Collection roleIds) {
permissions = sysMenuMapper.selectList(null).stream()
.map(SysMenuEntity::getPermission)
.filter(StrUtil::isNotEmpty)
- .collect(Collectors.toSet())
- ;
+ .collect(Collectors.toSet());
} else {
// 非超级管理员则通过角色ID,关联查询拥有的菜单,菜单上有权限名
@@ -254,34 +253,28 @@ private SysMenuBO entity2BO(SysMenuEntity entity) {
// 这里是兼容 JDK8 的写法,使用较高 JDK 版本可使用语法糖
switch (bo.getType()) {
case DIR:
- case BUTTON: {
+ case BUTTON:
bo
.setComponent(SysConstant.VBEN_ADMIN_BLANK_VIEW)
.setExternalLink(null)
- .setPath(StrPool.SLASH + snowflakeIdStr)
- ;
+ .setPath(StrPool.SLASH + snowflakeIdStr);
break;
- }
- case MENU: {
+ case MENU:
bo
.setExternalLink(null)
- .setPath(bo.getComponent())
- ;
+ .setPath(bo.getComponent());
// 防止用户忘记加了, 主动补充/
if (CharSequenceUtil.isNotBlank(bo.getPath()) && !bo.getPath().startsWith(StrPool.SLASH)) {
bo.setPath(StrPool.SLASH + bo.getPath());
}
break;
- }
- case EXTERNAL_LINK: {
+ case EXTERNAL_LINK:
bo
.setComponent(bo.getExternalLink())
- .setPath(bo.getExternalLink())
- ;
+ .setPath(bo.getExternalLink());
break;
- }
+ default: break;
}
-
return bo;
}
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysParamService.java b/src/main/java/cc/uncarbon/module/sys/service/SysParamService.java
index 4dd74c8..2434e33 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysParamService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysParamService.java
@@ -216,8 +216,7 @@ private PageResult entityPage2BOPage(Page entityPage
.setCurrent(entityPage.getCurrent())
.setSize(entityPage.getSize())
.setTotal(entityPage.getTotal())
- .setRecords(this.entityList2BOs(entityPage.getRecords()))
- ;
+ .setRecords(this.entityList2BOs(entityPage.getRecords()));
}
/**
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysRoleMenuRelationService.java b/src/main/java/cc/uncarbon/module/sys/service/SysRoleMenuRelationService.java
index 7928003..145d039 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysRoleMenuRelationService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysRoleMenuRelationService.java
@@ -71,9 +71,7 @@ public void cleanAndBind(Long roleId, Collection menuIds) {
return;
}
- /*
- 先删除不再需要的关联关系
- */
+ // 先删除不再需要的关联关系
sysRoleMenuRelationMapper.delete(
new QueryWrapper()
.lambda()
@@ -81,31 +79,24 @@ public void cleanAndBind(Long roleId, Collection menuIds) {
.notIn(SysRoleMenuRelationEntity::getMenuId, menuIds)
);
- /*
- 取出需要增量更新的部分
- */
+ // 取出需要增量更新的部分
Set existingMenuIds = sysRoleMenuRelationMapper.selectList(menuIdsQuery)
.stream().map(SysRoleMenuRelationEntity::getMenuId)
.collect(Collectors.toSet());
menuIds.removeAll(existingMenuIds);
- if (CollUtil.isEmpty(menuIds)) {
- // 没有需要增量更新的部分
- return;
- }
-
- /*
- 批量插入需要增量更新的部分
- */
- List entityList = new ArrayList<>(menuIds.size());
- for (Long menuId : menuIds) {
- entityList.add(
- SysRoleMenuRelationEntity.builder()
- .roleId(roleId)
- .menuId(menuId)
- .build()
- );
+ if (CollUtil.isNotEmpty(menuIds)) {
+ // 批量插入需要增量更新的部分
+ List entityList = new ArrayList<>(menuIds.size());
+ for (Long menuId : menuIds) {
+ entityList.add(
+ SysRoleMenuRelationEntity.builder()
+ .roleId(roleId)
+ .menuId(menuId)
+ .build()
+ );
+ }
+ entityList.forEach(sysRoleMenuRelationMapper::insert);
}
- entityList.forEach(sysRoleMenuRelationMapper::insert);
}
}
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysRoleService.java b/src/main/java/cc/uncarbon/module/sys/service/SysRoleService.java
index 1db43fe..cb82d7c 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysRoleService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysRoleService.java
@@ -1,13 +1,16 @@
package cc.uncarbon.module.sys.service;
import cc.uncarbon.framework.core.constant.HelioConstant;
+import cc.uncarbon.framework.core.context.UserContextHolder;
import cc.uncarbon.framework.core.exception.BusinessException;
import cc.uncarbon.framework.core.function.StreamFunction;
import cc.uncarbon.framework.core.page.PageParam;
import cc.uncarbon.framework.core.page.PageResult;
+import cc.uncarbon.module.sys.constant.SysConstant;
import cc.uncarbon.module.sys.entity.SysRoleEntity;
import cc.uncarbon.module.sys.enums.SysErrorEnum;
import cc.uncarbon.module.sys.mapper.SysRoleMapper;
+import cc.uncarbon.module.sys.model.interior.UserRoleContainer;
import cc.uncarbon.module.sys.model.request.AdminBindRoleMenuRelationDTO;
import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysRoleDTO;
import cc.uncarbon.module.sys.model.request.AdminListSysRoleDTO;
@@ -44,6 +47,7 @@ public class SysRoleService {
* 后台管理-分页列表
*/
public PageResult adminList(PageParam pageParam, AdminListSysRoleDTO dto) {
+ Set invisibleRoleIds = determineInvisibleRoleIds();
Page entityPage = sysRoleMapper.selectPage(
new Page<>(pageParam.getPageNum(), pageParam.getPageSize()),
new QueryWrapper()
@@ -52,11 +56,13 @@ public PageResult adminList(PageParam pageParam, AdminListSysRoleDTO
.like(CharSequenceUtil.isNotBlank(dto.getTitle()), SysRoleEntity::getTitle, CharSequenceUtil.cleanBlank(dto.getTitle()))
// 值
.like(CharSequenceUtil.isNotBlank(dto.getValue()), SysRoleEntity::getValue, CharSequenceUtil.cleanBlank(dto.getValue()))
+ // 不显示特定角色
+ .notIn(CollUtil.isNotEmpty(invisibleRoleIds), SysRoleEntity::getId, invisibleRoleIds)
// 排序
.orderByDesc(SysRoleEntity::getCreatedAt)
);
- return this.entityPage2BOPage(entityPage);
+ return this.entityPage2BOPage(entityPage, true);
}
/**
@@ -82,7 +88,7 @@ public SysRoleBO getOneById(Long id, boolean throwIfInvalidId) throws BusinessEx
SysErrorEnum.INVALID_ID.assertNotNull(entity);
}
- return this.entity2BO(entity);
+ return this.entity2BO(entity, true);
}
/**
@@ -93,6 +99,7 @@ public SysRoleBO getOneById(Long id, boolean throwIfInvalidId) throws BusinessEx
@Transactional(rollbackFor = Exception.class)
public Long adminInsert(AdminInsertOrUpdateSysRoleDTO dto) {
log.info("[后台管理-新增后台角色] >> 入参={}", dto);
+ preInsertOrUpdateCheck(dto);
this.checkExistence(dto);
dto.setId(null);
@@ -110,8 +117,11 @@ public Long adminInsert(AdminInsertOrUpdateSysRoleDTO dto) {
@Transactional(rollbackFor = Exception.class)
public void adminUpdate(AdminInsertOrUpdateSysRoleDTO dto) {
log.info("[后台管理-编辑后台角色] >> 入参={}", dto);
+ preInsertOrUpdateCheck(dto);
this.checkExistence(dto);
+ // 暂不检查该角色是否为当前用户关联的角色
+
SysRoleEntity entity = new SysRoleEntity();
BeanUtil.copyProperties(dto, entity);
@@ -124,6 +134,7 @@ public void adminUpdate(AdminInsertOrUpdateSysRoleDTO dto) {
@Transactional(rollbackFor = Exception.class)
public void adminDelete(Collection ids) {
log.info("[后台管理-删除后台角色] >> 入参={}", ids);
+ preDeleteCheck(ids);
sysRoleMapper.deleteBatchIds(ids);
}
@@ -134,12 +145,125 @@ public void adminDelete(Collection ids) {
*/
@Transactional(rollbackFor = Exception.class)
public Set adminBindMenus(AdminBindRoleMenuRelationDTO dto) {
+ preBindRoleMenuRelationCheck(dto);
Set newPermissions = sysMenuService.listPermissionsByMenuIds(dto.getMenuIds());
sysRoleMenuRelationService.cleanAndBind(dto.getRoleId(), dto.getMenuIds());
return newPermissions;
}
+ /**
+ * 后台管理-下拉框数据
+ */
+ public List adminSelectOptions() {
+ Set invisibleRoleIds = determineInvisibleRoleIds();
+ List entityList = sysRoleMapper.selectList(
+ new QueryWrapper()
+ .lambda()
+ // 只取特定字段
+ .select(SysRoleEntity::getId, SysRoleEntity::getTitle)
+ // 不显示特定角色
+ .notIn(CollUtil.isNotEmpty(invisibleRoleIds), SysRoleEntity::getId, invisibleRoleIds)
+ // 排序
+ .orderByAsc(SysRoleEntity::getId)
+ );
+ // 无需填充菜单IDs
+ return entityList2BOs(entityList, false);
+ }
+
+ /**
+ * 后台管理-删除指定租户的特定角色
+ * @param tenantIds 租户IDs,非主键ID,不能为空
+ * @param roleValues 角色值集合,可以为空
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public void adminDeleteTenantRoles(Collection tenantIds, Collection roleValues) {
+ if (CollUtil.isEmpty(tenantIds)) {
+ return;
+ }
+
+ sysRoleMapper.delete(
+ new QueryWrapper()
+ .lambda()
+ // 租户ID
+ .in(SysRoleEntity::getTenantId, tenantIds)
+ // 值相符
+ .in(CollUtil.isNotEmpty(roleValues), SysRoleEntity::getValue, roleValues)
+ );
+ }
+
+ /**
+ * 取用户ID拥有角色对应的 角色ID-角色名 map
+ *
+ * @param userId 用户ID
+ * @return 失败返回空 map
+ */
+ public Map getRoleMapByUserId(Long userId) {
+ Set roleIds = sysUserRoleRelationService.listRoleIdsByUserId(userId);
+
+ if (CollUtil.isEmpty(roleIds)) {
+ return Collections.emptyMap();
+ }
+
+ // 根据角色Ids取 map
+ return sysRoleMapper.selectList(
+ new QueryWrapper()
+ .lambda()
+ .select(SysRoleEntity::getId, SysRoleEntity::getValue)
+ .in(SysRoleEntity::getId, roleIds)
+ ).stream().collect(Collectors.toMap(SysRoleEntity::getId, SysRoleEntity::getValue, StreamFunction.ignoredThrowingMerger()));
+ }
+
+ /**
+ * 取当前用户关联角色信息
+ * 仅内部使用
+ */
+ protected UserRoleContainer getCurrentUserRoleContainer() {
+ return getSpecifiedUserRoleContainer(UserContextHolder.getUserId());
+ }
+
+ /**
+ * 取指定用户关联角色信息
+ * 仅内部使用
+ */
+ protected UserRoleContainer getSpecifiedUserRoleContainer(Long specifiedUserId) {
+ Set currentUserRoleIds = sysUserRoleRelationService.listRoleIdsByUserId(specifiedUserId);
+ List currentUserRoles = Collections.emptyList();
+ if (CollUtil.isNotEmpty(currentUserRoleIds)) {
+ currentUserRoles = sysRoleMapper.selectBatchIds(currentUserRoleIds);
+ }
+ return new UserRoleContainer(currentUserRoleIds, currentUserRoles);
+ }
+
+ /**
+ * 确定不可见角色IDs
+ * 仅内部使用
+ * 租户管理员:列表中不显示超级管理员角色
+ * 普通角色:列表中不显示超级管理员、租户管理员角色
+ * @return mutable Set,支持外部改变元素
+ */
+ protected Set determineInvisibleRoleIds() {
+ UserRoleContainer currentUser = getCurrentUserRoleContainer();
+ // 超级管理员:不限制
+ if (currentUser.isSuperAdmin()) {
+ return new HashSet<>();
+ }
+ // 租户管理员:列表中不显示超级管理员角色
+ if (currentUser.isTenantAdmin()) {
+ return CollUtil.newHashSet(SysConstant.SUPER_ADMIN_ROLE_ID);
+ }
+ // 普通角色:列表中不显示超级管理员、租户管理员角色
+ Set ret = sysRoleMapper.selectList(
+ new QueryWrapper()
+ .lambda()
+ // 仅取主键ID
+ .select(SysRoleEntity::getId)
+ // 值相符
+ .eq(SysRoleEntity::getValue, SysConstant.TENANT_ADMIN_ROLE_VALUE)
+ ).stream().map(SysRoleEntity::getId).collect(Collectors.toSet());
+ ret.add(SysConstant.SUPER_ADMIN_ROLE_ID);
+ return ret;
+ }
/*
----------------------------------------------------------------
@@ -151,9 +275,10 @@ public Set adminBindMenus(AdminBindRoleMenuRelationDTO dto) {
* 实体转 BO
*
* @param entity 实体
+ * @param fillMenuIds 是否根据实体ID,查询关联菜单IDs并填充到BO
* @return BO
*/
- private SysRoleBO entity2BO(SysRoleEntity entity) {
+ private SysRoleBO entity2BO(SysRoleEntity entity, boolean fillMenuIds) {
if (entity == null) {
return null;
}
@@ -162,7 +287,9 @@ private SysRoleBO entity2BO(SysRoleEntity entity) {
BeanUtil.copyProperties(entity, bo);
// 可以在此处为BO填充字段
- bo.setMenuIds(sysRoleMenuRelationService.listMenuIdsByRoleIds(Collections.singleton(bo.getId())));
+ if (fillMenuIds) {
+ bo.setMenuIds(sysRoleMenuRelationService.listMenuIdsByRoleIds(Collections.singleton(bo.getId())));
+ }
return bo;
}
@@ -170,13 +297,14 @@ private SysRoleBO entity2BO(SysRoleEntity entity) {
* 实体 List 转 BO List
*
* @param entityList 实体 List
+ * @param fillMenuIds 是否根据实体ID,查询关联菜单IDs并填充到BO
* @return BO List
*/
- private List entityList2BOs(List entityList) {
+ private List entityList2BOs(List entityList, boolean fillMenuIds) {
// 深拷贝
List ret = new ArrayList<>(entityList.size());
entityList.forEach(
- entity -> ret.add(this.entity2BO(entity))
+ entity -> ret.add(this.entity2BO(entity, fillMenuIds))
);
return ret;
@@ -186,15 +314,16 @@ private List entityList2BOs(List entityList) {
* 实体分页转 BO 分页
*
* @param entityPage 实体分页
+ * @param fillMenuIds 是否根据实体ID,查询关联菜单IDs并填充到BO
* @return BO 分页
*/
- private PageResult entityPage2BOPage(Page entityPage) {
+ private PageResult entityPage2BOPage(Page entityPage, boolean fillMenuIds) {
return new PageResult()
.setCurrent(entityPage.getCurrent())
.setSize(entityPage.getSize())
.setTotal(entityPage.getTotal())
- .setRecords(this.entityList2BOs(entityPage.getRecords()))
- ;
+ // 需填充菜单IDs
+ .setRecords(this.entityList2BOs(entityPage.getRecords(), fillMenuIds));
}
/**
@@ -216,27 +345,98 @@ private void checkExistence(AdminInsertOrUpdateSysRoleDTO dto) {
if (existingEntity != null && !existingEntity.getId().equals(dto.getId())) {
throw new BusinessException(400, "已存在相同后台角色,请重新输入");
}
+
+ if (dto.creatingNewTenantAdmin()) {
+ long qty = sysRoleMapper.selectCount(
+ new QueryWrapper()
+ .lambda()
+ // 租户ID相同
+ .eq(SysRoleEntity::getTenantId, dto.getTenantId())
+ // 角色编码相同
+ .eq(SysRoleEntity::getValue, dto.getValue())
+ .last(HelioConstant.CRUD.SQL_LIMIT_1)
+ );
+ SysErrorEnum.NEED_DELETE_EXISTING_TENANT_ADMIN_ROLE.assertTrue(qty <= 0L, dto.getTenantId());
+ }
}
/**
- * 取用户ID拥有角色对应的 角色ID-角色名 map
- *
- * @param userId 用户ID
- * @return 失败返回空 map
+ * 新增/编辑后台角色信息前检查
*/
- public Map getRoleMapByUserId(Long userId) {
- Set roleIds = sysUserRoleRelationService.listRoleIdsByUserId(userId);
+ private void preInsertOrUpdateCheck(AdminInsertOrUpdateSysRoleDTO dto) {
+ if (SysConstant.SUPER_ADMIN_ROLE_VALUE.equalsIgnoreCase(dto.getValue())) {
+ // 角色编码不能为SuperAdmin
+ throw new BusinessException(SysErrorEnum.ROLE_VALUE_CANNOT_BE, SysConstant.SUPER_ADMIN_ROLE_VALUE);
+ }
+ if (SysConstant.TENANT_ADMIN_ROLE_VALUE.equalsIgnoreCase(dto.getValue()) && !dto.creatingNewTenantAdmin()) {
+ // 除非是新增租户时,同时新增租户管理员角色,否则角色编码不能为Admin
+ throw new BusinessException(SysErrorEnum.ROLE_VALUE_CANNOT_BE, SysConstant.TENANT_ADMIN_ROLE_VALUE);
+ }
- if (CollUtil.isEmpty(roleIds)) {
- return Collections.emptyMap();
+ boolean isUpdating = Objects.nonNull(dto.getId());
+ if (isUpdating) {
+ SysRoleEntity existingRole = sysRoleMapper.selectById(dto.getId());
+ SysErrorEnum.INVALID_ID.assertNotNull(existingRole);
+ if (existingRole.isSuperAdmin() || existingRole.isTenantAdmin()) {
+ // 原来角色编码为SuperAdmin或Admin的,不能被改变
+ throw new BusinessException(SysErrorEnum.ROLE_VALUE_CANNOT_BE, existingRole.getValue());
+ }
}
+ }
- // 根据角色Ids取 map
- return sysRoleMapper.selectList(
- new QueryWrapper()
- .lambda()
- .select(SysRoleEntity::getId, SysRoleEntity::getValue)
- .in(SysRoleEntity::getId, roleIds)
- ).stream().collect(Collectors.toMap(SysRoleEntity::getId, SysRoleEntity::getValue, StreamFunction.ignoredThrowingMerger()));
+ /**
+ * 删除后台角色前检查
+ */
+ private void preDeleteCheck(Collection ids) {
+ if (CollUtil.contains(ids, SysConstant.SUPER_ADMIN_ROLE_ID)) {
+ throw new BusinessException(SysErrorEnum.CANNOT_DELETE_SUPER_ADMIN_ROLE);
+ }
+
+ List existingEntityList = sysRoleMapper.selectBatchIds(ids);
+ for (SysRoleEntity item : existingEntityList) {
+ if (item.isSuperAdmin()) {
+ throw new BusinessException(SysErrorEnum.CANNOT_DELETE_SUPER_ADMIN_ROLE);
+ }
+
+ if (item.isTenantAdmin()) {
+ throw new BusinessException(SysErrorEnum.CANNOT_DELETE_TENANT_ADMIN_ROLE);
+ }
+ }
+
+ UserRoleContainer currentUser = getCurrentUserRoleContainer();
+ if (CollUtil.containsAny(currentUser.getRelatedRoleIds(), ids)) {
+ throw new BusinessException(SysErrorEnum.CANNOT_DELETE_SELF_ROLE);
+ }
+ }
+
+ /**
+ * 绑定后台角色与菜单关联关系前检查
+ * 防止越权访问漏洞
+ */
+ private void preBindRoleMenuRelationCheck(AdminBindRoleMenuRelationDTO dto) {
+ UserRoleContainer currentUser = getCurrentUserRoleContainer();
+ if (SysConstant.SUPER_ADMIN_ROLE_ID.equals(dto.getRoleId())) {
+ throw new BusinessException(SysErrorEnum.CANNOT_BIND_MENUS_FOR_SUPER_ADMIN_ROLE);
+ }
+
+ if (CollUtil.contains(currentUser.getRelatedRoleIds(), dto.getRoleId())) {
+ // 不能动自身角色
+ throw new BusinessException(SysErrorEnum.CANNOT_BIND_MENUS_FOR_SELF);
+ }
+
+ // 有且只有当前用户为超级管理员,才可以为租户管理员绑定菜单
+ SysRoleEntity targetRole = sysRoleMapper.selectById(dto.getRoleId());
+ if (targetRole.isTenantAdmin() && !currentUser.isSuperAdmin()) {
+ throw new BusinessException(SysErrorEnum.CANNOT_BIND_MENUS_FOR_TENANT_ADMIN_ROLE);
+ }
+
+ if (CollUtil.isNotEmpty(dto.getMenuIds()) && !currentUser.isSuperAdmin()) {
+ // 超级管理员之外的角色,都需要校验自身菜单范围是否满足输入值
+ Set visibleMenuIds = sysRoleMenuRelationService.listMenuIdsByRoleIds(currentUser.getRelatedRoleIds());
+ if (!CollUtil.containsAll(visibleMenuIds, dto.getMenuIds())) {
+ // 可能存在超自身权限赋权
+ throw new BusinessException(SysErrorEnum.BEYOND_AUTHORITY_BIND_MENUS);
+ }
+ }
}
}
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysTenantService.java b/src/main/java/cc/uncarbon/module/sys/service/SysTenantService.java
index 933d57d..d466c7d 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysTenantService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysTenantService.java
@@ -13,6 +13,7 @@
import cc.uncarbon.module.sys.model.request.AdminUpdateSysTenantDTO;
import cc.uncarbon.module.sys.model.response.SysTenantBO;
import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -24,6 +25,7 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
@@ -58,7 +60,7 @@ public PageResult adminList(PageParam pageParam, AdminListSysTenant
.orderByDesc(SysTenantEntity::getCreatedAt)
);
- return this.entityPage2BOPage(entityPage);
+ return this.entityPage2BOPage(entityPage, true);
}
/**
@@ -84,7 +86,7 @@ public SysTenantBO getOneById(Long id, boolean throwIfInvalidId) throws Business
SysErrorEnum.INVALID_ID.assertNotNull(entity);
}
- return this.entity2BO(entity);
+ return this.entity2BO(entity, true);
}
/**
@@ -108,7 +110,6 @@ public SysTenantEntity adminInsert(AdminInsertSysTenantDTO dto) {
@Transactional(rollbackFor = Exception.class)
public void adminUpdate(AdminUpdateSysTenantDTO dto) {
log.info("[后台管理-编辑系统租户] >> 入参={}", dto);
- this.checkExistence(dto);
SysTenantEntity entity = new SysTenantEntity();
BeanUtil.copyProperties(dto, entity);
@@ -151,7 +152,7 @@ public SysTenantEntity getTenantEntityByTenantId(Long tenantId) {
*
* @param dto DTO
*/
- public void checkExistence(AdminUpdateSysTenantDTO dto) {
+ public void checkExistence(AdminInsertSysTenantDTO dto) {
SysTenantEntity existingEntity = sysTenantMapper.selectOne(
new QueryWrapper()
.lambda()
@@ -170,6 +171,18 @@ public void checkExistence(AdminUpdateSysTenantDTO dto) {
}
}
+ /**
+ * 根据主键IDs,取租户BOs
+ * @param fillTenantAdminUser 是否根据租户管理员用户ID,查询关联用户信息并填充到BO
+ */
+ public List listByIds(Collection ids, boolean fillTenantAdminUser) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+ List entityList = sysTenantMapper.selectBatchIds(ids);
+ return entityList2BOs(entityList, fillTenantAdminUser);
+ }
+
/*
----------------------------------------------------------------
私有方法 private methods
@@ -180,9 +193,10 @@ public void checkExistence(AdminUpdateSysTenantDTO dto) {
* 实体转 BO
*
* @param entity 实体
+ * @param fillTenantAdminUser 是否根据租户管理员用户ID,查询关联用户信息并填充到BO
* @return BO
*/
- private SysTenantBO entity2BO(SysTenantEntity entity) {
+ private SysTenantBO entity2BO(SysTenantEntity entity, boolean fillTenantAdminUser) {
if (entity == null) {
return null;
}
@@ -191,10 +205,8 @@ private SysTenantBO entity2BO(SysTenantEntity entity) {
BeanUtil.copyProperties(entity, bo);
// 可以在此处为BO填充字段
- if (ObjectUtil.isNotNull(entity.getTenantAdminUserId())) {
- bo
- .setTenantAdminUser(sysUserMapper.getBaseInfoByUserId(entity.getTenantAdminUserId()))
- ;
+ if (fillTenantAdminUser && ObjectUtil.isNotNull(entity.getTenantAdminUserId())) {
+ bo.setTenantAdminUser(sysUserMapper.getBaseInfoByUserId(entity.getTenantAdminUserId()));
}
return bo;
@@ -204,13 +216,14 @@ private SysTenantBO entity2BO(SysTenantEntity entity) {
* 实体 List 转 BO List
*
* @param entityList 实体 List
+ * @param fillTenantAdminUser 是否根据租户管理员用户ID,查询关联用户信息并填充到BO
* @return BO List
*/
- private List entityList2BOs(List entityList) {
+ private List entityList2BOs(List entityList, boolean fillTenantAdminUser) {
// 深拷贝
List ret = new ArrayList<>(entityList.size());
entityList.forEach(
- entity -> ret.add(this.entity2BO(entity))
+ entity -> ret.add(this.entity2BO(entity, fillTenantAdminUser))
);
return ret;
@@ -222,13 +235,12 @@ private List entityList2BOs(List entityList) {
* @param entityPage 实体分页
* @return BO 分页
*/
- private PageResult entityPage2BOPage(Page entityPage) {
+ private PageResult entityPage2BOPage(Page entityPage, boolean fillTenantAdminUser) {
return new PageResult()
.setCurrent(entityPage.getCurrent())
.setSize(entityPage.getSize())
.setTotal(entityPage.getTotal())
- .setRecords(this.entityList2BOs(entityPage.getRecords()))
- ;
+ .setRecords(this.entityList2BOs(entityPage.getRecords(), fillTenantAdminUser));
}
}
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysUserDeptRelationService.java b/src/main/java/cc/uncarbon/module/sys/service/SysUserDeptRelationService.java
index 0052976..52c9603 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysUserDeptRelationService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysUserDeptRelationService.java
@@ -3,6 +3,7 @@
import cc.uncarbon.framework.core.constant.HelioConstant;
import cc.uncarbon.module.sys.entity.SysUserDeptRelationEntity;
import cc.uncarbon.module.sys.mapper.SysUserDeptRelationMapper;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
@@ -10,8 +11,11 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
@@ -28,7 +32,7 @@ public class SysUserDeptRelationService {
/**
* 列举用户ID关联的部门IDs
*
- * @return 联的部门IDs;目前最多只有1个元素
+ * @return 关联的部门IDs;目前最多只有1个元素
*/
public List getUserDeptIds(Long userId) {
SysUserDeptRelationEntity entity = sysUserDeptRelationMapper.selectOne(
@@ -67,4 +71,21 @@ public void cleanAndBind(Long userId, Long deptId) {
}
+ /**
+ * 列举部门IDs关联的用户IDs
+ */
+ public Set listUserIdsByDeptIds(Collection deptIds) {
+ if (CollUtil.isEmpty(deptIds)) {
+ return Collections.emptySet();
+ }
+
+ return sysUserDeptRelationMapper.selectList(
+ new QueryWrapper()
+ .lambda()
+ // 只要用户ID
+ .select(SysUserDeptRelationEntity::getUserId)
+ .in(SysUserDeptRelationEntity::getDeptId, deptIds)
+ ).stream().map(SysUserDeptRelationEntity::getUserId).collect(Collectors.toSet());
+ }
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysUserRoleRelationService.java b/src/main/java/cc/uncarbon/module/sys/service/SysUserRoleRelationService.java
index 27a4713..404f563 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysUserRoleRelationService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysUserRoleRelationService.java
@@ -10,6 +10,7 @@
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
+import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
@@ -83,4 +84,23 @@ public Set listRoleIdsByUserId(Long userId) throws IllegalArgumentExceptio
.eq(SysUserRoleRelationEntity::getUserId, userId)
).stream().map(SysUserRoleRelationEntity::getRoleId).collect(Collectors.toSet());
}
+
+ /**
+ * 取角色IDs关联的用户IDs
+ *
+ * @param roleIds 角色IDs
+ * @return 空集合or用户IDs
+ */
+ public Set listUserIdsByRoleIds(Collection roleIds) {
+ if (CollUtil.isEmpty(roleIds)) {
+ return Collections.emptySet();
+ }
+
+ return sysUserRoleRelationMapper.selectList(
+ new QueryWrapper()
+ .lambda()
+ .select(SysUserRoleRelationEntity::getUserId)
+ .in(SysUserRoleRelationEntity::getRoleId, roleIds)
+ ).stream().map(SysUserRoleRelationEntity::getUserId).collect(Collectors.toSet());
+ }
}
diff --git a/src/main/java/cc/uncarbon/module/sys/service/SysUserService.java b/src/main/java/cc/uncarbon/module/sys/service/SysUserService.java
index ea20dbf..c9ea5b8 100644
--- a/src/main/java/cc/uncarbon/module/sys/service/SysUserService.java
+++ b/src/main/java/cc/uncarbon/module/sys/service/SysUserService.java
@@ -8,13 +8,15 @@
import cc.uncarbon.framework.core.page.PageParam;
import cc.uncarbon.framework.core.page.PageResult;
import cc.uncarbon.framework.core.props.HelioProperties;
+import cc.uncarbon.module.sys.constant.SysConstant;
import cc.uncarbon.module.sys.entity.SysTenantEntity;
import cc.uncarbon.module.sys.entity.SysUserEntity;
import cc.uncarbon.module.sys.enums.SysErrorEnum;
import cc.uncarbon.module.sys.enums.SysUserStatusEnum;
import cc.uncarbon.module.sys.mapper.SysUserMapper;
+import cc.uncarbon.module.sys.model.interior.UserDeptContainer;
+import cc.uncarbon.module.sys.model.interior.UserRoleContainer;
import cc.uncarbon.module.sys.model.request.*;
-import cc.uncarbon.module.sys.model.response.SysDeptBO;
import cc.uncarbon.module.sys.model.response.SysUserBO;
import cc.uncarbon.module.sys.model.response.SysUserLoginBO;
import cc.uncarbon.module.sys.model.response.VbenAdminUserInfoVO;
@@ -29,10 +31,12 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
+import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@@ -53,6 +57,7 @@ public class SysUserService {
private final SysTenantService sysTenantService;
private final SysUserDeptRelationService sysUserDeptRelationService;
private final SysUserRoleRelationService sysUserRoleRelationService;
+ private final SysRoleMenuRelationService sysRoleMenuRelationService;
private final HelioProperties helioProperties;
private boolean isTenantEnabled;
@@ -66,17 +71,41 @@ public void postConstruct() {
* 后台管理-分页列表
*/
public PageResult adminList(PageParam pageParam, AdminListSysUserDTO dto) {
+ // 预处理:根据【手动选择的部门】筛选用户
+ Set deptUserIds = Collections.emptySet();
+ if (dto.needFilterBySelectedDeptId()) {
+ deptUserIds = sysUserDeptRelationService.listUserIdsByDeptIds(Collections.singleton(dto.getSelectedDeptId()));
+ if (CollUtil.isEmpty(deptUserIds)) {
+ // 【手动选择的部门】没有任何用户ID,直接返回空列表
+ return new PageResult<>(pageParam);
+ }
+ }
+
+ // 预处理:根据【只能看到本部门及下级部门原则】筛选用户
+ Set visibleUserIds = determineVisibleDeptUserIds();
+ if (Objects.equals(CollUtil.getFirst(visibleUserIds), BigInteger.ZERO.longValue())) {
+ // 其实啥也看不到……
+ return new PageResult<>(pageParam);
+ }
+
+ Set invisibleUserIds = determineInvisibleUserIds();
Page entityPage = sysUserMapper.selectPage(
new Page<>(pageParam.getPageNum(), pageParam.getPageSize()),
new QueryWrapper()
.lambda()
// 手机号
.like(CharSequenceUtil.isNotBlank(dto.getPhoneNo()), SysUserEntity::getPhoneNo, CharSequenceUtil.cleanBlank(dto.getPhoneNo()))
+ // 根据【手动选择的部门ID】筛选用户
+ .in(CollUtil.isNotEmpty(deptUserIds), SysUserEntity::getId, deptUserIds)
+ // 根据【只能看到本部门及下级部门原则】筛选用户
+ .in(CollUtil.isNotEmpty(visibleUserIds), SysUserEntity::getId, visibleUserIds)
+ // 不显示特定用户
+ .notIn(CollUtil.isNotEmpty(invisibleUserIds), SysUserEntity::getId, invisibleUserIds)
// 排序
.orderByDesc(SysUserEntity::getCreatedAt)
);
- return this.entityPage2BOPage(entityPage);
+ return this.entityPage2BOPage(entityPage, true);
}
/**
@@ -97,12 +126,14 @@ public SysUserBO getOneById(Long id) {
* @return null or BO
*/
public SysUserBO getOneById(Long id, boolean throwIfInvalidId) throws BusinessException {
+ dataScopeCheck(Collections.singleton(id));
+
SysUserEntity entity = sysUserMapper.selectById(id);
if (throwIfInvalidId) {
SysErrorEnum.INVALID_ID.assertNotNull(entity);
}
- return this.entity2BO(entity);
+ return this.entity2BO(entity, true);
}
/**
@@ -115,6 +146,14 @@ public Long adminInsert(AdminInsertOrUpdateSysUserDTO dto) {
log.info("[后台管理-新增后台用户] >> 入参={}", dto);
this.checkExistence(dto);
+ if (Objects.nonNull(dto.getDeptId())) {
+ // 对传入的部门ID,做数据越权检查
+ UserDeptContainer deptContainer = sysDeptService.getCurrentUserDeptContainer(true);
+ if (deptContainer.hasVisibleDepts() && !CollUtil.contains(deptContainer.getVisibleDeptIds(), dto.getDeptId())) {
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_THIS_USER);
+ }
+ }
+
dto.setId(null);
SysUserEntity entity = new SysUserEntity();
BeanUtil.copyProperties(dto, entity);
@@ -123,8 +162,7 @@ public Long adminInsert(AdminInsertOrUpdateSysUserDTO dto) {
entity
.setSalt(salt)
.setPin(dto.getUsername())
- .setPwd(PwdUtil.encrypt(dto.getPasswordOfNewUser(), salt))
- ;
+ .setPwd(PwdUtil.encrypt(dto.getPasswordOfNewUser(), salt));
sysUserMapper.insert(entity);
@@ -139,16 +177,17 @@ public Long adminInsert(AdminInsertOrUpdateSysUserDTO dto) {
@Transactional(rollbackFor = Exception.class)
public void adminUpdate(AdminInsertOrUpdateSysUserDTO dto) {
log.info("[后台管理-编辑后台用户] >> 入参={}", dto);
+ preUpdateCheck(dto.getId(), dto.getStatus());
this.checkExistence(dto);
- SysUserEntity updateEntity = new SysUserEntity();
- BeanUtil.copyProperties(dto, updateEntity);
+ SysUserEntity entity = new SysUserEntity();
+ BeanUtil.copyProperties(dto, entity);
// 手动处理异名字段
- updateEntity.setPin(dto.getUsername());
+ entity.setPin(dto.getUsername());
sysUserDeptRelationService.cleanAndBind(dto.getId(), dto.getDeptId());
- sysUserMapper.updateById(updateEntity);
+ sysUserMapper.updateById(entity);
}
/**
@@ -157,6 +196,7 @@ public void adminUpdate(AdminInsertOrUpdateSysUserDTO dto) {
@Transactional(rollbackFor = Exception.class)
public void adminDelete(Collection ids) {
log.info("[后台管理-删除后台用户] >> 入参={}", ids);
+ preDeleteCheck(ids);
sysUserMapper.deleteBatchIds(ids);
}
@@ -166,7 +206,6 @@ public void adminDelete(Collection ids) {
public SysUserLoginBO adminLogin(SysUserLoginDTO dto) {
/*
如果启用了多租户功能,并且前端指定了租户ID,则先查库确认租户是否有效
-
注意:数据源级多租户,登录前【必须】主动指定租户ID,如: dto.setTenantId(101L)
*/
// ConcurrentHashMap 的 value 不能为 null,还是 new 一个吧
@@ -205,7 +244,7 @@ public SysUserLoginBO adminLogin(SysUserLoginDTO dto) {
this.updateLastLoginAt(sysUserEntity.getId(), LocalDateTimeUtil.now());
// 取账号完整信息
- SysUserBO sysUserBO = this.entity2BO(sysUserEntity);
+ SysUserBO sysUserBO = this.entity2BO(sysUserEntity, false);
Map roleMap = sysRoleService.getRoleMapByUserId(sysUserBO.getId());
Map> roleIdPermissionMap = sysMenuService.getRoleIdPermissionMap(roleMap.keySet());
@@ -221,8 +260,7 @@ public SysUserLoginBO adminLogin(SysUserLoginDTO dto) {
.setRoles(new ArrayList<>(roleMap.values()))
.setPermissions(permissions)
.setRoleIdPermissionMap(roleIdPermissionMap)
- .setTenantContext(tenantContext)
- ;
+ .setTenantContext(tenantContext);
return ret;
}
@@ -243,13 +281,13 @@ public VbenAdminUserInfoVO adminGetCurrentUserInfo() {
* 后台管理-重置某用户密码
*/
public void adminResetUserPassword(AdminResetSysUserPasswordDTO dto) {
+ preUpdateCheck(dto.getUserId(), null);
SysUserEntity sysUserEntity = sysUserMapper.selectById(dto.getUserId());
SysUserEntity templateEntity = new SysUserEntity();
templateEntity
.setPwd(PwdUtil.encrypt(dto.getRandomPassword(), sysUserEntity.getSalt()))
- .setId(dto.getUserId())
- ;
+ .setId(dto.getUserId());
sysUserMapper.updateById(templateEntity);
}
@@ -265,8 +303,7 @@ public void adminUpdateCurrentUserPassword(AdminUpdateCurrentSysUserPasswordDTO
sysUserEntity
.setPwd(PwdUtil.encrypt(dto.getConfirmNewPassword(), sysUserEntity.getSalt()))
- .setId(UserContextHolder.getUserId())
- ;
+ .setId(UserContextHolder.getUserId());
sysUserMapper.updateById(sysUserEntity);
}
@@ -275,6 +312,7 @@ public void adminUpdateCurrentUserPassword(AdminUpdateCurrentSysUserPasswordDTO
* 后台管理-绑定用户与角色关联关系
*/
public void adminBindRoles(AdminBindUserRoleRelationDTO dto) {
+ preBindUserRoleRelationCheck(dto);
sysUserRoleRelationService.cleanAndBind(dto.getUserId(), dto.getRoleIds());
}
@@ -294,10 +332,29 @@ public Set listRelatedRoleIds(Long userId) {
if (ObjectUtil.isNull(userId)) {
return Collections.emptySet();
}
-
return sysRoleService.getRoleMapByUserId(userId).keySet();
}
+ /**
+ * 后台管理 - 取租户用户IDs
+ * @param tenantId 租户ID,非主键ID
+ * @param statusEnums 仅保留符合指定状态的,可以为null
+ */
+ public List listUserIdsByTenantId(Long tenantId, Collection statusEnums) {
+ if (Objects.isNull(tenantId)) {
+ return Collections.emptyList();
+ }
+ // 备份原始租户上下文;以下查询方式可同时兼容行级、数据源级多租户
+ TenantContext originContext = TenantContextHolder.getTenantContext();
+ try {
+ // 临时切换租户
+ TenantContextHolder.setTenantContext(new TenantContext(tenantId, CharSequenceUtil.EMPTY));
+ return sysUserMapper.selectIds(statusEnums);
+ } finally {
+ TenantContextHolder.setTenantContext(originContext);
+ }
+ }
+
/*
----------------------------------------------------------------
私有方法 private methods
@@ -308,9 +365,10 @@ public Set listRelatedRoleIds(Long userId) {
* 实体转 BO
*
* @param entity 实体
+ * @param fillDeptInfo 是否根据实体部门ID,查询关联部门信息并填充到BO
* @return BO
*/
- private SysUserBO entity2BO(SysUserEntity entity) {
+ private SysUserBO entity2BO(SysUserEntity entity, boolean fillDeptInfo) {
if (entity == null) {
return null;
}
@@ -320,14 +378,11 @@ private SysUserBO entity2BO(SysUserEntity entity) {
// 可以在此处为BO填充字段
bo.setUsername(entity.getPin());
- SysDeptBO dept = sysDeptService.getPlainDeptByUserId(bo.getId());
- if (dept != null) {
- bo
- .setDeptId(dept.getId())
- .setDeptTitle(dept.getTitle())
- ;
+ if (fillDeptInfo) {
+ Optional.ofNullable(sysDeptService.getSpecifiedUserDeptContainer(bo.getId(), false))
+ .map(UserDeptContainer::primaryRelatedDept)
+ .ifPresent(deptInfo -> bo.setDeptId(deptInfo.getId()).setDeptTitle(deptInfo.getTitle()));
}
-
return bo;
}
@@ -335,9 +390,10 @@ private SysUserBO entity2BO(SysUserEntity entity) {
* 实体 List 转 BO List
*
* @param entityList 实体 List
+ * @param fillDeptInfo 是否根据实体部门ID,查询关联部门信息并填充到BO
* @return BO List
*/
- private List entityList2BOs(List entityList) {
+ private List entityList2BOs(List entityList, boolean fillDeptInfo) {
if (CollUtil.isEmpty(entityList)) {
return Collections.emptyList();
}
@@ -345,7 +401,7 @@ private List entityList2BOs(List entityList) {
// 深拷贝
List ret = new ArrayList<>(entityList.size());
entityList.forEach(
- entity -> ret.add(this.entity2BO(entity))
+ entity -> ret.add(this.entity2BO(entity, fillDeptInfo))
);
return ret;
@@ -355,15 +411,15 @@ private List entityList2BOs(List entityList) {
* 实体分页转 BO 分页
*
* @param entityPage 实体分页
+ * @param fillDeptInfo 是否根据实体部门ID,查询关联部门信息并填充到BO
* @return BO 分页
*/
- private PageResult entityPage2BOPage(Page entityPage) {
+ private PageResult entityPage2BOPage(Page entityPage, boolean fillDeptInfo) {
return new PageResult()
.setCurrent(entityPage.getCurrent())
.setSize(entityPage.getSize())
.setTotal(entityPage.getTotal())
- .setRecords(this.entityList2BOs(entityPage.getRecords()))
- ;
+ .setRecords(this.entityList2BOs(entityPage.getRecords(), fillDeptInfo));
}
/**
@@ -379,6 +435,46 @@ private void checkExistence(AdminInsertOrUpdateSysUserDTO dto) {
}
}
+ /**
+ * 确定本部门及下级部门用户IDs
+ * 返回空集合代表不限制
+ * 返回[0]或有元素集合,表示有限制
+ */
+ private Set determineVisibleDeptUserIds() {
+ Set visibleUserIds = Collections.emptySet();
+ UserDeptContainer deptContainer = sysDeptService.getCurrentUserDeptContainer(true);
+ if (deptContainer.hasVisibleDepts()) {
+ visibleUserIds = sysUserDeptRelationService.listUserIdsByDeptIds(deptContainer.getVisibleDeptIds());
+ if (CollUtil.isEmpty(visibleUserIds)) {
+ // 【可见部门】没有任何用户ID,直接返回[0]
+ return Collections.singleton(BigInteger.ZERO.longValue());
+ }
+ }
+ return visibleUserIds;
+ }
+
+ /**
+ * 确定不可见用户IDs
+ * 租户管理员:列表中不显示超级管理员用户
+ * 普通用户:列表中不显示超级管理员、租户管理员用户
+ */
+ private Set determineInvisibleUserIds() {
+ Set invisibleRoleIds = sysRoleService.determineInvisibleRoleIds();
+ return sysUserRoleRelationService.listUserIdsByRoleIds(invisibleRoleIds);
+ }
+
+ /**
+ * 数据越权检查
+ */
+ private void dataScopeCheck(Collection userIds) {
+ Set visibleUserIds = determineVisibleDeptUserIds();
+ Set invisibleUserIds = determineInvisibleUserIds();
+ if (CollUtil.isNotEmpty(visibleUserIds) && !CollUtil.containsAll(visibleUserIds, userIds)
+ || CollUtil.isNotEmpty(invisibleUserIds) && CollUtil.containsAny(invisibleUserIds, userIds)) {
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_THIS_USER);
+ }
+ }
+
/**
* 检查并获取租户上下文 bean,无效或被禁用则直接抛出异常
* @param tenantId 租户ID
@@ -398,16 +494,136 @@ private TenantContext checkAndGetTenantContext(Long tenantId) throws BusinessExc
return TenantContext.builder()
.tenantId(tenantEntity.getTenantId())
.tenantName(tenantEntity.getTenantName())
- .build()
- ;
+ .build();
}
private void updateLastLoginAt(Long userId, LocalDateTime lastLoginAt) {
SysUserEntity entity = new SysUserEntity();
entity
.setLastLoginAt(lastLoginAt)
- .setId(userId)
- ;
+ .setId(userId);
sysUserMapper.updateById(entity);
}
+
+ /**
+ * 编辑后台用户信息前检查
+ * @param specifiedUserId 被操作用户ID
+ * @param statusEnum 用户状态枚举,可以为null
+ */
+ private void preUpdateCheck(Long specifiedUserId, @Nullable SysUserStatusEnum statusEnum) {
+ UserRoleContainer currentUser = sysRoleService.getCurrentUserRoleContainer();
+ if (currentUser.isSuperAdmin()) {
+ // 超级管理员除禁用自己外为所欲为
+ if (statusEnum == SysUserStatusEnum.BANNED && Objects.equals(specifiedUserId, UserContextHolder.getUserId())) {
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_SELF_USER);
+ }
+ return;
+ }
+
+ if (Objects.equals(specifiedUserId, UserContextHolder.getUserId())) {
+ // 不能动自身用户
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_SELF_USER);
+ }
+
+ // 目标是超级管理员or租户管理员时,均不能编辑
+ UserRoleContainer specifiedUser = sysRoleService.getSpecifiedUserRoleContainer(specifiedUserId);
+ if (specifiedUser.isSuperAdmin() || specifiedUser.isTenantAdmin()) {
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_THIS_USER);
+ }
+
+ dataScopeCheck(Collections.singleton(specifiedUserId));
+ // 暂未实现角色层级,一律平级
+ }
+
+ /**
+ * 删除后台用户前检查
+ */
+ private void preDeleteCheck(Collection ids) {
+ if (CollUtil.contains(ids, UserContextHolder.getUserId())) {
+ // 不能动自身用户
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_SELF_USER);
+ }
+
+ // 目标是超级管理员时,不能删除
+ List specifiedUsers = ids.stream().map(sysRoleService::getSpecifiedUserRoleContainer).collect(Collectors.toList());
+ if (specifiedUsers.stream().anyMatch(UserRoleContainer::isSuperAdmin)) {
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_THIS_USER);
+ }
+
+ // 只有超级管理员可以删租户管理员用户
+ UserRoleContainer currentUser = sysRoleService.getCurrentUserRoleContainer();
+ if (specifiedUsers.stream().anyMatch(UserRoleContainer::isTenantAdmin) && !currentUser.isSuperAdmin()) {
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_THIS_USER);
+ }
+
+ dataScopeCheck(ids);
+ // 暂未实现角色层级,一律平级
+ }
+
+ /**
+ * 绑定后台用户与角色关联关系前检查
+ * 防止越权访问漏洞
+ */
+ private void preBindUserRoleRelationCheck(AdminBindUserRoleRelationDTO dto) {
+ UserRoleContainer currentUser = sysRoleService.getCurrentUserRoleContainer();
+ // 是否对自己操作
+ boolean selfFlag = Objects.equals(dto.getUserId(), UserContextHolder.getUserId());
+ if (currentUser.isSuperAdmin()) {
+ // 超级管理员不能去掉自己的超级管理员角色
+ if (selfFlag && !CollUtil.contains(dto.getRoleIds(), SysConstant.SUPER_ADMIN_ROLE_ID)) {
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_SELF_USER);
+ }
+ // 也不能赋予其他人超级管理员角色
+ if (!selfFlag && CollUtil.contains(dto.getRoleIds(), SysConstant.SUPER_ADMIN_ROLE_ID)) {
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_THIS_USER);
+ }
+ return;
+ }
+
+ if (selfFlag) {
+ // 不能动自身用户
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_SELF_USER);
+ }
+
+ // 目标已经是超级管理员or租户管理员时,均不能绑定
+ UserRoleContainer specifiedUser = sysRoleService.getSpecifiedUserRoleContainer(dto.getUserId());
+ if (specifiedUser.isSuperAdmin() || specifiedUser.isTenantAdmin()) {
+ throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_THIS_USER);
+ }
+
+ // 超级管理员之外的用户,都需要校验自身角色范围是否满足输入值
+ currentUserNotSuperAdmin(dto, currentUser);
+
+ dataScopeCheck(Collections.singleton(dto.getUserId()));
+ }
+
+ /**
+ * 绑定后台用户与角色关联关系前检查
+ * 超级管理员之外的用户,都需要校验自身角色范围是否满足输入值
+ * 拆分子方法以降低Cognitive Complexity
+ */
+ private void currentUserNotSuperAdmin(AdminBindUserRoleRelationDTO dto, UserRoleContainer currentUser) {
+ if (CollUtil.isNotEmpty(dto.getRoleIds()) && !currentUser.isSuperAdmin()) {
+ boolean overRoles = !CollUtil.containsAll(currentUser.getRelatedRoleIds(), dto.getRoleIds());
+ if (overRoles && currentUser.isNotAnyAdmin()) {
+ // 普通用户超自身角色授予了;如果当前用户拥有新角色的所有菜单,那么也放行
+ Set grantedMenuIds = sysRoleMenuRelationService.listMenuIdsByRoleIds(currentUser.getRelatedRoleIds());
+ Set needMenuIds = sysRoleMenuRelationService.listMenuIdsByRoleIds(dto.getRoleIds());
+ if (!CollUtil.containsAll(grantedMenuIds, needMenuIds)) {
+ throw new BusinessException(SysErrorEnum.BEYOND_AUTHORITY_BIND_ROLES);
+ }
+ }
+
+ if (currentUser.isTenantAdmin()) {
+ // 超自身权限,但作为租户管理员有额外情况
+ Set invisibleRoleIds = sysRoleService.determineInvisibleRoleIds();
+ // 除非超越了可见角色IDs授予 or 想要授予用户租户管理员角色,否则不管
+ invisibleRoleIds.addAll(currentUser.getRelatedRoleIds());
+ if (CollUtil.containsAny(invisibleRoleIds, dto.getRoleIds())) {
+ throw new BusinessException(SysErrorEnum.BEYOND_AUTHORITY_BIND_ROLES);
+ }
+ }
+ }
+ }
+
}
diff --git a/src/main/java/cc/uncarbon/module/sys/util/PwdUtil.java b/src/main/java/cc/uncarbon/module/sys/util/PwdUtil.java
index f238fab..b4b7b0e 100644
--- a/src/main/java/cc/uncarbon/module/sys/util/PwdUtil.java
+++ b/src/main/java/cc/uncarbon/module/sys/util/PwdUtil.java
@@ -11,7 +11,7 @@
@UtilityClass
public class PwdUtil {
public static String encrypt(String str, String salt) {
- if (CharSequenceUtil.isEmpty(str)){
+ if (CharSequenceUtil.isEmpty(str)) {
return "";
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index eec3277..ea10e6c 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -86,7 +86,8 @@ mybatis-plus:
logging:
level:
# 指定日志级别,开发、测试环境建议为 DEBUG,生产环境建议为 INFO
- cc.uncarbon: DEBUG
+ cc.uncarbon.framework: DEBUG
+ cc.uncarbon.module: DEBUG
# 详细配置文档:https://sa-token.cc/doc.html#/use/config
sa-token:
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
index cd4418e..7ef80b4 100644
--- a/src/main/resources/banner.txt
+++ b/src/main/resources/banner.txt
@@ -10,4 +10,4 @@ ${AnsiColor.BLUE}
${AnsiColor.GREEN}
Spring Boot Version: ${spring-boot.version}
-${AnsiColor.BLACK}
+${AnsiColor.WHITE}
diff --git "a/\345\244\232\346\250\241\345\235\227\347\211\210\350\257\267\345\210\207\346\215\242\350\207\263multi\345\210\206\346\224\257.md" "b/\345\244\232\346\250\241\345\235\227\347\211\210\350\257\267\347\247\273\346\255\245\350\207\263helio-boot-modular\351\241\271\347\233\256.md"
similarity index 100%
rename from "\345\244\232\346\250\241\345\235\227\347\211\210\350\257\267\345\210\207\346\215\242\350\207\263multi\345\210\206\346\224\257.md"
rename to "\345\244\232\346\250\241\345\235\227\347\211\210\350\257\267\347\247\273\346\255\245\350\207\263helio-boot-modular\351\241\271\347\233\256.md"