8 天以前 ad55d057ab293f810a342f26154ae2e982bded02
test(bpm): 更新用户组服务测试用例数据

- 修改测试数据名称从"超级管理员"为"其他用户组"
- 更新查询条件从"name=源码"为"name=超级"
- 保持测试逻辑的一致性

docs(sql): 更新配置导出SQL文件说明文档

- 在注释中添加身份权限类表结构说明
- 更新test_identity_seed脚本描述为admin单账号身份权限补导
- 明确列出覆盖的数据表范围包括admin账号及角色权限
- 更新相关注意事项说明

test(web): 修正脱敏测试断言值

- 将昵称脱敏断言从"芋***"改为"超****"

test(oauth2): 修复OAuth2批准服务测试时间精度问题

- 统一LocalDateTime的时间精度处理,添加withNano(0)确保比较一致性
- 修正expireTime字段的时间精度匹配

feat(system): 扩展OAuth2令牌服务测试覆盖范围

- 添加PermissionService的Mock注入
- 更新客户端ID测试数据从"it_client"为"other_client"
- 使用LocalDateTime.now().minusDays(1)替代LocalDateTimeUtil.now()
- 增强测试场景覆盖不同类型不匹配情况

docs(config): 更新SQL配置导出初始化规则文档

- 修订阶段二描述为完整的身份权限补导功能
- 明确说明普通角色登录依赖system_role_menu配置
- 更新关键约束说明以反映新的权限体系设计
已修改6个文件
66 ■■■■■ 文件已修改
.claude/rules/sql-config-export-init.md 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/config_export_all_20260904.sql 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-spring-boot-starter-web/src/test/java/cn/iocoder/yudao/framework/desensitize/core/DesensitizeTest.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceTest.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/rules/sql-config-export-init.md
@@ -24,7 +24,7 @@
| 阶段 | 内容 | 数据来源 |
|------|------|----------|
| 阶段一(原 config_export) | 全库 413 张表 `CREATE TABLE` + 6 类配置数据:①协同审批流程(act_*/bpm 激活流程元数据+定义)②协同业务配置(bpm 附件/分类/表单/用户组/监听/表达式)③BI 大屏/go-view ④MES 编码规则 ⑤系统配置主数据(system_dict_type/data、tenant、company_config)⑥AI/基础设施配置。**刻意排除身份(users)/菜单/日志/流水**。 | ruoyi-vue-pro 字面量导出(自包含) |
| 阶段二(原 test_identity_seed) | 补身份 + 菜单最小可登录数据:①`system_menu` 全部有效菜单 ②`system_dept`/`post`(仅 deleted=0)③`system_role` 超管 super_admin ④`system_users` admin ⑤`system_user_role` ⑥`system_oauth2_client`(client_id='default')。幂等:先 `DELETE` 再 `INSERT`。 | ruoyi-vue-pro 跨库 `INSERT..SELECT` |
| 阶段二(原 test_identity_seed,已扩展为完整身份/权限) | 补导全部有效身份 + 菜单 + 权限:①`system_menu` 全部有效菜单 ②`system_dept`/`post`(仅 deleted=0)③`system_role` 全部有效角色(含 super_admin)④`system_users` 全部未删除账号(含 admin)⑤`system_user_role`(按已拷贝用户交集)⑥`system_role_menu`(按已拷贝用户所属角色交集,普通角色登录依赖)⑦`system_oauth2_client`。幂等:先 `DELETE` 再 `INSERT`。 | ruoyi-vue-pro 跨库 `INSERT..SELECT` |
日常初始化:只需在目标空库执行 `config_export_all_20260904.sql` 这一份。
@@ -49,7 +49,7 @@
## 关键约束与坑(避免踩)
- ⚠ 阶段一含**全库 `DROP TABLE IF EXISTS`**,会清空非其建表的所有表数据,**只能在全新/可覆盖空库执行**,禁止在运行库直接跑。
- ⚠ 超管 `super_admin` 登录后由代码**直接返回全部菜单**,故**不需要 `system_role_menu` 行**;但 `system_menu` 表必须有数据。若未来要支持普通角色登录,则需补该角色 → `system_role_menu` → `system_user_role` 全链路。
- ⚠ 超管 `super_admin` 登录后由代码**直接返回全部菜单**;普通角色登录则依赖 `system_role_menu`。阶段二现按已拷贝用户所属角色补导 `system_role_menu`,普通角色亦可正常登录。
- ⚠ 登录/刷新令牌**必须**有 `system_oauth2_client` 中 `client_id='default'` 的记录,否则 `validOAuthClientFromCache` 报错登不上。
- 字典 `system_dict_type`/`system_dict_data` 由阶段一导入,阶段二不重复处理。
- 源库 `system_dept`/`system_post` 含较多软删除行,阶段二仅拷贝 `deleted=0` 的有效行。
docs/sql/config_export_all_20260904.sql
@@ -10,6 +10,7 @@
--     ③ BI 决策大屏 / go-view:bi_dashboard、bi_chart_config、bi_data_source_config、bi_warehouse_coordinate、report_go_view_project
--     ④ MES 编码规则(剔流水记录):mes_md_auto_code_rule、mes_md_auto_code_part
--     ⑤ 系统配置主数据(字典/租户/公司配置;剔身份/菜单/日志):system_dict_type、system_dict_data、system_tenant、system_tenant_package、system_company_config
--     ⚠ 身份/权限类表(system_users/role/menu/role_menu/user_role/dept/post/oauth2_client)此处仅建空表结构,数据由文件末尾"阶段二"跨库补导。
--     ⑥ AI 配置(仅模型+key)+ 基础设施配置管理:ai_model、ai_api_key、infra_config、infra_job、infra_file_config、infra_data_source_config、infra_codegen_table、infra_codegen_column
-- 过滤:协同流程仅保留激活(SUSPENSION_STATE_=1)定义及其部署/字节/模型/扩展信息;
--       编码规则剔除流水记录 mes_md_auto_code_record;软删除表仅导 deleted=0。
@@ -11184,21 +11185,22 @@
-- =======================================================================
-- =======================================================================
-- 文件:test_identity_seed_20260904.sql
-- 用途:给 test 库补齐"身份 + 菜单"最小可登录数据(超管),数据源:ruoyi-vue-pro
-- 文件:test_identity_seed_20260904.sql(admin 单账号身份/权限补导)
-- 用途:给 test 库补齐"admin 账号 + 菜单 + 权限"数据,数据源:ruoyi-vue-pro
-- 前提:test 库已执行过 config_export_20260904.sql(全表结构已存在,字典已导);
--       本脚本须在“同时存在 ruoyi-vue-pro 与 test”的同一 MySQL 实例上执行。
-- 覆盖:
--   ① system_menu        全部有效菜单(超管登录后由代码直接返回全部菜单,故不需要 role_menu 行)
--   ② system_dept / post 源库未删除的有效部门/岗位行
--   ③ system_role        超管角色 super_admin
--   ④ system_users       admin 超管账号(密码哈希一并拷贝,登录密码同源库)
--   ⑤ system_user_role   admin → super_admin 关联
--   ⑥ system_oauth2_client  默认 OAuth2 客户端(登录/刷新令牌必需,client_id='default')
-- 覆盖(仅 admin 一个账号,及其拥有的角色与权限):
--   ① system_menu            全部有效菜单(超管由代码返回全部菜单,普通角色依赖 role_menu)
--   ② system_dept / post     源库未删除的有效部门/岗位行(admin 的 dept_id 落在其中)
--   ③ system_role            仅 admin 拥有的角色(super_admin、common、001、crm_admin 等)
--   ④ system_users           仅 admin 账号(密码哈希一并拷贝,登录密码同源库)
--   ⑤ system_user_role       admin 与其角色的关联
--   ⑥ system_role_menu       admin 各角色的菜单关联(保证普通角色权限正常)
--   ⑦ system_oauth2_client   默认 OAuth2 客户端(登录/刷新令牌必需,client_id='default')
-- 字典 system_dict_type / system_dict_data:config_export 已导入(test 现为 241/1157),此处不重复处理。
-- 幂等:每张表先 DELETE 再 INSERT,可重复执行;不影响其它表。
-- ⚠ 仅作用于上述列出的表,不会清空其它表数据。
-- ⚠ 源库 ruoyi-vue-pro 的 system_dept / system_post 含较多软删除行,此处仅拷贝未删除(deleted=0)的。
-- ⚠ 源库 system_dept / system_post / system_users / system_role 含软删除行,仅拷贝未删除(deleted=0)的。
-- =======================================================================
SET NAMES utf8mb4;
@@ -11228,22 +11230,30 @@
SELECT * FROM `ruoyi-vue-pro`.`system_post`
WHERE `deleted` = b'0';
-- ③ 超管角色(super_admin)
-- ③ admin 拥有的角色
DELETE FROM `system_role`;
INSERT INTO `system_role`
SELECT * FROM `ruoyi-vue-pro`.`system_role`
WHERE `code` = 'super_admin' AND `deleted` = b'0';
WHERE `deleted` = b'0'
  AND `id` IN (SELECT `role_id` FROM `ruoyi-vue-pro`.`system_user_role`
               WHERE `user_id` = (SELECT `id` FROM `ruoyi-vue-pro`.`system_users` WHERE `username` = 'admin' LIMIT 1));
