@@ -3,6 +3,7 @@ package com.yunhai.userbase.controller.api;
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.platform.utils.enums.ClientEnum;
|
||||
import com.yunhai.platform.utils.enums.IEnum;
|
||||
import com.yunhai.platform.apistd.ApiResponse;
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.enums.*;
|
||||
import com.yunhai.userbase.utils.*;
|
||||
@@ -104,19 +105,22 @@ public class BaseController {
|
||||
* 上传成功后可直接用返回的 URL 访问,无需再改资源映射配置。<br>
|
||||
* 访问路径:POST {Config.API_PATH}/base/UploadFile(multipart/form-data)。<br>
|
||||
* 支持 type:image / file / icon / word(html 被 FileUtil 黑名单禁止上传)。<br>
|
||||
* 返回 {@link ApiResponse}({code,msg,data},data=文件 URL):
|
||||
* String 返回类型会走 StringHttpMessageConverter,被 ApiResponseAdvice 跳过包装,
|
||||
* 前端 Upload 组件拿到裸字符串无法按统一结构取 data。
|
||||
*
|
||||
* @param file 上传文件(必填,单文件上限 50MB,见 application.yml 的 multipart 配置)
|
||||
* @param type 存储子目录类型,默认 image
|
||||
* @return 文件对外可访问的 Web URL
|
||||
*/
|
||||
@PostMapping("UploadFile")
|
||||
public String uploadFile(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "type", defaultValue = "image") String type) {
|
||||
public ApiResponse<String> uploadFile(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "type", defaultValue = "image") String type) {
|
||||
if (!ALLOWED_UPLOAD_TYPES.contains(type)) {
|
||||
throw new DException("不支持的上传类型: " + type + ",可选: image/file/icon/word");
|
||||
}
|
||||
// FileUtil.upload 内部已处理空文件校验、危险后缀黑名单、目录创建与访问 URL 拼接
|
||||
return FileUtil.upload(file, type);
|
||||
return ApiResponse.success(FileUtil.upload(file, type));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,9 @@ public class UserPermissionUserVo {
|
||||
/** 是否主归属:1=本职主学校/部门,0=兼职兼任。 */
|
||||
private Integer isMain;
|
||||
|
||||
/** 该行对应的组ID(同一用户多组时每个组出一行,用于按组查看权限)。 */
|
||||
private Integer groupId;
|
||||
|
||||
/** 用户所属部门/组织路径,如 /上海市仙霞高级中学/教师/行政。 */
|
||||
private String groupPath;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,15 @@ public interface SysUserRoleRelRepository extends JpaRepository<SysUserRoleRel,
|
||||
/** 用户在某学校下、某状态下的全部角色关联 */
|
||||
List<SysUserRoleRel> findByUserIdAndSchoolIdAndStatus(Integer userId, Integer schoolId, StatusEnum status);
|
||||
|
||||
/** 删除用户在某学校下的全部身份行(按 schoolId 全量覆盖保存用) */
|
||||
void deleteByUserIdAndSchoolId(Integer userId, Integer schoolId);
|
||||
/**
|
||||
* 删除用户在某学校下的全部身份行(按 schoolId 全量覆盖保存用)。<br>
|
||||
* 必须 bulk 立即发 SQL:派生 delete 只把删除动作入队,同一事务内随后 saveAll 的 INSERT
|
||||
* 在刷新时会先于 DELETE 执行(Hibernate ActionQueue 顺序),与 uk(user_id, role_id, school_id)
|
||||
* 撞唯一键(编辑提交原身份集即触发 Duplicate entry)。
|
||||
*/
|
||||
@Modifying
|
||||
@Query("DELETE FROM SysUserRoleRel r WHERE r.userId = :userId AND r.schoolId = :schoolId")
|
||||
void deleteByUserIdAndSchoolId(@Param("userId") Integer userId, @Param("schoolId") Integer schoolId);
|
||||
|
||||
/** 删除用户全部身份行(级联删用户时清理,bulk delete 直接发 SQL) */
|
||||
@Modifying
|
||||
|
||||
@@ -53,6 +53,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author chenx
|
||||
@@ -753,6 +754,7 @@ public class GroupAuthzService implements IGroupAuthzService {
|
||||
vo.setAccount(user.getAccount());
|
||||
vo.setRealName(user.getRealName());
|
||||
vo.setIsMain(rel.getIsMain());
|
||||
vo.setGroupId(rel.getGroupId());
|
||||
vo.setGroupPath(groupPath);
|
||||
result.add(vo);
|
||||
}
|
||||
@@ -774,30 +776,43 @@ public class GroupAuthzService implements IGroupAuthzService {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 加载所有组,用于构建主归属路径
|
||||
// 加载所有组,用于构建各组的组织路径
|
||||
Map<Integer, SysGroup> allGroupMap = buildAllGroupMap();
|
||||
// 加载用户与组关联,找到每个用户的主归属组
|
||||
Set<Integer> userIds = new LinkedHashSet<>();
|
||||
for (SysUser user : users) {
|
||||
userIds.add(user.getId());
|
||||
}
|
||||
// 批量解析主归属组(isMain=1 取 id 最小,无主兜底 id 最小),一次 IN 查询,消灭全表扫描
|
||||
Map<Integer, SysUserGroupRel> mainRelMap = mainGroupResolver.resolveByUserIds(new ArrayList<>(userIds));
|
||||
// 同一用户在多个组时每个组出一行(各自组路径/主兼职标记),
|
||||
// 不再只解析主归属组——否则多组用户只能看到主组一条数据
|
||||
Map<Integer, List<SysUserGroupRel>> relsByUser = sysUserGroupRelRepository
|
||||
.findByUserIdIn(new ArrayList<>(userIds))
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(SysUserGroupRel::getUserId));
|
||||
|
||||
List<UserPermissionUserVo> result = new ArrayList<>();
|
||||
for (SysUser user : users) {
|
||||
SysUserGroupRel mainRel = mainRelMap.get(user.getId());
|
||||
String path = "";
|
||||
if (mainRel != null && mainRel.getGroupId() != null) {
|
||||
path = buildGroupPath(mainRel.getGroupId(), allGroupMap);
|
||||
List<SysUserGroupRel> userRels = relsByUser.get(user.getId());
|
||||
if (userRels == null || userRels.isEmpty()) {
|
||||
// 无组关系的用户仍出一行(组路径空),保持可选中查看权限
|
||||
UserPermissionUserVo vo = new UserPermissionUserVo();
|
||||
vo.setId(user.getId());
|
||||
vo.setAccount(user.getAccount());
|
||||
vo.setRealName(user.getRealName());
|
||||
vo.setGroupPath("");
|
||||
result.add(vo);
|
||||
continue;
|
||||
}
|
||||
for (SysUserGroupRel rel : userRels) {
|
||||
UserPermissionUserVo vo = new UserPermissionUserVo();
|
||||
vo.setId(user.getId());
|
||||
vo.setAccount(user.getAccount());
|
||||
vo.setRealName(user.getRealName());
|
||||
vo.setIsMain(rel.getIsMain());
|
||||
vo.setGroupId(rel.getGroupId());
|
||||
vo.setGroupPath(rel.getGroupId() != null
|
||||
? buildGroupPath(rel.getGroupId(), allGroupMap) : "");
|
||||
result.add(vo);
|
||||
}
|
||||
UserPermissionUserVo vo = new UserPermissionUserVo();
|
||||
vo.setId(user.getId());
|
||||
vo.setAccount(user.getAccount());
|
||||
vo.setRealName(user.getRealName());
|
||||
vo.setIsMain(mainRel != null ? mainRel.getIsMain() : null);
|
||||
vo.setGroupPath(path);
|
||||
result.add(vo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -168,12 +168,18 @@ public class SysOapiLogServiceImpl implements ISysOapiLogService {
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotBlank(query.getKeyword())) {
|
||||
String kw = "%" + query.getKeyword().trim() + "%";
|
||||
// 单输入框同时匹配:请求路径(requestUri) / 应用名(callerAppName) / appKey(callerAppKey)
|
||||
predicates.add(cb.or(
|
||||
String trimmed = query.getKeyword().trim();
|
||||
String kw = "%" + trimmed + "%";
|
||||
// 单输入框同时匹配:请求路径(requestUri) / 应用名(callerAppName);
|
||||
// 纯数字再按 appKey 数值等值匹配(cb.toString 会渲染成 CAST 表达式,MySQL 方言下 SQL 报错)
|
||||
Predicate textMatch = cb.or(
|
||||
cb.like(root.get("requestUri"), kw),
|
||||
cb.like(root.get("callerAppName"), kw),
|
||||
cb.like(cb.toString(root.get("callerAppKey")), kw)));
|
||||
cb.like(root.get("callerAppName"), kw));
|
||||
if (trimmed.matches("\\d+")) {
|
||||
predicates.add(cb.or(textMatch, cb.equal(root.get("callerAppKey"), Integer.valueOf(trimmed))));
|
||||
} else {
|
||||
predicates.add(textMatch);
|
||||
}
|
||||
}
|
||||
if (query.getStartTime() != null) {
|
||||
predicates.add(cb.greaterThanOrEqualTo(root.get("createTime"), query.getStartTime()));
|
||||
|
||||
@@ -496,7 +496,7 @@ public class SysUserService implements ISysUserService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 按页批量带出各用户主校身份名(动态解析主身份,无 N+1)。
|
||||
* 按页批量带出各用户身份名(动态解析主身份,无 N+1)。
|
||||
* roles 列已停写(冻结历史值),列表「用户身份」列一律切此新字段展示。
|
||||
*/
|
||||
private Page<SysUserVo> attachMainIdentityNames(Page<SysUserVo> page) {
|
||||
@@ -508,15 +508,32 @@ public class SysUserService implements ISysUserService {
|
||||
.map(SysUserVo::getId)
|
||||
.collect(Collectors.toList());
|
||||
Map<Integer, SysUserRoleRel> mainRelsByUser = resolveMainRoleRelsByUser(userIds);
|
||||
// 全部正常态身份行按用户分组:列表列展示全部身份名(主身份在前),不只展示主身份
|
||||
Map<Integer, List<SysUserRoleRel>> roleRelsByUser = sysUserRoleRelRepository
|
||||
.findByUserIdInAndStatus(userIds, StatusEnum.NORMAL)
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(SysUserRoleRel::getUserId));
|
||||
for (SysUserVo vo : content) {
|
||||
SysUserRoleRel mainRel = mainRelsByUser.get(vo.getId());
|
||||
if (mainRel == null) {
|
||||
List<SysUserRoleRel> rels = roleRelsByUser.get(vo.getId());
|
||||
if (rels == null || rels.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
SysRoleEnum e = IEnum.getByKey(SysRoleEnum.class, mainRel.getRoleId());
|
||||
String name = e != null ? e.getValue() : null;
|
||||
if (name != null) {
|
||||
vo.setIdentityNames(name);
|
||||
SysUserRoleRel mainRel = mainRelsByUser.get(vo.getId());
|
||||
String names = rels.stream()
|
||||
.sorted(Comparator.comparingInt((SysUserRoleRel r) ->
|
||||
mainRel != null && Objects.equals(r.getId(), mainRel.getId()) ? 0 : 1)
|
||||
.thenComparingInt(SysUserRoleRel::getSortNo)
|
||||
.thenComparingInt(SysUserRoleRel::getId))
|
||||
.map(SysUserRoleRel::getRoleId)
|
||||
.distinct()
|
||||
.map(roleId -> {
|
||||
SysRoleEnum e = IEnum.getByKey(SysRoleEnum.class, roleId);
|
||||
return e != null ? e.getValue() : null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.joining("、"));
|
||||
if (!names.isEmpty()) {
|
||||
vo.setIdentityNames(names);
|
||||
}
|
||||
}
|
||||
return page;
|
||||
|
||||
@@ -27,7 +27,9 @@ import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Order;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -164,10 +166,23 @@ public class PushRecordService {
|
||||
}
|
||||
|
||||
private LocalDateTime parseStart(String s) {
|
||||
return LocalDateTime.parse(s + "T00:00:00", DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
return parseTime(s, LocalTime.MIN);
|
||||
}
|
||||
|
||||
private LocalDateTime parseEnd(String s) {
|
||||
return LocalDateTime.parse(s + "T23:59:59", DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
return parseTime(s, LocalTime.of(23, 59, 59));
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容两种入参:yyyy-MM-dd(按日界补 00:00:00 / 23:59:59)与
|
||||
* yyyy-MM-dd HH:mm:ss(前端日期选择器带时刻的完整格式,原样解析)。
|
||||
* 前者按旧约定拼接解析,后者若仍走拼接会产生 "… HH:mm:ssT00:00:00" 解析异常。
|
||||
*/
|
||||
private LocalDateTime parseTime(String s, LocalTime dayEdge) {
|
||||
String v = s == null ? "" : s.trim();
|
||||
if (v.length() == 10) {
|
||||
return LocalDate.parse(v, DateTimeFormatter.ISO_LOCAL_DATE).atTime(dayEdge);
|
||||
}
|
||||
return LocalDateTime.parse(v, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>/app/logs/app.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>/app/logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
<fileNamePattern>/app/logs/app.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
|
||||
<maxHistory>60</maxHistory>
|
||||
<totalSizeCap>5GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.yunhai.userprofile;
|
||||
package com.yunhai.userbase;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.yunhai.userbase.service.impl;
|
||||
|
||||
import com.yunhai.userbase.model.BasePageQuery;
|
||||
import com.yunhai.userbase.model.oapilog.SysOapiLogQuery;
|
||||
import com.yunhai.userbase.model.push.PushRecordQuery;
|
||||
import com.yunhai.userbase.service.ISysOapiLogService;
|
||||
import com.yunhai.userbase.service.push.PushRecordService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
||||
/**
|
||||
* 日志搜索回归(真库执行 SQL,验证谓词语法与时间解析;空表也走完整 SQL):<br>
|
||||
* ① 开放接口日志关键字搜索:文本走 LIKE,纯数字走 appKey 数值等值——
|
||||
* 修复前 cb.toString(callerAppKey) 渲染 CAST 表达式,MySQL 方言 SQL 报错;<br>
|
||||
* ② 推送日志时间搜索:兼容 yyyy-MM-dd 与 yyyy-MM-dd HH:mm:ss——
|
||||
* 修复前端传带时刻格式时按旧约定拼接解析抛 DateTimeParseException。
|
||||
*/
|
||||
@SpringBootTest
|
||||
class LogSearchRegressionTest {
|
||||
|
||||
@Autowired
|
||||
private ISysOapiLogService sysOapiLogService;
|
||||
@Autowired
|
||||
private PushRecordService pushRecordService;
|
||||
|
||||
@Test
|
||||
void oapiLog_keywordSearch_textAndNumeric() {
|
||||
BasePageQuery page = new BasePageQuery();
|
||||
SysOapiLogQuery text = new SysOapiLogQuery();
|
||||
text.setKeyword("user");
|
||||
sysOapiLogService.pageQuery(text, page);
|
||||
|
||||
SysOapiLogQuery numeric = new SysOapiLogQuery();
|
||||
numeric.setKeyword("100001");
|
||||
sysOapiLogService.pageQuery(numeric, page);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushLog_timeSearch_bothFormats() {
|
||||
PushRecordQuery q = new PushRecordQuery();
|
||||
q.setStartTime("2026-09-01 00:00:00");
|
||||
q.setEndTime("2026-09-01 23:59:59");
|
||||
pushRecordService.query(q, PageRequest.of(0, 10));
|
||||
|
||||
q.setStartTime("2026-09-01");
|
||||
q.setEndTime("2026-09-01");
|
||||
pushRecordService.query(q, PageRequest.of(0, 10));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.yunhai.userbase.service.impl;
|
||||
|
||||
import com.yunhai.userbase.entity.SysGroup;
|
||||
import com.yunhai.userbase.entity.SysUserRoleRel;
|
||||
import com.yunhai.userbase.enums.StatusEnum;
|
||||
import com.yunhai.userbase.model.groupauthz.UserPermissionUserVo;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserQuery;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserRequest;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserVo;
|
||||
import com.yunhai.userbase.repository.SysGroupRepository;
|
||||
import com.yunhai.userbase.repository.SysUserRoleRelRepository;
|
||||
import com.yunhai.userbase.service.IGroupAuthzService;
|
||||
import com.yunhai.userbase.service.ISysUserService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* 身份写入回归(真库集成,事务回滚不留数据):<br>
|
||||
* ① 新增教师+管理员双身份 → 两行都落库;<br>
|
||||
* ② 编辑提交原身份集 → 不再因「派生 delete 入队 + saveAll 刷新时 INSERT 先于 DELETE」
|
||||
* 撞 uk(user_id, role_id, school_id)(修复:bulk 删除立即执行);<br>
|
||||
* ③ 编辑缩减身份 → 按 schoolId 全量覆盖;<br>
|
||||
* ④ 权限一览用户搜索 → 同一用户多组每个组出一行。
|
||||
*/
|
||||
@SpringBootTest
|
||||
@Transactional
|
||||
class SysUserIdentityRegressionTest {
|
||||
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
@Autowired
|
||||
private IGroupAuthzService groupAuthzService;
|
||||
@Autowired
|
||||
private SysGroupRepository sysGroupRepository;
|
||||
@Autowired
|
||||
private SysUserRoleRelRepository sysUserRoleRelRepository;
|
||||
|
||||
private SysUserRequest buildAddRequest(Integer groupId) {
|
||||
SysUserRequest add = new SysUserRequest();
|
||||
add.setAccount("regress_identity");
|
||||
add.setPassword("Rk8pb2mW!");
|
||||
add.setConfirmPassword("Rk8pb2mW!");
|
||||
add.setRealName("回归用户");
|
||||
add.setGender(0);
|
||||
add.setGroupId(groupId);
|
||||
add.setRoleIds(Arrays.asList(1, 7)); // 教师 + 管理员
|
||||
return add;
|
||||
}
|
||||
|
||||
private List<Integer> roleIdsOf(Integer userId) {
|
||||
return sysUserRoleRelRepository.findByUserIdAndStatus(userId, StatusEnum.NORMAL)
|
||||
.stream().map(SysUserRoleRel::getRoleId).sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void addThenEdit_identities() {
|
||||
SysGroup group = sysGroupRepository.findAll().stream().findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("库中无组数据"));
|
||||
|
||||
Integer userId = sysUserService.add(buildAddRequest(group.getId()));
|
||||
assertEquals(Arrays.asList(1, 7), roleIdsOf(userId), "新增教师+管理员双身份应两行都落库");
|
||||
|
||||
// 列表「用户身份」列应展示全部身份名(不只主身份),多身份按「、」连接
|
||||
SysUserQuery query = new SysUserQuery();
|
||||
query.setGroupId(group.getId());
|
||||
Page<SysUserVo> page = sysUserService.list(query, PageRequest.of(0, 10));
|
||||
SysUserVo mine = page.getContent().stream()
|
||||
.filter(v -> userId.equals(v.getId())).findFirst().orElse(null);
|
||||
assertNotNull(mine, "组列表应包含新建用户");
|
||||
assertEquals("老师、管理员", mine.getIdentityNames(), "用户身份列应展示全部身份名");
|
||||
|
||||
Integer schoolId = sysUserRoleRelRepository.findByUserId(userId).stream()
|
||||
.findFirst().map(SysUserRoleRel::getSchoolId).orElse(null);
|
||||
assertNotNull(schoolId, "新增后身份行应带 schoolId");
|
||||
|
||||
// 编辑①:身份不变重复提交(前端编辑总是携带原值)——修复前此处 Duplicate entry 撞 uk
|
||||
SysUserRequest keep = new SysUserRequest();
|
||||
keep.setId(userId);
|
||||
keep.setAccount("regress_identity");
|
||||
keep.setRealName("回归用户改");
|
||||
keep.setGender(0);
|
||||
keep.setSchoolId(schoolId);
|
||||
keep.setRoleIds(Arrays.asList(1, 7));
|
||||
sysUserService.edit(userId, keep);
|
||||
assertEquals(Arrays.asList(1, 7), roleIdsOf(userId), "编辑提交原身份集应原样保留");
|
||||
|
||||
// 编辑②:清掉管理员,仅剩教师——按 schoolId 全量覆盖
|
||||
SysUserRequest shrink = new SysUserRequest();
|
||||
shrink.setId(userId);
|
||||
shrink.setAccount("regress_identity");
|
||||
shrink.setRealName("回归用户改");
|
||||
shrink.setGender(0);
|
||||
shrink.setSchoolId(schoolId);
|
||||
shrink.setRoleIds(Collections.singletonList(1));
|
||||
sysUserService.edit(userId, shrink);
|
||||
assertEquals(Collections.singletonList(1), roleIdsOf(userId), "编辑缩减身份应按 schoolId 全量覆盖");
|
||||
}
|
||||
|
||||
@Test
|
||||
void searchUsers_multiGroup_shouldReturnRowPerGroup() {
|
||||
List<SysGroup> groups = sysUserRepository_findTwoGroups();
|
||||
Integer userId = sysUserService.add(buildAddRequest(groups.get(0).getId()));
|
||||
// 调整到两个组(第二个组为主组)
|
||||
sysUserService.changeGroups(Collections.singletonList(userId),
|
||||
Arrays.asList(groups.get(0).getId(), groups.get(1).getId()), groups.get(1).getId());
|
||||
|
||||
List<UserPermissionUserVo> rows = groupAuthzService.searchUsers("regress_identity");
|
||||
List<UserPermissionUserVo> mine = rows.stream()
|
||||
.filter(v -> userId.equals(v.getId())).collect(Collectors.toList());
|
||||
assertEquals(2, mine.size(), "同一用户在两个组应出两行");
|
||||
assertEquals(1, mine.stream().filter(v -> Integer.valueOf(1).equals(v.getIsMain())).count(),
|
||||
"两行中应恰有一行主归属");
|
||||
mine.forEach(v -> assertNotNull(v.getGroupId(), "每行应带 groupId(按组查看权限)"));
|
||||
}
|
||||
|
||||
private List<SysGroup> sysUserRepository_findTwoGroups() {
|
||||
List<SysGroup> all = sysGroupRepository.findAll();
|
||||
if (all.size() < 2) {
|
||||
throw new IllegalStateException("库中组数据不足两个,无法覆盖多组场景");
|
||||
}
|
||||
return all.subList(0, 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user