UserService.java 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083
  1. package com.zxyj.service;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.zxyj.config.JwtConfig;
  4. import com.zxyj.dto.*;
  5. import com.zxyj.dto.RegisterWithIdCardDTO;
  6. import com.zxyj.dto.RegisterWithInviteCodeDTO;
  7. import com.zxyj.entity.Child;
  8. import com.zxyj.entity.Family;
  9. import com.zxyj.entity.User;
  10. import com.zxyj.mapper.ChildMapper;
  11. import com.zxyj.mapper.FamilyMapper;
  12. import com.zxyj.mapper.UserMapper;
  13. import com.zxyj.service.VerificationCodeService;
  14. import com.zxyj.service.WechatService;
  15. import com.zxyj.service.api.UserServiceInterface;
  16. import javax.annotation.Resource;
  17. import org.springframework.stereotype.Service;
  18. import org.springframework.util.DigestUtils;
  19. import java.nio.charset.StandardCharsets;
  20. import java.util.*;
  21. @Service
  22. public class UserService implements UserServiceInterface {
  23. @Resource
  24. private UserMapper userMapper;
  25. @Resource
  26. private FamilyMapper familyMapper;
  27. @Resource
  28. private ChildMapper childMapper;
  29. @Resource
  30. private JwtConfig jwtConfig;
  31. @Resource
  32. private VerificationCodeService verificationCodeService;
  33. @Resource
  34. private WechatService wechatService;
  35. public LoginResultDTO wechatLogin(WechatLoginDTO dto) {
  36. // 模拟微信登录,实际需要调用微信API获取openid
  37. String openid = "mock_openid_" + dto.getCode();
  38. // 查询用户是否存在
  39. User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
  40. .eq(User::getOpenid, openid));
  41. if (user == null) {
  42. // 新用户,创建家庭和用户
  43. Family family = new Family();
  44. family.setName("我的家庭");
  45. family.setInviteCode(generateInviteCode());
  46. family.setCreatedAt(new Date());
  47. family.setUpdatedAt(new Date());
  48. familyMapper.insert(family);
  49. user = new User();
  50. user.setOpenid(openid);
  51. user.setUnionid("");
  52. user.setFamilyId(family.getId());
  53. user.setRole("parent");
  54. user.setNickname(dto.getNickname());
  55. user.setAvatar(dto.getAvatar());
  56. user.setPassword("");
  57. user.setCreatedAt(new Date());
  58. user.setUpdatedAt(new Date());
  59. userMapper.insert(user);
  60. } else {
  61. // 管理员不能登录小程序
  62. if ("admin".equals(user.getRole())) {
  63. throw new RuntimeException("该账号不允许登录小程序");
  64. }
  65. }
  66. // 生成token
  67. String token = jwtConfig.generateToken(user.getId(), user.getRole());
  68. LoginResultDTO result = new LoginResultDTO();
  69. result.setToken(token);
  70. result.setUserId(user.getId());
  71. result.setRole(user.getRole());
  72. result.setRoles(user.getRoles());
  73. result.setFamilyId(user.getFamilyId());
  74. result.setNickname(user.getNickname());
  75. populateTeacherFields(result, user);
  76. return result;
  77. }
  78. /**
  79. * 手机号验证码登录
  80. */
  81. public LoginResultDTO phoneLogin(PhoneLoginDTO dto) {
  82. // 使用手机号作为唯一标识
  83. String phone = dto.getPhone();
  84. // 先通过手机号查找用户(更准确的判断)
  85. User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
  86. .eq(User::getPhone, phone));
  87. // 管理员不能登录小程序
  88. if (user != null && "admin".equals(user.getRole())) {
  89. throw new RuntimeException("该账号不允许登录小程序");
  90. }
  91. boolean isNewUser = false;
  92. if (user == null) {
  93. // 新用户,创建新家庭和用户
  94. isNewUser = true;
  95. Family family = new Family();
  96. family.setName("我的家庭");
  97. family.setInviteCode(generateInviteCode());
  98. family.setCreatedAt(new Date());
  99. family.setUpdatedAt(new Date());
  100. familyMapper.insert(family);
  101. user = new User();
  102. user.setOpenid("phone:" + phone);
  103. user.setUnionid("");
  104. user.setFamilyId(family.getId());
  105. // 默认角色为家长,可以被覆盖
  106. user.setRole(dto.getRole() != null ? dto.getRole() : "parent");
  107. user.setNickname(dto.getNickname() != null ? dto.getNickname() : "用户" + phone.substring(7));
  108. user.setAvatar(dto.getAvatar());
  109. user.setPhone(phone);
  110. user.setBirthday(dto.getBirthday());
  111. user.setGender(dto.getGender());
  112. user.setPassword("");
  113. user.setCreatedAt(new Date());
  114. user.setUpdatedAt(new Date());
  115. userMapper.insert(user);
  116. } else {
  117. // 老用户,更新信息(如果提供了)
  118. if (dto.getNickname() != null && !dto.getNickname().isEmpty()) {
  119. user.setNickname(dto.getNickname());
  120. }
  121. if (dto.getAvatar() != null && !dto.getAvatar().isEmpty()) {
  122. user.setAvatar(dto.getAvatar());
  123. }
  124. user.setUpdatedAt(new Date());
  125. userMapper.updateById(user);
  126. }
  127. // 生成token
  128. String token = jwtConfig.generateToken(user.getId(), user.getRole());
  129. LoginResultDTO result = new LoginResultDTO();
  130. result.setToken(token);
  131. result.setUserId(user.getId());
  132. result.setRole(user.getRole());
  133. result.setRoles(user.getRoles());
  134. result.setFamilyId(user.getFamilyId());
  135. result.setNickname(user.getNickname());
  136. result.setIsNewUser(isNewUser);
  137. result.setNeedsRoleSelect(isNewUser);
  138. populateTeacherFields(result, user);
  139. return result;
  140. }
  141. /**
  142. * 微信手机号登录(正确获取openid和手机号)
  143. */
  144. public LoginResultDTO wechatPhoneLogin(WechatPhoneLoginDTO dto) {
  145. // 1. 通过code获取openid
  146. Map<String, String> sessionData = wechatService.code2Session(dto.getCode());
  147. String openid = sessionData.get("openid");
  148. String unionid = sessionData.get("unionid");
  149. // 2. 通过手机号code获取真实手机号
  150. String phone = wechatService.getPhoneNumber(dto.getPhoneCode());
  151. // 3. 先通过openid查找用户
  152. User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
  153. .eq(User::getOpenid, openid));
  154. boolean isNewUser = false;
  155. if (user == null) {
  156. // 4. openid不存在,通过手机号查找
  157. user = userMapper.selectOne(new LambdaQueryWrapper<User>()
  158. .eq(User::getPhone, phone));
  159. if (user == null) {
  160. // 5. 全新用户,创建新家庭和用户
  161. isNewUser = true;
  162. Family family = new Family();
  163. family.setName("我的家庭");
  164. family.setInviteCode(generateInviteCode());
  165. family.setCreatedAt(new Date());
  166. family.setUpdatedAt(new Date());
  167. familyMapper.insert(family);
  168. user = new User();
  169. user.setOpenid(openid);
  170. user.setUnionid(unionid);
  171. user.setFamilyId(family.getId());
  172. user.setRole("parent");
  173. user.setNickname(dto.getNickname() != null ? dto.getNickname() : "用户" + phone.substring(7));
  174. user.setAvatar(dto.getAvatar());
  175. user.setPhone(phone);
  176. user.setPassword("");
  177. user.setCreatedAt(new Date());
  178. user.setUpdatedAt(new Date());
  179. userMapper.insert(user);
  180. } else {
  181. // 6. 手机号已存在但openid不同,绑定openid
  182. user.setOpenid(openid);
  183. user.setUnionid(unionid);
  184. user.setUpdatedAt(new Date());
  185. userMapper.updateById(user);
  186. }
  187. } else {
  188. // 7. openid已存在,更新手机号(如果手机号变了)
  189. if (phone != null && !phone.equals(user.getPhone())) {
  190. user.setPhone(phone);
  191. user.setUpdatedAt(new Date());
  192. userMapper.updateById(user);
  193. }
  194. }
  195. // 管理员不能登录小程序
  196. if ("admin".equals(user.getRole())) {
  197. throw new RuntimeException("该账号不允许登录小程序");
  198. }
  199. // 生成token
  200. String token = jwtConfig.generateToken(user.getId(), user.getRole());
  201. LoginResultDTO result = new LoginResultDTO();
  202. result.setToken(token);
  203. result.setUserId(user.getId());
  204. result.setRole(user.getRole());
  205. result.setRoles(user.getRoles());
  206. result.setFamilyId(user.getFamilyId());
  207. result.setNickname(user.getNickname());
  208. result.setIsNewUser(isNewUser);
  209. result.setOpenid(openid);
  210. // 新用户需要完善信息(仅当需要选择角色时)
  211. result.setNeedsRoleSelect(isNewUser);
  212. populateTeacherFields(result, user);
  213. return result;
  214. }
  215. /**
  216. * 微信openid静默登录(用户再次打开小程序时自动登录)
  217. */
  218. public LoginResultDTO silentLogin(String code) {
  219. // 1. 通过code获取openid
  220. Map<String, String> sessionData = wechatService.code2Session(code);
  221. String openid = sessionData.get("openid");
  222. String unionid = sessionData.get("unionid");
  223. // 2. 查找用户
  224. User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
  225. .eq(User::getOpenid, openid));
  226. if (user == null) {
  227. throw new RuntimeException("用户不存在,请先登录");
  228. }
  229. // 管理员不能登录小程序
  230. if ("admin".equals(user.getRole())) {
  231. throw new RuntimeException("该账号不允许登录小程序");
  232. }
  233. // 3. 生成token
  234. String token = jwtConfig.generateToken(user.getId(), user.getRole());
  235. LoginResultDTO result = new LoginResultDTO();
  236. result.setToken(token);
  237. result.setUserId(user.getId());
  238. result.setRole(user.getRole());
  239. result.setRoles(user.getRoles());
  240. result.setFamilyId(user.getFamilyId());
  241. result.setNickname(user.getNickname());
  242. result.setIsNewUser(false);
  243. result.setOpenid(openid);
  244. populateTeacherFields(result, user);
  245. return result;
  246. }
  247. /**
  248. * 通过openid自动登录(前端已缓存openid时调用)
  249. */
  250. public LoginResultDTO autoLoginByOpenid(String openid) {
  251. User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
  252. .eq(User::getOpenid, openid));
  253. if (user == null) {
  254. return null;
  255. }
  256. // 管理员不能登录小程序
  257. if ("admin".equals(user.getRole())) {
  258. return null;
  259. }
  260. String token = jwtConfig.generateToken(user.getId(), user.getRole());
  261. LoginResultDTO result = new LoginResultDTO();
  262. result.setToken(token);
  263. result.setUserId(user.getId());
  264. result.setRole(user.getRole());
  265. result.setFamilyId(user.getFamilyId());
  266. result.setNickname(user.getNickname());
  267. result.setIsNewUser(false);
  268. result.setOpenid(openid);
  269. populateTeacherFields(result, user);
  270. return result;
  271. }
  272. public boolean setPassword(Long userId, String password) {
  273. User user = userMapper.selectById(userId);
  274. if (user == null) {
  275. return false;
  276. }
  277. user.setPassword(encryptPassword(password));
  278. user.setUpdatedAt(new Date());
  279. userMapper.updateById(user);
  280. return true;
  281. }
  282. public boolean verifyPassword(Long userId, String password) {
  283. User user = userMapper.selectById(userId);
  284. if (user == null || user.getPassword() == null) {
  285. return false;
  286. }
  287. return user.getPassword().equals(encryptPassword(password));
  288. }
  289. public User getUserInfo(Long userId) {
  290. return userMapper.selectById(userId);
  291. }
  292. /**
  293. * 检查手机号是否已注册
  294. * @param phone 手机号
  295. * @return true=已注册,false=未注册
  296. */
  297. public boolean isPhoneRegistered(String phone) {
  298. if (phone == null || phone.isEmpty()) {
  299. return false;
  300. }
  301. User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
  302. .eq(User::getPhone, phone));
  303. return user != null;
  304. }
  305. /**
  306. * 直接注册(测试环境:不验证验证码)
  307. */
  308. public LoginResultDTO directRegister(DirectRegisterDTO dto) {
  309. // 创建家庭
  310. Family family = new Family();
  311. family.setName("我的家庭");
  312. family.setInviteCode(generateInviteCode());
  313. family.setCreatedAt(new Date());
  314. family.setUpdatedAt(new Date());
  315. familyMapper.insert(family);
  316. // 创建用户
  317. User user = new User();
  318. user.setOpenid("phone:" + dto.getPhone());
  319. user.setUnionid("");
  320. user.setFamilyId(family.getId());
  321. user.setRole(dto.getRole() != null ? dto.getRole() : "parent");
  322. user.setNickname(dto.getNickname() != null ? dto.getNickname() : "用户" + dto.getPhone().substring(7));
  323. user.setAvatar(dto.getAvatar());
  324. user.setPhone(dto.getPhone());
  325. user.setBirthday(dto.getBirthday());
  326. user.setGender(dto.getGender());
  327. user.setPassword("");
  328. user.setCreatedAt(new Date());
  329. user.setUpdatedAt(new Date());
  330. userMapper.insert(user);
  331. // 如果有邀请码,加入家庭
  332. if (dto.getInviteCode() != null && !dto.getInviteCode().isEmpty()) {
  333. try {
  334. joinFamily(user.getId(), dto.getInviteCode());
  335. } catch (Exception e) {
  336. // 忽略邀请码错误,继续使用创建的家庭
  337. }
  338. }
  339. // 生成token
  340. String token = jwtConfig.generateToken(user.getId(), user.getRole());
  341. LoginResultDTO result = new LoginResultDTO();
  342. result.setToken(token);
  343. result.setUserId(user.getId());
  344. result.setRole(user.getRole());
  345. result.setRoles(user.getRoles());
  346. result.setFamilyId(user.getFamilyId());
  347. result.setNickname(user.getNickname());
  348. result.setIsNewUser(true);
  349. populateTeacherFields(result, user);
  350. return result;
  351. }
  352. public String switchMode(Long userId, String mode) {
  353. User user = userMapper.selectById(userId);
  354. if (user == null) {
  355. return null;
  356. }
  357. // 管理员角色不允许切换
  358. if ("admin".equals(user.getRole())) {
  359. throw new RuntimeException("管理员角色不允许切换");
  360. }
  361. // 验证用户是否拥有该角色
  362. if (!hasRole(user, mode)) {
  363. return null;
  364. }
  365. user.setRole(mode);
  366. user.setUpdatedAt(new Date());
  367. userMapper.updateById(user);
  368. return mode;
  369. }
  370. /**
  371. * 切换用户当前角色 (支持 parent/child/teacher)
  372. * 用户需要先拥有该角色才能切换
  373. */
  374. public String switchRole(Long userId, String targetRole) {
  375. User user = userMapper.selectById(userId);
  376. if (user == null) {
  377. return null;
  378. }
  379. // 管理员角色不允许切换
  380. if ("admin".equals(user.getRole())) {
  381. throw new RuntimeException("管理员角色不允许切换");
  382. }
  383. if (!hasRole(user, targetRole)) {
  384. throw new RuntimeException("您没有该角色权限");
  385. }
  386. if (!isValidRole(targetRole)) {
  387. return null;
  388. }
  389. user.setRole(targetRole);
  390. user.setUpdatedAt(new Date());
  391. userMapper.updateById(user);
  392. return targetRole;
  393. }
  394. public LoginResultDTO switchRoleForLogin(Long userId, String role) {
  395. User user = userMapper.selectById(userId);
  396. if (user == null) {
  397. throw new RuntimeException("用户不存在");
  398. }
  399. // 管理员角色不允许切换
  400. if ("admin".equals(user.getRole())) {
  401. throw new RuntimeException("管理员角色不允许切换");
  402. }
  403. if ("teacher".equals(role) && !hasRole(user, "teacher")) {
  404. throw new RuntimeException("您不是成长规划师,无法切换为规划师模式");
  405. }
  406. user.setRole(role);
  407. user.setUpdatedAt(new Date());
  408. userMapper.updateById(user);
  409. String token = jwtConfig.generateToken(user.getId(), user.getRole());
  410. LoginResultDTO result = new LoginResultDTO();
  411. result.setToken(token);
  412. result.setUserId(user.getId());
  413. result.setRole(user.getRole());
  414. result.setRoles(user.getRoles());
  415. result.setFamilyId(user.getFamilyId());
  416. result.setNickname(user.getNickname());
  417. result.setIsNewUser(false);
  418. populateTeacherFields(result, user);
  419. return result;
  420. }
  421. /**
  422. * 获取用户所有角色列表
  423. */
  424. public List<String> getUserRoles(Long userId) {
  425. User user = userMapper.selectById(userId);
  426. if (user == null) {
  427. return new ArrayList<>();
  428. }
  429. return parseRoles(user.getRoles());
  430. }
  431. /**
  432. * 检查用户是否拥有指定角色
  433. */
  434. public boolean hasRole(Long userId, String role) {
  435. User user = userMapper.selectById(userId);
  436. if (user == null) {
  437. return false;
  438. }
  439. return hasRole(user, role);
  440. }
  441. /**
  442. * 添加角色到用户 (用于用户注册成为老师时)
  443. */
  444. public boolean addRole(Long userId, String newRole) {
  445. User user = userMapper.selectById(userId);
  446. if (user == null || !isValidRole(newRole)) {
  447. return false;
  448. }
  449. // 如果用户还没有roles字段,设置默认值
  450. if (user.getRoles() == null || user.getRoles().isEmpty()) {
  451. user.setRoles(user.getRole()); // 当前角色作为第一个角色
  452. }
  453. // 检查是否已拥有该角色
  454. if (hasRole(user, newRole)) {
  455. return true; // 已有该角色
  456. }
  457. // 添加新角色
  458. List<String> roleList = parseRoles(user.getRoles());
  459. roleList.add(newRole);
  460. user.setRoles(joinRoles(roleList));
  461. user.setUpdatedAt(new Date());
  462. userMapper.updateById(user);
  463. return true;
  464. }
  465. /**
  466. * 检查用户是否拥有指定角色 (内部方法)
  467. */
  468. private boolean hasRole(User user, String role) {
  469. List<String> roles = parseRoles(user.getRoles());
  470. return roles.contains(role);
  471. }
  472. /**
  473. * 解析角色字符串为列表
  474. */
  475. private List<String> parseRoles(String rolesStr) {
  476. List<String> result = new ArrayList<>();
  477. if (rolesStr == null || rolesStr.isEmpty()) {
  478. return result;
  479. }
  480. // 支持JSON数组格式或逗号分隔格式
  481. if (rolesStr.startsWith("[")) {
  482. // JSON格式解析
  483. rolesStr = rolesStr.replace("[", "").replace("]", "")
  484. .replace("\"", "").replace(" ", "");
  485. String[] arr = rolesStr.split(",");
  486. for (String r : arr) {
  487. if (!r.isEmpty()) {
  488. result.add(r.trim());
  489. }
  490. }
  491. } else {
  492. // 逗号分隔格式
  493. String[] arr = rolesStr.split(",");
  494. for (String r : arr) {
  495. if (!r.trim().isEmpty()) {
  496. result.add(r.trim());
  497. }
  498. }
  499. }
  500. return result;
  501. }
  502. /**
  503. * 将角色列表转换为字符串
  504. */
  505. private String joinRoles(List<String> roles) {
  506. return String.join(",", roles);
  507. }
  508. /**
  509. * 检查角色是否有效
  510. */
  511. private boolean isValidRole(String role) {
  512. return "parent".equals(role) || "child".equals(role) || "teacher".equals(role);
  513. }
  514. /**
  515. * 家长切换到指定孩子身份(视图切换,不修改用户的实际角色)
  516. * 家长可以在家长模式下查看孩子的任务和积分
  517. */
  518. public Long switchToChild(Long userId, Long childId) {
  519. User user = userMapper.selectById(userId);
  520. if (user == null) {
  521. return null;
  522. }
  523. Child child = childMapper.selectById(childId);
  524. if (child == null || !child.getFamilyId().equals(user.getFamilyId())) {
  525. return null;
  526. }
  527. // 注意:不修改用户的 role 字段,只是返回 childId 让前端切换视图
  528. // 前端应该使用 currentRole 或 viewMode 来控制显示模式
  529. return childId;
  530. }
  531. /**
  532. * 从孩子视图切换回家长视图
  533. * 这只是确认切换操作,不需要修改数据库
  534. */
  535. public boolean switchBackToParent(Long userId) {
  536. User user = userMapper.selectById(userId);
  537. if (user == null) {
  538. return false;
  539. }
  540. // 不需要修改用户的 role 字段
  541. // 只是确认操作成功,前端切换视图
  542. return true;
  543. }
  544. public LoginResultDTO registerWithIdCard(RegisterWithIdCardDTO dto) {
  545. // 验证验证码
  546. boolean valid = verificationCodeService.verifyCode(dto.getPhone(), dto.getCode(), "login");
  547. if (!valid) {
  548. throw new RuntimeException("验证码错误或已过期");
  549. }
  550. // 通过身份证查找是否已有注册的用户
  551. User existingUser = userMapper.selectOne(new LambdaQueryWrapper<User>()
  552. .eq(User::getIdCard, dto.getIdCard()));
  553. Family family;
  554. User user;
  555. if (existingUser != null) {
  556. // 管理员不能登录小程序
  557. if ("admin".equals(existingUser.getRole())) {
  558. throw new RuntimeException("该账号不允许登录小程序");
  559. }
  560. // 如果存在相同身份证的用户,加入其家庭
  561. user = existingUser;
  562. family = familyMapper.selectById(user.getFamilyId());
  563. // 更新用户信息(昵称和头像可以更新)
  564. user.setNickname(dto.getNickname());
  565. user.setAvatar(dto.getAvatar());
  566. // 手机号也可以更新(如果需要)
  567. user.setOpenid("phone:" + dto.getPhone());
  568. user.setUpdatedAt(new Date());
  569. userMapper.updateById(user);
  570. } else {
  571. // 创建新家庭和用户
  572. family = new Family();
  573. family.setName("我的家庭");
  574. family.setInviteCode(generateInviteCode());
  575. family.setCreatedAt(new Date());
  576. family.setUpdatedAt(new Date());
  577. familyMapper.insert(family);
  578. user = new User();
  579. user.setOpenid("phone:" + dto.getPhone());
  580. user.setUnionid("");
  581. user.setFamilyId(family.getId());
  582. user.setRole("parent");
  583. user.setNickname(dto.getNickname());
  584. user.setAvatar(dto.getAvatar());
  585. user.setIdCard(dto.getIdCard());
  586. user.setRealName(dto.getRealName());
  587. user.setPassword(""); // 初始密码为空,需要后续设置
  588. user.setCreatedAt(new Date());
  589. user.setUpdatedAt(new Date());
  590. userMapper.insert(user);
  591. }
  592. // 生成token
  593. String token = jwtConfig.generateToken(user.getId(), user.getRole());
  594. LoginResultDTO result = new LoginResultDTO();
  595. result.setToken(token);
  596. result.setUserId(user.getId());
  597. result.setRole(user.getRole());
  598. result.setFamilyId(user.getFamilyId());
  599. result.setNickname(user.getNickname());
  600. populateTeacherFields(result, user);
  601. return result;
  602. }
  603. public ChildInfoDTO createChild(Long userId, CreateChildDTO dto) {
  604. User user = userMapper.selectById(userId);
  605. if (user == null) {
  606. return null;
  607. }
  608. if (dto.getNickname() == null || dto.getNickname().trim().isEmpty()) {
  609. throw new IllegalArgumentException("孩子昵称不能为空");
  610. }
  611. if (dto.getAge() == null || dto.getAge() < 1 || dto.getAge() > 18) {
  612. throw new IllegalArgumentException("请输入有效的年龄(1-18岁)");
  613. }
  614. Child child = new Child();
  615. child.setUserId(userId);
  616. child.setFamilyId(user.getFamilyId());
  617. child.setNickname(dto.getNickname().trim());
  618. child.setAge(dto.getAge());
  619. child.setBirthday(dto.getBirthday());
  620. child.setGender(dto.getGender());
  621. child.setDanLevel(dto.getDanLevel());
  622. child.setPhone(dto.getPhone());
  623. child.setIdCard(dto.getIdCard());
  624. // 扣分开关:优先使用传入值,否则根据年龄判断
  625. if (dto.getPenaltyEnabled() != null) {
  626. child.setPenaltyEnabled(dto.getPenaltyEnabled());
  627. } else {
  628. // 年龄可能为空,默认不扣分
  629. Integer age = dto.getAge();
  630. child.setPenaltyEnabled(age != null && age >= 6 ? 1 : 0);
  631. }
  632. child.setTheme("default");
  633. child.setTotalPoints(0);
  634. child.setStreakDays(0);
  635. child.setFocusMaxDaily(3);
  636. child.setFocusRemaining(3);
  637. child.setFocusResetDate(new Date());
  638. child.setCreatedAt(new Date());
  639. child.setUpdatedAt(new Date());
  640. childMapper.insert(child);
  641. return toChildInfoDTO(child);
  642. }
  643. public List<ChildInfoDTO> getChildren(Long userId) {
  644. User user = userMapper.selectById(userId);
  645. if (user == null) {
  646. return null;
  647. }
  648. List<Child> children = childMapper.selectList(new LambdaQueryWrapper<Child>()
  649. .eq(Child::getFamilyId, user.getFamilyId()));
  650. return children.stream().map(this::toChildInfoDTO).collect(java.util.stream.Collectors.toList());
  651. }
  652. public boolean updateChild(Long userId, Long childId, UpdateChildDTO dto) {
  653. User user = userMapper.selectById(userId);
  654. if (user == null) {
  655. return false;
  656. }
  657. Child child = childMapper.selectById(childId);
  658. if (child == null || child.getFamilyId() == null || !child.getFamilyId().equals(user.getFamilyId())) {
  659. return false;
  660. }
  661. if (dto.getNickname() != null) {
  662. child.setNickname(dto.getNickname());
  663. }
  664. if (dto.getAge() != null) {
  665. child.setAge(dto.getAge());
  666. }
  667. if (dto.getBirthday() != null) {
  668. child.setBirthday(dto.getBirthday());
  669. }
  670. if (dto.getGender() != null) {
  671. child.setGender(dto.getGender());
  672. }
  673. if (dto.getDanLevel() != null) {
  674. child.setDanLevel(dto.getDanLevel());
  675. }
  676. if (dto.getPhone() != null) {
  677. child.setPhone(dto.getPhone());
  678. }
  679. if (dto.getIdCard() != null) {
  680. child.setIdCard(dto.getIdCard());
  681. }
  682. if (dto.getPenaltyEnabled() != null) {
  683. child.setPenaltyEnabled(dto.getPenaltyEnabled());
  684. }
  685. if (dto.getTheme() != null) {
  686. child.setTheme(dto.getTheme());
  687. }
  688. child.setUpdatedAt(new Date());
  689. childMapper.updateById(child);
  690. return true;
  691. }
  692. private String encryptPassword(String password) {
  693. return DigestUtils.md5DigestAsHex(("xzyj_salt_" + password).getBytes(StandardCharsets.UTF_8));
  694. }
  695. private String generateInviteCode() {
  696. return UUID.randomUUID().toString().substring(0, 8).toUpperCase();
  697. }
  698. private ChildInfoDTO toChildInfoDTO(Child child) {
  699. ChildInfoDTO dto = new ChildInfoDTO();
  700. dto.setId(child.getId());
  701. dto.setNickname(child.getNickname());
  702. dto.setAge(child.getAge());
  703. dto.setBirthday(child.getBirthday());
  704. dto.setGender(child.getGender());
  705. dto.setDanLevel(child.getDanLevel());
  706. dto.setPhone(child.getPhone());
  707. dto.setIdCard(child.getIdCard());
  708. dto.setPenaltyEnabled(child.getPenaltyEnabled());
  709. dto.setTheme(child.getTheme());
  710. dto.setTotalPoints(child.getTotalPoints());
  711. dto.setStreakDays(child.getStreakDays());
  712. dto.setFocusRemaining(child.getFocusRemaining());
  713. return dto;
  714. }
  715. /**
  716. * 更新用户信息
  717. */
  718. public boolean updateUserInfo(Long userId, UpdateUserDTO dto) {
  719. User user = userMapper.selectById(userId);
  720. if (user == null) {
  721. return false;
  722. }
  723. // 管理员角色不允许修改
  724. if ("admin".equals(user.getRole())) {
  725. throw new RuntimeException("管理员角色不允许修改");
  726. }
  727. if (dto.getNickname() != null) {
  728. user.setNickname(dto.getNickname());
  729. }
  730. if (dto.getAvatar() != null) {
  731. user.setAvatar(dto.getAvatar());
  732. }
  733. if (dto.getRealName() != null) {
  734. user.setRealName(dto.getRealName());
  735. }
  736. // 更新角色
  737. if (dto.getRole() != null) {
  738. user.setRole(dto.getRole());
  739. }
  740. // 更新性别
  741. if (dto.getGender() != null) {
  742. user.setGender(dto.getGender());
  743. }
  744. // 更新生日(将字符串转换为Date)
  745. if (dto.getBirthday() != null && !dto.getBirthday().isEmpty()) {
  746. try {
  747. java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
  748. user.setBirthday(sdf.parse(dto.getBirthday()));
  749. } catch (Exception e) {
  750. // 忽略解析错误
  751. }
  752. }
  753. // 更新身份证号
  754. if (dto.getIdCard() != null && !dto.getIdCard().isEmpty()) {
  755. user.setIdCard(dto.getIdCard());
  756. // 如果没有手动设置生日,尝试从身份证号提取
  757. if (user.getBirthday() == null) {
  758. String birthdayFromIdCard = extractBirthdayFromIdCard(dto.getIdCard());
  759. if (birthdayFromIdCard != null) {
  760. try {
  761. java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
  762. user.setBirthday(sdf.parse(birthdayFromIdCard));
  763. } catch (Exception e) {
  764. // 忽略解析错误
  765. }
  766. }
  767. }
  768. }
  769. // 更新成长规划师信息
  770. if (dto.getTeacherNo() != null) {
  771. user.setTeacherNo(dto.getTeacherNo());
  772. }
  773. if (dto.getTeacherPhoto() != null) {
  774. user.setTeacherPhoto(dto.getTeacherPhoto());
  775. }
  776. if (dto.getCertificateImage() != null) {
  777. user.setCertificateImage(dto.getCertificateImage());
  778. }
  779. user.setUpdatedAt(new Date());
  780. userMapper.updateById(user);
  781. return true;
  782. }
  783. /**
  784. * 从身份证号提取生日
  785. * 15位:1985年07月08日 -> 1985-07-08
  786. * 18位:1985年07月08日 -> 1985-07-08
  787. */
  788. private String extractBirthdayFromIdCard(String idCard) {
  789. if (idCard == null || idCard.isEmpty()) {
  790. return null;
  791. }
  792. try {
  793. if (idCard.length() == 15) {
  794. // 15位身份证:出生日期在第6-12位
  795. String year = "19" + idCard.substring(6, 8);
  796. String month = idCard.substring(8, 10);
  797. String day = idCard.substring(10, 12);
  798. return String.format("%s-%s-%s", year, month, day);
  799. } else if (idCard.length() == 18) {
  800. // 18位身份证:出生日期在第6-14位
  801. String year = idCard.substring(6, 10);
  802. String month = idCard.substring(10, 12);
  803. String day = idCard.substring(12, 14);
  804. return String.format("%s-%s-%s", year, month, day);
  805. }
  806. } catch (Exception e) {
  807. // 忽略解析异常
  808. }
  809. return null;
  810. }
  811. /**
  812. * 通过邀请码加入家庭
  813. */
  814. public boolean joinFamily(Long userId, String inviteCode) {
  815. if (inviteCode == null || inviteCode.isEmpty()) {
  816. throw new RuntimeException("邀请码不能为空");
  817. }
  818. // 查找家庭
  819. Family family = familyMapper.selectOne(new LambdaQueryWrapper<Family>()
  820. .eq(Family::getInviteCode, inviteCode));
  821. if (family == null) {
  822. throw new RuntimeException("邀请码无效");
  823. }
  824. // 更新用户的家庭ID
  825. User user = userMapper.selectById(userId);
  826. if (user == null) {
  827. throw new RuntimeException("用户不存在");
  828. }
  829. user.setFamilyId(family.getId());
  830. user.setUpdatedAt(new Date());
  831. userMapper.updateById(user);
  832. // 如果家庭绑定了成长规划师,自动将家庭ID添加到成长规划师的 teacherFamilyIds
  833. if (family.getTeacherId() != null) {
  834. User teacher = userMapper.selectById(family.getTeacherId());
  835. if (teacher != null && "teacher".equals(teacher.getRole())) {
  836. String currentIds = teacher.getTeacherFamilyIds();
  837. String newFamilyId = String.valueOf(family.getId());
  838. if (currentIds == null || !currentIds.contains(newFamilyId)) {
  839. String updatedIds = currentIds == null ? newFamilyId : currentIds + "," + newFamilyId;
  840. teacher.setTeacherFamilyIds(updatedIds);
  841. teacher.setUpdatedAt(new Date());
  842. userMapper.updateById(teacher);
  843. }
  844. }
  845. }
  846. return true;
  847. }
  848. /**
  849. * 为用户创建新家庭
  850. */
  851. public Long createFamilyForUser(Long userId, String familyName) {
  852. User user = userMapper.selectById(userId);
  853. if (user == null) {
  854. throw new RuntimeException("用户不存在");
  855. }
  856. // 创建新家庭
  857. Family family = new Family();
  858. family.setName(familyName);
  859. family.setInviteCode(generateInviteCode());
  860. family.setCreatedAt(new Date());
  861. family.setUpdatedAt(new Date());
  862. familyMapper.insert(family);
  863. // 更新用户的家庭ID
  864. user.setFamilyId(family.getId());
  865. user.setUpdatedAt(new Date());
  866. userMapper.updateById(user);
  867. return family.getId();
  868. }
  869. /**
  870. * 使用邀请码注册并加入家庭
  871. */
  872. public LoginResultDTO registerWithInviteCode(RegisterWithInviteCodeDTO dto) {
  873. String phone = dto.getPhone();
  874. String inviteCode = dto.getInviteCode();
  875. String role = dto.getRole() != null ? dto.getRole() : "parent";
  876. String nickname = dto.getNickname() != null ? dto.getNickname() : "用户" + phone.substring(7);
  877. // 查找家庭
  878. Family family = familyMapper.selectOne(new LambdaQueryWrapper<Family>()
  879. .eq(Family::getInviteCode, inviteCode));
  880. User user;
  881. boolean isNewUser = false;
  882. if (family == null) {
  883. throw new RuntimeException("邀请码无效");
  884. }
  885. // 检查手机号是否已注册
  886. User existingUser = userMapper.selectOne(new LambdaQueryWrapper<User>()
  887. .eq(User::getPhone, phone));
  888. if (existingUser != null) {
  889. // 管理员不能登录小程序
  890. if ("admin".equals(existingUser.getRole())) {
  891. throw new RuntimeException("该账号不允许登录小程序");
  892. }
  893. // 手机号已注册,加入家庭
  894. user = existingUser;
  895. user.setFamilyId(family.getId());
  896. user.setUpdatedAt(new Date());
  897. userMapper.updateById(user);
  898. } else {
  899. // 创建新用户并加入家庭
  900. isNewUser = true;
  901. user = new User();
  902. user.setOpenid("phone:" + phone);
  903. user.setUnionid("");
  904. user.setFamilyId(family.getId());
  905. user.setRole(role);
  906. user.setNickname(nickname);
  907. user.setPhone(phone);
  908. user.setPassword("");
  909. user.setCreatedAt(new Date());
  910. user.setUpdatedAt(new Date());
  911. userMapper.insert(user);
  912. }
  913. // 如果家庭绑定了成长规划师,自动将家庭ID添加到成长规划师的 teacherFamilyIds
  914. if (family.getTeacherId() != null) {
  915. User teacher = userMapper.selectById(family.getTeacherId());
  916. if (teacher != null && "teacher".equals(teacher.getRole())) {
  917. String currentIds = teacher.getTeacherFamilyIds();
  918. String newFamilyId = String.valueOf(family.getId());
  919. if (currentIds == null || !currentIds.contains(newFamilyId)) {
  920. String updatedIds = currentIds == null ? newFamilyId : currentIds + "," + newFamilyId;
  921. teacher.setTeacherFamilyIds(updatedIds);
  922. teacher.setUpdatedAt(new Date());
  923. userMapper.updateById(teacher);
  924. }
  925. }
  926. }
  927. // 生成token
  928. String token = jwtConfig.generateToken(user.getId(), user.getRole());
  929. LoginResultDTO result = new LoginResultDTO();
  930. result.setToken(token);
  931. result.setUserId(user.getId());
  932. result.setRole(user.getRole());
  933. result.setRoles(user.getRoles());
  934. result.setFamilyId(user.getFamilyId());
  935. result.setNickname(user.getNickname());
  936. result.setIsNewUser(isNewUser);
  937. populateTeacherFields(result, user);
  938. return result;
  939. }
  940. /**
  941. * 重置用户密码(管理员功能)
  942. * @param userId 用户ID
  943. * @param newPassword 新密码
  944. * @return 是否成功
  945. */
  946. public boolean resetPassword(Long userId, String newPassword) {
  947. if (userId == null || newPassword == null || newPassword.length() < 6) {
  948. return false;
  949. }
  950. User user = userMapper.selectById(userId);
  951. if (user == null) {
  952. return false;
  953. }
  954. user.setPassword(encryptPassword(newPassword));
  955. user.setUpdatedAt(new Date());
  956. return userMapper.updateById(user) > 0;
  957. }
  958. private void populateTeacherFields(LoginResultDTO result, User user) {
  959. result.setTeacherStatus(user.getTeacherStatus() != null ? user.getTeacherStatus() : "pending");
  960. result.setTeacherRejectReason(user.getTeacherRejectReason());
  961. boolean hasTeacher = hasRole(user, "teacher");
  962. result.setHasTeacherRole(hasTeacher);
  963. }
  964. }