refactor permission system

This commit is contained in:
Timi
2026-08-11 23:57:59 +08:00
parent 6e43ff349d
commit 39ac73dc66
60 changed files with 2300 additions and 335 deletions
@@ -21,45 +21,45 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
STORE_DELETE("STORE:DELETE", "门店删除", false, true),
CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建"),
CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建", false, false),
CUSTOMER_READ("CUSTOMER:READ", "客户读取"),
CUSTOMER_READ("CUSTOMER:READ", "客户读取", false, false),
CUSTOMER_EXPORT("CUSTOMER:EXPORT", "客户导出", true),
CUSTOMER_EXPORT("CUSTOMER:EXPORT", "客户导出", true, false),
CUSTOMER_UPDATE("CUSTOMER:UPDATE", "客户修改"),
CUSTOMER_UPDATE("CUSTOMER:UPDATE", "客户修改", false, false),
CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除"),
CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除", false, false),
EVENT_CREATE("EVENT:CREATE", "登记事件创建"),
EVENT_CREATE("EVENT:CREATE", "登记事件创建", false, false),
EVENT_READ("EVENT:READ", "登记事件读取"),
EVENT_READ("EVENT:READ", "登记事件读取", false, false),
EVENT_UPDATE("EVENT:UPDATE", "登记事件修改"),
EVENT_UPDATE("EVENT:UPDATE", "登记事件修改", false, false),
EVENT_DELETE("EVENT:DELETE", "登记事件删除"),
EVENT_DELETE("EVENT:DELETE", "登记事件删除", false, false),
EVENT_RECORD_CREATE("EVENT_RECORD:CREATE", "登记事件记录创建"),
EVENT_RECORD_CREATE("EVENT_RECORD:CREATE", "登记事件记录创建", false, false),
EVENT_RECORD_READ("EVENT_RECORD:READ", "登记事件记录读取"),
EVENT_RECORD_READ("EVENT_RECORD:READ", "登记事件记录读取", false, false),
EVENT_RECORD_EXPORT("EVENT_RECORD:EXPORT", "登记事件记录导出", true),
EVENT_RECORD_EXPORT("EVENT_RECORD:EXPORT", "登记事件记录导出", true, false),
EVENT_RECORD_UPDATE("EVENT_RECORD:UPDATE", "登记事件记录修改"),
EVENT_RECORD_UPDATE("EVENT_RECORD:UPDATE", "登记事件记录修改", false, false),
EVENT_RECORD_DELETE("EVENT_RECORD:DELETE", "登记事件记录删除"),
EVENT_RECORD_DELETE("EVENT_RECORD:DELETE", "登记事件记录删除", false, false),
STAT_READ("STAT:READ", "统计读取"),
STAT_READ("STAT:READ", "统计读取", false, false),
QR_CODE_CREATE("QR_CODE:CREATE", "二维码创建"),
QR_CODE_CREATE("QR_CODE:CREATE", "二维码创建", false, false),
USER_CREATE("USER:CREATE", "用户创建", true),
USER_CREATE("USER:CREATE", "用户创建", true, false),
USER_READ("USER:READ", "用户读取", true),
USER_READ("USER:READ", "用户读取", true, false),
USER_UPDATE("USER:UPDATE", "用户修改", true),
USER_UPDATE("USER:UPDATE", "用户修改", true, false),
USER_DELETE("USER:DELETE", "用户删除", true);
USER_DELETE("USER:DELETE", "用户删除", true, false);
final String value;
final String defaultName;
@@ -72,14 +72,6 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
@Getter
final boolean grantToGlobalManager;
GaoPermissionCode(String value, String defaultName) {
this(value, defaultName, false, false);
}
GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager) {
this(value, defaultName, grantToStoreManager, false);
}
GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager, boolean grantToGlobalManager) {
this.value = value;
this.defaultName = defaultName;
@@ -111,4 +103,5 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
public boolean isGrantToAdmin() {
return false;
}
}
@@ -16,9 +16,7 @@ public enum GaoRoleCode implements BuiltinRoleCode {
GLOBAL_MANAGER("全局管理员", null),
/// 店长
STORE_MANAGER("店长", GLOBAL_MANAGER.name()),
EMPLOYEE("员工", STORE_MANAGER.name());
STORE_MANAGER("店长", GLOBAL_MANAGER.name());
final String defaultName;
final String parentCode;
@@ -42,4 +40,5 @@ public enum GaoRoleCode implements BuiltinRoleCode {
public String getParentCode() {
return parentCode;
}
}
@@ -2,11 +2,9 @@ package com.imyeyu.api.modules.gao.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.bean.RoleMatchMode;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRole;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.gao.entity.GaoCustomer;
@@ -46,7 +44,6 @@ import java.util.Map;
/// @author 夜雨
/// @since 2026-07-27 14:12
@RestController
@RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY)
@RequiredArgsConstructor
@RequestMapping("/gao/customer")
public class GaoCustomerController {
@@ -1,9 +1,7 @@
package com.imyeyu.api.modules.gao.controller;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.bean.RoleMatchMode;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRole;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.gao.entity.GaoEvent;
@@ -30,7 +28,6 @@ import java.util.Map;
/// @author 夜雨
/// @since 2026-07-27 14:13
@RestController
@RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY)
@RequiredArgsConstructor
@RequestMapping("/gao/event")
public class GaoEventController {
@@ -2,11 +2,9 @@ package com.imyeyu.api.modules.gao.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.bean.RoleMatchMode;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRole;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.gao.entity.GaoCustomer;
@@ -48,7 +46,6 @@ import java.util.Map;
/// @author 夜雨
/// @since 2026-07-27 14:11
@RestController
@RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY)
@RequiredArgsConstructor
@RequestMapping("/gao/event/record")
public class GaoEventRecordController {
@@ -4,7 +4,8 @@ import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRole;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.AuthorizationScopeService;
import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import lombok.RequiredArgsConstructor;
@@ -24,12 +25,13 @@ import java.util.List;
@RequestMapping("/gao/role")
public class GaoRoleController {
private final RoleService roleService;
private final UserLoginService userLoginService;
private final AuthorizationScopeService authorizationScopeService;
@AOPLog
@RequestRateLimit
@PostMapping("/available/list")
public List<Role> availableList() {
return roleService.listByModuleCode(ModuleCode.GAO);
return authorizationScopeService.listGrantableRole(userLoginService.getRequireLoginUserId(), ModuleCode.GAO);
}
}
@@ -1,6 +1,5 @@
package com.imyeyu.api.modules.gao.controller;
import com.imyeyu.api.bean.RoleMatchMode;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRole;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
@@ -34,13 +33,13 @@ import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("/gao/store")
@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
public class GaoStoreController {
private final GaoStoreService service;
private final UserLoginService userLoginService;
@AOPLog
@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_READ)
@PostMapping("/list")
@@ -49,6 +48,7 @@ public class GaoStoreController {
}
@AOPLog
@RequireGaoRole(GaoRoleCode.STORE_MANAGER)
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_READ)
@PostMapping("/detail")
@@ -66,6 +66,7 @@ public class GaoStoreController {
}
@AOPLog
@RequireGaoRole(GaoRoleCode.STORE_MANAGER)
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_UPDATE)
@PostMapping("/update")
@@ -74,6 +75,7 @@ public class GaoStoreController {
}
@AOPLog
@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_DELETE)
@PostMapping("/delete")
@@ -82,6 +84,7 @@ public class GaoStoreController {
}
@AOPLog
@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_UPDATE)
@PostMapping("/sort")
@@ -104,7 +107,6 @@ public class GaoStoreController {
@AOPLog
@RequestRateLimit
@RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY)
@PostMapping("/user")
public GaoStore getLoginStore() {
return service.getByUserId(userLoginService.getRequireLoginUserId());
@@ -39,7 +39,6 @@ import java.util.Map;
/// @author 夜雨
/// @since 2026-07-30 14:15
@RestController
@RequireGaoRole(GaoRoleCode.STORE_MANAGER)
@RequiredArgsConstructor
@RequestMapping("/gao/user")
public class GaoUserController {
@@ -65,6 +65,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
return mapper;
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(GaoCustomer customer) {
TimiException.required(customer.getName(), "not found customer.name");
@@ -118,6 +119,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void update(GaoCustomer customer) {
TimiException.required(customer, "not found customer");
@@ -196,6 +198,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void updateWithAttachment(GaoCustomer customer) {
update(customer);
@@ -0,0 +1,113 @@
package com.imyeyu.api.modules.gao.service.implement;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.bean.CoreRoleCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.gao.entity.GaoStore;
import com.imyeyu.api.modules.gao.service.GaoStoreService;
import com.imyeyu.api.modules.user.bean.AuthOwnerType;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.entity.UserRole;
import com.imyeyu.api.modules.user.mapper.RoleMapper;
import com.imyeyu.api.modules.user.mapper.UserRoleMapper;
import com.imyeyu.api.modules.user.service.ModuleResourceScopeResolver;
import com.imyeyu.api.modules.user.service.RoleHierarchyService;
import com.imyeyu.java.TimiJava;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/// GAO 资源范围解析器
///
/// @author Codex
/// @since 2026-08-07
@Service
@RequiredArgsConstructor
public class GaoResourceScopeResolver implements ModuleResourceScopeResolver {
private final RoleMapper roleMapper;
private final UserRoleMapper userRoleMapper;
private final GaoStoreService gaoStoreService;
private final RoleHierarchyService roleHierarchyService;
@Override
public ModuleCode moduleCode() {
return ModuleCode.GAO;
}
@Override
public boolean canManageUser(String operatorUserId, String targetUserId) {
if (hasCoreRole(operatorUserId, CoreRoleCode.ADMIN)) {
return true;
}
if (hasGaoRole(operatorUserId, GaoRoleCode.GLOBAL_MANAGER)) {
return true;
}
if (!hasGaoRole(operatorUserId, GaoRoleCode.STORE_MANAGER)) {
return false;
}
GaoStore operatorStore = gaoStoreService.getByUserId(operatorUserId);
GaoStore targetStore = gaoStoreService.getByUserId(targetUserId);
return operatorStore != null && targetStore != null && operatorStore.getId().equals(targetStore.getId());
}
@Override
public boolean canManageRole(String operatorUserId, Role role) {
if (hasCoreRole(operatorUserId, CoreRoleCode.ADMIN)) {
return true;
}
if (hasGaoRole(operatorUserId, GaoRoleCode.GLOBAL_MANAGER)) {
return true;
}
if (!hasGaoRole(operatorUserId, GaoRoleCode.STORE_MANAGER)) {
return false;
}
if (canUseOwnerScope(operatorUserId, role.getOwnerType(), role.getOwnerId())) {
return true;
}
return ModuleCode.GAO == role.getModuleCode()
&& role.getOwnerType() == AuthOwnerType.MODULE
&& (TimiJava.isEmpty(role.getOwnerId()) || ModuleCode.GAO.name().equals(role.getOwnerId()));
}
@Override
public boolean canUseOwnerScope(String operatorUserId, AuthOwnerType ownerType, String ownerId) {
if (hasCoreRole(operatorUserId, CoreRoleCode.ADMIN)) {
return ownerType == AuthOwnerType.MODULE && ModuleCode.GAO.name().equals(ownerId) || ownerType == AuthOwnerType.STORE;
}
if (hasGaoRole(operatorUserId, GaoRoleCode.GLOBAL_MANAGER)) {
return ownerType == AuthOwnerType.MODULE && ModuleCode.GAO.name().equals(ownerId) || ownerType == AuthOwnerType.STORE;
}
if (!hasGaoRole(operatorUserId, GaoRoleCode.STORE_MANAGER) || ownerType != AuthOwnerType.STORE) {
return false;
}
GaoStore store = gaoStoreService.getByUserId(operatorUserId);
return store != null && store.getId().equals(ownerId);
}
private boolean hasGaoRole(String userId, GaoRoleCode roleCode) {
if (TimiJava.isEmpty(userId)) {
return false;
}
List<String> roleIdList = userRoleMapper.selectByUserIdList(List.of(userId)).stream().map(UserRole::getRoleId).toList();
if (TimiJava.isEmpty(roleIdList)) {
return false;
}
// 身份角色只依赖系统、模块级继承,不能依赖当前正在计算的门店作用域
List<String> allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(roleIdList);
return roleMapper.selectByIdList(allRoleIdList).stream().anyMatch(role -> ModuleCode.GAO == role.getModuleCode() && roleCode.name().equals(role.getCode()));
}
private boolean hasCoreRole(String userId, CoreRoleCode roleCode) {
if (TimiJava.isEmpty(userId)) {
return false;
}
List<String> roleIdList = userRoleMapper.selectByUserIdList(List.of(userId)).stream().map(UserRole::getRoleId).toList();
if (TimiJava.isEmpty(roleIdList)) {
return false;
}
List<String> allRoleIdList = roleHierarchyService.listAllRoleIdIncludingChildren(roleIdList);
return roleMapper.selectByIdList(allRoleIdList).stream().anyMatch(role -> ModuleCode.CORE == role.getModuleCode() && roleCode.name().equals(role.getCode()));
}
}
@@ -50,6 +50,7 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
return mapper;
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(GaoStore store) {
TimiException.required(store, "not found store");
@@ -79,6 +80,7 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void update(GaoStore store) {
TimiException.required(store, "not found store");
@@ -114,6 +116,7 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void delete(String id) {
Logger logger = new Logger(Logger.Module.GAO, "GAO_STORE_DELETE");