8.6 KiB
8.6 KiB
按企业查询用户接口设计
- 日期:2026-07-27
- 模块:
txw-mhzc(门户主站) - 状态:待用户审阅
1. 背景与目标
1.1 需求
新增一个按企业名称(及登录账号)查询用户的接口:
- 入参:单个
key关键字,对dlzh(登录账号)或qymc(企业名称)做 OR 模糊匹配;key为空时返回空结果 - 出参:符合条件的
dlzh+qymc列表,分页返回 - 一个用户可关联多个企业,匹配结果每个企业一条记录(dlzh 可重复)
1.2 设计目标
- 与现有代码风格一致(XML 处理复杂 SQL、default 方法处理简单 wrapper)
- 不污染现有
getAllUser接口,独立新增 - 单条 SQL 完成三表 JOIN,分页由 MyBatis-Plus 插件自动包装
2. 数据模型
| 表 | 关键字段 |
|---|---|
txw_mhzc_yhxxb(用户表) |
yh_uuid、dlzh(登录账号)、yxbz(有效标志 Y/N)、sdbz(锁定标志 Y/N) |
txw_mhzc_qyxxb(企业表) |
qyuuid、qymc(企业名称) |
txw_mhzc_yhqygxb(用户-企业关系表) |
yh_uuid → qyuuid,一对多 |
用户过滤硬性条件:yxbz = 'Y' AND sdbz = 'N'(与 getYhxxByDlzh 等现有查询保持一致)。
3. 接口契约
3.1 请求
POST /user/listUserByQy
Content-Type: application/json
请求体 UserQyReqVO:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
key |
String | 是 | 模糊匹配关键字,对 dlzh 或 qymc 任一命中即返回 |
pageNo |
Integer | 是 | 页码(继承 PageParam) |
pageSize |
Integer | 是 | 每页大小(继承 PageParam) |
key 为空字符串或 null → 返回空结果;key 非空 → 对 dlzh 与 qymc 做 OR 模糊匹配。
3.2 响应
{
"code": 0,
"msg": "ok",
"data": {
"total": 42,
"current": 1,
"size": 10,
"pages": 5,
"records": [
{ "dlzh": "zhangsan@qy", "qymc": "某某科技有限公司" },
{ "dlzh": "zhangsan@qy", "qymc": "某某集团" }
]
}
}
UserQyVO 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
dlzh |
String | 登录账号 |
qymc |
String | 企业名称 |
4. 组件设计
4.1 新增文件
| 文件 | 作用 |
|---|---|
pojo/vo/UserQyReqVO.java |
入参 VO(含 dlzh、qymc,继承 PageParam) |
pojo/vo/UserQyVO.java |
出参 VO(dlzh + qymc) |
mapper/TxwMhzcYhxxbMapper.java |
新增 listUserByQy 方法 |
mapper/TxwMhzcYhxxbMapper.xml |
新增对应 <select> |
service/TxwMhzcYhxxbService.java |
新增接口方法 |
service/impl/TxwMhzcYhxxbServiceImpl.java |
新增实现 |
controller/UserController.java |
新增 controller 方法 |
4.2 调用链
Controller#listUserByQy
→ Service#listUserByQy
→ Mapper#listUserByQy(XML <select>)
→ DB: 三表 JOIN + LIKE + 分页
→ Page<UserQyVO>
5. SQL 设计
<select id="listUserByQy"
resultType="com.css.txw.mhzc.pojo.vo.UserQyVO">
SELECT y.dlzh AS dlzh, q.qymc AS qymc
FROM txw_mhzc_yhxxb y
JOIN txw_mhzc_yhqygxb g ON g.yh_uuid = y.yh_uuid
JOIN txw_mhzc_qyxxb q ON q.qyuuid = g.qyuuid
WHERE y.yxbz = 'Y' AND y.sdbz = 'N'
<choose>
<when test="reqVO.key != null and reqVO.key != ''">
AND (y.dlzh LIKE CONCAT('%', #{reqVO.key}, '%')
OR q.qymc LIKE CONCAT('%', #{reqVO.key}, '%'))
</when>
<otherwise>
AND 1 = 0
</otherwise>
</choose>
ORDER BY y.lrrq DESC
</select>
LIKE参数通过 MyBatis#{}占位符绑定,%由CONCAT在 SQL 内拼接,不存在 SQL 注入风险<choose>分支保证key为空时 SQL 必定返回空(AND 1=0短路),不依赖调用方- 分页由 MyBatis-Plus 的
PaginationInnerInterceptor自动包装(项目已有配置)
6. 代码骨架
6.1 UserQyReqVO.java
package com.css.txw.mhzc.pojo.vo;
import com.css.ggzc.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "按企业查询用户请求")
@Data
public class UserQyReqVO extends PageParam {
@Schema(description = "关键字,对登录账号或企业名称做模糊匹配(必填,为空时返回空结果)")
private String key;
}
6.2 UserQyVO.java
package com.css.txw.mhzc.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户-企业匹配结果")
@Data
public class UserQyVO {
@Schema(description = "登录账号")
private String dlzh;
@Schema(description = "企业名称")
private String qymc;
}
6.3 TxwMhzcYhxxbMapper.java 新增方法
Page<UserQyVO> listUserByQy(IPage<UserQyVO> page, @Param("reqVO") UserQyReqVO reqVO);
备注:项目内 MyBatis-Plus 习惯
default方法直接用QueryWrapper,复杂 SQL 才走 XML。本接口需三表 JOIN + LIKE + 分页,直接走 XML。Mapper 接口只声明方法,由TxwMhzcYhxxbMapper.xml中<select id="listUserByQy">提供实现;由于 MP 的PaginationInnerInterceptor仅在 mapper 方法签名出现IPage参数时才会自动注入SELECT COUNT(*)与LIMIT,必须把Page显式作为参数传入。
6.4 TxwMhzcYhxxbService.java 新增方法
Page<UserQyVO> listUserByQy(UserQyReqVO reqVO);
6.5 TxwMhzcYhxxbServiceImpl.java 新增实现
@Override
public Page<UserQyVO> listUserByQy(UserQyReqVO reqVO) {
Page<UserQyVO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
return yhxxbMapper.listUserByQy(page, reqVO);
}
6.6 UserController.java 新增方法
@PostMapping("/listUserByQy")
@Operation(summary = "按企业查询用户",
description = "按登录账号与企业名称模糊查询用户-企业匹配项")
public CommonResult<Page<UserQyVO>> listUserByQy(@RequestBody UserQyReqVO reqVO) {
return CommonResult.success(yhxxbService.listUserByQy(reqVO));
}
7. 错误处理
| 场景 | 行为 |
|---|---|
key 为空字符串或 null |
返回空结果(Page{records=[], total=0},HTTP 200,由 SQL <otherwise> AND 1=0 </otherwise> 短路保证) |
key 非空但无匹配 |
Page{records=[], total=0},HTTP 200,不抛业务异常 |
| SQL 异常 | 由全局 @RestControllerAdvice 兜底返回 CommonResult.error |
输入含 SQL 通配符(%、_) |
#{} 通过预编译参数阻断 SQL 注入(输入中的 ' 无法闭合 SQL 字符串),因此整体上是安全的。但需注意:#{} 不会转义 MySQL LIKE 模式元字符(%、_),输入若含这些字符仍会按 LIKE 通配符匹配。例如输入 100%,末位 % 会被 MySQL 识别为通配符,等价于 LIKE '%100<任意内容>%'。对本接口使用场景(按名称/账号搜索)影响极小,但若将来需要严格字面量匹配,可在 SQL 内对参数做 REPLACE(..., '%', '\\%') + 显式 ESCAPE '\\',或调用方自行过滤通配符。 |
8. 测试
与现有项目风格一致,以手动验证为主:
key为空:{key:"",pageNo:1,pageSize:10}→ 返回空结果(records=[]、total=0)key命中 dlzh:仅dlzh含子串的项返回key命中 qymc:仅qymc含子串的项返回key同时命中 dlzh 与 qymc:OR 关系,命中任一即返回- 多企业用户:同一 dlzh 出现多条,每条对应一个 qymc
- 过滤被锁定用户:
sdbz='Y'的用户不出现在结果中 - 过滤无效用户:
yxbz='N'的用户不出现在结果中 - 特殊字符:输入
'%' OR '1'='1(或类似注入尝试串)应被#{}预编译中性化,返回 HTTP 200 不报 500;其中注入部分(' OR '1'='1)因#{}不允许'闭合字符串而被作为字面量处理。需要特别注意的是:#{}不会转义 LIKE 元字符,因此输入中若含%或_仍会按 MySQL 通配符匹配——例如输入100%会被解读为LIKE '%100<任意内容>%'。对本按名称/账号搜索的使用场景无实际风险,但若未来需严格字面量匹配,应在 SQL 内通过REPLACE(...)+ 显式ESCAPE '\\'转义。 - 分页边界:
pageSize=10、pageNo超过总页数 → 返回空 records,total 仍正确
9. 不在范围内
- 不修改现有
getAllUser接口及UserVO - 不新增权限校验(与
getAllUser一致,无显式权限控制,依赖网关层) - 不缓存查询结果(用户与企业关联可能变化,首次上线不强加缓存策略)
- 不支持按其他字段(手机号、身份证号、纳税人识别号)筛选