mini-auth.service.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import { DataSource, Repository } from 'typeorm';
  2. import { UserEntity } from '@/server/modules/users/user.entity';
  3. import { FileService } from '@/server/modules/files/file.service';
  4. import { JWTUtil } from '@/server/utils/jwt.util';
  5. import axios from 'axios';
  6. import process from 'node:process'
  7. export class MiniAuthService {
  8. private userRepository: Repository<UserEntity>;
  9. private fileService: FileService;
  10. constructor(private dataSource: DataSource) {
  11. this.userRepository = dataSource.getRepository(UserEntity);
  12. this.fileService = new FileService(dataSource);
  13. }
  14. async miniLogin(code: string): Promise<{ token: string; user: UserEntity; isNewUser: boolean }> {
  15. // 1. 通过code获取openid
  16. const openidInfo = await this.getOpenIdByCode(code);
  17. // 2. 查找或创建用户
  18. let user = await this.userRepository.findOne({
  19. where: { openid: openidInfo.openid }
  20. });
  21. let isNewUser = false;
  22. if (!user) {
  23. // 自动注册新用户
  24. user = await this.createMiniUser(openidInfo);
  25. isNewUser = true;
  26. }
  27. // 3. 生成token
  28. const token = this.generateToken(user);
  29. return { token, user, isNewUser };
  30. }
  31. async updateUserProfile(userId: number, profile: { nickname?: string; avatarUrl?: string }): Promise<UserEntity> {
  32. const user = await this.userRepository.findOne({
  33. where: { id: userId },
  34. relations: ['avatarFile']
  35. });
  36. if (!user) throw new Error('用户不存在');
  37. if (profile.nickname) user.nickname = profile.nickname;
  38. // 处理头像:如果用户没有头像且提供了小程序头像URL,则下载保存
  39. if (profile.avatarUrl && !user.avatarFileId) {
  40. try {
  41. const avatarFileId = await this.downloadAndSaveAvatar(profile.avatarUrl, userId);
  42. if (avatarFileId) {
  43. user.avatarFileId = avatarFileId;
  44. }
  45. } catch (error) {
  46. // 头像下载失败不影响主要功能
  47. console.error('头像下载失败:', error);
  48. }
  49. }
  50. return await this.userRepository.save(user);
  51. }
  52. private async getOpenIdByCode(code: string): Promise<{ openid: string; unionid?: string; session_key: string }> {
  53. const appId = process.env.WX_MINI_APP_ID;
  54. const appSecret = process.env.WX_MINI_APP_SECRET;
  55. if (!appId || !appSecret) {
  56. throw new Error('微信小程序配置缺失');
  57. }
  58. const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code`;
  59. try {
  60. const response = await axios.get(url, { timeout: 10000 });
  61. if (response.data.errcode) {
  62. throw new Error(`微信API错误: ${response.data.errmsg}`);
  63. }
  64. return {
  65. openid: response.data.openid,
  66. unionid: response.data.unionid,
  67. session_key: response.data.session_key
  68. };
  69. } catch (error) {
  70. if (axios.isAxiosError(error)) {
  71. throw new Error('微信服务器连接失败');
  72. }
  73. throw error;
  74. }
  75. }
  76. private async createMiniUser(openidInfo: { openid: string; unionid?: string }): Promise<UserEntity> {
  77. const user = this.userRepository.create({
  78. username: `wx_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
  79. password: '', // 小程序用户不需要密码
  80. openid: openidInfo.openid,
  81. unionid: openidInfo.unionid,
  82. nickname: '微信用户',
  83. registrationSource: 'miniapp',
  84. isDisabled: 0,
  85. isDeleted: 0
  86. });
  87. return await this.userRepository.save(user);
  88. }
  89. private async downloadAndSaveAvatar(avatarUrl: string, userId: number): Promise<number | null> {
  90. try {
  91. const result = await this.fileService.downloadAndSaveFromUrl(
  92. avatarUrl,
  93. {
  94. uploadUserId: userId,
  95. customPath: `avatars/`,
  96. mimeType: 'image/jpeg'
  97. },
  98. { timeout: 10000 }
  99. );
  100. return result.file.id;
  101. } catch (error) {
  102. console.error('下载保存头像失败:', error);
  103. return null;
  104. }
  105. }
  106. private generateToken(user: UserEntity): string {
  107. return JWTUtil.generateToken(user);
  108. }
  109. }