package com.yunhai.userbase.service; import com.yunhai.platform.utils.model.BoaRoleInfo; import com.yunhai.userbase.model.sysuser.BatchImportResult; import com.yunhai.userbase.model.sysuser.SysUserGroupDeleteItem; 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.model.sysuser.UserIdentityVo; import org.springframework.web.multipart.MultipartFile; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; /** * @author chenx * @date 2026/7/20 16:14 * @description 系统用户服务接口 */ public interface ISysUserService { /** 新增用户:校验密码/手机号/身份证,BCrypt 加密,并写入组用户关联。返回创建后的用户ID。 */ Integer add(SysUserRequest request); /** 编辑用户;密码为空时不修改密码。 */ void edit(Integer id, SysUserRequest request); /** * 批量新增用户:逐条走完整校验(含密码强度),返回创建后的用户ID列表(顺序与入参一致)。 */ List batchAdd(List requests); /** * 把用户从指定组中移除;若移除后该用户已无其它组关联,则级联删除用户本身。 * * @param userId 用户ID * @param groupId 当前组ID */ void deleteFromGroup(Integer userId, Integer groupId); /** * 批量把用户从指定组中移除;无其它组关联者级联删除用户本身。 * * @param userIds 用户ID列表 * @param groupId 当前组ID */ void batchDeleteFromGroup(List userIds, Integer groupId); /** * 开放接口专用:在学校(组)范围内按组解绑用户。
* 与 {@link #deleteFromGroup(Integer, Integer)} 语义一致,但额外强制边界:
* ① groupId 不能等于 schoolId(禁止对学校根组解绑,避免整校移除);
* ② groupId 必须属于 schoolId 子树(禁止跨校解绑)。
* 任意一条不满足即抛异常。 * * @param userId 用户ID * @param groupId 当前组ID * @param schoolId 学校组ID(限定解绑范围) */ void deleteFromGroup(Integer userId, Integer groupId, Integer schoolId); /** * 开放接口专用:批量删除用户-组关系;每条携带自身 (userId, groupId, schoolId),逐条做上述学校边界校验后解绑。 * * @param items 待删除项列表({@link SysUserGroupDeleteItem}) */ void deleteUsers(List items); /** * 启用/禁用用户。
* * @param id 用户ID * @param disabled true=禁用,false=启用 */ void changeStatus(Integer id, boolean disabled); /** * 批量启用/禁用用户。 * * @param userIds 用户ID列表 * @param disabled true=禁用,false=启用 */ void batchChangeStatus(List userIds, boolean disabled); /** * 分页查询组下用户。
* 依据 query.groupId 与 query.withSub 决定组范围,query.name 做多字段匹配。
* * @param query 查询条件 * @param pageable 分页与排序 * @return 用户分页 */ Page list(SysUserQuery query, Pageable pageable); /** * 查询指定用户当前所属的所有组ID。 * * @param userId 用户ID * @return 组ID列表 */ List findGroupIdsByUserId(Integer userId); /** * 查询用户身份(管理端「用户身份」回显):后端按所在组解析 schoolId 后返回 {schoolId, roleIds};
* 同一学校多组返回同一 schoolId 与角色并集。前端回显唯一来源(roles 列已停写,不再作为真源)。
* 编辑请求提交时原样携带返回的 {schoolId, roleIds},由后端按 schoolId 全量覆盖写库并对齐。 * * @param userId 用户ID * @param groupId 所在组ID(Select 当前选中项;schoolId 解析规则见 {@link com.yunhai.userbase.service.ISysGroupService#resolveSchoolId}) * @return {schoolId, roleIds} */ UserIdentityVo getUserIdentity(Integer userId, Integer groupId); /** * 批量调整用户所属组:覆盖式更新 sys_user_group_rel。
* 主归属组按三级降级确定:
* ① mainGroupId 显式传入(须 ∈ groupIds,否则 400)→ 所有用户统一置主;
* ② 未传 → 逐用户沿用旧主组(旧主组 ∈ 新 groupIds);
* ③ 旧主组不在新集合 → 取 groupIds 首项(现状行为兜底)。
* ② 级为行为增强:只要用户没把主组勾掉,调整组后主组不动(旧实现按数组顺序随机重指)。 * * @param userIds 待调整用户ID列表 * @param groupIds 目标组ID列表 * @param mainGroupId 主归属组ID(可选;null 走 ②③ 级降级) */ void changeGroups(List userIds, List groupIds, Integer mainGroupId); /** * 开放接口专用:在学校(组)范围内移动用户。
* 语义同 {@link #changeGroups}(覆盖式把用户组归属替换为目标组,目标设为主组),但额外强制:
* ① 用户当前必须已在该学校子树内(至少一个组归属落在 schoolId 子树);
* ② 目标 groupId 必须也在该学校子树内。
* 任意一条不满足即抛异常,禁止跨校移动。 * * @param userId 待移动用户ID * @param groupId 目标组ID(移动后的主归属组) * @param schoolId 学校组ID(限定移动范围,仅允许同校内移动) */ void moveUserWithinSchool(Integer userId, Integer groupId, Integer schoolId); /** * 调整用户在某组内的排序号(sort_no)。
* sortNo 为 null 时清除排序号,列表回退到 user_id 升序兜底。 * * @param userId 用户ID * @param groupId 组ID(排序号按组维度,必须落到具体组) * @param sortNo 排序号(1~999),可空表示清除 */ void changeSortNo(Integer userId, Integer groupId, Integer sortNo); /** * 批量导入用户:按模板解析 Excel,逐行校验并新增。 * 失败行继续处理,返回成功/失败统计及失败原因。 * * @param file Excel 文件 * @return 导入结果 */ BatchImportResult batchImport(MultipartFile file); /** * 下载用户批量导入模板(真实 .xlsx):平铺指定组及其所有子孙组,仅填充组名(路径)与组id。 * * @param groupId 当前组ID(可空,空则取组织树根) * @param response 直接写入 Excel 流 */ void downloadImportTemplate(Integer groupId, HttpServletResponse response) throws IOException; /** * 导出组下所有用户(含子组)为 Excel:与导入模板同构,末尾追加「用户id」列。
* 组名 / 组id 取每个用户实际所属的主组(isMain=1);密码为密文不可逆,留空。 * * @param groupId 当前组ID(可空,空则取组织树根) * @param withSub 是否包含子组:1=含所有子孙组;其它=仅当前组 * @param name 关键字(可选) * @param response 直接写入 Excel 流 */ void exportUsers(Integer groupId, Integer withSub, String name, HttpServletResponse response) throws IOException; /** * 查询用户的业务角色身份列表(oapi 开放接口专用)。 * * @param userId 用户ID * @return 角色身份列表(roleId/roleName + 主归属学校组上下文) */ List findUserRoles(Integer userId); /** * 修改用户密码:先校验旧密码,再写入新密码(BCrypt)。
* oapi 开放接口专用;不加操作日志(避免明文密码入日志)。 * * @param userId 用户ID * @param oldPassword 旧密码(须与库中 BCrypt 密文匹配) * @param newPassword 新密码(弱密码规则校验后加密写入) */ void changePassword(Integer userId, String oldPassword, String newPassword); /** * 修改用户头像:校验图片(非空/≤20MB/扩展名白名单)→ 压缩 → 存储至 avatar 子目录 → * 写入 profile_picture。oapi 开放接口专用;返回新头像完整 Web URL。 * * @param userId 用户ID * @param file 头像图片文件(multipart) * @return 新头像完整可访问 URL */ String changeProfilePicture(Integer userId, MultipartFile file); /** * 开放接口专用:设置用户在指定组上的状态(转出/恢复)。
* 仅作关系标记,不影响成员查询、组织链与下游推送;同值重复调用幂等。
* 边界:groupId 必须属于 schoolId 子树且不能等于 schoolId(禁止跨校/学校根组操作)。 * * @param userId 用户ID * @param groupId 组ID * @param schoolId 学校组ID(限定范围) * @param state 目标状态:0=恢复正常,1=转出 */ void changeUserGroupState(Integer userId, Integer groupId, Integer schoolId, Integer state); /** * 开放接口专用:查询用户在指定组上的状态(0=正常,1=转出)。
* 存量数据 status 为 NULL 视为正常返回 0。 * * @param userId 用户ID * @param groupId 组ID * @param schoolId 学校组ID(限定范围) * @return 状态值 0/1 */ Integer getUserGroupState(Integer userId, Integer groupId, Integer schoolId); /** * 开放接口专用:跨校转入(转出后迁移到其他学校)。
* 前置:用户已在源学校(fromSchoolId 子树内)存在至少一条「转出」状态的组关系; * 操作成功后:删除源学校范围内全部组关系(其他学校保留),在目标学校建立目标组关系 * (主归属、状态恢复正常,转出流程结束),并推送编辑用户订阅(UPDATE_USER)。
* 边界:fromSchoolId 与目标 schoolId 均须为学校组且不相同;目标组须属于目标学校子树且不等于学校根组; * 用户已在目标学校存在组关系时拒绝(转入语义为进入全新学校)。 * * @param userId 待迁移用户ID * @param fromSchoolId 原学校组ID(源学校) * @param toSchoolId 目标学校组ID * @param groupId 目标组ID(目标学校内的挂载组,迁移后的主归属) */ void transferUserToSchool(Integer userId, Integer fromSchoolId, Integer toSchoolId, Integer groupId); }