|
|
@@ -0,0 +1,133 @@
|
|
|
+import { DataSource, Repository } from 'typeorm';
|
|
|
+import { UserEntity } from '@d8d/user-module';
|
|
|
+import { FileService } from '@d8d/file-module';
|
|
|
+import { JWTUtil } from '@d8d/shared-utils';
|
|
|
+import axios from 'axios';
|
|
|
+import process from 'node:process'
|
|
|
+
|
|
|
+export class MiniAuthService {
|
|
|
+ private userRepository: Repository<UserEntity>;
|
|
|
+ private fileService: FileService;
|
|
|
+
|
|
|
+ constructor(dataSource: DataSource) {
|
|
|
+ this.userRepository = dataSource.getRepository(UserEntity);
|
|
|
+ this.fileService = new FileService(dataSource);
|
|
|
+ }
|
|
|
+
|
|
|
+ async miniLogin(code: string): Promise<{ token: string; user: UserEntity; isNewUser: boolean }> {
|
|
|
+ // 1. 通过code获取openid
|
|
|
+ const openidInfo = await this.getOpenIdByCode(code);
|
|
|
+
|
|
|
+ // 2. 查找或创建用户
|
|
|
+ let user = await this.userRepository.findOne({
|
|
|
+ where: { openid: openidInfo.openid }
|
|
|
+ });
|
|
|
+
|
|
|
+ let isNewUser = false;
|
|
|
+
|
|
|
+ if (!user) {
|
|
|
+ // 自动注册新用户
|
|
|
+ user = await this.createMiniUser(openidInfo);
|
|
|
+ isNewUser = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 生成token
|
|
|
+ const token = this.generateToken(user);
|
|
|
+
|
|
|
+ return { token, user, isNewUser };
|
|
|
+ }
|
|
|
+
|
|
|
+ async updateUserProfile(userId: number, profile: { nickname?: string; avatarUrl?: string }): Promise<UserEntity> {
|
|
|
+ const user = await this.userRepository.findOne({
|
|
|
+ where: { id: userId },
|
|
|
+ relations: ['avatarFile']
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!user) throw new Error('用户不存在');
|
|
|
+
|
|
|
+ if (profile.nickname) user.nickname = profile.nickname;
|
|
|
+
|
|
|
+ // 处理头像:如果用户没有头像且提供了小程序头像URL,则下载保存
|
|
|
+ if (profile.avatarUrl && !user.avatarFileId) {
|
|
|
+ try {
|
|
|
+ const avatarFileId = await this.downloadAndSaveAvatar(profile.avatarUrl, userId);
|
|
|
+ if (avatarFileId) {
|
|
|
+ user.avatarFileId = avatarFileId;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ // 头像下载失败不影响主要功能
|
|
|
+ console.error('头像下载失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return await this.userRepository.save(user);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async getOpenIdByCode(code: string): Promise<{ openid: string; unionid?: string; session_key: string }> {
|
|
|
+ const appId = process.env.WX_MINI_APP_ID;
|
|
|
+ const appSecret = process.env.WX_MINI_APP_SECRET;
|
|
|
+
|
|
|
+ if (!appId || !appSecret) {
|
|
|
+ throw new Error('微信小程序配置缺失');
|
|
|
+ }
|
|
|
+
|
|
|
+ const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code`;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.get(url, { timeout: 10000 });
|
|
|
+
|
|
|
+ if (response.data.errcode) {
|
|
|
+ throw new Error(`微信API错误: ${response.data.errmsg}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ openid: response.data.openid,
|
|
|
+ unionid: response.data.unionid,
|
|
|
+ session_key: response.data.session_key
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ if (axios.isAxiosError(error)) {
|
|
|
+ throw new Error('微信服务器连接失败');
|
|
|
+ }
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async createMiniUser(openidInfo: { openid: string; unionid?: string }): Promise<UserEntity> {
|
|
|
+ const user = this.userRepository.create({
|
|
|
+ username: `wx_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
|
|
|
+ password: '', // 小程序用户不需要密码
|
|
|
+ openid: openidInfo.openid,
|
|
|
+ unionid: openidInfo.unionid,
|
|
|
+ nickname: '微信用户',
|
|
|
+ registrationSource: 'miniapp',
|
|
|
+ isDisabled: 0,
|
|
|
+ isDeleted: 0
|
|
|
+ });
|
|
|
+
|
|
|
+ return await this.userRepository.save(user);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async downloadAndSaveAvatar(avatarUrl: string, userId: number): Promise<number | null> {
|
|
|
+ try {
|
|
|
+ const result = await this.fileService.downloadAndSaveFromUrl(
|
|
|
+ avatarUrl,
|
|
|
+ {
|
|
|
+ uploadUserId: userId,
|
|
|
+ customPath: `avatars/`,
|
|
|
+ mimeType: 'image/jpeg'
|
|
|
+ },
|
|
|
+ { timeout: 10000 }
|
|
|
+ );
|
|
|
+
|
|
|
+ return result.file.id;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('下载保存头像失败:', error);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private generateToken(user: UserEntity): string {
|
|
|
+ return JWTUtil.generateToken(user);
|
|
|
+ }
|
|
|
+}
|