Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,18 @@ public interface UserRoleService {
* @return 是否已关联(true:已关联;false:未关联)
*/
boolean isRoleIdExists(List<Long> roleIds);

/**
* 检查系统内置用户是否在用户列表中,如果存在则抛出异常
*
* @param userIds 用户 ID 列表
*/
void checkSystemUserAssignment(List<Long> userIds);

/**
* 检查系统内置用户是否在用户角色关联列表中,如果存在则抛出异常
*
* @param userRoleIds 用户角色关联 ID 列表
Comment on lines +108 to +117
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation for these methods should clarify their specific purpose regarding super admin roles. The current descriptions don't mention that checkSystemUserAssignment is intended to prevent assignment to non-super-admin roles, and checkSystemUserUnassignment should (but currently doesn't) allow unassignment of super admin roles. Consider updating the documentation to be more specific about the relationship with super admin roles.

Suggested change
* 检查系统内置用户是否在用户列表中如果存在则抛出异常
*
* @param userIds 用户 ID 列表
*/
void checkSystemUserAssignment(List<Long> userIds);
/**
* 检查系统内置用户是否在用户角色关联列表中如果存在则抛出异常
*
* @param userRoleIds 用户角色关联 ID 列表
* 检查系统内置用户超级管理员在分配角色时是否包含在用户列表中
* <p>
* 用于防止给超级管理员分配非超级管理员角色或进行不符合约束的角色分配
* 如果检测到系统内置用户出现在不允许的分配场景中则抛出异常
*
* @param userIds 用户 ID 列表用于角色分配的目标用户
*/
void checkSystemUserAssignment(List<Long> userIds);
/**
* 检查系统内置用户超级管理员在解除角色分配角色解绑时是否包含在用户角色关联列表中
* <p>
* 该方法的目的在于在遵守超级管理员相关约束的前提下允许对超级管理员角色进行解绑操作
* 如果检测到本次解绑操作会违反对超级管理员的保护规则则抛出异常
*
* @param userRoleIds 用户角色关联 ID 列表待解绑的用户角色关系

Copilot uses AI. Check for mistakes.
*/
void checkSystemUserUnassignment(List<Long> userRoleIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,8 @@ private FileInfo upload(Object file, String parentPath, String storageCode, Stri

// 生成唯一文件名(处理重名情况)
String originalFileName = getOriginalFileName(file);
String uniqueFileName = FileNameGenerator.generateUniqueName(originalFileName, parentPath, storage.getId(), baseMapper);
String uniqueFileName = FileNameGenerator.generateUniqueName(originalFileName, parentPath, storage
.getId(), baseMapper);

UploadPretreatment uploadPretreatment = fileStorageService.of(file)
.setPlatform(storage.getCode())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ public MultipartUploadInitResp initMultipartUpload(MultipartUploadInitReq multiP
}

// 生成唯一文件名(处理重名情况)
String uniqueFileName = FileNameGenerator.generateUniqueName(originalFileName, parentPath, storageDO.getId(), fileMapper);
String uniqueFileName = FileNameGenerator.generateUniqueName(originalFileName, parentPath, storageDO
.getId(), fileMapper);
multiPartUploadInitReq.setFileName(uniqueFileName);

StorageHandler storageHandler = storageHandlerFactory.createHandler(storageDO.getType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ public void updatePermission(Long id, RolePermissionUpdateReq req) {
public void assignToUsers(Long id, List<Long> userIds) {
RoleDO role = super.getById(id);
CheckUtils.throwIf(Boolean.TRUE.equals(role.getIsSystem()), "[{}] 是系统内置角色,不允许分配角色给其他用户", role.getName());
// 防止将系统内置用户分配给非超级管理员角色
if (!SystemConstants.SUPER_ADMIN_ROLE_ID.equals(id)) {
userRoleService.checkSystemUserAssignment(userIds);
}
Comment on lines +167 to +170
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appears to be an inconsistency in the business logic. The assignRolesToUser method (line 94) prevents assigning the super admin role to ANY user with the check "不允许分配超级管理员角色" (Do not allow assigning super admin role). However, the new code in RoleServiceImpl.assignToUsers (lines 168-170) explicitly allows assigning system users to the super admin role by skipping the checkSystemUserAssignment validation when the role is super admin.

This means:

  • Assigning users to super admin role via RoleServiceImpl.assignToUsers: Allowed for system users
  • Assigning super admin role to users via UserRoleServiceImpl.assignRolesToUser: Blocked for all users

Consider reviewing whether these two code paths should have consistent behavior, or if the difference is intentional and should be documented.

Copilot uses AI. Check for mistakes.
// 保存用户和角色关联
userRoleService.assignRoleToUsers(id, userIds);
// 更新用户上下文
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ public boolean assignRoleToUsers(Long roleId, List<Long> userIds) {

@Override
public void deleteByIds(List<Long> ids) {
// 检查是否包含系统内置用户的角色关联
this.checkSystemUserUnassignment(ids);
baseMapper.deleteByIds(ids);
}

Expand Down Expand Up @@ -165,4 +167,42 @@ public boolean isRoleIdExists(List<Long> roleIds) {
}
return baseMapper.lambdaQuery().in(UserRoleDO::getRoleId, roleIds).exists();
}

@Override
public void checkSystemUserAssignment(List<Long> userIds) {
if (CollUtil.isEmpty(userIds)) {
return;
}
// 查询用户列表中是否包含系统内置用户
List<UserDO> systemUsers = userService.lambdaQuery()
.select(UserDO::getId, UserDO::getNickname)
.in(UserDO::getId, userIds)
.eq(UserDO::getIsSystem, true)
.list();
CheckUtils.throwIfNotEmpty(systemUsers, "[{}] 是系统内置用户,不允许分配给非超级管理员角色", systemUsers.get(0).getNickname());
}
Comment on lines +170 to +183
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

systemUsers 为空时 get(0) 会抛出 IndexOutOfBoundsException

Java 方法参数是即时求值的。第 182 行 systemUsers.get(0).getNickname() 会在 CheckUtils.throwIfNotEmpty 执行之前被求值。当查询结果为空列表时,get(0) 将直接抛出 IndexOutOfBoundsException,而非正常通过校验。

🐛 建议修复:仅在非空时获取昵称
-        CheckUtils.throwIfNotEmpty(systemUsers, "[{}] 是系统内置用户,不允许分配给非超级管理员角色", systemUsers.get(0).getNickname());
+        if (CollUtil.isNotEmpty(systemUsers)) {
+            CheckUtils.throwIfNotEmpty(systemUsers, "[{}] 是系统内置用户,不允许分配给非超级管理员角色", systemUsers.get(0).getNickname());
+        }

或者更简洁地:

-        CheckUtils.throwIfNotEmpty(systemUsers, "[{}] 是系统内置用户,不允许分配给非超级管理员角色", systemUsers.get(0).getNickname());
+        CheckUtils.throwIfNotEmpty(systemUsers, "[{}] 是系统内置用户,不允许分配给非超级管理员角色",
+            CollUtil.isEmpty(systemUsers) ? "" : systemUsers.get(0).getNickname());
🤖 Prompt for AI Agents
In
`@continew-system/src/main/java/top/continew/admin/system/service/impl/UserRoleServiceImpl.java`
around lines 170 - 183, In checkSystemUserAssignment, calling
systemUsers.get(0).getNickname() inside the parameter list of
CheckUtils.throwIfNotEmpty causes an IndexOutOfBoundsException when systemUsers
is empty; change the logic to call CheckUtils.throwIfNotEmpty(systemUsers,
"...") first (or check if systemUsers is not empty) and only then retrieve
systemUsers.get(0).getNickname() to include the nickname in the message—update
the method around the systemUsers query and the CheckUtils.throwIfNotEmpty
invocation accordingly so nickname is fetched only when systemUsers is
non-empty.


@Override
public void checkSystemUserUnassignment(List<Long> userRoleIds) {
if (CollUtil.isEmpty(userRoleIds)) {
return;
}
// 查询用户角色关联列表
List<UserRoleDO> userRoleList = baseMapper.lambdaQuery()
.select(UserRoleDO::getUserId)
.in(UserRoleDO::getId, userRoleIds)
.list();
if (CollUtil.isEmpty(userRoleList)) {
return;
}
// 获取用户ID列表
List<Long> userIds = userRoleList.stream().map(UserRoleDO::getUserId).distinct().toList();
// 查询是否包含系统内置用户
List<UserDO> systemUsers = userService.lambdaQuery()
.select(UserDO::getId, UserDO::getNickname)
.in(UserDO::getId, userIds)
.eq(UserDO::getIsSystem, true)
.list();
CheckUtils.throwIfNotEmpty(systemUsers, "[{}] 是系统内置用户,不允许取消分配角色", systemUsers.get(0).getNickname());
}
Comment on lines +185 to +207
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

同样的 IndexOutOfBoundsException 问题。

第 206 行与 checkSystemUserAssignment 存在完全相同的问题:systemUsers 为空时 get(0) 会抛异常。

🐛 建议修复
-        CheckUtils.throwIfNotEmpty(systemUsers, "[{}] 是系统内置用户,不允许取消分配角色", systemUsers.get(0).getNickname());
+        if (CollUtil.isNotEmpty(systemUsers)) {
+            CheckUtils.throwIfNotEmpty(systemUsers, "[{}] 是系统内置用户,不允许取消分配角色", systemUsers.get(0).getNickname());
+        }
🤖 Prompt for AI Agents
In
`@continew-system/src/main/java/top/continew/admin/system/service/impl/UserRoleServiceImpl.java`
around lines 185 - 207, The call in checkSystemUserUnassignment builds the error
message using systemUsers.get(0).getNickname(), which will throw
IndexOutOfBoundsException when systemUsers is empty because argument evaluation
happens before CheckUtils.throwIfNotEmpty; fix by computing the nickname safely
(e.g. use systemUsers.stream().findFirst().map(UserDO::getNickname).orElse(""))
or first check systemUsers.isEmpty() then call
CheckUtils.throwIfNotEmpty(systemUsers, "[{}] 是系统内置用户,不允许取消分配角色", nickname)
where nickname is computed only when systemUsers is non-empty; ensure the unsafe
systemUsers.get(0).getNickname() usage is removed.

Comment on lines +186 to +207
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method prevents system built-in users from having ANY role unassigned, including super admin roles. This is inconsistent with the assignment logic in RoleServiceImpl.assignToUsers (lines 168-170) which explicitly allows system users to be assigned to super admin roles.

Consider adding similar logic here to allow unassignment of super admin role associations for system users, while preventing unassignment of other roles. You could check the role IDs associated with the user-role records and only throw an exception if non-super-admin roles are being unassigned.

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ private FileNameGenerator() {
* <p>
* 当目标目录存在同名文件时,自动添加序号后缀:
* <ul>
* <li>file.txt → file(1).txt → file(2).txt → ...</li>
* <li>无扩展名:README → README(1) → README(2) → ...</li>
* <li>隐藏文件:.gitignore → .gitignore(1) → .gitignore(2) → ...</li>
* <li>file.txt → file(1).txt → file(2).txt → ...</li>
* <li>无扩展名:README → README(1) → README(2) → ...</li>
* <li>隐藏文件:.gitignore → .gitignore(1) → .gitignore(2) → ...</li>
* </ul>
* </p>
*
Expand Down Expand Up @@ -90,7 +90,9 @@ public static String generateUniqueName(String fileName, String parentPath, Long
// 安全限制,防止无限循环
if (counter > 9999) {
log.warn("文件名重命名超过最大限制,使用当前时间戳: {}", fileName);
return baseName + "_" + System.currentTimeMillis() + (StrUtil.isNotBlank(extension) ? "." + extension : "");
return baseName + "_" + System.currentTimeMillis() + (StrUtil.isNotBlank(extension)
? "." + extension
: "");
}
}
}
Expand All @@ -102,18 +104,18 @@ public static String generateUniqueName(String fileName, String parentPath, Long
* 示例:
* </p>
* <ul>
* <li>"document.pdf" → ["document", "pdf"]</li>
* <li>"README" → ["README", ""]</li>
* <li>".gitignore" → [".gitignore", ""]</li>
* <li>"archive.tar.gz" → ["archive.tar", "gz"]</li>
* <li>"document.pdf" → ["document", "pdf"]</li>
* <li>"README" → ["README", ""]</li>
* <li>".gitignore" → [".gitignore", ""]</li>
* <li>"archive.tar.gz" → ["archive.tar", "gz"]</li>
* </ul>
*
* @param fileName 文件名
* @return 数组 [基础名, 扩展名],扩展名可能为空字符串
*/
public static String[] parseFileName(String fileName) {
if (StrUtil.isBlank(fileName)) {
return new String[]{"", ""};
return new String[] {"", ""};
}

// 处理隐藏文件(以.开头)
Expand All @@ -122,26 +124,28 @@ public static String[] parseFileName(String fileName) {

// 处理空文件名(如只有"."的情况)
if (nameWithoutDot.isEmpty()) {
return new String[]{fileName, ""};
return new String[] {fileName, ""};
}

// 查找最后一个点号位置
int lastDotIndex = nameWithoutDot.lastIndexOf('.');

// 点号不存在或在开头(如 ".bashrc"),视为无扩展名
if (lastDotIndex <= 0) {
return new String[]{fileName, ""};
return new String[] {fileName, ""};
}

String baseName = isHidden ? "." + nameWithoutDot.substring(0, lastDotIndex) : nameWithoutDot.substring(0, lastDotIndex);
String baseName = isHidden
? "." + nameWithoutDot.substring(0, lastDotIndex)
: nameWithoutDot.substring(0, lastDotIndex);
String extension = nameWithoutDot.substring(lastDotIndex + 1);

// 扩展名不应包含路径分隔符(安全检查)
if (extension.contains("/") || extension.contains("\\")) {
return new String[]{fileName, ""};
return new String[] {fileName, ""};
}

return new String[]{baseName, extension};
return new String[] {baseName, extension};
}

/**
Expand Down Expand Up @@ -194,7 +198,10 @@ private static boolean existsByName(String parentPath, Long storageId, String na
* @param fileMapper 文件Mapper
* @return 文件名列表
*/
private static List<String> selectNamesByParentPath(String parentPath, Long storageId, String namePrefix, FileMapper fileMapper) {
private static List<String> selectNamesByParentPath(String parentPath,
Long storageId,
String namePrefix,
FileMapper fileMapper) {
var wrapper = fileMapper.lambdaQuery()
.eq(FileDO::getParentPath, parentPath)
.eq(FileDO::getStorageId, storageId)
Expand Down
Loading