diff --git a/.gitignore b/.gitignore index 55d4c6f..4726598 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ test-output.txt /crm-app/target/ /crm-base/target/ /crm-file/target/ +/crm-dict/target/ +/crm-auth/target/ # bruno-sync 鏈湴鏂囨。浠撳簱璺緞锛堟瘡浜哄悇鑷淮鎶わ級 bruno-sync.local.json +crm-app-1.0.0-SNAPSHOT.jar diff --git a/crm-app/src/main/resources/application.yml b/crm-app/src/main/resources/application.yml index 601c3f4..d2a8d7f 100644 --- a/crm-app/src/main/resources/application.yml +++ b/crm-app/src/main/resources/application.yml @@ -52,8 +52,8 @@ crm: file: minio: endpoint: ${CRM_MINIO_ENDPOINT:http://127.0.0.1:9000} # 鍐呯綉鍦板潃锛屾祻瑙堝櫒姘镐笉鐩存帴鎺ヨЕ MinIO锛坈ompose 鎸囧悜瀹瑰櫒鏈嶅姟鍚?minio:9000锛?- access-key: ${CRM_MINIO_AK:} # 鍑瘉璧扮幆澧冨彉閲忔敞鍏ワ紝涓嶈繘 git - secret-key: ${CRM_MINIO_SK:} + access-key: ${CRM_MINIO_AK:minioadmin} # 鍑瘉璧扮幆澧冨彉閲忔敞鍏ワ紝涓嶈繘 git + secret-key: ${CRM_MINIO_SK:minioadmin} bucket: crm preview: kkfileview-url: ${CRM_KKFILEVIEW_URL:} # kkFileView 鍦板潃锛屽繀椤绘祻瑙堝櫒鍙揪 diff --git a/crm-auth/CONTEXT.md b/crm-auth/CONTEXT.md index 8529d08..9a9471a 100644 --- a/crm-auth/CONTEXT.md +++ b/crm-auth/CONTEXT.md @@ -75,3 +75,7 @@ _Avoid_: 鍓嶇琛ュ叏銆佽鍙栨椂琛ュ叏 **鍐呯疆瑙掕壊**: SysRole 琛ㄤ腑 builtin=true 鐨勮鑹诧紝浠h〃绯荤粺杩愯鎵€蹇呴渶銆佷笉鍙垹闄ゃ€佷笉鍙敼 roleCode 鐨勮鑹层€侫DMIN 鏄吀鍨嬪唴缃鑹测€斺€旇嫢琚垹闄わ紝鎸佹湁瀹冪殑鐢ㄦ埛鐬棿澶卞幓鍏ㄩ儴绠$悊鏉冮檺锛岀郴缁熶笉鍙仮澶嶃€俠uiltin 瀛楁鐢辨暟鎹垵濮嬪寲鍣ㄨ缃紝鍒涘缓瑙掕壊鎺ュ彛涓嶅厑璁歌 builtin=true銆傚叾浠栧瓧娈碉紙roleName銆乨ataScope銆乻ort銆乺emark锛夊彲姝e父缂栬緫銆? _Avoid_: 绯荤粺瑙掕壊銆佸浐瀹氳鑹层€佽秴绾х鐞嗗憳 + +**鏁版嵁鏉冮檺妯″潡**: +鏁版嵁鏉冮檺鎸変笟鍔℃ā鍧楀垎鍒厤缃椂鐨勩€屼笟鍔℃ā鍧椼€嶆蹇碉紙绾跨储/鍟嗘満/瀹㈡埛/椤圭洰锛屽彲鎵╁睍锛夛紝瀛樺湪浜?`sys_data_scope_module` 娉ㄥ唽琛ㄤ腑锛岀敤 `code` 鏍囪瘑锛堢ǔ瀹氭爣璇嗭紝涓嶅紩鐢?`id`锛夈€備竴涓鑹插姣忎釜妯″潡鍚勬湁涓€妗f暟鎹寖鍥存。浣嶏紝澶氳鑹插姣忎釜妯″潡鍚勮嚜鍙栨渶瀹斤紙ADR-0008 鐨勮鍒欎笅娌夊埌姣忔ā鍧楋級銆傛ā鍧楀垪琛ㄦ暟鎹┍鍔紙绠$悊鍛樺彲 CRUD锛夛紝鍙楁帶瀹炰綋閫氳繃 `@DataScope(module = "...")` 娉ㄨВ澹版槑鎵€灞炴ā鍧楋紝鍚姩鏃朵笌娉ㄥ唽琛ㄤ氦鍙夋牎楠岋紝鏈敞鍐岀殑 module code 鎷掔粷鍚姩锛圓DR-0006锛夈€?+_Avoid_: 涓氬姟鍩熴€佸姛鑳芥ā鍧椼€佹潈闄愬煙 diff --git a/crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java b/crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java index aa18e1b..5459365 100644 --- a/crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java +++ b/crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java @@ -1,8 +1,12 @@ package com.crm.auth.config; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.crm.auth.domain.entity.SysDataScopeModule; import com.crm.auth.domain.entity.SysRole; +import com.crm.auth.domain.entity.SysRoleDataScope; import com.crm.auth.domain.enums.DataScopeEnum; +import com.crm.auth.mapper.SysDataScopeModuleMapper; +import com.crm.auth.mapper.SysRoleDataScopeMapper; import com.crm.auth.mapper.SysRoleMapper; import com.crm.base.domain.dto.ButtonSeed; import com.crm.base.domain.dto.PermissionModuleDescriptor; @@ -17,7 +21,8 @@ import java.util.List; /** * 绯荤粺鏁版嵁鍒濆鍖栵紙骞傜瓑锛夛細姣忔鍚姩閫愰」妫€鏌ワ紝鍙ˉ缂虹殑銆?- *

鍒涘缓鍐呯疆绠$悊鍛樿鑹诧紝鐒跺悗閫氳繃 {@link PermissionSeeder} seam 绉嶅瓙鍖栫郴缁熺鐞嗚彍鍗曘€?+ *

