package com.etotem.cfc.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.etotem.cfc.config.JwtConfig; import com.etotem.cfc.dto.*; import com.etotem.cfc.dto.RegisterWithIdCardDTO; import com.etotem.cfc.dto.RegisterWithInviteCodeDTO; import com.etotem.cfc.entity.FamilyMember; import com.etotem.cfc.entity.Family; import com.etotem.cfc.entity.User; import com.etotem.cfc.mapper.FamilyMemberMapper; import com.etotem.cfc.mapper.FamilyMapper; import com.etotem.cfc.mapper.UserMapper; import com.etotem.cfc.service.VerificationCodeService; import com.etotem.cfc.entity.DistributionRelation; import com.etotem.cfc.service.DistributionService; import com.etotem.cfc.common.Result; import com.etotem.cfc.service.api.UserServiceInterface; import lombok.extern.slf4j.Slf4j; import javax.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.util.DigestUtils; import java.nio.charset.StandardCharsets; import java.util.*; import com.etotem.cfc.entity.UserProfileHistory; import com.etotem.cfc.mapper.UserProfileHistoryMapper; import com.etotem.cfc.service.FamilyJoinRequestService; @Slf4j @Service public class UserService implements UserServiceInterface { @Resource private UserMapper userMapper; @Resource private FamilyMapper familyMapper; @Resource private FamilyMemberMapper familyMemberMapper; @Resource private JwtConfig jwtConfig; @Resource private VerificationCodeService verificationCodeService; @Resource private WechatService wechatService; @Resource private DanshopSyncService danshopSyncService; @Resource private DistributionService distributionService; @Resource private OnboardingService onboardingService; @Resource private FamilyInvitationService familyInvitationService; @Resource private UserProfileHistoryMapper userProfileHistoryMapper; @Resource private FamilyMemberService familyMemberService; @Resource private FamilyJoinRequestService familyJoinRequestService; public LoginResultDTO wechatLogin(WechatLoginDTO dto) { // 1. 通过code获取openid Map sessionData = wechatService.code2Session(dto.getCode()); String openid = sessionData.get("openid"); String unionid = sessionData.get("unionid"); if (openid == null || openid.isEmpty()) { throw new RuntimeException("获取openid失败,请重试"); } // 2. 如果提供了phoneCode,获取真实手机号 String phone = null; if (dto.getPhoneCode() != null && !dto.getPhoneCode().isEmpty()) { phone = wechatService.getPhoneNumber(dto.getPhoneCode()); } // 3. 先通过openid查找用户 User user = userMapper.selectOne(new LambdaQueryWrapper() .eq(User::getOpenid, openid)); boolean isNewUser = false; if (user == null) { // 4. openid不存在,如果有手机号则通过手机号查找 if (phone != null) { user = userMapper.selectOne(new LambdaQueryWrapper() .eq(User::getPhone, phone)); } if (user == null) { // 5. 完全查不到 → 全新用户,自动创建 isNewUser = true; user = new User(); user.setOpenid(openid); user.setUnionid(unionid != null ? unionid : ""); user.setRole("parent"); user.setNickname(dto.getNickname() != null ? dto.getNickname() : (phone != null ? "用户" + phone.substring(7) : "微信用户")); user.setAvatar(dto.getAvatar()); user.setPhone(phone); user.setPassword(""); user.setSessionKey(sessionData.get("session_key")); user.setCreatedAt(new Date()); user.setUpdatedAt(new Date()); createUserAndFamily(user); try { onboardingService.initForUser(user.getId()); onboardingService.completeTask(user.getId(), "REGISTER"); } catch (Exception e) { /* onboarding init failure should not block login */ } } else { // 6. 手机号已存在但openid不同,绑定openid user.setOpenid(openid); user.setUnionid(unionid != null ? unionid : ""); user.setSessionKey(sessionData.get("session_key")); user.setUpdatedAt(new Date()); userMapper.updateById(user); } } else { // 7. openid已存在,刷新sessionKey(虚拟支付signature签名必需,每次登录更新)+ 更新手机号(如果手机号变了) user.setSessionKey(sessionData.get("session_key")); if (phone != null && !phone.equals(user.getPhone())) { user.setPhone(phone); } user.setUpdatedAt(new Date()); userMapper.updateById(user); } ensureUserHasFamily(user); // 管理员不能登录小程序 if ("admin".equals(user.getRole())) { throw new RuntimeException("该账号不允许登录小程序"); } // 生成token String token = jwtConfig.generateToken(user.getId(), user.getRole()); LoginResultDTO result = new LoginResultDTO(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); result.setRoles(user.getRoles()); result.setFamilyId(user.getFamilyId()); result.setNickname(user.getNickname()); result.setIsNewUser(isNewUser); result.setOpenid(openid); // 新用户需要完善信息(仅当需要选择角色时) result.setNeedsRoleSelect(isNewUser); result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L); populateTeacherFields(result, user); return result; } /** * 手机号验证码登录 */ public LoginResultDTO phoneLogin(PhoneLoginDTO dto) { // 使用手机号作为唯一标识 String phone = dto.getPhone(); // 先通过手机号查找用户(更准确的判断) User user = userMapper.selectOne(new LambdaQueryWrapper() .eq(User::getPhone, phone)); // 管理员不能登录小程序 if (user != null && "admin".equals(user.getRole())) { throw new RuntimeException("该账号不允许登录小程序"); } boolean isNewUser = false; if (user == null) { // 新用户,仅创建用户(家庭待首次使用家庭功能时创建) isNewUser = true; user = new User(); user.setOpenid("phone:" + phone); user.setUnionid(""); // 默认角色为家长,可以被覆盖 user.setRole(dto.getRole() != null ? dto.getRole() : "parent"); user.setNickname(dto.getNickname() != null ? dto.getNickname() : "用户" + phone.substring(7)); user.setAvatar(dto.getAvatar()); user.setPhone(phone); user.setBirthday(dto.getBirthday()); user.setGender(dto.getGender()); user.setPassword(""); user.setCreatedAt(new Date()); user.setUpdatedAt(new Date()); createUserAndFamily(user); try { onboardingService.initForUser(user.getId()); onboardingService.completeTask(user.getId(), "REGISTER"); } catch (Exception e) { /* onboarding init failure should not block login */ } } else { // 老用户,更新信息(如果提供了) if (dto.getNickname() != null && !dto.getNickname().isEmpty()) { user.setNickname(dto.getNickname()); } if (dto.getAvatar() != null && !dto.getAvatar().isEmpty()) { user.setAvatar(dto.getAvatar()); } user.setUpdatedAt(new Date()); userMapper.updateById(user); } ensureUserHasFamily(user); // 生成token String token = jwtConfig.generateToken(user.getId(), user.getRole()); LoginResultDTO result = new LoginResultDTO(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); result.setRoles(user.getRoles()); result.setFamilyId(user.getFamilyId()); result.setNickname(user.getNickname()); result.setIsNewUser(isNewUser); result.setNeedsRoleSelect(isNewUser); result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L); populateTeacherFields(result, user); return result; } /** * 微信手机号登录(委托给wechatLogin处理) */ public LoginResultDTO wechatPhoneLogin(WechatPhoneLoginDTO dto) { WechatLoginDTO mergedDto = new WechatLoginDTO(); mergedDto.setCode(dto.getCode()); mergedDto.setPhoneCode(dto.getPhoneCode()); mergedDto.setNickname(dto.getNickname()); mergedDto.setAvatar(dto.getAvatar()); return wechatLogin(mergedDto); } /** * 微信openid静默登录(用户再次打开小程序时自动登录) */ public LoginResultDTO silentLogin(String code) { // 1. 通过code获取openid Map sessionData = wechatService.code2Session(code); String openid = sessionData.get("openid"); String unionid = sessionData.get("unionid"); // 2. 查找用户 User user = userMapper.selectOne(new LambdaQueryWrapper() .eq(User::getOpenid, openid)); if (user == null) { throw new RuntimeException("用户不存在,请先登录"); } // 管理员不能登录小程序 if ("admin".equals(user.getRole())) { throw new RuntimeException("该账号不允许登录小程序"); } // 持久化最新 sessionKey(虚拟支付 signature 签名用) user.setSessionKey(sessionData.get("session_key")); user.setUpdatedAt(new Date()); userMapper.updateById(user); ensureUserHasFamily(user); // 4. 生成token String token = jwtConfig.generateToken(user.getId(), user.getRole()); LoginResultDTO result = new LoginResultDTO(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); result.setRoles(user.getRoles()); result.setFamilyId(user.getFamilyId()); result.setNickname(user.getNickname()); result.setIsNewUser(false); result.setOpenid(openid); result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L); populateTeacherFields(result, user); return result; } /** * 通过openid自动登录(前端已缓存openid时调用) */ public LoginResultDTO autoLoginByOpenid(String openid) { User user = userMapper.selectOne(new LambdaQueryWrapper() .eq(User::getOpenid, openid)); if (user == null) { return null; } // 管理员不能登录小程序 if ("admin".equals(user.getRole())) { return null; } ensureUserHasFamily(user); String token = jwtConfig.generateToken(user.getId(), user.getRole()); LoginResultDTO result = new LoginResultDTO(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); result.setFamilyId(user.getFamilyId()); result.setNickname(user.getNickname()); result.setIsNewUser(false); result.setOpenid(openid); result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L); populateTeacherFields(result, user); return result; } public boolean setPassword(Long userId, String password) { User user = userMapper.selectById(userId); if (user == null) { return false; } user.setPassword(encryptPassword(password)); user.setUpdatedAt(new Date()); userMapper.updateById(user); return true; } public boolean verifyPassword(Long userId, String password) { User user = userMapper.selectById(userId); if (user == null || user.getPassword() == null || user.getPassword().isEmpty()) { return false; } if (!verifyPasswordHash(password, user.getPassword())) { return false; } // 旧 MD5 哈希自动升级为 BCrypt if (!user.getPassword().startsWith("$2")) { user.setPassword(BC_PASSWORD_ENCODER.encode(password)); user.setUpdatedAt(new Date()); userMapper.updateById(user); log.info("User {} 密码已从 MD5 升级为 BCrypt", userId); } return true; } public User getUserInfo(Long userId) { return userMapper.selectById(userId); } /** 回写字段白名单(user 目标) */ private static final String[] USER_BACKFILL_WHITELIST = {"phone", "gender", "birthday", "birthHour", "dietPreferences", "address"}; /** 回写字段白名单(member 目标,family_members 表无 dietPreferences/address 列) */ private static final String[] MEMBER_BACKFILL_WHITELIST = {"phone", "gender", "birthday", "birthHour"}; /** * 就地补充信息回写(字段白名单校验 + 目标归属校验) * @return Map: { updatedFields: List, profile: Object(最新完整信息) } */ public Map backfillProfile(Long userId, ProfileBackfillDTO dto) { if (dto == null || dto.getFields() == null || dto.getFields().isEmpty()) { throw new RuntimeException("回写字段不能为空"); } User me = userMapper.selectById(userId); if (me == null) { throw new RuntimeException("用户不存在"); } List updatedFields = new ArrayList<>(); Object profile; if ("user".equals(dto.getTargetType())) { // 仅允许回写自己 if (dto.getTargetId() != null && !dto.getTargetId().equals(userId)) { throw new RuntimeException("仅能回写本人的个人信息"); } for (String field : USER_BACKFILL_WHITELIST) { String value = dto.getFields().get(field); if (value == null || value.isEmpty()) continue; applyUserBackfillField(me, field, value.trim()); updatedFields.add(field); } me.setUpdatedAt(new Date()); userMapper.updateById(me); profile = me; } else if ("member".equals(dto.getTargetType())) { Long memberId = dto.getTargetId(); if (memberId == null) { throw new RuntimeException("回写家庭成员时 targetId 不能为空"); } FamilyMember member = familyMemberMapper.selectById(memberId); if (member == null) { throw new RuntimeException("家庭成员不存在"); } // 归属校验:成员必须属于当前用户家庭 Long myFamilyId = me.getFamilyId(); if (myFamilyId == null || !myFamilyId.equals(member.getFamilyId())) { throw new RuntimeException("该成员不属于您的家庭"); } for (String field : MEMBER_BACKFILL_WHITELIST) { String value = dto.getFields().get(field); if (value == null || value.isEmpty()) continue; applyMemberBackfillField(member, field, value.trim()); updatedFields.add(field); } familyMemberMapper.updateById(member); profile = member; } else { throw new RuntimeException("targetType 仅支持 user/member"); } Map result = new HashMap<>(); result.put("updatedFields", updatedFields); result.put("profile", profile); return result; } private void applyUserBackfillField(User user, String field, String value) { switch (field) { case "phone": if (!value.matches("^1[3-9]\\d{9}$")) throw new RuntimeException("手机号格式不正确"); user.setPhone(value); break; case "gender": if (!"male".equals(value) && !"female".equals(value)) throw new RuntimeException("性别取值非法"); user.setGender(value); break; case "birthday": try { java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); user.setBirthday(sdf.parse(value)); } catch (Exception e) { throw new RuntimeException("生日格式不正确,应为 yyyy-MM-dd"); } break; case "birthHour": if (!"子丑寅卯辰巳午未申酉戌亥".contains(value)) throw new RuntimeException("出生时辰取值非法"); user.setBirthHour(value); break; case "dietPreferences": user.setDietPreferences(value); break; case "address": user.setAddress(value); break; default: break; } } private void applyMemberBackfillField(FamilyMember member, String field, String value) { switch (field) { case "phone": if (!value.matches("^1[3-9]\\d{9}$")) throw new RuntimeException("手机号格式不正确"); member.setPhone(value); break; case "gender": if (!"male".equals(value) && !"female".equals(value)) throw new RuntimeException("性别取值非法"); member.setGender(value); break; case "birthday": try { java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); member.setBirthday(sdf.parse(value)); } catch (Exception e) { throw new RuntimeException("生日格式不正确,应为 yyyy-MM-dd"); } break; case "birthHour": if (!"子丑寅卯辰巳午未申酉戌亥".contains(value)) throw new RuntimeException("出生时辰取值非法"); member.setBirthHour(value); break; default: break; } } /** * 检查手机号是否已注册 * @param phone 手机号 * @return true=已注册,false=未注册 */ public boolean isPhoneRegistered(String phone) { if (phone == null || phone.isEmpty()) { return false; } User user = userMapper.selectOne(new LambdaQueryWrapper() .eq(User::getPhone, phone)); return user != null; } /** * 直接注册(测试环境:不验证验证码) */ public LoginResultDTO directRegister(DirectRegisterDTO dto) { // 创建用户(家庭待首次使用家庭功能时创建,或通过邀请码加入) User user = new User(); user.setOpenid("phone:" + dto.getPhone()); user.setUnionid(""); user.setRole(dto.getRole() != null ? dto.getRole() : "parent"); user.setNickname(dto.getNickname() != null ? dto.getNickname() : "用户" + dto.getPhone().substring(7)); user.setAvatar(dto.getAvatar()); user.setPhone(dto.getPhone()); user.setBirthday(dto.getBirthday()); user.setGender(dto.getGender()); user.setPassword(""); user.setCreatedAt(new Date()); user.setUpdatedAt(new Date()); createUserAndFamily(user); // 如果有邀请码,提交加入家庭请求(需管理员审批) if (dto.getInviteCode() != null && !dto.getInviteCode().isEmpty()) { try { familyJoinRequestService.createJoinRequest(user.getId(), dto.getInviteCode()); } catch (Exception e) { // 忽略邀请码错误 } } // 生成token String token = jwtConfig.generateToken(user.getId(), user.getRole()); LoginResultDTO result = new LoginResultDTO(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); result.setRoles(user.getRoles()); result.setFamilyId(user.getFamilyId()); result.setNickname(user.getNickname()); result.setIsNewUser(true); result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L); populateTeacherFields(result, user); return result; } @Deprecated public String switchMode(Long userId, String mode) { log.warn("已废弃的 switchMode 被调用, userId={}, mode={} - 应使用 /api/family/member/switch", userId, mode); User user = userMapper.selectById(userId); if (user == null) { return null; } // 管理员角色不允许切换 if ("admin".equals(user.getRole())) { throw new RuntimeException("管理员角色不允许切换"); } // 验证用户是否拥有该角色 if (!hasRole(user, mode)) { return null; } user.setRole(mode); user.setUpdatedAt(new Date()); userMapper.updateById(user); return mode; } /** * 切换用户当前角色 (支持 parent/child/teacher) * 用户需要先拥有该角色才能切换 */ public String switchRole(Long userId, String targetRole) { User user = userMapper.selectById(userId); if (user == null) { return null; } // 管理员角色不允许切换 if ("admin".equals(user.getRole())) { throw new RuntimeException("管理员角色不允许切换"); } if (!hasRole(user, targetRole)) { throw new RuntimeException("您没有该角色权限"); } if (!isValidRole(targetRole)) { return null; } user.setRole(targetRole); user.setUpdatedAt(new Date()); userMapper.updateById(user); return targetRole; } public LoginResultDTO switchRoleForLogin(Long userId, String role) { User user = userMapper.selectById(userId); if (user == null) { throw new RuntimeException("用户不存在"); } // 管理员角色不允许切换 if ("admin".equals(user.getRole())) { throw new RuntimeException("管理员角色不允许切换"); } if ("teacher".equals(role) && !hasRole(user, "teacher")) { throw new RuntimeException("您不是成长规划师,无法切换为规划师模式"); } user.setRole(role); user.setUpdatedAt(new Date()); userMapper.updateById(user); String token = jwtConfig.generateToken(user.getId(), user.getRole()); LoginResultDTO result = new LoginResultDTO(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); result.setRoles(user.getRoles()); result.setFamilyId(user.getFamilyId()); result.setNickname(user.getNickname()); result.setIsNewUser(false); result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L); populateTeacherFields(result, user); return result; } /** * 获取用户所有角色列表 */ public List getUserRoles(Long userId) { User user = userMapper.selectById(userId); if (user == null) { return new ArrayList<>(); } return parseRoles(user.getRoles()); } /** * 检查用户是否拥有指定角色 */ public boolean hasRole(Long userId, String role) { User user = userMapper.selectById(userId); if (user == null) { return false; } return hasRole(user, role); } /** * 添加角色到用户 (用于用户注册成为老师时) */ public boolean addRole(Long userId, String newRole) { User user = userMapper.selectById(userId); if (user == null || !isValidRole(newRole)) { return false; } // 如果用户还没有roles字段,设置默认值 if (user.getRoles() == null || user.getRoles().isEmpty()) { user.setRoles(user.getRole()); // 当前角色作为第一个角色 } // 检查是否已拥有该角色 if (hasRole(user, newRole)) { return true; // 已有该角色 } // 添加新角色 List roleList = parseRoles(user.getRoles()); roleList.add(newRole); user.setRoles(joinRoles(roleList)); user.setUpdatedAt(new Date()); userMapper.updateById(user); return true; } /** * 检查用户是否拥有指定角色 (内部方法) */ private boolean hasRole(User user, String role) { List roles = parseRoles(user.getRoles()); return roles.contains(role); } /** * 解析角色字符串为列表 */ public List parseRoles(String rolesStr) { List result = new ArrayList<>(); if (rolesStr == null || rolesStr.isEmpty()) { return result; } // 支持JSON数组格式或逗号分隔格式 if (rolesStr.startsWith("[")) { // JSON格式解析 rolesStr = rolesStr.replace("[", "").replace("]", "") .replace("\"", "").replace(" ", ""); String[] arr = rolesStr.split(","); for (String r : arr) { if (!r.isEmpty()) { result.add(r.trim()); } } } else { // 逗号分隔格式 String[] arr = rolesStr.split(","); for (String r : arr) { if (!r.trim().isEmpty()) { result.add(r.trim()); } } } return result; } /** * 将角色列表转换为字符串 */ private String joinRoles(List roles) { return String.join(",", roles); } /** * 检查角色是否有效 */ private boolean isValidRole(String role) { return "parent".equals(role) || "child".equals(role) || "teacher".equals(role) || "supplier_admin".equals(role); } /** * 家长切换到指定孩子身份(视图切换,不修改用户的实际角色) * 家长可以在家长模式下查看孩子的任务和积分 */ @Deprecated public Long switchToChild(Long userId, Long memberId) { log.warn("已废弃的 switchToChild 被调用, userId={}, memberId={} - 应使用 /api/family/member/switch", userId, memberId); User user = userMapper.selectById(userId); if (user == null) { return null; } FamilyMember child = familyMemberMapper.selectById(memberId); if (child == null || !child.getFamilyId().equals(user.getFamilyId())) { return null; } // 注意:不修改用户的 role 字段,只是返回 memberId 让前端切换视图 // 前端应该使用 currentRole 或 viewMode 来控制显示模式 return memberId; } /** * 从孩子视图切换回家长视图(旧版,无密码校验) * 保留以兼容旧 JWT token */ public boolean switchBackToParent(Long userId) { log.warn("无密码的 switchBackToParent 被调用, userId={} - 建议前端使用 /switch-back-verify", userId); User user = userMapper.selectById(userId); if (user == null) { return false; } return true; } /** * 带密码校验的切换回家长视图 * @param userId 用户ID * @param password 密码 * @return 校验并切换成功返回 true */ public boolean verifySwitchBack(Long userId, String password) { if (password == null || password.isEmpty()) { return false; } if (!verifyPassword(userId, password)) { return false; } return true; } /** * 获取对家庭成员可见的成员列表(家长+孩子) */ public List getVisibleMembers(Long userId) { User user = userMapper.selectById(userId); if (user == null) { return new ArrayList<>(); } if (user.getFamilyId() == null) { // 兜底:无家庭时自动创建,避免"暂无家庭"空态 ensureUserHasFamily(user); } Long familyId = user.getFamilyId(); List result = new ArrayList<>(); Family family = familyMapper.selectById(familyId); Long creatorId = (family != null) ? family.getCreatorId() : null; boolean isAdmin = creatorId != null && creatorId.equals(userId); // 家长:show_to_family=1 的同家庭家长(自己始终可见) List parents = userMapper.selectList(new LambdaQueryWrapper() .eq(User::getFamilyId, familyId) .eq(User::getRole, "parent")); for (User p : parents) { if (p.getId().equals(userId) || (p.getShowToFamily() != null && p.getShowToFamily() == 1)) { FamilyMemberVO vo = new FamilyMemberVO(); vo.setId(p.getId()); vo.setSource("parent"); vo.setUserId(p.getId()); vo.setNickname(p.getNickname()); vo.setAvatar(p.getAvatar()); vo.setPhone(p.getPhone()); vo.setGender(p.getGender()); vo.setEffectiveRole("parent"); vo.setMemberType("parent"); vo.setRoleLabel(p.getFamilyRole() != null ? p.getFamilyRole() : "家长"); vo.setTotalPoints(p.getTotalPoints()); vo.setShowToFamily(p.getShowToFamily() != null ? p.getShowToFamily() : 1); vo.setIsAdmin(isAdmin); vo.setIsSelf(p.getId().equals(userId)); result.add(vo); } } // 孩子/其他成员:复用 FamilyMemberService.listMembers 的完整字段计算(关系标签/辈分/配偶/信任分等), // 再按 show_to_family 过滤(自己始终可见) List members = familyMemberService.listMembers(userId); for (FamilyMemberVO vo : members) { boolean isSelf = vo.getUserId() != null && vo.getUserId().equals(userId); if (isSelf || vo.getShowToFamily() == null || vo.getShowToFamily() == 1) { vo.setIsSelf(isSelf); result.add(vo); } } return result; } /** * 更新成员可见性 */ public boolean updateVisibility(Long userId, Long memberId, String memberType, Integer showToFamily) { User user = userMapper.selectById(userId); if (user == null) { return false; } if ("child".equals(memberType)) { FamilyMember child = familyMemberMapper.selectById(memberId); if (child == null || !child.getFamilyId().equals(user.getFamilyId())) { return false; } child.setShowToFamily(showToFamily); child.setUpdatedAt(new Date()); familyMemberMapper.updateById(child); return true; } else if ("parent".equals(memberType)) { // 家长只能改自己的可见性 if (!memberId.equals(userId)) { return false; } user.setShowToFamily(showToFamily); user.setUpdatedAt(new Date()); userMapper.updateById(user); return true; } return false; } public LoginResultDTO registerWithIdCard(RegisterWithIdCardDTO dto) { // 验证验证码 boolean valid = verificationCodeService.verifyCode(dto.getPhone(), dto.getCode(), "login"); if (!valid) { throw new RuntimeException("验证码错误或已过期"); } // 通过身份证查找是否已有注册的用户 User existingUser = userMapper.selectOne(new LambdaQueryWrapper() .eq(User::getIdCard, dto.getIdCard())); User user; if (existingUser != null) { // 管理员不能登录小程序 if ("admin".equals(existingUser.getRole())) { throw new RuntimeException("该账号不允许登录小程序"); } // 如果存在相同身份证的用户,沿用其已有信息 user = existingUser; // 更新用户信息(昵称和头像可以更新) user.setNickname(dto.getNickname()); user.setAvatar(dto.getAvatar()); // 手机号也可以更新(如果需要) user.setOpenid("phone:" + dto.getPhone()); user.setUpdatedAt(new Date()); userMapper.updateById(user); } else { // 新用户,仅创建用户(家庭待首次使用家庭功能时创建) user = new User(); user.setOpenid("phone:" + dto.getPhone()); user.setUnionid(""); user.setRole("parent"); user.setNickname(dto.getNickname()); user.setAvatar(dto.getAvatar()); user.setIdCard(dto.getIdCard()); user.setRealName(dto.getRealName()); user.setPassword(""); // 初始密码为空,需要后续设置 user.setCreatedAt(new Date()); user.setUpdatedAt(new Date()); createUserAndFamily(user); } ensureUserHasFamily(user); // 生成token String token = jwtConfig.generateToken(user.getId(), user.getRole()); LoginResultDTO result = new LoginResultDTO(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); result.setFamilyId(user.getFamilyId()); result.setNickname(user.getNickname()); result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L); populateTeacherFields(result, user); return result; } public ChildInfoDTO createChild(Long userId, CreateChildDTO dto) { User user = userMapper.selectById(userId); if (user == null) { return null; } if (dto.getNickname() == null || dto.getNickname().trim().isEmpty()) { throw new IllegalArgumentException("孩子昵称不能为空"); } if (dto.getAge() == null || dto.getAge() < 1 || dto.getAge() > 18) { throw new IllegalArgumentException("请输入有效的年龄(1-18岁)"); } FamilyMember child = new FamilyMember(); child.setUserId(userId); child.setFamilyId(user.getFamilyId()); child.setNickname(dto.getNickname().trim()); child.setAge(dto.getAge()); child.setBirthday(dto.getBirthday()); child.setGender(dto.getGender()); child.setPhone(dto.getPhone()); child.setIdCard(dto.getIdCard()); // 扣分开关:优先使用传入值,否则根据年龄判断 if (dto.getPenaltyEnabled() != null) { child.setPenaltyEnabled(dto.getPenaltyEnabled()); } else { // 年龄可能为空,默认不扣分 Integer age = dto.getAge(); child.setPenaltyEnabled(age != null && age >= 6 ? 1 : 0); } child.setTheme("default"); child.setTotalPoints(0); child.setStreakDays(0); child.setFocusMaxDaily(3); child.setFocusRemaining(3); child.setFocusResetDate(new Date()); child.setCreatedAt(new Date()); child.setUpdatedAt(new Date()); familyMemberMapper.insert(child); return toChildInfoDTO(child); } public List getChildren(Long userId) { User user = userMapper.selectById(userId); if (user == null) { return null; } List children = familyMemberMapper.selectList(new LambdaQueryWrapper() .eq(FamilyMember::getFamilyId, user.getFamilyId())); return children.stream().map(this::toChildInfoDTO).collect(java.util.stream.Collectors.toList()); } public boolean updateChild(Long userId, Long memberId, UpdateChildDTO dto) { User user = userMapper.selectById(userId); if (user == null) { return false; } FamilyMember child = familyMemberMapper.selectById(memberId); if (child == null || child.getFamilyId() == null || !child.getFamilyId().equals(user.getFamilyId())) { return false; } if (dto.getNickname() != null) { child.setNickname(dto.getNickname()); } if (dto.getAge() != null) { child.setAge(dto.getAge()); } if (dto.getBirthday() != null) { child.setBirthday(dto.getBirthday()); } if (dto.getGender() != null) { child.setGender(dto.getGender()); } if (dto.getPhone() != null) { child.setPhone(dto.getPhone()); } if (dto.getIdCard() != null) { child.setIdCard(dto.getIdCard()); } if (dto.getPenaltyEnabled() != null) { child.setPenaltyEnabled(dto.getPenaltyEnabled()); } if (dto.getTheme() != null) { child.setTheme(dto.getTheme()); } if (dto.getShowToFamily() != null) { child.setShowToFamily(dto.getShowToFamily()); } child.setUpdatedAt(new Date()); familyMemberMapper.updateById(child); return true; } private static final BCryptPasswordEncoder BC_PASSWORD_ENCODER = new BCryptPasswordEncoder(); private String encryptPassword(String password) { return BC_PASSWORD_ENCODER.encode(password); } private boolean verifyPasswordHash(String password, String storedHash) { if (storedHash == null) { return false; } if (storedHash.startsWith("$2a$") || storedHash.startsWith("$2b$")) { return BC_PASSWORD_ENCODER.matches(password, storedHash); } // 旧 MD5 哈希:验证通过后自动升级 String md5 = DigestUtils.md5DigestAsHex(("xzyj_salt_" + password).getBytes(StandardCharsets.UTF_8)); return storedHash.equals(md5); } private String generateInviteCode() { return UUID.randomUUID().toString().substring(0, 8).toUpperCase(); } private ChildInfoDTO toChildInfoDTO(FamilyMember child) { ChildInfoDTO dto = new ChildInfoDTO(); dto.setId(child.getId()); dto.setNickname(child.getNickname()); dto.setAge(child.getAge()); dto.setBirthday(child.getBirthday()); dto.setGender(child.getGender()); dto.setPhone(child.getPhone()); dto.setIdCard(child.getIdCard()); dto.setPenaltyEnabled(child.getPenaltyEnabled()); dto.setTheme(child.getTheme()); dto.setTotalPoints(child.getTotalPoints()); dto.setSystemPoints(child.getSystemPoints()); dto.setStreakDays(child.getStreakDays()); dto.setFocusRemaining(child.getFocusRemaining()); dto.setShowToFamily(child.getShowToFamily()); return dto; } /** * 更新用户信息 */ public boolean updateUserInfo(Long userId, UpdateUserDTO dto) { User user = userMapper.selectById(userId); if (user == null) { return false; } // 管理员角色不允许修改 if ("admin".equals(user.getRole())) { throw new RuntimeException("管理员角色不允许修改"); } if (dto.getNickname() != null) { user.setNickname(dto.getNickname()); } if (dto.getAvatar() != null) { user.setAvatar(dto.getAvatar()); } if (dto.getRealName() != null) { user.setRealName(dto.getRealName()); } // 更新角色 if (dto.getRole() != null) { user.setRole(dto.getRole()); } // 更新性别 if (dto.getGender() != null) { String gender = dto.getGender(); if ("男".equals(gender) || "0".equals(gender)) { gender = "male"; } else if ("女".equals(gender) || "1".equals(gender)) { gender = "female"; } if (!"male".equals(gender) && !"female".equals(gender)) { throw new RuntimeException("性别取值非法,仅支持 male/female"); } user.setGender(gender); } // 更新生日(将字符串转换为Date) if (dto.getBirthday() != null && !dto.getBirthday().isEmpty()) { try { java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); user.setBirthday(sdf.parse(dto.getBirthday())); } catch (Exception e) { // 忽略解析错误 } } // 更新身份证号 if (dto.getIdCard() != null && !dto.getIdCard().isEmpty()) { user.setIdCard(dto.getIdCard()); // 如果没有手动设置生日,尝试从身份证号提取 if (user.getBirthday() == null) { String birthdayFromIdCard = extractBirthdayFromIdCard(dto.getIdCard()); if (birthdayFromIdCard != null) { try { java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); user.setBirthday(sdf.parse(birthdayFromIdCard)); } catch (Exception e) { // 忽略解析错误 } } } } // 更新成长规划师信息 if (dto.getTeacherNo() != null) { user.setTeacherNo(dto.getTeacherNo()); } if (dto.getTeacherPhoto() != null) { user.setTeacherPhoto(dto.getTeacherPhoto()); } if (dto.getCertificateImage() != null) { user.setCertificateImage(dto.getCertificateImage()); } // 更新个人信息补充字段 if (dto.getEthnicity() != null) { user.setEthnicity(dto.getEthnicity()); } if (dto.getBloodType() != null) { user.setBloodType(dto.getBloodType()); } if (dto.getHighestEducation() != null) { user.setHighestEducation(dto.getHighestEducation()); } if (dto.getMaritalStatus() != null) { user.setMaritalStatus(dto.getMaritalStatus()); } if (dto.getFamilyRole() != null) { user.setFamilyRole(dto.getFamilyRole()); } // 更新AI助手形象 if (dto.getMascot() != null) { user.setMascot(dto.getMascot()); } // 更新兴趣爱好 if (dto.getHobbies() != null) { String oldHobbies = user.getHobbies(); user.setHobbies(dto.getHobbies()); recordProfileChange(userId, "hobbies", oldHobbies, dto.getHobbies(), "user_edit"); } // 更新饮食偏好 if (dto.getDietPreferences() != null) { String oldDiet = user.getDietPreferences(); user.setDietPreferences(dto.getDietPreferences()); recordProfileChange(userId, "dietPreferences", oldDiet, dto.getDietPreferences(), "user_edit"); } // 更新地址 if (dto.getAddress() != null) { user.setAddress(dto.getAddress()); } userMapper.updateById(user); return true; } /** * 记录用户信息变更历史(仅当新旧值不同时写入) */ private void recordProfileChange(Long userId, String fieldName, String oldValue, String newValue, String source) { if (oldValue == null && newValue == null) return; if (oldValue != null && oldValue.equals(newValue)) return; if (newValue != null && newValue.equals(oldValue)) return; UserProfileHistory history = new UserProfileHistory(); history.setUserId(userId); history.setFieldName(fieldName); history.setOldValue(oldValue); history.setNewValue(newValue); history.setSource(source); history.setChangedAt(new Date()); history.setCreatedAt(new Date()); userProfileHistoryMapper.insert(history); } /** * 从身份证号提取生日 * 15位:1985年07月08日 -> 1985-07-08 * 18位:1985年07月08日 -> 1985-07-08 */ private String extractBirthdayFromIdCard(String idCard) { if (idCard == null || idCard.isEmpty()) { return null; } try { if (idCard.length() == 15) { // 15位身份证:出生日期在第6-12位 String year = "19" + idCard.substring(6, 8); String month = idCard.substring(8, 10); String day = idCard.substring(10, 12); return String.format("%s-%s-%s", year, month, day); } else if (idCard.length() == 18) { // 18位身份证:出生日期在第6-14位 String year = idCard.substring(6, 10); String month = idCard.substring(10, 12); String day = idCard.substring(12, 14); return String.format("%s-%s-%s", year, month, day); } } catch (Exception e) { // 忽略解析异常 } return null; } /** * 通过邀请码加入家庭 */ public boolean joinFamily(Long userId, String inviteCode) { if (inviteCode == null || inviteCode.isEmpty()) { throw new RuntimeException("邀请码不能为空"); } // 查找家庭 Family family = familyMapper.selectOne(new LambdaQueryWrapper() .eq(Family::getInviteCode, inviteCode)); if (family == null) { throw new RuntimeException("邀请码无效"); } // 更新用户的家庭ID User user = userMapper.selectById(userId); if (user == null) { throw new RuntimeException("用户不存在"); } if (user.getFamilyId() != null && !user.getFamilyId().equals(family.getId())) { familyInvitationService.mergeIntoFamily(userId, family.getId()); } else if (user.getFamilyId() == null) { user.setFamilyId(family.getId()); user.setUpdatedAt(new Date()); userMapper.updateById(user); } // 如果家庭绑定了成长规划师,自动将家庭ID添加到成长规划师的 teacherFamilyIds if (family.getTeacherId() != null) { User teacher = userMapper.selectById(family.getTeacherId()); if (teacher != null && "teacher".equals(teacher.getRole())) { String currentIds = teacher.getTeacherFamilyIds(); String newFamilyId = String.valueOf(family.getId()); if (currentIds == null || !currentIds.contains(newFamilyId)) { String updatedIds = currentIds == null ? newFamilyId : currentIds + "," + newFamilyId; teacher.setTeacherFamilyIds(updatedIds); teacher.setUpdatedAt(new Date()); userMapper.updateById(teacher); } } } return true; } /** * 创建用户并自动创建家庭(注册时用) * @return 创建后的 user(含 familyId 和 isFamilyAdmin) */ private User createUserAndFamily(User user) { // 1. 插入 user userMapper.insert(user); // 2. 创建家庭 Family family = new Family(); String familyName = (user.getNickname() != null && !user.getNickname().isEmpty()) ? user.getNickname() + "的家庭" : "我的家庭"; family.setName(familyName); family.setInviteCode(generateInviteCode()); family.setCreatorId(user.getId()); family.setCreatedAt(new Date()); family.setUpdatedAt(new Date()); familyMapper.insert(family); // 3. 设置 user 的家庭关联 user.setFamilyId(family.getId()); user.setIsFamilyAdmin(true); user.setUpdatedAt(new Date()); userMapper.updateById(user); log.info("已为用户 {} 自动创建家庭 {} ({})", user.getId(), family.getId(), familyName); return user; } /** * 确保用户有家庭(安全兜底:注册/登录时如果 familyId 为空则自动创建) */ private void ensureUserHasFamily(User user) { if (user.getFamilyId() != null && user.getFamilyId() != 0L) { return; // 已有家庭 } Family family = new Family(); String familyName = (user.getNickname() != null && !user.getNickname().isEmpty()) ? user.getNickname() + "的家庭" : "我的家庭"; family.setName(familyName); family.setInviteCode(generateInviteCode()); family.setCreatorId(user.getId()); family.setCreatedAt(new Date()); family.setUpdatedAt(new Date()); familyMapper.insert(family); user.setFamilyId(family.getId()); user.setIsFamilyAdmin(true); user.setUpdatedAt(new Date()); userMapper.updateById(user); log.info("安全兜底:为用户 {} 自动创建家庭 {}", user.getId(), family.getId()); } /** * 确保用户有家庭(公开兜底入口,供查询家庭类接口在 familyId 为空时调用) * @return 用户的 familyId;用户不存在时返回 null */ public Long ensureUserFamily(Long userId) { User user = userMapper.selectById(userId); if (user == null) { return null; } ensureUserHasFamily(user); return user.getFamilyId(); } /** * 为用户创建新家庭 */ public Long createFamilyForUser(Long userId, String familyName) { User user = userMapper.selectById(userId); if (user == null) { throw new RuntimeException("用户不存在"); } // 创建新家庭 Family family = new Family(); family.setName(familyName); family.setInviteCode(generateInviteCode()); family.setCreatedAt(new Date()); family.setUpdatedAt(new Date()); familyMapper.insert(family); // 更新用户的家庭ID user.setFamilyId(family.getId()); user.setUpdatedAt(new Date()); userMapper.updateById(user); return family.getId(); } /** * 使用邀请码注册并加入家庭 */ public LoginResultDTO registerWithInviteCode(RegisterWithInviteCodeDTO dto) { String phone = dto.getPhone(); String inviteCode = dto.getInviteCode(); String role = dto.getRole() != null ? dto.getRole() : "parent"; String nickname = dto.getNickname() != null ? dto.getNickname() : "用户" + phone.substring(7); Result distResult = distributionService.findByInviteCode(inviteCode); if (distResult.getCode() == 200 && distResult.getData() != null) { User user = new User(); user.setOpenid("phone:" + phone); user.setUnionid(""); user.setRole(role); user.setNickname(nickname); user.setPhone(phone); user.setPassword(""); user.setCreatedAt(new Date()); user.setUpdatedAt(new Date()); createUserAndFamily(user); distributionService.registerByInviteCode(inviteCode, user.getId()); ensureUserHasFamily(user); String token = jwtConfig.generateToken(user.getId(), user.getRole()); LoginResultDTO result = new LoginResultDTO(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); result.setRoles(user.getRoles()); result.setFamilyId(user.getFamilyId()); result.setNickname(user.getNickname()); result.setIsNewUser(true); result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L); populateTeacherFields(result, user); return result; } Family family = familyMapper.selectOne(new LambdaQueryWrapper() .eq(Family::getInviteCode, inviteCode)); if (family == null) { throw new RuntimeException("邀请码无效"); } User user; boolean isNewUser = false; // 检查手机号是否已注册 User existingUser = userMapper.selectOne(new LambdaQueryWrapper() .eq(User::getPhone, phone)); if (existingUser != null) { // 管理员不能登录小程序 if ("admin".equals(existingUser.getRole())) { throw new RuntimeException("该账号不允许登录小程序"); } user = existingUser; ensureUserHasFamily(user); try { familyJoinRequestService.createJoinRequest(user.getId(), inviteCode); log.info("已为用户 {} 提交加入家庭 {} 的请求", user.getId(), family.getId()); } catch (Exception e) { log.warn("提交加入家庭请求失败: {}", e.getMessage()); } } else { isNewUser = true; user = new User(); user.setOpenid("phone:" + phone); user.setUnionid(""); user.setRole(role); user.setNickname(nickname); user.setPhone(phone); user.setPassword(""); user.setCreatedAt(new Date()); user.setUpdatedAt(new Date()); createUserAndFamily(user); try { familyJoinRequestService.createJoinRequest(user.getId(), inviteCode); log.info("已为用户 {} 提交加入家庭 {} 的请求", user.getId(), family.getId()); } catch (Exception e) { log.warn("提交加入家庭请求失败: {}", e.getMessage()); } } ensureUserHasFamily(user); // 生成token String token = jwtConfig.generateToken(user.getId(), user.getRole()); LoginResultDTO result = new LoginResultDTO(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); result.setRoles(user.getRoles()); result.setFamilyId(user.getFamilyId()); result.setNickname(user.getNickname()); result.setIsNewUser(isNewUser); result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L); populateTeacherFields(result, user); return result; } /** * 重置用户密码(管理员功能) * @param userId 用户ID * @param newPassword 新密码 * @return 是否成功 */ public boolean resetPassword(Long userId, String newPassword) { if (userId == null || newPassword == null || newPassword.length() < 6) { return false; } User user = userMapper.selectById(userId); if (user == null) { return false; } user.setPassword(encryptPassword(newPassword)); user.setUpdatedAt(new Date()); return userMapper.updateById(user) > 0; } /** * 设置用户的AI助手形象(吉祥物) * @param userId 用户ID * @param mascotCode 吉祥物代码: "xibao"/"fubao"/null */ public void setMascot(Long userId, String mascotCode) { User user = userMapper.selectById(userId); if (user == null) { throw new RuntimeException("用户不存在"); } user.setMascot(mascotCode); user.setUpdatedAt(new Date()); userMapper.updateById(user); } // ==================== Family Member API (new) ==================== /** * 获取家庭成员列表(孩子) */ public List getFamilyMemberren(Long userId) { User user = userMapper.selectById(userId); if (user == null || user.getFamilyId() == null) { return new ArrayList<>(); } List members = familyMemberMapper.selectList( new LambdaQueryWrapper() .eq(FamilyMember::getFamilyId, user.getFamilyId())); return members.stream().map(this::toFamilyMemberInfoDTO).collect(java.util.stream.Collectors.toList()); } /** * 创建家庭成员 */ public FamilyMemberInfoDTO createFamilyMember(Long userId, CreateFamilyMemberDTO dto) { User user = userMapper.selectById(userId); if (user == null) { throw new RuntimeException("用户不存在"); } FamilyMember member = new FamilyMember(); member.setUserId(userId); member.setFamilyId(user.getFamilyId()); member.setNickname(dto.getNickname()); member.setPhone(dto.getPhone()); member.setAvatar(dto.getAvatar()); member.setGender(dto.getGender()); try { if (dto.getBirthday() != null && !dto.getBirthday().isEmpty()) { java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); member.setBirthday(sdf.parse(dto.getBirthday())); } } catch (Exception e) { // ignore parse error } if (dto.getAge() != null) { member.setAge(dto.getAge()); } member.setTotalPoints(0); member.setCreatedAt(new Date()); member.setUpdatedAt(new Date()); familyMemberMapper.insert(member); return toFamilyMemberInfoDTO(member); } /** * 更新家庭成员信息 */ public boolean updateFamilyMember(Long userId, Long memberId, UpdateFamilyMemberDTO dto) { User user = userMapper.selectById(userId); if (user == null || user.getFamilyId() == null) { return false; } FamilyMember member = familyMemberMapper.selectById(memberId); if (member == null || !member.getFamilyId().equals(user.getFamilyId())) { return false; } if (dto.getNickname() != null) { member.setNickname(dto.getNickname()); } if (dto.getPhone() != null) { member.setPhone(dto.getPhone()); } if (dto.getAvatar() != null) { member.setAvatar(dto.getAvatar()); } if (dto.getGender() != null) { member.setGender(dto.getGender()); } try { if (dto.getBirthday() != null && !dto.getBirthday().isEmpty()) { java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); member.setBirthday(sdf.parse(dto.getBirthday())); } } catch (Exception e) { // ignore parse error } if (dto.getAge() != null) { member.setAge(dto.getAge()); } if (dto.getShowToFamily() != null) { member.setShowToFamily(dto.getShowToFamily()); } member.setUpdatedAt(new Date()); return familyMemberMapper.updateById(member) > 0; } /** * 家长切换到指定家庭成员身份 */ public Long switchToFamilyMember(Long userId, Long memberId) { User user = userMapper.selectById(userId); if (user == null) { throw new RuntimeException("用户不存在"); } // 验证家庭成员属于该用户所在家庭 FamilyMember member = familyMemberMapper.selectById(memberId); if (member == null || !member.getFamilyId().equals(user.getFamilyId())) { throw new RuntimeException("家庭成员不存在或不属于您的家庭"); } // 切换用户role为child,记录原始角色以便切回 user.setRole("child"); user.setUpdatedAt(new Date()); userMapper.updateById(user); return member.getId(); } private FamilyMemberInfoDTO toFamilyMemberInfoDTO(FamilyMember member) { FamilyMemberInfoDTO dto = new FamilyMemberInfoDTO(); dto.setId(member.getId()); dto.setNickname(member.getNickname()); dto.setAge(member.getAge()); dto.setBirthday(member.getBirthday()); dto.setGender(member.getGender()); dto.setPhone(member.getPhone()); dto.setAvatar(member.getAvatar()); dto.setGeneration(member.getGeneration()); dto.setShowToFamily(member.getShowToFamily()); dto.setTotalPoints(member.getTotalPoints()); return dto; } private void populateTeacherFields(LoginResultDTO result, User user) { result.setTeacherStatus(user.getTeacherStatus() != null ? user.getTeacherStatus() : "pending"); result.setTeacherRejectReason(user.getTeacherRejectReason()); boolean hasTeacher = hasRole(user, "teacher"); result.setHasTeacherRole(hasTeacher); } /** * 获取用户有效的 familyId 集合:自己的 familyId + teacherFamilyIds 中关联的家庭。 * @param userId 用户ID * @return familyId 集合,永不为 null(空用户返回空 set) */ public Set getEffectiveFamilyIds(Long userId) { Set ids = new HashSet<>(); User user = userMapper.selectById(userId); if (user == null) { return ids; } if (user.getFamilyId() != null) { ids.add(user.getFamilyId()); } // 解析 teacherFamilyIds(逗号分隔格式) String teacherFamilyIds = user.getTeacherFamilyIds(); if (teacherFamilyIds != null && !teacherFamilyIds.trim().isEmpty()) { String[] parts = teacherFamilyIds.split(","); for (String part : parts) { String trimmed = part.trim(); if (!trimmed.isEmpty()) { try { ids.add(Long.parseLong(trimmed)); } catch (NumberFormatException ignored) {} } } } return ids; } public boolean deleteUser(Long userId) { User user = userMapper.selectById(userId); if (user == null) { return false; } return userMapper.deleteById(userId) > 0; } }