diff --git a/backend/src/main/java/com/xly/erp/module/usr/dto/UserQueryDTO.java b/backend/src/main/java/com/xly/erp/module/usr/dto/UserQueryDTO.java index 09bb579..41bd45d 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/dto/UserQueryDTO.java +++ b/backend/src/main/java/com/xly/erp/module/usr/dto/UserQueryDTO.java @@ -1,6 +1,5 @@ package com.xly.erp.module.usr.dto; -import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Pattern; @@ -18,7 +17,7 @@ public class UserQueryDTO { @Max(100) private Integer pageSize = 20; - /** 可空:缺省视为不过滤;服务层白名单校验后映射到 column 字段 */ + /** 可空:缺省视为不过滤;服务层白名单映射为 SQL 列名后通过 mapper @Param 单独传入 */ @Pattern(regexp = "^(username|staffname|userno|department|usertype|language|deleted|lastLoginDate|createdBy)?$", message = "queryField 非法") private String queryField; @@ -30,9 +29,4 @@ public class UserQueryDTO { /** 可空:缺省视为不过滤 */ @Size(max = 100) private String queryValue; - - /** 服务层白名单映射后的实际 SQL 列名(如 "u.sUserName")。 - * Jackson 忽略——前端不能也无法直接传入;XML mapper 用 ${query.column} 渲染。 */ - @JsonIgnore - private String column; } diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java index 5cfe50d..0643443 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java @@ -9,6 +9,11 @@ import org.apache.ibatis.annotations.Param; public interface UserMapper extends BaseMapper { - /** REQ-USR-003 用户列表查询:跨表 JOIN tStaff,按 query 过滤 + 分页。XML 实现。 */ - IPage searchUsers(IPage page, @Param("query") UserQueryDTO query); + /** + * REQ-USR-003 用户列表查询:跨表 JOIN tStaff,按 query 过滤 + 分页。XML 实现。 + * @param column service 层白名单映射后的 SQL 列字符串(如 "u.sUserName");外部输入绝不直接走这里。 + */ + IPage searchUsers(IPage page, + @Param("query") UserQueryDTO query, + @Param("column") String column); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java b/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java index d1cd0f5..ace875b 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java +++ b/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java @@ -187,13 +187,15 @@ public class UserServiceImpl implements UserService { @Override @Transactional(readOnly = true) public PageResult search(UserQueryDTO query) { - // 1. queryField 白名单 + 列映射(防 SQL 注入) + // 1. queryField 白名单 + 列映射(防 SQL 注入)。 + // column 是 service 内部局部变量,通过 mapper @Param("column") 单独传入;不写回 DTO, + // 避免 GET query-string 绑定(@JsonIgnore 仅对 Jackson 生效,无法防 setter 注入)。 + String column = null; if (query.getQueryField() != null && !query.getQueryField().isEmpty()) { - String mapped = QUERY_COLUMN_MAP.get(query.getQueryField()); - if (mapped == null) { + column = QUERY_COLUMN_MAP.get(query.getQueryField()); + if (column == null) { throw new BizException(ErrorCode.PARAM_INVALID, "queryField 非法: " + query.getQueryField()); } - query.setColumn(mapped); } // 2. matchType 白名单 @@ -202,13 +204,26 @@ public class UserServiceImpl implements UserService { throw new BizException(ErrorCode.PARAM_INVALID, "matchType 非法: " + query.getMatchType()); } - // 3. 默认值兜底 + // 3. spec § 业务规则 6:deleted 字段值标准化('true'/'1' → '1';'false'/'0' → '0';其它非法) + if ("deleted".equals(query.getQueryField()) + && query.getQueryValue() != null && !query.getQueryValue().isEmpty()) { + String v = query.getQueryValue().trim().toLowerCase(); + if ("true".equals(v) || "1".equals(v)) { + query.setQueryValue("1"); + } else if ("false".equals(v) || "0".equals(v)) { + query.setQueryValue("0"); + } else { + throw new BizException(ErrorCode.PARAM_INVALID, "deleted queryValue 仅支持 true/false/1/0"); + } + } + + // 4. 默认值兜底 int pageNum = query.getPageNum() == null ? 1 : query.getPageNum(); int pageSize = query.getPageSize() == null ? 20 : query.getPageSize(); - // 4. MP 分页查询 + // 5. MP 分页查询 IPage page = new Page<>(pageNum, pageSize); - IPage result = userMapper.searchUsers(page, query); + IPage result = userMapper.searchUsers(page, query, column); return PageResult.of(result); } diff --git a/backend/src/main/resources/mapper/usr/UserMapper.xml b/backend/src/main/resources/mapper/usr/UserMapper.xml index 095614c..4207d51 100644 --- a/backend/src/main/resources/mapper/usr/UserMapper.xml +++ b/backend/src/main/resources/mapper/usr/UserMapper.xml @@ -3,7 +3,8 @@ + column 由 service 层白名单映射后通过 @Param("column") 单独传入, + 绝不接受 DTO 中可被 GET query-string 绑定的字段。 -->