import { Injectable, BadRequestException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { BaseAdapter } from './base.adapter'; import { UserEntity } from '../../entities/user.entity'; import { PlatformUserEntity, PlatformType } from '../../entities/platform-user.entity'; import { PlatformLoginData, PlatformRegisterData, UserAuthResult, PlatformUserInfo, TokenRefreshResult, } from '../interfaces/platform.interface'; import crypto from 'crypto'; interface WechatAuthResponse { openid: string; session_key: string; unionid?: string; errcode?: number; errmsg?: string; } interface WechatUserInfo { openId: string; nickName: string; gender: number; language: string; city: string; province: string; country: string; avatarUrl: string; } @Injectable() export class WechatAdapter extends BaseAdapter { platform = PlatformType.WECHAT; constructor( httpService: HttpService, configService: ConfigService, @InjectRepository(UserEntity) userRepository: Repository, @InjectRepository(PlatformUserEntity) platformUserRepository: Repository, jwtService: JwtService, ) { super( httpService, configService, userRepository, platformUserRepository, jwtService, ); } private readonly wechatConfig = { appId: this.configService.get('WECHAT_APP_ID'), appSecret: this.configService.get('WECHAT_APP_SECRET'), }; async login(loginData: PlatformLoginData): Promise { try { // 1. 使用code换取session_key和openid const authResult = await this.getWechatAuth(loginData.code); // 2. 解密用户信息(如果提供) let userInfo: WechatUserInfo; if (loginData.encryptedData && loginData.iv) { userInfo = this.decryptWechatUserInfo( loginData.encryptedData, loginData.iv, authResult.session_key, ); } else { // 使用基础信息创建用户 userInfo = { openId: authResult.openid, nickName: loginData.userInfo?.nickName || '微信用户', avatarUrl: loginData.userInfo?.avatarUrl || '', gender: loginData.userInfo?.gender || 0, language: 'zh_CN', city: '', province: '', country: 'CN', }; } // 3. 创建或查找统一用户 const platformUserInfo: PlatformUserInfo = { platformUserId: authResult.openid, nickname: userInfo.nickName, avatarUrl: userInfo.avatarUrl, gender: userInfo.gender, country: userInfo.country, province: userInfo.province, city: userInfo.city, unionid: authResult.unionid, }; const unifiedUser = await this.findOrCreateUnifiedUser(platformUserInfo); // 4. 更新平台用户数据 await this.updatePlatformUserData(unifiedUser.id, authResult, userInfo); // 5. 生成JWT令牌 const tokens = await this.generateTokens(unifiedUser, this.platform); return { user: this.formatUnifiedUserInfo(unifiedUser), tokens, platformData: userInfo, }; } catch (error) { const platformError = this.handlePlatformError(error, '微信'); throw new BadRequestException(`微信登录失败: ${platformError.message}`); } } async register(registerData: PlatformRegisterData): Promise { // 微信小程序通常通过login方法完成注册 return this.login(registerData); } async getUserInfo( token: string, platformUserId: string, ): Promise { const platformUser = await this.platformUserRepository.findOne({ where: { platform: this.platform, platformUserId, }, relations: ['user'], }); if (!platformUser) { throw new BadRequestException('用户不存在'); } return { platformUserId: platformUser.platformUserId, nickname: platformUser.user.nickname, avatarUrl: platformUser.user.avatarUrl, phone: platformUser.user.phone, email: platformUser.user.email, ...platformUser.platformData, }; } async updateUserInfo( userId: string, updateData: Partial, ): Promise { // 更新统一用户信息 await this.userRepository.update(userId, { nickname: updateData.nickname, avatarUrl: updateData.avatarUrl, phone: updateData.phone, email: updateData.email, }); // 更新平台用户数据 const platformUser = await this.platformUserRepository.findOne({ where: { userId, platform: this.platform }, }); if (platformUser) { platformUser.platformData = { ...platformUser.platformData, ...updateData, }; await this.platformUserRepository.save(platformUser); } } async refreshToken(refreshToken: string): Promise { // 微信小程序的session_key通常不支持刷新,需要重新登录 throw new BadRequestException('微信平台需要重新登录'); } async validateToken(token: string): Promise { try { // 验证微信session_key是否有效 // 通常通过调用微信API验证 return true; } catch (error) { return false; } } async revokeToken(token: string): Promise { // 微信平台令牌撤销逻辑 // 清除本地存储的session_key } /** * 使用code获取微信授权信息 */ private async getWechatAuth(code: string): Promise { const url = `https://api.weixin.qq.com/sns/jscode2session`; const params = { appid: this.wechatConfig.appId, secret: this.wechatConfig.appSecret, js_code: code, grant_type: 'authorization_code', }; const response = await this.httpService.axiosRef.get(url, { params }); if (response.data.errcode) { throw new Error(`微信认证失败: ${response.data.errmsg}`); } return response.data; } /** * 解密微信用户信息 */ private decryptWechatUserInfo( encryptedData: string, iv: string, sessionKey: string, ): WechatUserInfo { // 实现微信用户信息解密逻辑 // 使用crypto模块进行AES解密 const decipher = crypto.createDecipheriv( 'aes-128-cbc', Buffer.from(sessionKey, 'base64'), Buffer.from(iv, 'base64'), ); let decrypted = decipher.update(encryptedData, 'base64', 'utf8'); decrypted += decipher.final('utf8'); return JSON.parse(decrypted); } }