فهرست منبع

feat: 完善数据迁移服务和用户认证流程

- LoginResultDTO添加openid/hasTeacherRole字段
- 新增SwitchRoleDTO角色切换DTO
- UserService重构:完整实现directRegister/isPhoneRegistered,新增switchRole/registerWithInviteCode/resetPassword
- UserServiceInterface添加缺失方法声明
- 新增GrowthPlanMapper支持成长规划表数据迁移

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
User 4 ماه پیش
والد
کامیت
0f279328ce

+ 2 - 0
zxyj-backend/src/main/java/com/zxyj/dto/LoginResultDTO.java

@@ -14,4 +14,6 @@ public class LoginResultDTO {
     private Boolean isNewUser; // 是否新用户
     private String openid; // 微信openid,用于前端缓存实现自动登录
     private Boolean needsRoleSelect; // 新用户是否需要选择角色
+    private String teacherStatus; // 成长规划师审核状态: pending/approved/rejected
+    private Boolean hasTeacherRole; // 用户是否拥有teacher角色
 }

+ 8 - 0
zxyj-backend/src/main/java/com/zxyj/dto/SwitchRoleDTO.java

@@ -0,0 +1,8 @@
+package com.zxyj.dto;
+
+import lombok.Data;
+
+@Data
+public class SwitchRoleDTO {
+    private String role;
+}

+ 7 - 0
zxyj-backend/src/main/java/com/zxyj/mapper/GrowthPlanMapper.java

@@ -0,0 +1,7 @@
+package com.zxyj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zxyj.entity.GrowthPlan;
+
+public interface GrowthPlanMapper extends BaseMapper<GrowthPlan> {
+}

+ 1024 - 996
zxyj-backend/src/main/java/com/zxyj/service/UserService.java