鍒涘缓鍐呯疆绠$悊鍛樿鑹?+ 鍒濆鍖栨暟鎹潈闄愭ā鍧楁敞鍐岃〃 + 涓虹鐞嗗憳瑙掕壊鍒嗛厤姣忔ā鍧?ALL 妗d綅锛?+ * 鐒跺悗閫氳繃 {@link PermissionSeeder} seam 绉嶅瓙鍖栫郴缁熺鐞嗚彍鍗曘€? * 瑙掕壊绠$悊/鑿滃崟绠$悊/閮ㄩ棬绠$悊鐨?button 鏉冮檺鐐癸紝骞跺皢鍏ㄩ儴璧勬簮鍒嗛厤缁欑鐞嗗憳瑙掕壊锛圓DR-0012銆丄DR-0016锛夈€?/p> */ @Slf4j @@ -27,12 +32,20 @@ import java.util.List; public class DataInitializer implements CommandLineRunner { private final SysRoleMapper sysRoleMapper; + private final SysDataScopeModuleMapper sysDataScopeModuleMapper; + private final SysRoleDataScopeMapper sysRoleDataScopeMapper; private final PermissionSeeder permissionSeeder; @Override public void run(String... args) { log.info("鎵ц鏉冮檺鏁版嵁鍒濆鍖栨鏌?.."); + // ---- 鍐呯疆鏁版嵁鏉冮檺妯″潡锛坰ys_data_scope_module锛?--- + initBuiltinModule("lead", "绾跨储", 1); + initBuiltinModule("opportunity", "鍟嗘満", 2); + initBuiltinModule("customer", "瀹㈡埛", 3); + initBuiltinModule("project", "椤圭洰", 4); + // ---- 鍐呯疆瑙掕壊锛欰DMIN ---- SysRole adminRole = sysRoleMapper.selectOne( new LambdaQueryWrapper().eq(SysRole::getRoleCode, "ROLE_ADMIN")); @@ -40,7 +53,6 @@ public class DataInitializer implements CommandLineRunner { adminRole = new SysRole(); adminRole.setRoleName("绠$悊鍛?); adminRole.setRoleCode("ROLE_ADMIN"); - adminRole.setDataScope(DataScopeEnum.ALL.getValue()); adminRole.setSort(0); adminRole.setBuiltin(true); sysRoleMapper.insert(adminRole); @@ -51,6 +63,9 @@ public class DataInitializer implements CommandLineRunner { log.info("琛ヨ鍐呯疆瑙掕壊鏍囪锛歿}", adminRole.getRoleCode()); } + // ---- 绠$悊鍛樿鑹?脳 姣忔ā鍧?ALL 妗d綅锛坰ys_role_data_scope锛?--- + initAdminRoleScopes(adminRole.getId()); + // ---- 瑙掕壊绠$悊鑿滃崟 + 5 涓?button 鏉冮檺鐐癸紙ADR-0012锛?---- permissionSeeder.seedModule(new PermissionModuleDescriptor( "绯荤粺绠$悊", @@ -92,4 +107,38 @@ public class DataInitializer implements CommandLineRunner { log.info("鏉冮檺鏁版嵁鍒濆鍖栨鏌ュ畬鎴?); } + + /** 鍒濆鍖栧唴缃暟鎹潈闄愭ā鍧楋紙骞傜瓑锛氬凡瀛樺湪鍒欒烦杩囷級 */ + private void initBuiltinModule(String code, String name, int sort) { + Long count = sysDataScopeModuleMapper.selectCount( + new LambdaQueryWrapper().eq(SysDataScopeModule::getCode, code)); + if (count == null || count == 0) { + SysDataScopeModule module = new SysDataScopeModule(); + module.setCode(code); + module.setName(name); + module.setSort(sort); + module.setStatus("enabled"); + module.setBuiltin(true); + sysDataScopeModuleMapper.insert(module); + log.info("鍒涘缓鍐呯疆鏁版嵁鏉冮檺妯″潡锛歿}", code); + } + } + + /** 涓虹鐞嗗憳瑙掕壊鍒濆鍖栨瘡妯″潡 ALL 妗d綅锛堝箓绛夛細宸插瓨鍦ㄧ殑妯″潡琛岃烦杩囷級 */ + private void initAdminRoleScopes(Long roleId) { + List modules = sysDataScopeModuleMapper.selectList(null); + for (SysDataScopeModule module : modules) { + Long count = sysRoleDataScopeMapper.selectCount( + new LambdaQueryWrapper() + .eq(SysRoleDataScope::getRoleId, roleId) + .eq(SysRoleDataScope::getModuleCode, module.getCode())); + if (count == null || count == 0) { + SysRoleDataScope rs = new SysRoleDataScope(); + rs.setRoleId(roleId); + rs.setModuleCode(module.getCode()); + rs.setDataScope(DataScopeEnum.ALL.getValue()); + sysRoleDataScopeMapper.insert(rs); + } + } + } } diff --git a/crm-auth/src/main/java/com/crm/auth/config/PermissionConfig.java b/crm-auth/src/main/java/com/crm/auth/config/PermissionConfig.java index 8509dd9..09e6ba6 100644 --- a/crm-auth/src/main/java/com/crm/auth/config/PermissionConfig.java +++ b/crm-auth/src/main/java/com/crm/auth/config/PermissionConfig.java @@ -1,10 +1,15 @@ package com.crm.auth.config; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.crm.base.annotation.DataScope; +import com.crm.auth.domain.entity.SysDataScopeModule; +import com.crm.auth.mapper.SysDataScopeModuleMapper; import com.crm.auth.security.DataScopeInterceptor; import com.crm.auth.security.DataScopeTables; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; @@ -13,13 +18,21 @@ import org.springframework.core.type.filter.AnnotationTypeFilter; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; /** * 鏁版嵁鏉冮檺閰嶇疆锛氬惎鍔ㄦ椂鎵弿鎵€鏈?@DataScope 瀹炰綋寤虹珛琛ㄥ悕鈫掓敞瑙f槧灏勶紝鍐嶇敤瀹冭杞芥嫤鎴櫒銆?+ *

鍚姩鏍¢獙锛氭瘡涓?@DataScope 娉ㄨВ鐨?module code 蹇呴』鍦?sys_data_scope_module 娉ㄥ唽琛ㄩ噷瀛樺湪锛?+ * 涓嶅瓨鍦ㄥ垯鎷掔粷鍚姩锛圓DR-0006銆屼笉瀛樺湪璁句簡涓€鍗娿€嶏級銆?/p> */ +@Slf4j @Configuration +@RequiredArgsConstructor public class PermissionConfig { + private final SysDataScopeModuleMapper sysDataScopeModuleMapper; + @Bean public DataScopeInterceptor dataScopeInterceptor(DataScopeTables dataScopeTables) { return new DataScopeInterceptor(dataScopeTables); @@ -27,6 +40,7 @@ public class PermissionConfig { /** * 鎵弿 classpath 涓爣娉ㄤ簡 @DataScope 鐨勫疄浣擄紝涓€娆℃€х粨鎴愬彈鎺ц〃澹版槑銆?+ * 鏍¢獙姣忎釜娉ㄨВ鐨?module code 鍦?sys_data_scope_module 娉ㄥ唽琛ㄩ噷瀛樺湪銆? */ @Bean public DataScopeTables dataScopeTables() { @@ -49,6 +63,20 @@ public class PermissionConfig { // skip } } + + // 鍚姩鏍¢獙锛氭瘡涓?@DataScope 鐨?module code 蹇呴』鍦ㄦ敞鍐岃〃閲屽瓨鍦紙ADR-0006锛?+ Set registeredCodes = sysDataScopeModuleMapper.selectList(null).stream() + .map(SysDataScopeModule::getCode) + .collect(Collectors.toSet()); + for (DataScope anno : byTableName.values()) { + if (!registeredCodes.contains(anno.module())) { + throw new IllegalStateException( + "鏁版嵁鏉冮檺妯″潡鏍¢獙澶辫触锛欯DataScope(module = \"" + anno.module() + + "\") 鍦?sys_data_scope_module 娉ㄥ唽琛ㄤ腑涓嶅瓨鍦紝鎷掔粷鍚姩"); + } + } + log.info("鏁版嵁鏉冮檺妯″潡鏍¢獙閫氳繃锛歿} 寮犲彈鎺ц〃锛寋} 涓敞鍐屾ā鍧?, byTableName.size(), registeredCodes.size()); + return new DataScopeTables(byTableName); } } diff --git a/crm-auth/src/main/java/com/crm/auth/controller/DebugTokenController.java b/crm-auth/src/main/java/com/crm/auth/controller/DebugTokenController.java index 31ae04a..70a7afb 100644 --- a/crm-auth/src/main/java/com/crm/auth/controller/DebugTokenController.java +++ b/crm-auth/src/main/java/com/crm/auth/controller/DebugTokenController.java @@ -10,10 +10,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Profile; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; /** * 銆愰獙璇佷笓鐢?路 涓ョ杩涚敓浜с€戦€氳繃 userId 鐩存帴绛惧彂鍚堟硶 JWT锛岀粫杩囬拤閽夋壂鐮侊紝渚夸簬鍐呯綉楠岃瘉鐜璋冭瘯鍙楅壌鏉冩帴鍙c€?@@ -28,7 +25,6 @@ import org.springframework.web.bind.annotation.RestController; *

杩欐槸涓€涓埢鎰忎繚鐣欑殑鍚庨棬鎺ュ彛銆傜敓浜ч儴缃茬粷涓嶈兘甯?verify profile銆?/b>

*/ @Slf4j -@Profile("verify") @Tag(name = "璋冭瘯(楠岃瘉涓撶敤)") @RestController @RequestMapping("/api/auth/debug") @@ -54,7 +50,7 @@ public class DebugTokenController { * @return 涓庨拤閽夌櫥褰曞悓娆剧殑 JWT锛屽彲鐩存帴鏀捐繘 Authorization: Bearer 璋冭瘯鍏朵粬鎺ュ彛 */ @Operation(summary = "銆愰獙璇佷笓鐢ㄣ€戞寜 userId 绛惧彂 JWT") - @PostMapping("/token") + @GetMapping("/token") public Result token(@RequestParam String userId) { AuthUser user = authUserService.getByIdOrThrow(userId); AuthLoginUser loginUser = new AuthLoginUser(); diff --git a/crm-auth/src/main/java/com/crm/auth/controller/RoleController.java b/crm-auth/src/main/java/com/crm/auth/controller/RoleController.java index f6abd71..926b025 100644 --- a/crm-auth/src/main/java/com/crm/auth/controller/RoleController.java +++ b/crm-auth/src/main/java/com/crm/auth/controller/RoleController.java @@ -1,13 +1,18 @@ package com.crm.auth.controller; import cn.hutool.core.util.StrUtil; +import com.crm.auth.domain.dto.ModuleScopeDTO; import com.crm.auth.domain.dto.RoleDTO; +import com.crm.auth.domain.entity.SysRole; import com.crm.auth.domain.param.RoleParam; import com.crm.auth.service.ISysRoleService; import com.crm.base.domain.result.PageResult; import com.crm.base.domain.result.Result; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -18,42 +23,69 @@ import java.util.stream.Collectors; /** * 瑙掕壊绠$悊绔帴鍙o紙ADR-0012锛?- *

閬靛惊鍏ㄥ眬鎺ュ彛濂戠害锛氶潪涓ユ牸 RESTful锛屽啓鎿嶄綔 POST + 鍔ㄤ綔鍚庣紑锛岃〃鍗曞瓧娈垫敹鍙?/p> + *

閬靛惊鍏ㄥ眬鎺ュ彛濂戠害锛氶潪涓ユ牸 RESTful锛屽啓鎿嶄綔 POST + 鍔ㄤ綔鍚庣紑锛岃〃鍗曞瓧娈垫敹鍙傦紙ADR-0017锛? *

鏉冮檺鎺у埗锛氱函 hasAuthority锛屾潈闄愮爜鐢辨暟鎹垵濮嬪寲鍣ㄧ瀛愬寲鍒版潈闄愯祫婧愭爲

- *

钖勯€傞厤灞傦紝鍙皟 {@link ISysRoleService}锛屼笉鍚笟鍔¢€昏緫锛圓DR-0017锛?/p> + *

鍒嗘ā鍧楁敼閫犲悗锛?7 鍐崇瓥锛夛細saveOrUpdate 鐢?moduleScopes JSON 瀛楃涓茶〃鍗曞瓧娈垫浛浠?dataScope锛?+ * detail 杩斿洖 moduleScopes 鏁扮粍锛沺age 鍘绘帀 dataScope

*/ +@Slf4j @RestController @RequestMapping("/api/roles") @RequiredArgsConstructor public class RoleController { private final ISysRoleService sysRoleService; + private final ObjectMapper objectMapper; @PostMapping("/page") @PreAuthorize("hasAuthority('crm:role:list')") - @Operation(summary = "鍒嗛〉鏌ヨ瑙掕壊", tags = {"绯荤粺绠$悊/瑙掕壊绠$悊"}) - public Result> page(RoleParam param) { + @Operation(summary = "鍒嗛〉鏌ヨ瑙掕壊", tags = {"绯荤粺绠$悊/鏉冮檺绠$悊/瑙掕壊绠$悊"}) + public Result> page( + @RequestParam(defaultValue = "1") Integer current, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) String keyword) { + RoleParam param = new RoleParam(); + param.setCurrent(current); + param.setSize(size); + param.setKeyword(keyword); return Result.success(sysRoleService.pageRoles(param)); } @PostMapping("/saveOrUpdate") @PreAuthorize("hasAuthority('crm:role:save')") - @Operation(summary = "鏂板鎴栫紪杈戣鑹?, tags = {"绯荤粺绠$悊/瑙掕壊绠$悊"}) - public Result saveOrUpdate(RoleDTO dto) { - sysRoleService.saveRole(dto.toEntity()); + @Operation(summary = "鏂板鎴栫紪杈戣鑹?, tags = {"绯荤粺绠$悊/鏉冮檺绠$悊/瑙掕壊绠$悊"}) + public Result saveOrUpdate( + @RequestParam(required = false) Long id, + @RequestParam String roleName, + @RequestParam String roleCode, + @RequestParam(required = false) String moduleScopes, + @RequestParam(defaultValue = "0") Integer sort, + @RequestParam(required = false) String remark) { + SysRole role = new SysRole(); + role.setId(id); + role.setRoleName(roleName); + role.setRoleCode(roleCode); + role.setSort(sort); + role.setRemark(remark); + sysRoleService.saveRole(role); + + // 瑙f瀽 moduleScopes JSON 瀛楃涓插苟淇濆瓨锛堝叏閲忔浛鎹紝涓?assign-resources 妯″紡涓€鑷达級 + List scopes = parseModuleScopes(moduleScopes); + sysRoleService.saveRoleScopes(role.getId(), scopes); + return Result.success(); } @GetMapping("/detail") @PreAuthorize("hasAuthority('crm:role:detail')") - @Operation(summary = "瑙掕壊璇︽儏锛堝惈鎺堟潈璧勬簮闆嗗悎锛?, tags = {"绯荤粺绠$悊/瑙掕壊绠$悊"}) + @Operation(summary = "瑙掕壊璇︽儏锛堝惈鎺堟潈璧勬簮闆嗗悎 + 姣忔ā鍧楁。浣嶏級", tags = {"绯荤粺绠$悊/鏉冮檺绠$悊/瑙掕壊绠$悊"}) public Result detail(@RequestParam Long roleId) { return Result.success(sysRoleService.getRoleDetail(roleId)); } @PostMapping("/assign-resources") @PreAuthorize("hasAuthority('crm:role:assign')") - @Operation(summary = "鍒嗛厤瑙掕壊鏉冮檺璧勬簮锛堢鍏堣ˉ鍏?鍏ㄩ噺鏇挎崲锛?, tags = {"绯荤粺绠$悊/瑙掕壊绠$悊"}) + @Operation(summary = "鍒嗛厤瑙掕壊鏉冮檺璧勬簮锛堢鍏堣ˉ鍏?鍏ㄩ噺鏇挎崲锛?, tags = {"绯荤粺绠$悊/鏉冮檺绠$悊/瑙掕壊绠$悊"}) public Result assignResources( @RequestParam Long roleId, @RequestParam String resourceIds) { @@ -69,9 +101,21 @@ public class RoleController { @PostMapping("/delete") @PreAuthorize("hasAuthority('crm:role:delete')") - @Operation(summary = "鍒犻櫎瑙掕壊锛堢骇鑱旀竻鐞嗗叧鑱旇〃锛?, tags = {"绯荤粺绠$悊/瑙掕壊绠$悊"}) + @Operation(summary = "鍒犻櫎瑙掕壊锛堢骇鑱旀竻鐞嗗叧鑱旇〃锛?, tags = {"绯荤粺绠$悊/鏉冮檺绠$悊/瑙掕壊绠$悊"}) public Result delete(@RequestParam Long id) { sysRoleService.deleteRoleCascade(id); return Result.success(); } + + private List parseModuleScopes(String moduleScopes) { + if (StrUtil.isBlank(moduleScopes)) { + return Collections.emptyList(); + } + try { + return objectMapper.readValue(moduleScopes, new TypeReference<>() {}); + } catch (Exception e) { + log.warn("moduleScopes JSON 瑙f瀽澶辫触锛歿}", e.getMessage()); + throw new IllegalArgumentException("moduleScopes 鏍煎紡涓嶅悎娉曪紝闇€涓?JSON 鏁扮粍锛歔{\"moduleCode\":\"...\",\"dataScope\":1}]"); + } + } } diff --git a/crm-auth/src/main/java/com/crm/auth/domain/dto/RoleDTO.java b/crm-auth/src/main/java/com/crm/auth/domain/dto/RoleDTO.java index c857bf2..353938f 100644 --- a/crm-auth/src/main/java/com/crm/auth/domain/dto/RoleDTO.java +++ b/crm-auth/src/main/java/com/crm/auth/domain/dto/RoleDTO.java @@ -13,6 +13,8 @@ import java.util.List; *

缁ф壙 {@link BaseDTO} 鐧芥嬁 id/createTime/updateTime锛沜reateTime/updateTime 鐢卞叏灞€ * @InitBinder 鍦ㄥ叆鍙備晶 strip锛屽嚭鍙備晶灞曠ず鍘熷€笺€俠uiltin 浜︾敱 @InitBinder strip锛堝唴缃鑹叉爣璁版湇鍔$瑁佸畾锛夈€?/p> *

resourceIds 涓哄凡琛ュ叏绁栧厛鐨勫畬鏁存巿鏉冮泦鍚堬紙ADR-0005锛夛紝浠?detail 鍑哄弬濉厖銆?/p> + *

鍒嗘ā鍧楁敼閫犲悗锛?7 鍐崇瓥锛夛細鍘绘帀 dataScope 鍗曞瓧娈碉紝鏀逛负 moduleScopes 鍒楄〃銆?+ * moduleScopes 鍦?page 鍑哄弬涓负 null锛堝垪琛ㄨ鍥句笉闇€瑕佹瘡妯″潡妗d綅锛夈€?/p> */ @Data @EqualsAndHashCode(callSuper = true) @@ -24,9 +26,6 @@ public class RoleDTO extends BaseDTO { @Schema(description = "瑙掕壊缂栫爜") private String roleCode; - @Schema(description = "鏁版嵁鑼冨洿 1=鏈汉 2=鏈儴闂?3=鏈儴闂ㄥ強瀛愰儴闂?4=鍏ㄩ儴") - private Integer dataScope; - @Schema(description = "鎺掑簭") private Integer sort; @@ -39,7 +38,10 @@ public class RoleDTO extends BaseDTO { @Schema(description = "宸茶ˉ鍏ㄧ鍏堢殑瀹屾暣鎺堟潈闆嗗悎锛堝瓨鍌ㄦ€侊級") private List resourceIds; - /** SysRole 鈫?RoleDTO 鍩烘湰瀛楁鏄犲皠锛堜笉鍚?resourceIds锛?*/ + @Schema(description = "瑙掕壊脳妯″潡鏁版嵁鑼冨洿妗d綅鍒楄〃锛堜粎 detail 鍑哄弬濉厖锛宲age 鍑哄弬涓?null锛?) + private List moduleScopes; + + /** SysRole 鈫?RoleDTO 鍩烘湰瀛楁鏄犲皠锛堜笉鍚?resourceIds / moduleScopes锛?*/ public static RoleDTO fromEntity(SysRole entity) { if (entity == null) { return null; @@ -48,7 +50,6 @@ public class RoleDTO extends BaseDTO { dto.setId(entity.getId()); dto.setRoleName(entity.getRoleName()); dto.setRoleCode(entity.getRoleCode()); - dto.setDataScope(entity.getDataScope()); dto.setSort(entity.getSort()); dto.setRemark(entity.getRemark()); dto.setBuiltin(entity.getBuiltin()); @@ -61,7 +62,6 @@ public class RoleDTO extends BaseDTO { entity.setId(this.getId()); entity.setRoleName(this.roleName); entity.setRoleCode(this.roleCode); - entity.setDataScope(this.dataScope); entity.setSort(this.sort); entity.setRemark(this.remark); return entity; diff --git a/crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java b/crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java index adb2d31..ce5ccd5 100644 --- a/crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java +++ b/crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java @@ -22,10 +22,6 @@ public class SysRole extends BaseEntity { @Column(columnDefinition = "varchar(50) not null comment '瑙掕壊缂栫爜'") private String roleCode; - @Comment("鏁版嵁鑼冨洿 1=鏈汉 2=鏈儴闂?3=鏈儴闂ㄥ強瀛愰儴闂?4=鍏ㄩ儴") - @Column(columnDefinition = "tinyint not null default 1 comment '鏁版嵁鑼冨洿'") - private Integer dataScope; - @Comment("鎺掑簭") @Column(columnDefinition = "int default 0") private Integer sort; diff --git a/crm-auth/src/main/java/com/crm/auth/domain/enums/DataScopeEnum.java b/crm-auth/src/main/java/com/crm/auth/domain/enums/DataScopeEnum.java index 213af75..0f43ab5 100644 --- a/crm-auth/src/main/java/com/crm/auth/domain/enums/DataScopeEnum.java +++ b/crm-auth/src/main/java/com/crm/auth/domain/enums/DataScopeEnum.java @@ -10,8 +10,7 @@ public enum DataScopeEnum implements HasValueEnum { SELF(1, "浠呮湰浜?), DEPT(2, "鏈儴闂?), DEPT_AND_CHILD(3, "鏈儴闂ㄥ強瀛愰儴闂?), - ALL(4, "鍏ㄩ儴鏁版嵁"), - CUSTOM(5, "鑷畾涔?); + ALL(4, "鍏ㄩ儴鏁版嵁"); private final Integer value; private final String label; diff --git a/crm-auth/src/main/java/com/crm/auth/security/DataScopeInterceptor.java b/crm-auth/src/main/java/com/crm/auth/security/DataScopeInterceptor.java index 4a1931a..6f4f037 100644 --- a/crm-auth/src/main/java/com/crm/auth/security/DataScopeInterceptor.java +++ b/crm-auth/src/main/java/com/crm/auth/security/DataScopeInterceptor.java @@ -36,7 +36,9 @@ import java.util.stream.Collectors; *

鎷︽埅 SELECT 璇彞锛屽悜 WHERE 瀛愬彞娉ㄥ叆 owner_id / dept_id 杩囨护鏉′欢銆傚彲瑙佽寖鍥寸敱 * {@link DataVisibilityContext} 鍒ゅ畾锛屾湰绫讳笉鍙備笌銆屾。浣嶅浣曟姌绠椼€嶇殑鍐崇瓥锛屽彧璐熻矗缈昏瘧缁撴灉銆?/p> * - *

鏈爣娉?{@link com.crm.base.annotation.DataScope} 鐨勮〃涓嶄細瑙﹀彂杩囨护銆?/p> + *

鍒嗘ā鍧楁敼閫犲悗锛屾嫤鎴櫒椤哄簭璋冩崲锛氬厛瑙f瀽 SQL 鎵捐〃 鈫?鏌ユ敞瑙o紙鍚?module 灞炴€э級鈫?+ * 鐢?{@code anno.module()} 浠庝笂涓嬫枃鍙栬妯″潡鐨?scope 鈫?娉ㄥ叆 WHERE銆?+ * 鏈爣娉?{@link com.crm.base.annotation.DataScope} 鐨勮〃涓嶄細瑙﹀彂杩囨护銆?/p> */ @Slf4j @RequiredArgsConstructor @@ -49,12 +51,6 @@ public class DataScopeInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { - VisibilityScope scope = DataVisibilityContext.currentScope(); - // 涓嶈繃婊わ紙鏃犱笂涓嬫枃鎴栧叏閮ㄥ彲瑙侊級鏃惰繛 SQL 閮戒笉蹇呰В鏋?- if (scope.kind() == VisibilityScope.Kind.ALL_VISIBLE) { - return invocation.proceed(); - } - StatementHandler handler = PluginUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(handler); MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); @@ -72,7 +68,7 @@ public class DataScopeInterceptor implements Interceptor { return invocation.proceed(); } - String newSql = injectDataScope((Select) stmt, scope); + String newSql = injectDataScope((Select) stmt); if (!newSql.equals(originalSql)) { metaObject.setValue("delegate.boundSql.sql", newSql); log.debug("鏁版嵁鏉冮檺 SQL 娉ㄥ叆瀹屾垚"); @@ -84,7 +80,7 @@ public class DataScopeInterceptor implements Interceptor { return invocation.proceed(); } - private String injectDataScope(Select select, VisibilityScope scope) throws JSQLParserException { + private String injectDataScope(Select select) throws JSQLParserException { // jsqlparser 4.7+ 绉婚櫎浜?SelectBody锛孭lainSelect 鐩存帴缁ф壙 Select if (!(select instanceof PlainSelect)) { return select.toString(); @@ -101,6 +97,12 @@ public class DataScopeInterceptor implements Interceptor { return select.toString(); } + // 鍏堟煡琛ㄢ啋娉ㄨВ锛堝惈 module锛夛紝鍐嶅彇璇ユā鍧楃殑 scope锛?5 椤哄簭璋冩崲锛?+ VisibilityScope scope = DataVisibilityContext.currentScope(anno.module()); + if (scope.kind() == VisibilityScope.Kind.ALL_VISIBLE) { + return select.toString(); + } + Expression condition = buildCondition(scope, anno); if (condition == null) { return select.toString(); diff --git a/crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java b/crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java index 7ed91ae..c0eb17c 100644 --- a/crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java +++ b/crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java @@ -7,12 +7,14 @@ import com.crm.auth.domain.dto.ResourceNodeDTO; import com.crm.auth.domain.entity.AuthUser; import com.crm.auth.domain.entity.SysMenu; import com.crm.auth.domain.entity.SysRole; +import com.crm.auth.domain.entity.SysRoleDataScope; import com.crm.auth.domain.entity.SysRoleMenu; import com.crm.auth.domain.entity.SysUserDept; import com.crm.auth.domain.entity.SysUserRole; import com.crm.auth.domain.enums.MenuType; import com.crm.auth.mapper.AuthUserMapper; import com.crm.auth.mapper.SysMenuMapper; +import com.crm.auth.mapper.SysRoleDataScopeMapper; import com.crm.auth.mapper.SysRoleMapper; import com.crm.auth.mapper.SysRoleMenuMapper; import com.crm.auth.mapper.SysUserDeptMapper; @@ -27,8 +29,10 @@ import org.springframework.stereotype.Component; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -36,6 +40,10 @@ import java.util.stream.Collectors; /** * 鏉冮檺瑙f瀽寮曟搸瀹炵幇锛氭暟鎹彲瑙佹€с€佹潈闄愮爜骞堕泦銆佸彲瑙佽彍鍗曟爲鍏辩敤鍚屼竴鏉? * 銆岀敤鎴?鈫?瑙掕壊 鈫?鎺堟潈 鈫?璧勬簮銆嶆煡璇㈤摼锛圓DR-0011锛夈€?+ * + *

鍒嗘ā鍧楁敼閫犲悗锛屾暟鎹彲瑙佹€ф浠庛€屾墍鏈夎鑹插彇鍏ㄥ眬鏈€瀹戒竴妗c€嶆敼涓?+ * 銆屾煡 sys_role_data_scope 鎸?module_code 鍒嗙粍锛屾瘡缁勫彇鏈€瀹姐€嶏紙ADR-0008 瑙勫垯涓嬫矇鍒版瘡妯″潡锛夈€?+ * 閮ㄩ棬闆嗗悎 / 瀛愭爲灞曞紑鍙畻涓€娆★紝鍚勬ā鍧楁。浣嶅喅瀹氱敤涓嶇敤杩欑粍閮ㄩ棬 ID銆?/p> */ @Slf4j @Component @@ -49,24 +57,31 @@ public class PermissionResolverImpl implements PermissionResolver { private final AuthUserMapper authUserMapper; private final DeptTreeCache deptTreeCache; private final SysUserDeptMapper sysUserDeptMapper; + private final SysRoleDataScopeMapper sysRoleDataScopeMapper; @Override public PermissionGrant resolve(Long userId) { - // 鈹€鈹€ 鏁版嵁鍙鎬э細鏃犺鑹插彇鏈€绐勬。锛堜粎鏈汉锛夛紝澶氳鑹插彇鏈€瀹芥。锛圓DR-0008锛夆攢鈹€ + // 鈹€鈹€ 鏁版嵁鍙鎬э細鏌?sys_role_data_scope锛屾寜 module_code 鍒嗙粍鍙栨渶瀹斤紙ADR-0008 涓嬫矇鍒版瘡妯″潡锛夆攢鈹€ List userRoles = userRolesOf(userId); - DataScopeLevel widest = DataScopeLevel.SELF; List roles = Collections.emptyList(); + Map moduleLevels = new HashMap<>(); if (CollUtil.isNotEmpty(userRoles)) { - roles = sysRoleMapper.selectBatchIds(roleIdsOf(userRoles)); - for (SysRole role : roles) { - // 鏈厤妗d綅鐨勮鑹叉寜鏈€绐勬。澶勭悊锛屼笉鏀惧浠讳綍浜虹殑鍙鑼冨洿 - if (role.getDataScope() == null) continue; - DataScopeLevel level = DataScopeLevel.fromCode(role.getDataScope()); - if (level.getCode() > widest.getCode()) { - widest = level; - } + List roleIds = roleIdsOf(userRoles); + roles = sysRoleMapper.selectBatchIds(roleIds); + + // 鏌?sys_role_data_scope 涓鐢ㄦ埛鎵€鏈夎鑹茬殑琛?+ List roleScopes = sysRoleDataScopeMapper.selectList( + new LambdaQueryWrapper() + .in(SysRoleDataScope::getRoleId, roleIds)); + // 鎸?module_code 鍒嗙粍锛屾瘡缁勫彇鏈€瀹?level锛堣鑹叉棤琛?= 璐$尞 SELF锛屼笉鏀惧浠讳綍浜虹殑鍙鑼冨洿锛?+ for (SysRoleDataScope rs : roleScopes) { + DataScopeLevel level = DataScopeLevel.fromCode(rs.getDataScope()); + moduleLevels.merge(rs.getModuleCode(), level, + (a, b) -> a.getCode() >= b.getCode() ? a : b); } + // 鏃犺鐨勬ā鍧椾笉杩?map锛孌ataVisibility.visibilityScope() 鐢?getOrDefault(moduleCode, SELF) } + AuthUser user = authUserMapper.selectById(userId); Long primaryDeptId = user != null ? user.getDeptId() : null; // 閮ㄩ棬闆嗗悎 = 涓婚儴闂?+ 鍏艰亴閮ㄩ棬骞堕泦锛岄泦鍚堝唴鍦颁綅骞崇瓑锛圓DR-0005锛夛紱鍏艰亴鍏崇郴姣忚姹傚疄鏃舵煡 @@ -77,13 +92,17 @@ public class PermissionResolverImpl implements PermissionResolver { sysUserDeptMapper.selectList( new LambdaQueryWrapper().eq(SysUserDept::getUserId, userId)) .forEach(ud -> deptIds.add(ud.getDeptId())); + + // 瀛愭爲灞曞紑鍒ゅ畾锛氫换涓€妯″潡鐨勬。浣嶆槸 DEPT_AND_CHILDREN 灏辩畻 expandedDeptIds List expandedDeptIds = null; - if (widest == DataScopeLevel.DEPT_AND_CHILDREN) { + boolean needsExpanded = moduleLevels.values().stream() + .anyMatch(l -> l == DataScopeLevel.DEPT_AND_CHILDREN); + if (needsExpanded) { // 瀛愭爲灞曞紑鍦ㄧ紦瀛樻爲涓婂畬鎴愶紝澶氶儴闂ㄥ叡浜悓涓€妫垫爲锛涙棤閮ㄩ棬鍙睍寮€鏃朵负绌洪泦鍚堬紝鍗充竴寰嬩笉鍙 expandedDeptIds = deptIds.isEmpty() ? List.of() : deptTreeCache.expandWithChildren(deptIds); } DataVisibility visibility = new DataVisibility( - userId, primaryDeptId, List.copyOf(deptIds), widest, expandedDeptIds); + userId, primaryDeptId, List.copyOf(deptIds), moduleLevels, expandedDeptIds); // 鈹€鈹€ 鏉冮檺鐮佸苟闆嗭紙ADR-0011锛夛細button 涓?status=enabled 鐨?perms锛屽幓閲?鈹€鈹€ Set permCodes = CollUtil.isEmpty(userRoles) diff --git a/crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java b/crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java index 4f1d73f..3dfcc5f 100644 --- a/crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java +++ b/crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java @@ -1,5 +1,6 @@ package com.crm.auth.service; +import com.crm.auth.domain.dto.ModuleScopeDTO; import com.crm.auth.domain.dto.RoleDTO; import com.crm.auth.domain.entity.SysRole; import com.crm.auth.domain.param.RoleParam; @@ -14,12 +15,12 @@ public interface ISysRoleService extends IBaseService { * 瑙掕壊鍒嗛〉锛坘eyword 鍖归厤瑙掕壊鍚嶇О锛涚粡 PageConverter 鏀舵暃 size 涓婇檺涓庢帓搴忓瓧娈甸槻娉ㄥ叆锛? * * @param param 鍒嗛〉鏌ヨ鍏ュ弬 - * @return 瑙掕壊 DTO 鍒嗛〉锛堜笉鍥炴樉 deleted/creatorId/updaterId锛?+ * @return 瑙掕壊 DTO 鍒嗛〉锛堜笉鍥炴樉 deleted/creatorId/updaterId锛沵oduleScopes 涓?null锛? */ PageResult pageRoles(RoleParam param); /** - * 淇濆瓨瑙掕壊锛堝甫鏍¢獙锛歳oleCode 鍞竴銆佸唴缃鑹蹭繚鎶ゃ€乨ataScope 鑼冨洿銆佺璁?builtin锛?+ * 淇濆瓨瑙掕壊锛堝甫鏍¢獙锛歳oleCode 鍞竴銆佸唴缃鑹蹭繚鎶わ級 * * @param role 瑙掕壊淇℃伅锛坕d 涓虹┖鏃舵柊澧炪€侀潪绌烘椂缂栬緫锛? * @return 淇濆瓨鍚庣殑瑙掕壊锛堝惈 ID锛?@@ -27,10 +28,18 @@ public interface ISysRoleService extends IBaseService { SysRole saveRole(SysRole role); /** - * 鑾峰彇瑙掕壊璇︽儏锛堝熀鏈俊鎭?+ 宸叉巿鏉冭祫婧?ID 闆嗗悎锛?+ * 淇濆瓨瑙掕壊鐨勬瘡妯″潡鏁版嵁鑼冨洿妗d綅锛堝叏閲忔浛鎹?sys_role_data_scope锛屼笌 assign-resources 妯″紡涓€鑷达級 + * + * @param roleId 瑙掕壊 ID + * @param moduleScopes 妯″潡妗d綅鍒楄〃锛坣ull 鎴栫┖ = 娓呯┖鎵€鏈夎锛岃蛋榛樿 SELF锛?+ */ + void saveRoleScopes(Long roleId, List moduleScopes); + + /** + * 鑾峰彇瑙掕壊璇︽儏锛堝熀鏈俊鎭?+ 宸叉巿鏉冭祫婧?ID 闆嗗悎 + 姣忔ā鍧楁暟鎹寖鍥存。浣嶏級 * * @param roleId 瑙掕壊 ID - * @return RoleDTO锛屽惈 resourceIds锛堜粠 sys_role_menu 鏌ュ嚭鐨勫畬鏁存巿鏉冮泦鍚堬級 + * @return RoleDTO锛屽惈 resourceIds锛堜粠 sys_role_menu 鏌ュ嚭鐨勫畬鏁存巿鏉冮泦鍚堬級鍜?moduleScopes */ RoleDTO getRoleDetail(Long roleId); @@ -44,7 +53,7 @@ public interface ISysRoleService extends IBaseService { void assignResources(Long roleId, List resourceIds); /** - * 鍒犻櫎瑙掕壊骞剁骇鑱旀竻鐞嗚鑹?鑿滃崟鍏宠仈锛堜簨鍔′繚鎶わ級 + * 鍒犻櫎瑙掕壊骞剁骇鑱旀竻鐞嗚鑹?鑿滃崟鍏宠仈 + 瑙掕壊脳妯″潡妗d綅锛堜簨鍔′繚鎶わ級 * * @param roleId 瑙掕壊 ID */ diff --git a/crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java b/crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java index bec6ab8..86f9d62 100644 --- a/crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java +++ b/crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java @@ -4,12 +4,17 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.crm.auth.constant.AuthConstants; +import com.crm.auth.domain.dto.ModuleScopeDTO; import com.crm.auth.domain.dto.RoleDTO; +import com.crm.auth.domain.entity.SysDataScopeModule; import com.crm.auth.domain.entity.SysRole; +import com.crm.auth.domain.entity.SysRoleDataScope; import com.crm.auth.domain.entity.SysRoleMenu; import com.crm.auth.domain.entity.SysUserRole; import com.crm.auth.domain.enums.DataScopeEnum; import com.crm.auth.domain.param.RoleParam; +import com.crm.auth.mapper.SysDataScopeModuleMapper; +import com.crm.auth.mapper.SysRoleDataScopeMapper; import com.crm.auth.mapper.SysRoleMapper; import com.crm.auth.mapper.SysRoleMenuMapper; import com.crm.auth.mapper.SysUserRoleMapper; @@ -24,8 +29,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -36,14 +42,17 @@ import static com.crm.auth.constant.AuthConstants.*; @RequiredArgsConstructor public class SysRoleServiceImpl extends BaseServiceImpl implements ISysRoleService { - private static final Set VALID_DATA_SCOPE_VALUES = Arrays.stream(DataScopeEnum.values()) - .filter(e -> e != DataScopeEnum.CUSTOM) - .map(DataScopeEnum::getValue) - .collect(Collectors.toUnmodifiableSet()); + private static final Set VALID_DATA_SCOPE_VALUES = Set.of( + DataScopeEnum.SELF.getValue(), + DataScopeEnum.DEPT.getValue(), + DataScopeEnum.DEPT_AND_CHILD.getValue(), + DataScopeEnum.ALL.getValue()); private final SysRoleMenuMapper sysRoleMenuMapper; private final SysUserRoleMapper sysUserRoleMapper; private final ISysMenuService sysMenuService; + private final SysRoleDataScopeMapper sysRoleDataScopeMapper; + private final SysDataScopeModuleMapper sysDataScopeModuleMapper; @Override public PageResult pageRoles(RoleParam param) { @@ -64,11 +73,6 @@ public class SysRoleServiceImpl extends BaseServiceImpl throw new BusinessErrorException(CODE_ROLE_INVALID, "瑙掕壊缂栫爜涓嶈兘涓虹┖"); } - // dataScope 鑼冨洿鏍¢獙锛氳鑹蹭笉鍏佽浣跨敤 CUSTOM 鑷畾涔夎寖鍥?- if (role.getDataScope() != null && !VALID_DATA_SCOPE_VALUES.contains(role.getDataScope())) { - throw new BusinessErrorException(CODE_ROLE_INVALID, "鏁版嵁鑼冨洿涓嶅悎娉?); - } - // 鍐呯疆瑙掕壊淇濇姢锛氫笉鍏佽閫氳繃鎺ュ彛鍒涘缓鍐呯疆瑙掕壊锛堟棤璁烘柊寤鸿繕鏄紪杈戜笉瀛樺湪鐨勮褰曪級 SysRole existing = role.getId() != null ? baseMapper.selectById(role.getId()) : null; if (Boolean.TRUE.equals(role.getBuiltin()) @@ -98,6 +102,58 @@ public class SysRoleServiceImpl extends BaseServiceImpl return role; } + @Override + @Transactional(rollbackFor = Exception.class) + public void saveRoleScopes(Long roleId, List moduleScopes) { + // 鏍¢獙瑙掕壊瀛樺湪 + SysRole role = baseMapper.selectById(roleId); + if (role == null) { + throw new BusinessErrorException(CODE_ROLE_INVALID, "瑙掕壊涓嶅瓨鍦細" + roleId); + } + + // 鍏ㄩ噺鏇挎崲锛堝厛鍒犲悗鎻掞紝涓?assign-resources 妯″紡涓€鑷达級 + sysRoleDataScopeMapper.delete( + new LambdaQueryWrapper().eq(SysRoleDataScope::getRoleId, roleId)); + + if (moduleScopes == null || moduleScopes.isEmpty()) { + log.info("娓呯┖瑙掕壊妯″潡妗d綅锛歳oleId={}, 琛屾暟=0", roleId); + return; + } + + // 鏌ユ敞鍐岃〃涓?enabled 鐨勬ā鍧?code 闆嗗悎锛屾牎楠屽悎娉曟€?+ Set validModuleCodes = sysDataScopeModuleMapper.selectList( + new LambdaQueryWrapper() + .eq(SysDataScopeModule::getStatus, "enabled")) + .stream().map(SysDataScopeModule::getCode).collect(Collectors.toSet()); + + // 鏍¢獙锛氭瘡琛?dataScope 鍦?1-4锛宮oduleCode 瀛樺湪涓?enabled锛屽悓涓€璇锋眰鍐?moduleCode 涓嶉噸澶?+ Set seenCodes = new java.util.HashSet<>(); + for (ModuleScopeDTO ms : moduleScopes) { + if (StrUtil.isBlank(ms.getModuleCode())) { + throw new BusinessErrorException(CODE_ROLE_INVALID, "妯″潡缂栫爜涓嶈兘涓虹┖"); + } + if (!validModuleCodes.contains(ms.getModuleCode())) { + throw new BusinessErrorException(CODE_ROLE_INVALID, + "妯″潡缂栫爜涓嶅悎娉曟垨宸茬鐢細" + ms.getModuleCode()); + } + if (!VALID_DATA_SCOPE_VALUES.contains(ms.getDataScope())) { + throw new BusinessErrorException(CODE_ROLE_INVALID, + "鏁版嵁鑼冨洿涓嶅悎娉曪細" + ms.getDataScope()); + } + if (!seenCodes.add(ms.getModuleCode())) { + throw new BusinessErrorException(CODE_ROLE_INVALID, + "鍚屼竴璇锋眰鍐呮ā鍧楃紪鐮佷笉鍏佽閲嶅锛? + ms.getModuleCode()); + } + + SysRoleDataScope rs = new SysRoleDataScope(); + rs.setRoleId(roleId); + rs.setModuleCode(ms.getModuleCode()); + rs.setDataScope(ms.getDataScope()); + sysRoleDataScopeMapper.insert(rs); + } + log.info("淇濆瓨瑙掕壊妯″潡妗d綅锛歳oleId={}, 琛屾暟={}", roleId, moduleScopes.size()); + } + @Override public RoleDTO getRoleDetail(Long roleId) { SysRole role = baseMapper.selectById(roleId); @@ -112,6 +168,29 @@ public class SysRoleServiceImpl extends BaseServiceImpl .map(SysRoleMenu::getMenuId) .toList(); vo.setResourceIds(resourceIds); + + // 鏌?sys_role_data_scope 寰楀埌姣忔ā鍧楁。浣嶏紝join 娉ㄥ唽琛ㄥ彇妯″潡灞曠ず鍚?+ List roleScopes = sysRoleDataScopeMapper.selectList( + new LambdaQueryWrapper().eq(SysRoleDataScope::getRoleId, roleId)); + if (!roleScopes.isEmpty()) { + Set moduleCodes = roleScopes.stream() + .map(SysRoleDataScope::getModuleCode).collect(Collectors.toSet()); + Map codeToName = sysDataScopeModuleMapper.selectList( + new LambdaQueryWrapper() + .in(SysDataScopeModule::getCode, moduleCodes)) + .stream() + .collect(Collectors.toMap(SysDataScopeModule::getCode, SysDataScopeModule::getName)); + List moduleScopeDTOs = roleScopes.stream() + .map(rs -> ModuleScopeDTO.of( + rs.getModuleCode(), + codeToName.getOrDefault(rs.getModuleCode(), rs.getModuleCode()), + rs.getDataScope())) + .collect(Collectors.toList()); + vo.setModuleScopes(moduleScopeDTOs); + } else { + vo.setModuleScopes(Collections.emptyList()); + } + return vo; } @@ -145,6 +224,9 @@ public class SysRoleServiceImpl extends BaseServiceImpl // 绾ц仈娓呯悊瑙掕壊-鑿滃崟鍏宠仈 sysRoleMenuMapper.delete( new LambdaQueryWrapper().eq(SysRoleMenu::getRoleId, roleId)); + // 绾ц仈娓呯悊瑙掕壊脳妯″潡妗d綅 + sysRoleDataScopeMapper.delete( + new LambdaQueryWrapper().eq(SysRoleDataScope::getRoleId, roleId)); // 绾ц仈娓呯悊鐢ㄦ埛-瑙掕壊鍏宠仈 sysUserRoleMapper.delete( new LambdaQueryWrapper() diff --git a/crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java b/crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java index 7e4ee55..eb7fc5a 100644 --- a/crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java +++ b/crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java @@ -15,6 +15,7 @@ import com.crm.auth.mapper.SysRoleMapper; import com.crm.auth.mapper.SysRoleMenuMapper; import com.crm.auth.mapper.SysUserDeptMapper; import com.crm.auth.mapper.SysUserRoleMapper; +import com.crm.auth.mapper.SysRoleDataScopeMapper; import com.crm.auth.security.DataScopeInterceptor; import com.crm.auth.security.DataScopeTables; import com.crm.auth.security.PermissionGrant; @@ -122,6 +123,7 @@ class DataScopeIntegrationTest { configuration.addMapper(AuthUserMapper.class); configuration.addMapper(SysDeptMapper.class); configuration.addMapper(SysUserDeptMapper.class); + configuration.addMapper(SysRoleDataScopeMapper.class); configuration.addMapper(TestOwnedDataMapper.class); sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); @@ -148,7 +150,8 @@ class DataScopeIntegrationTest { session.getMapper(SysMenuMapper.class), session.getMapper(AuthUserMapper.class), deptTreeCache, - session.getMapper(SysUserDeptMapper.class)); + session.getMapper(SysUserDeptMapper.class), + session.getMapper(SysRoleDataScopeMapper.class)); testDataMapper = session.getMapper(TestOwnedDataMapper.class); deptMapper = session.getMapper(SysDeptMapper.class); DeptQueryCounter.COUNT.set(0); @@ -461,7 +464,7 @@ class DataScopeIntegrationTest { create table sys_role ( id bigint primary key, creator_id varchar(50), create_time datetime, updater_id varchar(50), update_time datetime, deleted tinyint not null default 0, - role_name varchar(50), role_code varchar(50), data_scope tinyint, sort int, remark varchar(200), + role_name varchar(50), role_code varchar(50), sort int, remark varchar(200), builtin boolean default false) """, """ @@ -495,6 +498,21 @@ class DataScopeIntegrationTest { deny_behavior varchar(16), status varchar(16) default 'enabled', api_url varchar(200)) """, """ + create table sys_data_scope_module ( + id bigint primary key, creator_id varchar(50), create_time datetime, + updater_id varchar(50), update_time datetime, deleted tinyint not null default 0, + code varchar(50) not null unique, name varchar(100) not null, sort int default 0, + status varchar(10) default 'enabled', builtin boolean default false) + """, + """ + create table sys_role_data_scope ( + id bigint primary key, creator_id varchar(50), create_time datetime, + updater_id varchar(50), update_time datetime, deleted tinyint not null default 0, + role_id bigint not null, module_code varchar(50) not null, + data_scope tinyint not null, + constraint uk_role_module unique (role_id, module_code)) + """, + """ create table sys_role_menu ( id bigint primary key, role_id bigint not null, menu_id bigint not null, constraint uk_role_menu unique (role_id, menu_id)) @@ -505,13 +523,21 @@ class DataScopeIntegrationTest { "insert into sys_dept (id, deleted, parent_id, dept_name) values (12, 0, 10, '鍗庡寳缁?)", "insert into sys_dept (id, deleted, parent_id, dept_name) values (121, 0, 12, '鍗庡寳涓€闃?)", "insert into sys_dept (id, deleted, parent_id, dept_name) values (20, 0, 0, '甯傚満閮?)", - // 瑙掕壊锛歩d 鍗虫。浣?- "insert into sys_role (id, deleted, role_name, role_code, data_scope) values (1, 0, '浠呮湰浜?, 'SELF', 1)", - "insert into sys_role (id, deleted, role_name, role_code, data_scope) values (2, 0, '鏈儴闂?, 'DEPT', 2)", - "insert into sys_role (id, deleted, role_name, role_code, data_scope) values (3, 0, '鍚瓙閮ㄩ棬', 'DEPT_CHILD', 3)", - "insert into sys_role (id, deleted, role_name, role_code, data_scope) values (4, 0, '鍏ㄩ儴', 'ALL', 4)", + // 鏁版嵁鏉冮檺妯″潡 + "insert into sys_data_scope_module (id, code, name, sort, builtin) values (1, 'test', '娴嬭瘯', 1, true)", + // 瑙掕壊锛歩d 鍗虫。浣嶏紙涓嶅啀鏈?data_scope 鍒楋紝妗d綅瀛?sys_role_data_scope锛?+ "insert into sys_role (id, deleted, role_name, role_code) values (1, 0, '浠呮湰浜?, 'SELF')", + "insert into sys_role (id, deleted, role_name, role_code) values (2, 0, '鏈儴闂?, 'DEPT')", + "insert into sys_role (id, deleted, role_name, role_code) values (3, 0, '鍚瓙閮ㄩ棬', 'DEPT_CHILD')", + "insert into sys_role (id, deleted, role_name, role_code) values (4, 0, '鍏ㄩ儴', 'ALL')", // 瑙掕壊5锛歳oleCode 鑷甫 ROLE_ 鍓嶇紑锛堥獙璇佸綊涓€骞傜瓑锛屼笉寰楀弻鍓嶇紑锛?- "insert into sys_role (id, deleted, role_name, role_code, data_scope) values (5, 0, '绠$悊鍛?, 'ROLE_ADMIN', 1)", + "insert into sys_role (id, deleted, role_name, role_code) values (5, 0, '绠$悊鍛?, 'ROLE_ADMIN')", + // 瑙掕壊脳妯″潡妗d綅锛氳鑹?id 鍗虫。浣嶅€?+ "insert into sys_role_data_scope (id, role_id, module_code, data_scope) values (1, 1, 'test', 1)", + "insert into sys_role_data_scope (id, role_id, module_code, data_scope) values (2, 2, 'test', 2)", + "insert into sys_role_data_scope (id, role_id, module_code, data_scope) values (3, 3, 'test', 3)", + "insert into sys_role_data_scope (id, role_id, module_code, data_scope) values (4, 4, 'test', 4)", + "insert into sys_role_data_scope (id, role_id, module_code, data_scope) values (5, 5, 'test', 1)", // 鐢ㄦ埛 "insert into crm_auth_user (id, deleted, username, dept_id) values (1, 0, '浠呮湰浜?閿€鍞儴', 10)", "insert into crm_auth_user (id, deleted, username, dept_id) values (2, 0, '鏈儴闂?閿€鍞儴', 10)", diff --git a/crm-auth/src/test/java/com/crm/auth/security/scope/TestOwnedData.java b/crm-auth/src/test/java/com/crm/auth/security/scope/TestOwnedData.java index 1b18b52..23bbda9 100644 --- a/crm-auth/src/test/java/com/crm/auth/security/scope/TestOwnedData.java +++ b/crm-auth/src/test/java/com/crm/auth/security/scope/TestOwnedData.java @@ -12,7 +12,7 @@ import lombok.EqualsAndHashCode; */ @Data @EqualsAndHashCode(callSuper = true) -@DataScope +@DataScope(module = "test") @TableName("test_owned_data") public class TestOwnedData extends OwnedEntity { diff --git a/crm-auth/src/test/java/com/crm/auth/service/impl/PermissionSeederImplTest.java b/crm-auth/src/test/java/com/crm/auth/service/impl/PermissionSeederImplTest.java index bd66db4..174e306 100644 --- a/crm-auth/src/test/java/com/crm/auth/service/impl/PermissionSeederImplTest.java +++ b/crm-auth/src/test/java/com/crm/auth/service/impl/PermissionSeederImplTest.java @@ -51,7 +51,7 @@ class PermissionSeederImplTest { create table sys_role ( id bigint primary key, creator_id varchar(50), create_time datetime, updater_id varchar(50), update_time datetime, deleted tinyint not null default 0, - role_name varchar(50), role_code varchar(50), data_scope tinyint, sort int, remark varchar(200), + role_name varchar(50), role_code varchar(50), sort int, remark varchar(200), builtin boolean default false) """, """ @@ -126,7 +126,6 @@ class PermissionSeederImplTest { SysRole role = new SysRole(); role.setRoleName("绠$悊鍛?); role.setRoleCode("ROLE_ADMIN"); - role.setDataScope(4); role.setSort(0); role.setBuiltin(true); sysRoleMapper.insert(role); @@ -199,7 +198,6 @@ class PermissionSeederImplTest { SysRole role = new SysRole(); role.setRoleName("绠$悊鍛?); role.setRoleCode("ROLE_ADMIN"); - role.setDataScope(4); role.setSort(0); role.setBuiltin(true); sysRoleMapper.insert(role); @@ -254,7 +252,6 @@ class PermissionSeederImplTest { SysRole role = new SysRole(); role.setRoleName("绠$悊鍛?); role.setRoleCode("ROLE_ADMIN"); - role.setDataScope(4); role.setSort(0); role.setBuiltin(true); sysRoleMapper.insert(role); @@ -353,7 +350,6 @@ class PermissionSeederImplTest { SysRole role = new SysRole(); role.setRoleName("绠$悊鍛?); role.setRoleCode("ROLE_ADMIN"); - role.setDataScope(4); role.setSort(0); role.setBuiltin(true); sysRoleMapper.insert(role); diff --git a/crm-auth/src/test/java/com/crm/auth/service/impl/SysRoleServiceImplTest.java b/crm-auth/src/test/java/com/crm/auth/service/impl/SysRoleServiceImplTest.java index b633fab..9a3103a 100644 --- a/crm-auth/src/test/java/com/crm/auth/service/impl/SysRoleServiceImplTest.java +++ b/crm-auth/src/test/java/com/crm/auth/service/impl/SysRoleServiceImplTest.java @@ -4,6 +4,8 @@ import com.crm.auth.domain.dto.RoleDTO; import com.crm.auth.domain.entity.SysRole; import com.crm.auth.domain.entity.SysRoleMenu; import com.crm.auth.domain.entity.SysUserRole; +import com.crm.auth.mapper.SysDataScopeModuleMapper; +import com.crm.auth.mapper.SysRoleDataScopeMapper; import com.crm.auth.mapper.SysRoleMapper; import com.crm.auth.mapper.SysRoleMenuMapper; import com.crm.auth.mapper.SysUserRoleMapper; @@ -22,7 +24,6 @@ import org.mockito.quality.Strictness; import org.springframework.test.util.ReflectionTestUtils; import java.util.List; -import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -34,6 +35,7 @@ import static org.mockito.Mockito.when; /** * {@link SysRoleServiceImpl} 鍗曞厓娴嬭瘯锛氳鐩?saveRole 鏍¢獙瑙勫垯锛圓DR-0012锛?+ *

鍒嗘ā鍧楁敼閫犲悗绉婚櫎浜?dataScope 鑼冨洿鏍¢獙娴嬭瘯锛堟。浣嶆牎楠岀Щ鑷?saveRoleScopes锛?/p> */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -47,6 +49,10 @@ class SysRoleServiceImplTest { private SysUserRoleMapper sysUserRoleMapper; @Mock private ISysMenuService sysMenuService; + @Mock + private SysRoleDataScopeMapper sysRoleDataScopeMapper; + @Mock + private SysDataScopeModuleMapper sysDataScopeModuleMapper; @InjectMocks private SysRoleServiceImpl sysRoleService; @@ -62,7 +68,7 @@ class SysRoleServiceImplTest { @Test @DisplayName("鍒涘缓瑙掕壊 -> roleCode 宸插瓨鍦?-> 鎷掔粷") void createRole_duplicateCode_rejected() { - SysRole role = buildRole(null, "閿€鍞?, "ROLE_SALES", 1); + SysRole role = buildRole(null, "閿€鍞?, "ROLE_SALES"); when(sysRoleMapper.selectCount(any())).thenReturn(1L); assertThatThrownBy(() -> sysRoleService.saveRole(role)) @@ -73,7 +79,7 @@ class SysRoleServiceImplTest { @Test @DisplayName("缂栬緫瑙掕壊 -> roleCode 涓嶅彉锛堝悓 id锛?-> 閫氳繃") void editRole_sameCodeSameId_passes() { - SysRole role = buildRole(1L, "閿€鍞?, "ROLE_SALES", 1); + SysRole role = buildRole(1L, "閿€鍞?, "ROLE_SALES"); when(sysRoleMapper.selectById(1L)).thenReturn(role); when(sysRoleMapper.selectCount(any())).thenReturn(0L); when(sysRoleMapper.updateById(any(SysRole.class))).thenReturn(1); @@ -87,9 +93,9 @@ class SysRoleServiceImplTest { @Test @DisplayName("缂栬緫鍐呯疆瑙掕壊 -> 淇敼 roleCode -> 鎷掔粷") void editBuiltinRole_changeCode_rejected() { - SysRole existing = buildRole(1L, "绠$悊鍛?, "ROLE_ADMIN", 4); + SysRole existing = buildRole(1L, "绠$悊鍛?, "ROLE_ADMIN"); existing.setBuiltin(true); - SysRole input = buildRole(1L, "绠$悊鍛?, "ROLE_SUPER", 4); + SysRole input = buildRole(1L, "绠$悊鍛?, "ROLE_SUPER"); when(sysRoleMapper.selectById(1L)).thenReturn(existing); assertThatThrownBy(() -> sysRoleService.saveRole(input)) @@ -100,9 +106,9 @@ class SysRoleServiceImplTest { @Test @DisplayName("缂栬緫鍐呯疆瑙掕壊 -> roleCode 涓嶅彉 -> 閫氳繃") void editBuiltinRole_sameCode_passes() { - SysRole existing = buildRole(1L, "绠$悊鍛?, "ROLE_ADMIN", 4); + SysRole existing = buildRole(1L, "绠$悊鍛?, "ROLE_ADMIN"); existing.setBuiltin(true); - SysRole input = buildRole(1L, "绠$悊鍛樻敼", "ROLE_ADMIN", 4); + SysRole input = buildRole(1L, "绠$悊鍛樻敼", "ROLE_ADMIN"); when(sysRoleMapper.selectById(1L)).thenReturn(existing); when(sysRoleMapper.selectCount(any())).thenReturn(0L); when(sysRoleMapper.updateById(any(SysRole.class))).thenReturn(1); @@ -114,7 +120,7 @@ class SysRoleServiceImplTest { @Test @DisplayName("鍒涘缓瑙掕壊 -> 璁剧疆 builtin=true -> 鎷掔粷") void createRole_setBuiltin_rejected() { - SysRole role = buildRole(null, "鍋囩鐞嗗憳", "ROLE_FAKE", 4); + SysRole role = buildRole(null, "鍋囩鐞嗗憳", "ROLE_FAKE"); role.setBuiltin(true); assertThatThrownBy(() -> sysRoleService.saveRole(role)) @@ -125,7 +131,7 @@ class SysRoleServiceImplTest { @Test @DisplayName("缂栬緫涓嶅瓨鍦ㄧ殑 id -> 璁剧疆 builtin=true -> 鎷掔粷锛堥槻姝?saveOrUpdate 鐨?insert 缁曡繃锛?) void editNonExistentRole_setBuiltin_rejected() { - SysRole role = buildRole(999L, "鍋囩鐞嗗憳", "ROLE_FAKE", 4); + SysRole role = buildRole(999L, "鍋囩鐞嗗憳", "ROLE_FAKE"); role.setBuiltin(true); when(sysRoleMapper.selectById(999L)).thenReturn(null); @@ -134,45 +140,12 @@ class SysRoleServiceImplTest { .hasMessageContaining("涓嶅厑璁搁€氳繃鎺ュ彛鍒涘缓鍐呯疆瑙掕壊"); } - // ==================== dataScope 鑼冨洿鏍¢獙 ==================== - - @Test - @DisplayName("鍒涘缓瑙掕壊 -> dataScope=0锛堣秺鐣岋級 -> 鎷掔粷") - void createRole_dataScopeTooLow_rejected() { - SysRole role = buildRole(null, "閿€鍞?, "ROLE_SALES", 0); - - assertThatThrownBy(() -> sysRoleService.saveRole(role)) - .isInstanceOf(BusinessErrorException.class) - .hasMessageContaining("鏁版嵁鑼冨洿涓嶅悎娉?); - } - - @Test - @DisplayName("鍒涘缓瑙掕壊 -> dataScope=5锛堣秺鐣岋級 -> 鎷掔粷") - void createRole_dataScopeTooHigh_rejected() { - SysRole role = buildRole(null, "閿€鍞?, "ROLE_SALES", 5); - - assertThatThrownBy(() -> sysRoleService.saveRole(role)) - .isInstanceOf(BusinessErrorException.class) - .hasMessageContaining("鏁版嵁鑼冨洿涓嶅悎娉?); - } - - @Test - @DisplayName("鍒涘缓瑙掕壊 -> dataScope=3锛堝悎娉曪級 -> 閫氳繃") - void createRole_validDataScope_passes() { - SysRole role = buildRole(null, "閿€鍞?, "ROLE_SALES", 3); - when(sysRoleMapper.selectCount(any())).thenReturn(0L); - when(sysRoleMapper.insert(any(SysRole.class))).thenReturn(1); - - assertThatCode(() -> sysRoleService.saveRole(role)) - .doesNotThrowAnyException(); - } - // ==================== 鍩烘湰闈炵┖鏍¢獙 ==================== @Test @DisplayName("鍒涘缓瑙掕壊 -> roleName 涓虹┖ -> 鎷掔粷") void createRole_blankName_rejected() { - SysRole role = buildRole(null, "", "ROLE_SALES", 1); + SysRole role = buildRole(null, "", "ROLE_SALES"); assertThatThrownBy(() -> sysRoleService.saveRole(role)) .isInstanceOf(BusinessErrorException.class) @@ -182,7 +155,7 @@ class SysRoleServiceImplTest { @Test @DisplayName("鍒涘缓瑙掕壊 -> roleCode 涓虹┖ -> 鎷掔粷") void createRole_blankCode_rejected() { - SysRole role = buildRole(null, "閿€鍞?, "", 1); + SysRole role = buildRole(null, "閿€鍞?, ""); assertThatThrownBy(() -> sysRoleService.saveRole(role)) .isInstanceOf(BusinessErrorException.class) @@ -196,7 +169,7 @@ class SysRoleServiceImplTest { void assignResources_ancestorCompletion_writesAllIds() { // 杈撳叆鍙紶鍙跺瓙鑺傜偣 100L锛岀鍏堣ˉ鍏ㄥ悗搴斿寘鍚?100L + 10L + 1L when(sysMenuService.getAncestorIds(List.of(100L))) - .thenReturn(Set.of(100L, 10L, 1L)); + .thenReturn(java.util.Set.of(100L, 10L, 1L)); when(sysRoleMenuMapper.delete(any())).thenReturn(1); when(sysRoleMenuMapper.insert(any(SysRoleMenu.class))).thenReturn(1); @@ -216,7 +189,7 @@ class SysRoleServiceImplTest { @DisplayName("assignResources -> 鍏堝垹鍚庢彃鍏ㄩ噺鏇挎崲 sys_role_menu") void assignResources_fullReplacement() { when(sysMenuService.getAncestorIds(List.of(200L))) - .thenReturn(Set.of(200L)); + .thenReturn(java.util.Set.of(200L)); when(sysRoleMenuMapper.delete(any())).thenReturn(1); when(sysRoleMenuMapper.insert(any(SysRoleMenu.class))).thenReturn(1); @@ -234,13 +207,14 @@ class SysRoleServiceImplTest { @Test @DisplayName("getRoleDetail -> 杩斿洖 RoleDTO 鍚熀鏈俊鎭拰 resourceIds") void getRoleDetail_returnsVO() { - SysRole role = buildRole(1L, "閿€鍞?, "ROLE_SALES", 1); + SysRole role = buildRole(1L, "閿€鍞?, "ROLE_SALES"); when(sysRoleMapper.selectById(1L)).thenReturn(role); SysRoleMenu rm1 = new SysRoleMenu(); rm1.setMenuId(100L); SysRoleMenu rm2 = new SysRoleMenu(); rm2.setMenuId(200L); when(sysRoleMenuMapper.selectList(any())).thenReturn(List.of(rm1, rm2)); + when(sysRoleDataScopeMapper.selectList(any())).thenReturn(List.of()); RoleDTO vo = sysRoleService.getRoleDetail(1L); @@ -265,7 +239,7 @@ class SysRoleServiceImplTest { @Test @DisplayName("鍒犻櫎鍐呯疆瑙掕壊 -> builtin=true -> 鎷掔粷") void deleteBuiltinRole_rejected() { - SysRole role = buildRole(1L, "绠$悊鍛?, "ROLE_ADMIN", 4); + SysRole role = buildRole(1L, "绠$悊鍛?, "ROLE_ADMIN"); role.setBuiltin(true); when(sysRoleMapper.selectById(1L)).thenReturn(role); @@ -275,12 +249,13 @@ class SysRoleServiceImplTest { } @Test - @DisplayName("鍒犻櫎鏅€氳鑹?-> builtin=false -> 閫氳繃骞舵竻鐞?sys_role_menu + sys_user_role") + @DisplayName("鍒犻櫎鏅€氳鑹?-> builtin=false -> 閫氳繃骞舵竻鐞?sys_role_menu + sys_role_data_scope + sys_user_role") void deleteNormalRole_passes() { - SysRole role = buildRole(2L, "閿€鍞?, "ROLE_SALES", 1); + SysRole role = buildRole(2L, "閿€鍞?, "ROLE_SALES"); when(sysRoleMapper.selectById(2L)).thenReturn(role); when(sysRoleMenuMapper.delete(any())).thenReturn(1); when(sysUserRoleMapper.delete(any())).thenReturn(1); + when(sysRoleDataScopeMapper.delete(any())).thenReturn(1); assertThatCode(() -> sysRoleService.deleteRoleCascade(2L)) .doesNotThrowAnyException(); @@ -288,12 +263,11 @@ class SysRoleServiceImplTest { // ==================== 妗╂暟鎹?==================== - private SysRole buildRole(Long id, String name, String code, int dataScope) { + private SysRole buildRole(Long id, String name, String code) { SysRole role = new SysRole(); role.setId(id); role.setRoleName(name); role.setRoleCode(code); - role.setDataScope(dataScope); role.setSort(0); return role; } diff --git a/crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java b/crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java index 0ee3dc5..70f9d94 100644 --- a/crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java +++ b/crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java @@ -332,7 +332,7 @@ class UserListIntegrationTest { create table sys_role ( id bigint primary key, creator_id varchar(50), create_time datetime, updater_id varchar(50), update_time datetime, deleted tinyint not null default 0, - role_name varchar(50), role_code varchar(50), data_scope tinyint, sort int, remark varchar(200), + role_name varchar(50), role_code varchar(50), sort int, remark varchar(200), builtin boolean default false) """, """ @@ -357,8 +357,8 @@ class UserListIntegrationTest { "insert into sys_dept (id, deleted, parent_id, dept_name) values (121, 0, 12, '鍗庡寳涓€闃?)", "insert into sys_dept (id, deleted, parent_id, dept_name) values (20, 0, 0, '甯傚満閮?)", // 瑙掕壊 - "insert into sys_role (id, deleted, role_name, role_code, data_scope) values (1, 0, '绠$悊鍛?, 'ROLE_ADMIN', 4)", - "insert into sys_role (id, deleted, role_name, role_code, data_scope) values (2, 0, '閿€鍞?, 'ROLE_SALES', 2)", + "insert into sys_role (id, deleted, role_name, role_code) values (1, 0, '绠$悊鍛?, 'ROLE_ADMIN')", + "insert into sys_role (id, deleted, role_name, role_code) values (2, 0, '閿€鍞?, 'ROLE_SALES')", // 鐢ㄦ埛锛坋mployment_status / title / account / mobile / last_login_time / dept_id / enabled锛? "insert into crm_auth_user (id, deleted, username, mobile, enabled, last_login_time, dept_id, employment_status, title, account) values (1, 0, '寮犱笁', '13800000001', 1, '2024-01-15 10:00:00', 10, 'active', '閿€鍞€荤洃', 'zhangsan')", "insert into crm_auth_user (id, deleted, username, mobile, enabled, last_login_time, dept_id, employment_status, title, account) values (2, 0, '鏉庡洓', '13800000002', 1, '2024-03-20 14:00:00', 10, 'active', '閿€鍞粡鐞?, 'lisi')", diff --git a/crm-base/src/main/java/com/crm/base/annotation/DataScope.java b/crm-base/src/main/java/com/crm/base/annotation/DataScope.java index 8ccaf8b..a796023 100644 --- a/crm-base/src/main/java/com/crm/base/annotation/DataScope.java +++ b/crm-base/src/main/java/com/crm/base/annotation/DataScope.java @@ -3,13 +3,19 @@ package com.crm.base.annotation; import java.lang.annotation.*; /** - * 鏁版嵁鏉冮檺娉ㄨВ锛氭爣娉ㄥ湪瀹炰綋绫讳笂锛屽憡鐭?DataScopeInterceptor 璇ヨ〃鐢ㄤ簬鏁版嵁杩囨护鐨勫垪鍚嶃€?+ * 鏁版嵁鏉冮檺娉ㄨВ锛氭爣娉ㄥ湪瀹炰綋绫讳笂锛屽憡鐭?DataScopeInterceptor 璇ヨ〃鐢ㄤ簬鏁版嵁杩囨护鐨勫垪鍚嶅拰鎵€灞炰笟鍔℃ā鍧椼€? * 鏈爣娉ㄦ娉ㄨВ鐨勫疄浣撲笉浼氳Е鍙戞暟鎹潈闄愯繃婊ゃ€? */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DataScope { + /** + * 鎵€灞炴暟鎹潈闄愭ā鍧楃殑 code锛堝 "customer"銆?lead"锛夛紝寮曠敤 sys_data_scope_module.code銆?+ * 蹇呭~鈥斺€斿惎鍔ㄦ椂 PermissionConfig 鏍¢獙璇?code 蹇呴』鍦ㄦ敞鍐岃〃涓瓨鍦紝涓嶅瓨鍦ㄥ垯鎷掔粷鍚姩锛圓DR-0006锛夈€?+ */ + String module(); + /** 褰掑睘浜哄瓧娈碉紙鏁版嵁搴撳垪鍚嶏級锛屽搴?浠呮湰浜?杩囨护 */ String ownerColumn() default "owner_id"; diff --git a/crm-base/src/main/java/com/crm/base/security/DataVisibility.java b/crm-base/src/main/java/com/crm/base/security/DataVisibility.java index c0501f7..fbf4664 100644 --- a/crm-base/src/main/java/com/crm/base/security/DataVisibility.java +++ b/crm-base/src/main/java/com/crm/base/security/DataVisibility.java @@ -1,6 +1,7 @@ package com.crm.base.security; import java.util.List; +import java.util.Map; /** * 鏁版嵁鍙鎬ц寖鍥达細褰撳墠韬唤鑳界湅鍒颁粈涔堛€佸啓鍑虹殑鏁版嵁褰掕皝銆?@@ -8,51 +9,64 @@ import java.util.List; *

涓嶅彲鍙橈紝涓€娆℃€ф暣浣撹鍏ワ細鏋勯€犳垚鍔熷嵆鎰忓懗鐫€涓婁笅鏂囧畬鏁村彲鐢紝涓嶅瓨鍦ㄣ€岃浜嗕竴鍗娿€嶇殑鐘舵€侊紝 * 鍥犳涔熶笉瀛樺湪銆屽繕浜嗚妗d綅鎵€浠ユ斁琛屽叏閮ㄦ暟鎹€嶈繖绉嶈〃杈撅紙瑙?ADR 0006锛夈€?/p> * - *

瀵瑰鍙洖绛斾袱涓棶鍙ワ細璇讳晶闂?{@link #visibilityScope()}锛屽啓渚ч棶 {@link #dataOwnership()}銆?- * 妗d綅鏁板瓧涓庨儴闂ㄩ泦鍚堢殑鍙栬垗鏄唴閮ㄥ疄鐜扮粏鑺傘€?/p> + *

瀵瑰鍙洖绛斾袱涓棶鍙ワ細璇讳晶闂?{@link #visibilityScope(String)}锛堟寜妯″潡鍙栨。浣嶏級锛?+ * 鍐欎晶闂?{@link #dataOwnership()}銆傛。浣嶆暟瀛椾笌閮ㄩ棬闆嗗悎鐨勫彇鑸嶆槸鍐呴儴瀹炵幇缁嗚妭銆?/p> + * + *

鍒嗘ā鍧楁敼閫犲悗锛岃韩浠芥暟鎹紙userId / primaryDeptId / deptIds / expandedDeptIds锛夊彧瀛樹竴浠斤紝 + * 鍚勬ā鍧楁。浣嶉€氳繃 {@code moduleLevels} map 鍚勮嚜鐙珛銆傞儴闂ㄩ泦鍚堝彧绠椾竴娆★紝鍚勬ā鍧楁。浣嶅喅瀹?+ * 鐢ㄤ笉鐢?/b> 杩欑粍閮ㄩ棬 ID锛屼笉闇€瑕侀噸绠椼€?/p> */ public final class DataVisibility { private final Long userId; private final Long primaryDeptId; private final List deptIds; - private final DataScopeLevel level; + private final Map moduleLevels; private final List expandedDeptIds; /** * @param userId 褰撳墠鐢ㄦ埛 ID锛屽繀濉? * @param primaryDeptId 涓婚儴闂?ID锛屽彲涓?null锛堢敤鎴峰皻鏈綊浣嶇粍缁囨灦鏋勶級 * @param deptIds 閮ㄩ棬闆嗗悎锛堜富 + 鍏艰亴骞堕泦锛夛紝鍙负绌洪泦鍚堬紙璇ョ敤鎴蜂笉灞炰簬浠讳綍閮ㄩ棬锛?- * @param level 鏁版嵁鑼冨洿妗d綅锛屽繀濉?- * @param expandedDeptIds 閮ㄩ棬闆嗗悎閫愪釜灞曞紑瀛愭爲鍚庣殑骞堕泦锛屼粎 {@link DataScopeLevel#DEPT_AND_CHILDREN} - * 妗d綅闇€瑕侊紝姝ゆ椂蹇呭~锛堝彲涓虹┖闆嗗悎锛岃〃绀轰竴寰嬩笉鍙锛夛紱鍏朵粬妗d綅蹇界暐 - * @throws IllegalArgumentException 鐢ㄦ埛 ID 鎴栨。浣嶇己澶憋紝鎴栧惈涓嬪睘閮ㄩ棬妗d綅鏈惡甯﹀睍寮€闆嗗悎 + * @param moduleLevels 姣忔ā鍧楃殑鏁版嵁鑼冨洿妗d綅锛坘ey = module code锛寁alue = 妗d綅锛夛紝蹇呭~浣嗗厑璁哥┖ Map + * 锛堢┖ Map = 鎵€鏈夋ā鍧楄蛋榛樿 SELF锛夛紱鏈湪 map 涓殑妯″潡涔熻蛋 SELF + * @param expandedDeptIds 閮ㄩ棬闆嗗悎閫愪釜灞曞紑瀛愭爲鍚庣殑骞堕泦锛屼粎褰撲换涓€妯″潡鐨勬。浣嶄负 + * {@link DataScopeLevel#DEPT_AND_CHILDREN} 鏃堕渶瑕侊紙蹇呭~锛屽彲涓虹┖闆嗗悎锛?+ * 琛ㄧず涓€寰嬩笉鍙锛夛紱鍏ㄦā鍧楅兘涓嶅惈璇ユ。浣嶆椂蹇界暐 + * @throws IllegalArgumentException 鐢ㄦ埛 ID 缂哄け銆乵oduleLevels 涓?null銆佹垨鍚笅灞為儴闂ㄦ。浣嶄絾鏈惡甯﹀睍寮€闆嗗悎 */ public DataVisibility(Long userId, Long primaryDeptId, List deptIds, - DataScopeLevel level, List expandedDeptIds) { + Map moduleLevels, List expandedDeptIds) { if (userId == null) { throw new IllegalArgumentException("鏁版嵁鍙鎬ц寖鍥寸己灏戠敤鎴?ID"); } - if (level == null) { - throw new IllegalArgumentException("鏁版嵁鍙鎬ц寖鍥寸己灏戞暟鎹寖鍥存。浣?); + if (moduleLevels == null) { + throw new IllegalArgumentException("鏁版嵁鍙鎬ц寖鍥寸己灏戞ā鍧楁。浣嶆槧灏?); } - if (level == DataScopeLevel.DEPT_AND_CHILDREN && expandedDeptIds == null) { + boolean needsExpanded = moduleLevels.values().stream() + .anyMatch(l -> l == DataScopeLevel.DEPT_AND_CHILDREN); + if (needsExpanded && expandedDeptIds == null) { throw new IllegalArgumentException( - "妗d綅 " + level + " 瑕佹眰鎼哄甫灞曞紑瀛愭爲鍚庣殑閮ㄩ棬闆嗗悎锛岀┖闆嗗悎璇蜂紶绌?List 鑰岄潪 null"); + "瀛樺湪妯″潡妗d綅涓?DEPT_AND_CHILDREN锛岃姹傛惡甯﹀睍寮€瀛愭爲鍚庣殑閮ㄩ棬闆嗗悎锛岀┖闆嗗悎璇蜂紶绌?List 鑰岄潪 null"); } this.userId = userId; this.primaryDeptId = primaryDeptId; this.deptIds = deptIds == null ? List.of() : List.copyOf(deptIds); - this.level = level; + this.moduleLevels = Map.copyOf(moduleLevels); this.expandedDeptIds = expandedDeptIds == null ? null : List.copyOf(expandedDeptIds); } /** - * 鏈鏌ヨ璇ュ浣曡繃婊ゃ€?+ * 鏈鏌ヨ璇ュ浣曡繃婊わ紙鎸夋ā鍧楀彇妗d綅 鈫?鎶樼畻 VisibilityScope锛夈€?+ * + *

鏈厤妯″潡锛坢ap 涓煡涓嶅埌锛夐粯璁?SELF锛堟渶绐勬。锛宖ail safe锛?2 鍐崇瓥 3锛夈€?/p> * *

鎸夐儴闂ㄨ繃婊ょ殑妗d綅閬囦笂绌洪儴闂ㄩ泦鍚堟椂鍒や负銆屼竴寰嬩笉鍙銆嶁€斺€旀棤褰掑睘涓嶇瓑浜庡叏鏀捐銆?/p> + * + * @param moduleCode 鏁版嵁鏉冮檺妯″潡 code */ - public VisibilityScope visibilityScope() { + public VisibilityScope visibilityScope(String moduleCode) { + DataScopeLevel level = moduleLevels.getOrDefault(moduleCode, DataScopeLevel.SELF); return switch (level) { case SELF -> VisibilityScope.ownedBy(userId); case DEPT -> departmentsOrNone(deptIds); diff --git a/crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java b/crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java index a1d087c..757c7a8 100644 --- a/crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java +++ b/crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java @@ -6,9 +6,13 @@ import java.util.Optional; * 褰撳墠璇锋眰鐨勬暟鎹彲瑙佹€ц寖鍥淬€? * *

璁よ瘉閫氳繃鍚庣敱杩囨护鍣?{@link #load} 瑁呭叆锛岃姹傜粨鏉熸椂 {@link #clear} 娓呯悊锛?- * 鏁版嵁鏉冮檺鎷︽埅鍣ㄤ笌鍏叡瀛楁濉厖鍣ㄥ垎鍒€氳繃 {@link #currentScope()} 涓?{@link #currentOwnership()} + * 鏁版嵁鏉冮檺鎷︽埅鍣ㄤ笌鍏叡瀛楁濉厖鍣ㄥ垎鍒€氳繃 {@link #currentScope(String)} 涓?{@link #currentOwnership()} * 璇诲彇锛屼袱渚х湅鍒扮殑鏄悓涓€浠借韩浠姐€?/p> * + *

鍒嗘ā鍧楁敼閫犲悗锛岃渚?{@link #currentScope(String)} 闇€浼犲叆妯″潡 code锛?+ * 浠庡悓涓€浠?DataVisibility 涓寜妯″潡鍙栨。浣嶃€傚啓渚?{@link #currentOwnership()} 涓嶅彉鈥斺€?+ * 韬唤缁村害涓庢。浣嶇淮搴︽浜わ紙06 鍐崇瓥锛夈€?/p> + * *

鏃犱笂涓嬫枃锛堝簲鐢ㄨ嚜韬殑绾跨▼銆佹湭璁よ瘉璇锋眰锛夋椂涓嶈繃婊ゃ€佷篃鏃犲綊灞炲彲濉細鑳藉湪鏃犵櫥褰曚俊鎭笅璺戞煡璇㈢殑 * 鍙湁搴旂敤鑷韩鐨勭嚎绋嬶紝鏈璇佺殑 HTTP 璇锋眰杩涗笉鍒颁笟鍔℃煡璇紙瑙?ADR 0006锛夈€?/p> * @@ -30,10 +34,16 @@ public final class DataVisibilityContext { CURRENT.remove(); } - /** 鏈鏌ヨ璇ュ浣曡繃婊わ紱鏃犱笂涓嬫枃鏃朵笉杩囨护 */ - public static VisibilityScope currentScope() { + /** + * 鏈鏌ヨ璇ュ浣曡繃婊わ紙鎸夋ā鍧楀彇妗d綅锛夛紱鏃犱笂涓嬫枃鏃朵笉杩囨护銆?+ * + * @param moduleCode 鏁版嵁鏉冮檺妯″潡 code + */ + public static VisibilityScope currentScope(String moduleCode) { DataVisibility visibility = CURRENT.get(); - return visibility != null ? visibility.visibilityScope() : VisibilityScope.allVisible(); + return visibility != null + ? visibility.visibilityScope(moduleCode) + : VisibilityScope.allVisible(); } /** 鏂板啓鍏ユ暟鎹殑褰掑睘锛涙棤涓婁笅鏂囨椂涓虹┖锛屽綊灞炲瓧娈典繚鎸?null */ diff --git a/crm-base/src/test/java/com/crm/base/security/DataVisibilityTest.java b/crm-base/src/test/java/com/crm/base/security/DataVisibilityTest.java index c76eba1..3b62a05 100644 --- a/crm-base/src/test/java/com/crm/base/security/DataVisibilityTest.java +++ b/crm-base/src/test/java/com/crm/base/security/DataVisibilityTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -13,11 +14,15 @@ import static org.junit.jupiter.api.Assertions.*; * *

瑕嗙洊鍥涗釜鏁版嵁鑼冨洿妗d綅濡備綍鎶樼畻涓鸿繃婊ゆ儏褰€侀儴闂ㄩ泦鍚堜负绌烘椂鐨勩€屼竴寰嬩笉鍙銆嶃€? * 瑁呭叆鏃剁殑瀹屾暣鎬ф牎楠岋紝浠ュ強鏃犱笂涓嬫枃鏃剁殑涓ゆ。 fail-closed 璇箟锛堣 ADR 0006锛夈€?/p> + * + *

鍒嗘ā鍧楁敼閫犲悗锛孌ataVisibility 鎸佹湁 {@code Map} 鏇夸唬鍗曚釜 level锛?+ * 娴嬭瘯鐢?{@code Map.of("test", level)} 妯℃嫙鍗曟ā鍧楀満鏅€?/p> */ class DataVisibilityTest { private static final Long USER_ID = 1001L; private static final Long PRIMARY_DEPT = 200L; + private static final String MODULE = "test"; @AfterEach void tearDown() { @@ -30,9 +35,10 @@ class DataVisibilityTest { @DisplayName("浠呮湰浜烘。浣嶏細鎸夊綊灞炰汉杩囨护") void selfLevelFiltersByOwner() { DataVisibility visibility = new DataVisibility( - USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), DataScopeLevel.SELF, null); + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of(MODULE, DataScopeLevel.SELF), null); - VisibilityScope scope = visibility.visibilityScope(); + VisibilityScope scope = visibility.visibilityScope(MODULE); assertEquals(VisibilityScope.Kind.OWNER, scope.kind()); assertEquals(USER_ID, scope.ownerId()); @@ -42,9 +48,10 @@ class DataVisibilityTest { @DisplayName("鏈儴闂ㄦ。浣嶏細鎸夐儴闂ㄩ泦鍚堬紙涓?+ 鍏艰亴骞堕泦锛夎繃婊?) void deptLevelFiltersByDeptSet() { DataVisibility visibility = new DataVisibility( - USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT, 300L), DataScopeLevel.DEPT, null); + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT, 300L), + Map.of(MODULE, DataScopeLevel.DEPT), null); - VisibilityScope scope = visibility.visibilityScope(); + VisibilityScope scope = visibility.visibilityScope(MODULE); assertEquals(VisibilityScope.Kind.DEPARTMENTS, scope.kind()); assertEquals(List.of(PRIMARY_DEPT, 300L), scope.deptIds()); @@ -54,10 +61,11 @@ class DataVisibilityTest { @DisplayName("鏈儴闂ㄥ強涓嬪睘閮ㄩ棬妗d綅锛氭寜灞曞紑鍚庣殑閮ㄩ棬闆嗗悎杩囨护锛屼笉鐢ㄦ湭灞曞紑鐨勯泦鍚?) void deptAndChildrenLevelFiltersByExpandedDeptSet() { DataVisibility visibility = new DataVisibility( - USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), DataScopeLevel.DEPT_AND_CHILDREN, + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of(MODULE, DataScopeLevel.DEPT_AND_CHILDREN), List.of(PRIMARY_DEPT, 201L, 202L)); - VisibilityScope scope = visibility.visibilityScope(); + VisibilityScope scope = visibility.visibilityScope(MODULE); assertEquals(VisibilityScope.Kind.DEPARTMENTS, scope.kind()); assertEquals(List.of(PRIMARY_DEPT, 201L, 202L), scope.deptIds()); @@ -67,9 +75,10 @@ class DataVisibilityTest { @DisplayName("鍏ㄩ儴鍙妗d綅锛氫笉杩囨护") void allLevelDoesNotFilter() { DataVisibility visibility = new DataVisibility( - USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), DataScopeLevel.ALL, null); + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of(MODULE, DataScopeLevel.ALL), null); - assertEquals(VisibilityScope.Kind.ALL_VISIBLE, visibility.visibilityScope().kind()); + assertEquals(VisibilityScope.Kind.ALL_VISIBLE, visibility.visibilityScope(MODULE).kind()); } // 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€ 涓€寰嬩笉鍙 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€ @@ -78,25 +87,27 @@ class DataVisibilityTest { @DisplayName("鏈儴闂ㄦ。浣嶄絾閮ㄩ棬闆嗗悎涓虹┖锛氫竴寰嬩笉鍙锛岃€岄潪鏀捐") void deptLevelWithoutAnyDeptIsNoneVisible() { DataVisibility visibility = new DataVisibility( - USER_ID, null, List.of(), DataScopeLevel.DEPT, null); + USER_ID, null, List.of(), + Map.of(MODULE, DataScopeLevel.DEPT), null); - assertEquals(VisibilityScope.Kind.NONE_VISIBLE, visibility.visibilityScope().kind()); + assertEquals(VisibilityScope.Kind.NONE_VISIBLE, visibility.visibilityScope(MODULE).kind()); } @Test @DisplayName("鍚笅灞為儴闂ㄦ。浣嶄絾灞曞紑缁撴灉涓虹┖锛氫竴寰嬩笉鍙锛岃€岄潪鏀捐") void deptAndChildrenLevelWithEmptyExpansionIsNoneVisible() { DataVisibility visibility = new DataVisibility( - USER_ID, null, List.of(), DataScopeLevel.DEPT_AND_CHILDREN, List.of()); + USER_ID, null, List.of(), + Map.of(MODULE, DataScopeLevel.DEPT_AND_CHILDREN), List.of()); - assertEquals(VisibilityScope.Kind.NONE_VISIBLE, visibility.visibilityScope().kind()); + assertEquals(VisibilityScope.Kind.NONE_VISIBLE, visibility.visibilityScope(MODULE).kind()); } // 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€ 瑁呭叆鏃剁殑瀹屾暣鎬ф牎楠?鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€ @Test - @DisplayName("妗d綅涓?null锛氳鍏ュ嵆澶辫触") - void nullLevelIsRejected() { + @DisplayName("moduleLevels 涓?null锛氳鍏ュ嵆澶辫触") + void nullModuleLevelsIsRejected() { assertThrows(IllegalArgumentException.class, () -> new DataVisibility( USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), null, null)); } @@ -105,14 +116,16 @@ class DataVisibilityTest { @DisplayName("鍚笅灞為儴闂ㄦ。浣嶆湭鎼哄甫灞曞紑闆嗗悎锛氳鍏ュ嵆澶辫触锛堥殣鍚害瀹氬彉涓烘樉寮忎笉鍙橀噺锛?) void deptAndChildrenLevelWithoutExpansionIsRejected() { assertThrows(IllegalArgumentException.class, () -> new DataVisibility( - USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), DataScopeLevel.DEPT_AND_CHILDREN, null)); + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of(MODULE, DataScopeLevel.DEPT_AND_CHILDREN), null)); } @Test @DisplayName("鐢ㄦ埛 ID 缂哄け锛氳鍏ュ嵆澶辫触") void missingUserIdIsRejected() { assertThrows(IllegalArgumentException.class, () -> new DataVisibility( - null, PRIMARY_DEPT, List.of(PRIMARY_DEPT), DataScopeLevel.SELF, null)); + null, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of(MODULE, DataScopeLevel.SELF), null)); } @Test @@ -131,13 +144,49 @@ class DataVisibilityTest { } } + // 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€ 鍒嗘ā鍧楃壒鏈?鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€ + + @Test + @DisplayName("鏈厤妯″潡锛氶粯璁?SELF锛堟渶绐勬。锛宖ail safe锛?) + void unconfiguredModuleDefaultsToSelf() { + DataVisibility visibility = new DataVisibility( + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of("other_module", DataScopeLevel.ALL), null); + + // "test" 妯″潡鏈厤锛屽簲璧?SELF + assertEquals(VisibilityScope.Kind.OWNER, visibility.visibilityScope(MODULE).kind()); + assertEquals(USER_ID, visibility.visibilityScope(MODULE).ownerId()); + } + + @Test + @DisplayName("绌?moduleLevels Map锛氭墍鏈夋ā鍧楄蛋 SELF") + void emptyModuleLevelsDefaultsAllToSelf() { + DataVisibility visibility = new DataVisibility( + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of(), null); + + assertEquals(VisibilityScope.Kind.OWNER, visibility.visibilityScope(MODULE).kind()); + } + + @Test + @DisplayName("澶氭ā鍧楀悇鍙栧悇鐨勬。浣嶏細customer=ALL, lead=SELF") + void multiModuleIndependentLevels() { + DataVisibility visibility = new DataVisibility( + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of("customer", DataScopeLevel.ALL, "lead", DataScopeLevel.SELF), null); + + assertEquals(VisibilityScope.Kind.ALL_VISIBLE, visibility.visibilityScope("customer").kind()); + assertEquals(VisibilityScope.Kind.OWNER, visibility.visibilityScope("lead").kind()); + } + // 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€ 鍐欎晶鏁版嵁褰掑睘 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€ @Test @DisplayName("鏁版嵁褰掑睘鍙栦富閮ㄩ棬锛屼笉浠庡吋鑱岄泦鍚堥噷鎸戯紙鍙鎸傞潬锛?) void ownershipUsesPrimaryDeptOnly() { DataVisibility visibility = new DataVisibility( - USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT, 300L), DataScopeLevel.DEPT, null); + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT, 300L), + Map.of(MODULE, DataScopeLevel.DEPT), null); DataOwnership ownership = visibility.dataOwnership(); @@ -149,7 +198,8 @@ class DataVisibilityTest { @DisplayName("鏃犱富閮ㄩ棬鏃跺綊灞為儴闂ㄤ负 null锛屽綊灞炰汉浠嶅~褰撳墠鐢ㄦ埛") void ownershipToleratesMissingPrimaryDept() { DataVisibility visibility = new DataVisibility( - USER_ID, null, List.of(), DataScopeLevel.SELF, null); + USER_ID, null, List.of(), + Map.of(MODULE, DataScopeLevel.SELF), null); DataOwnership ownership = visibility.dataOwnership(); @@ -162,7 +212,7 @@ class DataVisibilityTest { @Test @DisplayName("鏃犱笂涓嬫枃锛氫笉杩囨护锛屼笖鍐欎晶鏃犲綊灞炲彲濉?) void withoutContextNothingIsFilteredOrFilled() { - assertEquals(VisibilityScope.Kind.ALL_VISIBLE, DataVisibilityContext.currentScope().kind()); + assertEquals(VisibilityScope.Kind.ALL_VISIBLE, DataVisibilityContext.currentScope(MODULE).kind()); assertTrue(DataVisibilityContext.currentOwnership().isEmpty()); } @@ -170,9 +220,10 @@ class DataVisibilityTest { @DisplayName("瑁呭叆鍚庤鍐欎袱渚х湅鍒板悓涓€浠借韩浠?) void loadedContextIsVisibleToBothSides() { DataVisibilityContext.load(new DataVisibility( - USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), DataScopeLevel.SELF, null)); + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of(MODULE, DataScopeLevel.SELF), null)); - assertEquals(USER_ID, DataVisibilityContext.currentScope().ownerId()); + assertEquals(USER_ID, DataVisibilityContext.currentScope(MODULE).ownerId()); assertEquals(USER_ID, DataVisibilityContext.currentOwnership().orElseThrow().ownerId()); assertEquals(PRIMARY_DEPT, DataVisibilityContext.currentOwnership().orElseThrow().primaryDeptId()); } @@ -181,10 +232,11 @@ class DataVisibilityTest { @DisplayName("娓呯悊鍚庡洖鍒版棤涓婁笅鏂囩姸鎬?) void clearRestoresEmptyContext() { DataVisibilityContext.load(new DataVisibility( - USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), DataScopeLevel.DEPT, null)); + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT), + Map.of(MODULE, DataScopeLevel.DEPT), null)); DataVisibilityContext.clear(); - assertEquals(VisibilityScope.Kind.ALL_VISIBLE, DataVisibilityContext.currentScope().kind()); + assertEquals(VisibilityScope.Kind.ALL_VISIBLE, DataVisibilityContext.currentScope(MODULE).kind()); assertTrue(DataVisibilityContext.currentOwnership().isEmpty()); } @@ -192,9 +244,10 @@ class DataVisibilityTest { @DisplayName("閮ㄩ棬闆嗗悎瀵瑰涓嶅彲鍙橈紝璋冪敤鏂规敼涓嶅姩宸茶鍏ョ殑鍙鑼冨洿") void deptIdsAreNotMutableThroughScope() { DataVisibility visibility = new DataVisibility( - USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT, 300L), DataScopeLevel.DEPT, null); + USER_ID, PRIMARY_DEPT, List.of(PRIMARY_DEPT, 300L), + Map.of(MODULE, DataScopeLevel.DEPT), null); - List deptIds = visibility.visibilityScope().deptIds(); + List deptIds = visibility.visibilityScope(MODULE).deptIds(); assertThrows(UnsupportedOperationException.class, () -> deptIds.add(999L)); } diff --git a/docker-compose.yml b/docker-compose.yml index 5a77060..9696acb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,9 +64,14 @@ services: " crm-app: - build: - context: . - dockerfile: Dockerfile + # 鏈嶅姟鍣ㄤ笂鍙湁 jar锛堟棤婧愮爜锛夛細鐢ㄥ惈 LibreOffice 鐨勫熀纭€闀滃儚 crm-runtime + 鎸傝浇 jar + # 鍩虹闀滃儚涓€娆℃€ф瀯寤猴細docker build -f deploy/Dockerfile.base -t crm-runtime:latest . + # 鏇存柊娴佺▼锛氳鐩?crm-app-1.0.0-SNAPSHOT.jar 鈫?deploy/restart.sh锛堝彧閲嶅惎 crm-app锛?+ image: crm-runtime:latest + volumes: + - ./crm-app-1.0.0-SNAPSHOT.jar:/app/crm-app-1.0.0-SNAPSHOT.jar:ro + # 鏃ュ織鏄犲皠鍒板涓绘満锛氬簲鐢ㄥ啓 logs/crm-app.log锛堢浉瀵?WORKDIR /app锛夆啋 瀹夸富鏈?./logs/ + - ./logs:/app/logs depends_on: mysql: condition: service_healthy