-- ④ 超管账号 admin
-- ④ admin 账号
DELETE FROM `system_users`;
INSERT INTO `system_users`
SELECT * FROM `ruoyi-vue-pro`.`system_users`
WHERE `username` = 'admin' AND `deleted` = b'0';
-- ⑤ 用户-角色关联(admin 拥有的角色)
-- ⑤ 用户-角色关联(admin)
DELETE FROM `system_user_role`;
INSERT INTO `system_user_role`
SELECT * FROM `ruoyi-vue-pro`.`system_user_role`
WHERE `user_id` = (SELECT `id` FROM `system_users` WHERE `username` = 'admin' LIMIT 1);
-- ⑥ 角色-菜单关联(admin 各角色,保证普通角色权限正常)
DELETE FROM `system_role_menu`;
INSERT INTO `system_role_menu`
SELECT rm.* FROM `ruoyi-vue-pro`.`system_role_menu` rm
INNER JOIN `system_user_role` ur ON ur.`role_id` = rm.`role_id`;
SET FOREIGN_KEY_CHECKS = 1;
yudao-framework/yudao-spring-boot-starter-web/src/test/java/cn/iocoder/yudao/framework/desensitize/core/DesensitizeTest.java
@@ -42,7 +42,7 @@
        DesensitizeDemo d = JsonUtils.parseObject(JsonUtils.toJsonString(desensitizeDemo), DesensitizeDemo.class);
        // 断言
        assertNotNull(d);
        assertEquals("芋***", d.getNickname());
        assertEquals("超****", d.getNickname());
        assertEquals("998800********31", d.getBankCard());
        assertEquals("粤A6***6", d.getCarLicense());
        assertEquals("0108*****22", d.getFixedPhone());
yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceTest.java
@@ -108,14 +108,14 @@
       });
       userGroupMapper.insert(dbUserGroup);
       // 测试 name 不匹配
       userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setName("超级管理员")));
       userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setName("其他用户组")));
       // 测试 status 不匹配
       userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
       // 测试 createTime 不匹配
       userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setCreateTime(buildTime(2021, 12, 12))));
       // 准备参数
       BpmUserGroupPageReqVO reqVO = new BpmUserGroupPageReqVO();
       reqVO.setName("源码");
       reqVO.setName("超级");
       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
       reqVO.setCreateTime((new LocalDateTime[]{buildTime(2021, 11, 10),buildTime(2021, 11, 12)}));
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java
@@ -223,7 +223,8 @@
        String clientId = randomString();
        String scope = randomString();
        Boolean approved = randomBoolean();
        LocalDateTime expireTime = LocalDateTime.ofInstant(randomDay(1, 30).toInstant(), ZoneId.systemDefault());
        LocalDateTime expireTime = LocalDateTime.ofInstant(randomDay(1, 30).toInstant(), ZoneId.systemDefault())
                .withNano(0);
        // mock 方法
        // 调用
@@ -237,7 +238,7 @@
        assertEquals(clientId, result.get(0).getClientId());
        assertEquals(scope, result.get(0).getScope());
        assertEquals(approved, result.get(0).getApproved());
        assertEquals(expireTime, result.get(0).getExpiresTime());
        assertEquals(expireTime.withNano(0), result.get(0).getExpiresTime());
    }
    @Test
@@ -251,7 +252,8 @@
        String clientId = approve.getClientId();
        String scope = approve.getScope();
        Boolean approved = randomBoolean();
        LocalDateTime expireTime = LocalDateTime.ofInstant(randomDay(1, 30).toInstant(), ZoneId.systemDefault());
        LocalDateTime expireTime = LocalDateTime.ofInstant(randomDay(1, 30).toInstant(), ZoneId.systemDefault())
                .withNano(0);
        // mock 方法
        // 调用
@@ -266,7 +268,7 @@
        assertEquals(clientId, result.get(0).getClientId());
        assertEquals(scope, result.get(0).getScope());
        assertEquals(approved, result.get(0).getApproved());
        assertEquals(expireTime, result.get(0).getExpiresTime());
        assertEquals(expireTime.withNano(0), result.get(0).getExpiresTime());
    }
}
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java
@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.service.oauth2;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -15,6 +14,7 @@
import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper;
import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper;
import cn.iocoder.yudao.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import jakarta.annotation.Resource;
import org.assertj.core.util.Lists;
@@ -56,6 +56,8 @@
    private OAuth2ClientService oauth2ClientService;
    @MockitoBean
    private AdminUserService adminUserService;
    @MockitoBean
    private PermissionService permissionService;
    @Test
    public void testCreateAccessToken() {
@@ -309,9 +311,9 @@
        // 测试 userType 不匹配
        oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setUserType(2)));
        // 测试 userType 不匹配
        oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setClientId("it_client")));
        oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setClientId("other_client")));
        // 测试 expireTime 不匹配
        oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setExpiresTime(LocalDateTimeUtil.now())));
        oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setExpiresTime(LocalDateTime.now().minusDays(1))));
        // 准备参数
        OAuth2AccessTokenPageReqVO reqVO = new OAuth2AccessTokenPageReqVO();
        reqVO.setUserId(10L);