diff --git a/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java b/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java index 924a3f3..6fe3796 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java +++ b/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java @@ -9,4 +9,6 @@ public interface UserService { Map create(CreateUserDTO dto); Integer update(Integer id, UpdateUserDTO dto); + + Map list(String field, String match, String value, Integer pageNum, Integer pageSize); } 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 b2860d3..35afd8b 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 @@ -7,6 +7,7 @@ import com.xly.erp.common.security.SecurityContextHelper; import com.xly.erp.module.usr.dto.CreateUserDTO; import com.xly.erp.module.usr.dto.UpdateUserDTO; import com.xly.erp.module.usr.entity.User; +import com.xly.erp.module.usr.vo.UserListVO; import com.xly.erp.module.usr.entity.UserPermission; import com.xly.erp.module.usr.mapper.PermissionCategoryMapper; import com.xly.erp.module.usr.mapper.StaffMapper; @@ -18,7 +19,9 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -32,6 +35,30 @@ public class UserServiceImpl implements UserService { static final Set LANGUAGES = Set.of("zh", "en", "zh-TW"); static final String DEFAULT_PASSWORD = "666666"; + static final int MAX_PAGE_SIZE = 100; + static final String STRING_TYPE = "STRING"; + static final String BOOLEAN_TYPE = "BOOLEAN"; + static final String DATE_TYPE = "DATE"; + + private static final Map FIELD_MAP = Map.of( + "用户名", new FieldDef("u.sUserName", STRING_TYPE), + "员工名", new FieldDef("s.sStaffName", STRING_TYPE), + "用户号", new FieldDef("u.sUserNo", STRING_TYPE), + "部门", new FieldDef("s.sDepartment", STRING_TYPE), + "用户类型", new FieldDef("u.sUserType", STRING_TYPE), + "作废", new FieldDef("u.bDeleted", BOOLEAN_TYPE), + "登录日期", new FieldDef("DATE(u.tLastLoginDate)", DATE_TYPE), + "制单人", new FieldDef("u.sCreatedBy", STRING_TYPE) + ); + + private static final Map MATCH_MAP = Map.of( + "包含", "contains", + "不包含", "notContains", + "等于", "equals" + ); + + private record FieldDef(String column, String type) {} + private final UserMapper userMapper; private final UserPermissionMapper userPermissionMapper; private final StaffMapper staffMapper; @@ -171,4 +198,63 @@ public class UserServiceImpl implements UserService { } return id; } + + @Override + @Transactional(readOnly = true) + public Map list(String field, String match, String value, Integer pageNum, Integer pageSize) { + int p = (pageNum == null || pageNum < 1) ? 1 : pageNum; + int size = (pageSize == null) ? 20 : pageSize; + if (size > MAX_PAGE_SIZE) { + throw new BizException(40002, "pageSize 超过 100"); + } + String f = (field == null || field.isBlank()) ? "用户名" : field.trim(); + String m = (match == null || match.isBlank()) ? "包含" : match.trim(); + String v = value == null ? "" : value.trim(); + + FieldDef def = FIELD_MAP.get(f); + if (def == null) { + throw new BizException(40001, "field 取值非法"); + } + String matchOp = MATCH_MAP.get(m); + if (matchOp == null) { + throw new BizException(40001, "match 取值非法"); + } + if (!STRING_TYPE.equals(def.type()) && !"equals".equals(matchOp)) { + throw new BizException(40001, "field/match 组合非法"); + } + + Object queryValue = v; + if (!v.isEmpty()) { + if (BOOLEAN_TYPE.equals(def.type())) { + queryValue = parseBoolean(v); + } else if (DATE_TYPE.equals(def.type())) { + try { + queryValue = LocalDate.parse(v).toString(); + } catch (DateTimeParseException e) { + throw new BizException(40001, "登录日期值格式非法(应为 YYYY-MM-DD)"); + } + } + } else { + queryValue = ""; + } + + int offset = (p - 1) * size; + List records = userMapper.pageWithFilter(def.column(), matchOp, queryValue, offset, size); + long total = userMapper.countWithFilter(def.column(), matchOp, queryValue); + + Map result = new LinkedHashMap<>(); + result.put("records", records); + result.put("total", total); + result.put("pageNum", p); + result.put("pageSize", size); + return result; + } + + private Integer parseBoolean(String v) { + return switch (v.toLowerCase()) { + case "true", "1" -> 1; + case "false", "0" -> 0; + default -> -1; + }; + } } diff --git a/backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java b/backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java index 7ac1e5d..e6731fa 100644 --- a/backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java +++ b/backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java @@ -28,7 +28,9 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -316,6 +318,95 @@ class UserServiceImplTest { verify(userPermissionMapper, never()).insert(any(UserPermission.class)); } + @Test + void listWithDefaults_invokesMapperWithUserNameContainsEmpty() { + when(userMapper.pageWithFilter(eq("u.sUserName"), eq("contains"), eq(""), eq(0), eq(20))) + .thenReturn(List.of()); + when(userMapper.countWithFilter(eq("u.sUserName"), eq("contains"), eq(""))).thenReturn(0L); + + Map r = service.list(null, null, null, null, null); + + assertThat(r).containsEntry("total", 0L).containsEntry("pageNum", 1).containsEntry("pageSize", 20); + } + + @Test + void listWithEmptyValue_skipsFilterCondition() { + when(userMapper.pageWithFilter(any(), any(), any(), anyInt(), anyInt())).thenReturn(List.of()); + when(userMapper.countWithFilter(any(), any(), any())).thenReturn(0L); + + service.list("用户号", "等于", "", 1, 20); + + ArgumentCaptor valueCap = ArgumentCaptor.forClass(Object.class); + verify(userMapper).pageWithFilter(eq("u.sUserNo"), eq("equals"), valueCap.capture(), eq(0), eq(20)); + assertThat(valueCap.getValue()).isEqualTo(""); + } + + @Test + void listWithKeywordTrim() { + when(userMapper.pageWithFilter(any(), any(), any(), anyInt(), anyInt())).thenReturn(List.of()); + when(userMapper.countWithFilter(any(), any(), any())).thenReturn(0L); + + service.list("用户名", "包含", " abc ", 1, 10); + + verify(userMapper).pageWithFilter(eq("u.sUserName"), eq("contains"), eq("abc"), eq(0), eq(10)); + } + + @Test + void listReturnsEmptyRecords_whenMapperReturnsEmptyPage() { + when(userMapper.pageWithFilter(any(), any(), any(), anyInt(), anyInt())).thenReturn(List.of()); + when(userMapper.countWithFilter(any(), any(), any())).thenReturn(0L); + + Map r = service.list("用户名", "包含", "xyz", 1, 20); + + assertThat((List) r.get("records")).isEmpty(); + assertThat(r).containsEntry("total", 0L); + } + + @Test + void listWithInvalidField_throws40001() { + assertThatThrownBy(() -> service.list("未知字段", "包含", "x", 1, 20)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40001); + } + + @Test + void listWithInvalidMatch_throws40001() { + assertThatThrownBy(() -> service.list("用户名", "未知方式", "x", 1, 20)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40001); + } + + @Test + void listWithIncompatibleFieldMatch_throws40001() { + assertThatThrownBy(() -> service.list("作废", "包含", "true", 1, 20)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40001); + } + + @Test + void listWithPageSizeExceeds100_throws40002() { + assertThatThrownBy(() -> service.list("用户名", "包含", "", 1, 101)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40002); + } + + @Test + void listWithInvalidLoginDateFormat_throws40001() { + assertThatThrownBy(() -> service.list("登录日期", "等于", "abc", 1, 20)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40001); + } + + @Test + void listWithBooleanFieldEqualsTrue_passesIntegerOne() { + when(userMapper.pageWithFilter(any(), any(), any(), anyInt(), anyInt())).thenReturn(List.of()); + when(userMapper.countWithFilter(any(), any(), any())).thenReturn(0L); + + service.list("作废", "等于", "true", 1, 20); + + verify(userMapper).pageWithFilter(eq("u.bDeleted"), eq("equals"), eq(1), eq(0), eq(20)); + } + private UpdateUserDTO baseUpdateDto() { UpdateUserDTO dto = new UpdateUserDTO(); dto.setSUserNo("u_new");