| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692 |
- 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<String, String> 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<User>()
- .eq(User::getOpenid, openid));
- boolean isNewUser = false;
- if (user == null) {
- // 4. openid不存在,如果有手机号则通过手机号查找
- if (phone != null) {
- user = userMapper.selectOne(new LambdaQueryWrapper<User>()
- .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<User>()
- .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<String, String> sessionData = wechatService.code2Session(code);
- String openid = sessionData.get("openid");
- String unionid = sessionData.get("unionid");
- // 2. 查找用户
- User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
- .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<User>()
- .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<String>, profile: Object(最新完整信息) }
- */
- public Map<String, Object> 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<String> 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<String, Object> 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<User>()
- .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(), null);
- } 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<String> 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<String> 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<String> roles = parseRoles(user.getRoles());
- return roles.contains(role);
- }
- /**
- * 解析角色字符串为列表
- */
- public List<String> parseRoles(String rolesStr) {
- List<String> 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<String> 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<FamilyMemberVO> 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<FamilyMemberVO> 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<User> parents = userMapper.selectList(new LambdaQueryWrapper<User>()
- .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<FamilyMemberVO> 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<User>()
- .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<ChildInfoDTO> getChildren(Long userId) {
- User user = userMapper.selectById(userId);
- if (user == null) {
- return null;
- }
- List<FamilyMember> children = familyMemberMapper.selectList(new LambdaQueryWrapper<FamilyMember>()
- .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<Family>()
- .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());
- // 创建人自己也是家庭成员
- addCreatorAsMember(user, family.getId());
- }
- private void addCreatorAsMember(User user, Long familyId) {
- // 已存在则跳过(兜底幂等)
- LambdaQueryWrapper<FamilyMember> check = new LambdaQueryWrapper<FamilyMember>()
- .eq(FamilyMember::getFamilyId, familyId)
- .eq(FamilyMember::getUserId, user.getId());
- if (familyMemberMapper.selectCount(check) > 0) {
- return;
- }
- FamilyMember member = new FamilyMember();
- member.setFamilyId(familyId);
- member.setUserId(user.getId());
- member.setNickname(user.getNickname() != null ? user.getNickname() : "我");
- member.setPhone(user.getPhone());
- member.setAvatar(user.getAvatar());
- member.setGender(user.getGender());
- member.setGeneration(0);
- member.setTotalPoints(0);
- member.setCreatedAt(new Date());
- member.setUpdatedAt(new Date());
- familyMemberMapper.insert(member);
- }
- /**
- * 确保用户有家庭(公开兜底入口,供查询家庭类接口在 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.setCreatorId(userId);
- family.setCreatedAt(new Date());
- family.setUpdatedAt(new Date());
- familyMapper.insert(family);
- // 更新用户的家庭ID
- user.setFamilyId(family.getId());
- user.setUpdatedAt(new Date());
- userMapper.updateById(user);
- // 创建人自己也是家庭成员(generation=0 表示创建者)
- addCreatorAsMember(user, family.getId());
- 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<DistributionRelation> 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<Family>()
- .eq(Family::getInviteCode, inviteCode));
- if (family == null) {
- throw new RuntimeException("邀请码无效");
- }
- User user;
- boolean isNewUser = false;
- // 检查手机号是否已注册
- User existingUser = userMapper.selectOne(new LambdaQueryWrapper<User>()
- .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, null);
- 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, null);
- 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<FamilyMemberInfoDTO> getFamilyMemberren(Long userId) {
- User user = userMapper.selectById(userId);
- if (user == null || user.getFamilyId() == null) {
- return new ArrayList<>();
- }
- List<FamilyMember> members = familyMemberMapper.selectList(
- new LambdaQueryWrapper<FamilyMember>()
- .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<Long> getEffectiveFamilyIds(Long userId) {
- Set<Long> 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;
- }
- }
|