@@ -1,996 +1,1024 @@
-package com.zxyj.service;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.zxyj.config.JwtConfig;
-import com.zxyj.dto.*;
-import com.zxyj.dto.RegisterWithIdCardDTO;
-import com.zxyj.dto.RegisterWithInviteCodeDTO;
-import com.zxyj.entity.Child;
-import com.zxyj.entity.Family;
-import com.zxyj.entity.User;
-import com.zxyj.mapper.ChildMapper;
-import com.zxyj.mapper.FamilyMapper;
-import com.zxyj.mapper.UserMapper;
-import com.zxyj.service.VerificationCodeService;
-import com.zxyj.service.WechatService;
-import com.zxyj.service.api.UserServiceInterface;
-import javax.annotation.Resource;
-import org.springframework.stereotype.Service;
-import org.springframework.util.DigestUtils;
-
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-
-
-@Service
-public class UserService implements UserServiceInterface {
-
-@Resource
-private UserMapper userMapper;
-
-@Resource
-private FamilyMapper familyMapper;
-
-@Resource
-private ChildMapper childMapper;
-
-@Resource
-private JwtConfig jwtConfig;
-
-@Resource
-private VerificationCodeService verificationCodeService;
-
-@Resource
-private WechatService wechatService;
-
-    public LoginResultDTO wechatLogin(WechatLoginDTO dto) {
-        // 模拟微信登录,实际需要调用微信API获取openid
-        String openid = "mock_openid_" + dto.getCode();
-        
-        // 查询用户是否存在
-        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
-                .eq(User::getOpenid, openid));
-
-        if (user == null) {
-            // 新用户,创建家庭和用户
-            Family family = new Family();
-            family.setName("我的家庭");
-            family.setInviteCode(generateInviteCode());
-            family.setCreatedAt(new Date());
-            family.setUpdatedAt(new Date());
-            familyMapper.insert(family);
-
-            user = new User();
-            user.setOpenid(openid);
-            user.setUnionid("");
-            user.setFamilyId(family.getId());
-            user.setRole("parent");
-            user.setNickname(dto.getNickname());
-            user.setAvatar(dto.getAvatar());
-            user.setPassword("");
-            user.setCreatedAt(new Date());
-            user.setUpdatedAt(new Date());
-            userMapper.insert(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());
-
-        return result;
-    }
-
-    /**
-     * 手机号验证码登录
-     */
-    public LoginResultDTO phoneLogin(PhoneLoginDTO dto) {
-  // 使用手机号作为唯一标识
-  String phone = dto.getPhone();
-
-  // 先通过手机号查找用户(更准确的判断)
-  User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
-      .eq(User::getPhone, phone));
-
-  boolean isNewUser = false;
-
-  if (user == null) {
-    // 新用户,创建新家庭和用户
-    isNewUser = true;
-
-    Family family = new Family();
-    family.setName("我的家庭");
-    family.setInviteCode(generateInviteCode());
-    family.setCreatedAt(new Date());
-    family.setUpdatedAt(new Date());
-    familyMapper.insert(family);
-
-    user = new User();
-    user.setOpenid("phone:" + phone);
-    user.setUnionid("");
-    user.setFamilyId(family.getId());
-    // 默认角色为家长,可以被覆盖
-    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());
-    userMapper.insert(user);
-  } 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);
-  }
-
-  // 生成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);
-
-  return result;
-}
-
-    /**
-     * 微信手机号登录(正确获取openid和手机号)
-     */
-    public LoginResultDTO wechatPhoneLogin(WechatPhoneLoginDTO dto) {
-        // 1. 通过code获取openid
-        Map<String, String> sessionData = wechatService.code2Session(dto.getCode());
-        String openid = sessionData.get("openid");
-        String unionid = sessionData.get("unionid");
-
-        // 2. 通过手机号code获取真实手机号
-        String 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不存在,通过手机号查找
-            user = userMapper.selectOne(new LambdaQueryWrapper<User>()
-                    .eq(User::getPhone, phone));
-
-            if (user == null) {
-                // 5. 全新用户,创建新家庭和用户
-                isNewUser = true;
-
-                Family family = new Family();
-                family.setName("我的家庭");
-                family.setInviteCode(generateInviteCode());
-                family.setCreatedAt(new Date());
-                family.setUpdatedAt(new Date());
-                familyMapper.insert(family);
-
-                user = new User();
-                user.setOpenid(openid);
-                user.setUnionid(unionid);
-                user.setFamilyId(family.getId());
-                user.setRole("parent");
-                user.setNickname(dto.getNickname() != null ? dto.getNickname() : "用户" + phone.substring(7));
-                user.setAvatar(dto.getAvatar());
-                user.setPhone(phone);
-                user.setPassword("");
-                user.setCreatedAt(new Date());
-                user.setUpdatedAt(new Date());
-                userMapper.insert(user);
-            } else {
-                // 6. 手机号已存在但openid不同,绑定openid
-                user.setOpenid(openid);
-                user.setUnionid(unionid);
-                user.setUpdatedAt(new Date());
-                userMapper.updateById(user);
-            }
-        } else {
-            // 7. openid已存在,更新手机号(如果手机号变了)
-            if (phone != null && !phone.equals(user.getPhone())) {
-                user.setPhone(phone);
-                user.setUpdatedAt(new Date());
-                userMapper.updateById(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.setOpenid(openid);
-
-        // 新用户需要完善信息(仅当需要选择角色时)
-        result.setNeedsRoleSelect(isNewUser);
-
-        return result;
-    }
-
-    /**
-     * 微信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("用户不存在,请先登录");
-        }
-
-        // 3. 生成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);
-
-        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;
-        }
-
-        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);
-
-        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) {
-            return false;
-        }
-        return user.getPassword().equals(encryptPassword(password));
-    }
-
-    public User getUserInfo(Long userId) {
-        return userMapper.selectById(userId);
-    }
-
-    /**
-     * 检查手机号是否已注册
-     * @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) {
-        // 创建家庭
-        Family family = new Family();
-        family.setName("我的家庭");
-        family.setInviteCode(generateInviteCode());
-        family.setCreatedAt(new Date());
-        family.setUpdatedAt(new Date());
-        familyMapper.insert(family);
-
-        // 创建用户
-        User user = new User();
-        user.setOpenid("phone:" + dto.getPhone());
-        user.setUnionid("");
-        user.setFamilyId(family.getId());
-        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());
-        userMapper.insert(user);
-
-        // 如果有邀请码,加入家庭
-        if (dto.getInviteCode() != null && !dto.getInviteCode().isEmpty()) {
-            try {
-                joinFamily(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);
-
-        return result;
-    }
-
-    public String switchMode(Long userId, String mode) {
-        User user = userMapper.selectById(userId);
-        if (user == null) {
-            return null;
-        }
-        // 验证用户是否拥有该角色
-        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 (!hasRole(user, targetRole)) {
-            throw new RuntimeException("您没有该角色权限");
-        }
-        // 检查目标角色是否有效
-        if (!isValidRole(targetRole)) {
-            return null;
-        }
-        user.setRole(targetRole);
-        user.setUpdatedAt(new Date());
-        userMapper.updateById(user);
-        return targetRole;
-    }
-
-    /**
-     * 获取用户所有角色列表
-     */
-    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);
-    }
-
-    /**
-     * 解析角色字符串为列表
-     */
-    private 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);
-    }
-
-/**
-   * 家长切换到指定孩子身份(视图切换,不修改用户的实际角色)
-   * 家长可以在家长模式下查看孩子的任务和积分
-   */
-  public Long switchToChild(Long userId, Long childId) {
-    User user = userMapper.selectById(userId);
-    if (user == null) {
-      return null;
-    }
-
-    Child child = childMapper.selectById(childId);
-    if (child == null || !child.getFamilyId().equals(user.getFamilyId())) {
-      return null;
-    }
-
-    // 注意:不修改用户的 role 字段,只是返回 childId 让前端切换视图
-    // 前端应该使用 currentRole 或 viewMode 来控制显示模式
-    return childId;
-  }
-
-  /**
-   * 从孩子视图切换回家长视图
-   * 这只是确认切换操作,不需要修改数据库
-   */
-  public boolean switchBackToParent(Long userId) {
-    User user = userMapper.selectById(userId);
-    if (user == null) {
-      return false;
-    }
-    // 不需要修改用户的 role 字段
-    // 只是确认操作成功,前端切换视图
-    return true;
-  }
-
-    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()));
-        
-        Family family;
-        User user;
-        
-        if (existingUser != null) {
-            // 如果存在相同身份证的用户,加入其家庭
-            user = existingUser;
-            family = familyMapper.selectById(user.getFamilyId());
-            
-            // 更新用户信息(昵称和头像可以更新)
-            user.setNickname(dto.getNickname());
-            user.setAvatar(dto.getAvatar());
-            // 手机号也可以更新(如果需要)
-            user.setOpenid("phone:" + dto.getPhone());
-            user.setUpdatedAt(new Date());
-            userMapper.updateById(user);
-        } else {
-            // 创建新家庭和用户
-            family = new Family();
-            family.setName("我的家庭");
-            family.setInviteCode(generateInviteCode());
-            family.setCreatedAt(new Date());
-            family.setUpdatedAt(new Date());
-            familyMapper.insert(family);
-
-            user = new User();
-            user.setOpenid("phone:" + dto.getPhone());
-            user.setUnionid("");
-            user.setFamilyId(family.getId());
-            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());
-            userMapper.insert(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());
-
-        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岁)");
-        }
-
-        Child child = new Child();
-        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.setDanLevel(dto.getDanLevel());
-        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());
-        childMapper.insert(child);
-
-        return toChildInfoDTO(child);
-    }
-
-    public List<ChildInfoDTO> getChildren(Long userId) {
-        User user = userMapper.selectById(userId);
-        if (user == null) {
-            return null;
-        }
-
-        List<Child> children = childMapper.selectList(new LambdaQueryWrapper<Child>()
-                .eq(Child::getFamilyId, user.getFamilyId()));
-
-        return children.stream().map(this::toChildInfoDTO).collect(java.util.stream.Collectors.toList());
-    }
-
-    public boolean updateChild(Long userId, Long childId, UpdateChildDTO dto) {
-        User user = userMapper.selectById(userId);
-        if (user == null) {
-            return false;
-        }
-
-        Child child = childMapper.selectById(childId);
-        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.getDanLevel() != null) {
-            child.setDanLevel(dto.getDanLevel());
-        }
-        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());
-        }
-        child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
-        return true;
-    }
-
-    private String encryptPassword(String password) {
-        return DigestUtils.md5DigestAsHex(("xzyj_salt_" + password).getBytes(StandardCharsets.UTF_8));
-    }
-
-    private String generateInviteCode() {
-        return UUID.randomUUID().toString().substring(0, 8).toUpperCase();
-    }
-
-    private ChildInfoDTO toChildInfoDTO(Child 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.setDanLevel(child.getDanLevel());
-        dto.setPhone(child.getPhone());
-        dto.setIdCard(child.getIdCard());
-        dto.setPenaltyEnabled(child.getPenaltyEnabled());
-        dto.setTheme(child.getTheme());
-        dto.setTotalPoints(child.getTotalPoints());
-        dto.setStreakDays(child.getStreakDays());
-        dto.setFocusRemaining(child.getFocusRemaining());
-        return dto;
-    }
-
-    /**
-     * 更新用户信息
-     */
-    public boolean updateUserInfo(Long userId, UpdateUserDTO dto) {
-        User user = userMapper.selectById(userId);
-        if (user == null) {
-            return false;
-        }
-        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) {
-            user.setGender(dto.getGender());
-        }
-        // 更新生日(将字符串转换为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());
-        }
-        user.setUpdatedAt(new Date());
-        userMapper.updateById(user);
-        return true;
-    }
-
-    /**
-     * 从身份证号提取生日
-     * 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("用户不存在");
-        }
-        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;
-    }
-
-    /**
-     * 为用户创建新家庭
-     */
-    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);
-        
-        // 查找家庭
-        Family family = familyMapper.selectOne(new LambdaQueryWrapper<Family>()
-                .eq(Family::getInviteCode, inviteCode));
-        
-        User user;
-        boolean isNewUser = false;
-        
-        if (family == null) {
-            throw new RuntimeException("邀请码无效");
-        }
-        
-        // 检查手机号是否已注册
-        User existingUser = userMapper.selectOne(new LambdaQueryWrapper<User>()
-                .eq(User::getPhone, phone));
-        
-        if (existingUser != null) {
-            // 手机号已注册,加入家庭
-            user = existingUser;
-            user.setFamilyId(family.getId());
-            user.setUpdatedAt(new Date());
-            userMapper.updateById(user);
-        } else {
-            // 创建新用户并加入家庭
-            isNewUser = true;
-            user = new User();
-            user.setOpenid("phone:" + phone);
-            user.setUnionid("");
-            user.setFamilyId(family.getId());
-            user.setRole(role);
-            user.setNickname(nickname);
-            user.setPhone(phone);
-            user.setPassword("");
-            user.setCreatedAt(new Date());
-            user.setUpdatedAt(new Date());
-            userMapper.insert(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);
-                }
-            }
-        }
-        
-        // 生成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);
-        
-        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;
-        }
-        String encryptedPassword = DigestUtils.md5DigestAsHex(("xzyj_user_" + newPassword).getBytes(StandardCharsets.UTF_8));
-        user.setPassword(encryptedPassword);
-        user.setUpdatedAt(new Date());
-        return userMapper.updateById(user) > 0;
-    }
-}
+package com.zxyj.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zxyj.config.JwtConfig;
+import com.zxyj.dto.*;
+import com.zxyj.dto.RegisterWithIdCardDTO;
+import com.zxyj.dto.RegisterWithInviteCodeDTO;
+import com.zxyj.entity.Child;
+import com.zxyj.entity.Family;
+import com.zxyj.entity.User;
+import com.zxyj.mapper.ChildMapper;
+import com.zxyj.mapper.FamilyMapper;
+import com.zxyj.mapper.UserMapper;
+import com.zxyj.service.VerificationCodeService;
+import com.zxyj.service.WechatService;
+import com.zxyj.service.api.UserServiceInterface;
+import javax.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+
+@Service
+public class UserService implements UserServiceInterface {
+
+@Resource
+private UserMapper userMapper;
+
+@Resource
+private FamilyMapper familyMapper;
+
+@Resource
+private ChildMapper childMapper;
+
+@Resource
+private JwtConfig jwtConfig;
+
+@Resource
+private VerificationCodeService verificationCodeService;
+
+@Resource
+private WechatService wechatService;
+
+    public LoginResultDTO wechatLogin(WechatLoginDTO dto) {
+        // 模拟微信登录,实际需要调用微信API获取openid
+        String openid = "mock_openid_" + dto.getCode();
+        
+        // 查询用户是否存在
+        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
+                .eq(User::getOpenid, openid));
+
+        if (user == null) {
+            // 新用户,创建家庭和用户
+            Family family = new Family();
+            family.setName("我的家庭");
+            family.setInviteCode(generateInviteCode());
+            family.setCreatedAt(new Date());
+            family.setUpdatedAt(new Date());
+            familyMapper.insert(family);
+
+            user = new User();
+            user.setOpenid(openid);
+            user.setUnionid("");
+            user.setFamilyId(family.getId());
+            user.setRole("parent");
+            user.setNickname(dto.getNickname());
+            user.setAvatar(dto.getAvatar());
+            user.setPassword("");
+            user.setCreatedAt(new Date());
+            user.setUpdatedAt(new Date());
+            userMapper.insert(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());
+
+        return result;
+    }
+
+    /**
+     * 手机号验证码登录
+     */
+    public LoginResultDTO phoneLogin(PhoneLoginDTO dto) {
+  // 使用手机号作为唯一标识
+  String phone = dto.getPhone();
+
+  // 先通过手机号查找用户(更准确的判断)
+  User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
+      .eq(User::getPhone, phone));
+
+  boolean isNewUser = false;
+
+  if (user == null) {
+    // 新用户,创建新家庭和用户
+    isNewUser = true;
+
+    Family family = new Family();
+    family.setName("我的家庭");
+    family.setInviteCode(generateInviteCode());
+    family.setCreatedAt(new Date());
+    family.setUpdatedAt(new Date());
+    familyMapper.insert(family);
+
+    user = new User();
+    user.setOpenid("phone:" + phone);
+    user.setUnionid("");
+    user.setFamilyId(family.getId());
+    // 默认角色为家长,可以被覆盖
+    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());
+    userMapper.insert(user);
+  } 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);
+  }
+
+  // 生成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);
+
+  return result;
+}
+
+    /**
+     * 微信手机号登录(正确获取openid和手机号)
+     */
+    public LoginResultDTO wechatPhoneLogin(WechatPhoneLoginDTO dto) {
+        // 1. 通过code获取openid
+        Map<String, String> sessionData = wechatService.code2Session(dto.getCode());
+        String openid = sessionData.get("openid");
+        String unionid = sessionData.get("unionid");
+
+        // 2. 通过手机号code获取真实手机号
+        String 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不存在,通过手机号查找
+            user = userMapper.selectOne(new LambdaQueryWrapper<User>()
+                    .eq(User::getPhone, phone));
+
+            if (user == null) {
+                // 5. 全新用户,创建新家庭和用户
+                isNewUser = true;
+
+                Family family = new Family();
+                family.setName("我的家庭");
+                family.setInviteCode(generateInviteCode());
+                family.setCreatedAt(new Date());
+                family.setUpdatedAt(new Date());
+                familyMapper.insert(family);
+
+                user = new User();
+                user.setOpenid(openid);
+                user.setUnionid(unionid);
+                user.setFamilyId(family.getId());
+                user.setRole("parent");
+                user.setNickname(dto.getNickname() != null ? dto.getNickname() : "用户" + phone.substring(7));
+                user.setAvatar(dto.getAvatar());
+                user.setPhone(phone);
+                user.setPassword("");
+                user.setCreatedAt(new Date());
+                user.setUpdatedAt(new Date());
+                userMapper.insert(user);
+            } else {
+                // 6. 手机号已存在但openid不同,绑定openid
+                user.setOpenid(openid);
+                user.setUnionid(unionid);
+                user.setUpdatedAt(new Date());
+                userMapper.updateById(user);
+            }
+        } else {
+            // 7. openid已存在,更新手机号(如果手机号变了)
+            if (phone != null && !phone.equals(user.getPhone())) {
+                user.setPhone(phone);
+                user.setUpdatedAt(new Date());
+                userMapper.updateById(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.setOpenid(openid);
+
+        // 新用户需要完善信息(仅当需要选择角色时)
+        result.setNeedsRoleSelect(isNewUser);
+
+        return result;
+    }
+
+    /**
+     * 微信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("用户不存在,请先登录");
+        }
+
+        // 3. 生成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);
+
+        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;
+        }
+
+        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);
+
+        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) {
+            return false;
+        }
+        return user.getPassword().equals(encryptPassword(password));
+    }
+
+    public User getUserInfo(Long userId) {
+        return userMapper.selectById(userId);
+    }
+
+    /**
+     * 检查手机号是否已注册
+     * @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) {
+        // 创建家庭
+        Family family = new Family();
+        family.setName("我的家庭");
+        family.setInviteCode(generateInviteCode());
+        family.setCreatedAt(new Date());
+        family.setUpdatedAt(new Date());
+        familyMapper.insert(family);
+
+        // 创建用户
+        User user = new User();
+        user.setOpenid("phone:" + dto.getPhone());
+        user.setUnionid("");
+        user.setFamilyId(family.getId());
+        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());
+        userMapper.insert(user);
+
+        // 如果有邀请码,加入家庭
+        if (dto.getInviteCode() != null && !dto.getInviteCode().isEmpty()) {
+            try {
+                joinFamily(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);
+
+        return result;
+    }
+
+    public String switchMode(Long userId, String mode) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return null;
+        }
+        // 验证用户是否拥有该角色
+        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 (!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 ("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);
+        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);
+    }
+
+    /**
+     * 解析角色字符串为列表
+     */
+    private 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);
+    }
+
+/**
+   * 家长切换到指定孩子身份(视图切换,不修改用户的实际角色)
+   * 家长可以在家长模式下查看孩子的任务和积分
+   */
+  public Long switchToChild(Long userId, Long childId) {
+    User user = userMapper.selectById(userId);
+    if (user == null) {
+      return null;
+    }
+
+    Child child = childMapper.selectById(childId);
+    if (child == null || !child.getFamilyId().equals(user.getFamilyId())) {
+      return null;
+    }
+
+    // 注意:不修改用户的 role 字段,只是返回 childId 让前端切换视图
+    // 前端应该使用 currentRole 或 viewMode 来控制显示模式
+    return childId;
+  }
+
+  /**
+   * 从孩子视图切换回家长视图
+   * 这只是确认切换操作,不需要修改数据库
+   */
+  public boolean switchBackToParent(Long userId) {
+    User user = userMapper.selectById(userId);
+    if (user == null) {
+      return false;
+    }
+    // 不需要修改用户的 role 字段
+    // 只是确认操作成功,前端切换视图
+    return true;
+  }
+
+    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()));
+        
+        Family family;
+        User user;
+        
+        if (existingUser != null) {
+            // 如果存在相同身份证的用户,加入其家庭
+            user = existingUser;
+            family = familyMapper.selectById(user.getFamilyId());
+            
+            // 更新用户信息(昵称和头像可以更新)
+            user.setNickname(dto.getNickname());
+            user.setAvatar(dto.getAvatar());
+            // 手机号也可以更新(如果需要)
+            user.setOpenid("phone:" + dto.getPhone());
+            user.setUpdatedAt(new Date());
+            userMapper.updateById(user);
+        } else {
+            // 创建新家庭和用户
+            family = new Family();
+            family.setName("我的家庭");
+            family.setInviteCode(generateInviteCode());
+            family.setCreatedAt(new Date());
+            family.setUpdatedAt(new Date());
+            familyMapper.insert(family);
+
+            user = new User();
+            user.setOpenid("phone:" + dto.getPhone());
+            user.setUnionid("");
+            user.setFamilyId(family.getId());
+            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());
+            userMapper.insert(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());
+
+        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岁)");
+        }
+
+        Child child = new Child();
+        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.setDanLevel(dto.getDanLevel());
+        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());
+        childMapper.insert(child);
+
+        return toChildInfoDTO(child);
+    }
+
+    public List<ChildInfoDTO> getChildren(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return null;
+        }
+
+        List<Child> children = childMapper.selectList(new LambdaQueryWrapper<Child>()
+                .eq(Child::getFamilyId, user.getFamilyId()));
+
+        return children.stream().map(this::toChildInfoDTO).collect(java.util.stream.Collectors.toList());
+    }
+
+    public boolean updateChild(Long userId, Long childId, UpdateChildDTO dto) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return false;
+        }
+
+        Child child = childMapper.selectById(childId);
+        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.getDanLevel() != null) {
+            child.setDanLevel(dto.getDanLevel());
+        }
+        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());
+        }
+        child.setUpdatedAt(new Date());
+        childMapper.updateById(child);
+        return true;
+    }
+
+    private String encryptPassword(String password) {
+        return DigestUtils.md5DigestAsHex(("xzyj_salt_" + password).getBytes(StandardCharsets.UTF_8));
+    }
+
+    private String generateInviteCode() {
+        return UUID.randomUUID().toString().substring(0, 8).toUpperCase();
+    }
+
+    private ChildInfoDTO toChildInfoDTO(Child 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.setDanLevel(child.getDanLevel());
+        dto.setPhone(child.getPhone());
+        dto.setIdCard(child.getIdCard());
+        dto.setPenaltyEnabled(child.getPenaltyEnabled());
+        dto.setTheme(child.getTheme());
+        dto.setTotalPoints(child.getTotalPoints());
+        dto.setStreakDays(child.getStreakDays());
+        dto.setFocusRemaining(child.getFocusRemaining());
+        return dto;
+    }
+
+    /**
+     * 更新用户信息
+     */
+    public boolean updateUserInfo(Long userId, UpdateUserDTO dto) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return false;
+        }
+        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) {
+            user.setGender(dto.getGender());
+        }
+        // 更新生日(将字符串转换为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());
+        }
+        user.setUpdatedAt(new Date());
+        userMapper.updateById(user);
+        return true;
+    }
+
+    /**
+     * 从身份证号提取生日
+     * 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("用户不存在");
+        }
+        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;
+    }
+
+    /**
+     * 为用户创建新家庭
+     */
+    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);
+        
+        // 查找家庭
+        Family family = familyMapper.selectOne(new LambdaQueryWrapper<Family>()
+                .eq(Family::getInviteCode, inviteCode));
+        
+        User user;
+        boolean isNewUser = false;
+        
+        if (family == null) {
+            throw new RuntimeException("邀请码无效");
+        }
+        
+        // 检查手机号是否已注册
+        User existingUser = userMapper.selectOne(new LambdaQueryWrapper<User>()
+                .eq(User::getPhone, phone));
+        
+        if (existingUser != null) {
+            // 手机号已注册,加入家庭
+            user = existingUser;
+            user.setFamilyId(family.getId());
+            user.setUpdatedAt(new Date());
+            userMapper.updateById(user);
+        } else {
+            // 创建新用户并加入家庭
+            isNewUser = true;
+            user = new User();
+            user.setOpenid("phone:" + phone);
+            user.setUnionid("");
+            user.setFamilyId(family.getId());
+            user.setRole(role);
+            user.setNickname(nickname);
+            user.setPhone(phone);
+            user.setPassword("");
+            user.setCreatedAt(new Date());
+            user.setUpdatedAt(new Date());
+            userMapper.insert(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);
+                }
+            }
+        }
+        
+        // 生成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);
+        
+        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;
+        }
+        String encryptedPassword = DigestUtils.md5DigestAsHex(("xzyj_user_" + newPassword).getBytes(StandardCharsets.UTF_8));
+        user.setPassword(encryptedPassword);
+        user.setUpdatedAt(new Date());
+        return userMapper.updateById(user) > 0;
+    }
+
+    private void populateTeacherFields(LoginResultDTO result, User user) {
+        result.setTeacherStatus(user.getTeacherStatus() != null ? user.getTeacherStatus() : "pending");
+        boolean hasTeacher = hasRole(user, "teacher");
+        result.setHasTeacherRole(hasTeacher);
+    }
+}

