feat: 扩展用户管理统一搜索范围

- 支持按账号、昵称、电话、邮件和角色名搜索

- 补充搜索提示、特殊字符处理及前后端测试
This commit is contained in:
2026-08-07 12:14:55 +08:00
parent 6544cdcff0
commit 170b315459
6 changed files with 252 additions and 1 deletions

View File

@@ -5,7 +5,10 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.alibaba.fastjson2.JSONObject;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryCondition;
import com.mybatisflex.core.query.QueryMethods;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.transaction.annotation.Transactional;
@@ -49,6 +52,10 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static tech.easyflow.system.entity.table.SysAccountRoleTableDef.SYS_ACCOUNT_ROLE;
import static tech.easyflow.system.entity.table.SysAccountTableDef.SYS_ACCOUNT;
import static tech.easyflow.system.entity.table.SysRoleTableDef.SYS_ROLE;
/**
* 用户表 控制层。
*
@@ -58,6 +65,7 @@ import java.util.stream.Collectors;
@RestController("sysAccountController")
@RequestMapping("/api/v1/sysAccount")
public class SysAccountController extends BaseCurdController<SysAccountService, SysAccount> {
private static final String ACCOUNT_SEARCH_KEYWORD_PARAM = "keyword";
private static final String SUPER_ADMIN_HOME_PATH = "/dashboard/workspace";
private static final String USER_HOME_PATH = "/ai/agent-chat";
@@ -81,6 +89,53 @@ public class SysAccountController extends BaseCurdController<SysAccountService,
this.sysRoleService = sysRoleService;
}
/**
* 构造账号列表查询条件。
*
* <p>统一关键字同时匹配账号、昵称、电话、邮件和已关联角色名称。角色条件使用相关
* {@code EXISTS} 子查询,避免多角色关联导致分页记录重复。</p>
*
* @param request 当前 HTTP 请求
* @return 账号列表查询条件
*/
@Override
protected QueryWrapper buildQueryWrapper(HttpServletRequest request) {
String keyword = request.getParameter(ACCOUNT_SEARCH_KEYWORD_PARAM);
if (!StringUtil.hasText(keyword)) {
return super.buildQueryWrapper(request);
}
String likePattern = "%" + escapeLikeKeyword(keyword.trim()) + "%";
QueryWrapper roleExistsQuery = QueryMethods.selectOne()
.from(SYS_ACCOUNT_ROLE)
.innerJoin(SYS_ROLE)
.on(SYS_ACCOUNT_ROLE.ROLE_ID.eq(SYS_ROLE.ID))
.where(SYS_ACCOUNT_ROLE.ACCOUNT_ID.eq(SYS_ACCOUNT.ID))
// 关联角色必须与账号属于同一租户,避免异常关系数据跨租户命中。
.and(SYS_ROLE.TENANT_ID.eq(SYS_ACCOUNT.TENANT_ID))
.and(SYS_ROLE.ROLE_NAME.likeRaw(likePattern));
QueryCondition keywordCondition = SYS_ACCOUNT.LOGIN_NAME.likeRaw(likePattern)
.or(SYS_ACCOUNT.NICKNAME.likeRaw(likePattern))
.or(SYS_ACCOUNT.MOBILE.likeRaw(likePattern))
.or(SYS_ACCOUNT.EMAIL.likeRaw(likePattern))
.or(QueryMethods.exists(roleExistsQuery));
return QueryWrapper.create().and(keywordCondition);
}
/**
* 转义 MySQL LIKE 模式中的特殊字符,使用户输入按普通文本匹配。
*
* @param keyword 已去除首尾空格的关键字
* @return 可安全放入 LIKE 模式的文本
*/
private String escapeLikeKeyword(String keyword) {
return keyword
.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_");
}
@Override
@LogRecord("分页查询")
protected Page<SysAccount> queryPage(Page<SysAccount> page, QueryWrapper queryWrapper) {