+ 7 - 0
zxyj-backend/src/main/java/com/zxyj/service/api/UserServiceInterface.java

@@ -8,12 +8,16 @@ public interface UserServiceInterface {
     LoginResultDTO wechatLogin(WechatLoginDTO dto);
     LoginResultDTO phoneLogin(PhoneLoginDTO dto);
     LoginResultDTO wechatPhoneLogin(WechatPhoneLoginDTO dto);
+    LoginResultDTO silentLogin(String code);
+    LoginResultDTO autoLoginByOpenid(String openid);
     boolean setPassword(Long userId, String password);
     boolean verifyPassword(Long userId, String password);
     User getUserInfo(Long userId);
     boolean isPhoneRegistered(String phone);
     LoginResultDTO directRegister(DirectRegisterDTO dto);
     String switchMode(Long userId, String mode);
+    String switchRole(Long userId, String targetRole);
+    LoginResultDTO switchRoleForLogin(Long userId, String role);
     List<String> getUserRoles(Long userId);
     boolean hasRole(Long userId, String role);
     boolean addRole(Long userId, String newRole);
@@ -23,6 +27,9 @@ public interface UserServiceInterface {
     List<ChildInfoDTO> getChildren(Long userId);
     boolean updateChild(Long userId, Long childId, UpdateChildDTO dto);
     boolean updateUserInfo(Long userId, UpdateUserDTO dto);
+    LoginResultDTO registerWithIdCard(RegisterWithIdCardDTO dto);
     boolean joinFamily(Long userId, String inviteCode);
+    Long createFamilyForUser(Long userId, String familyName);
+    LoginResultDTO registerWithInviteCode(RegisterWithInviteCodeDTO dto);
     boolean resetPassword(Long userId, String newPassword);
 }