243 lines
6.7 KiB
TypeScript
243 lines
6.7 KiB
TypeScript
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<UserEntity>,
|
||
@InjectRepository(PlatformUserEntity)
|
||
platformUserRepository: Repository<PlatformUserEntity>,
|
||
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<UserAuthResult> {
|
||
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<UserAuthResult> {
|
||
// 微信小程序通常通过login方法完成注册
|
||
return this.login(registerData);
|
||
}
|
||
|
||
async getUserInfo(
|
||
token: string,
|
||
platformUserId: string,
|
||
): Promise<PlatformUserInfo> {
|
||
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<PlatformUserInfo>,
|
||
): Promise<void> {
|
||
// 更新统一用户信息
|
||
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<TokenRefreshResult> {
|
||
// 微信小程序的session_key通常不支持刷新,需要重新登录
|
||
throw new BadRequestException('微信平台需要重新登录');
|
||
}
|
||
|
||
async validateToken(token: string): Promise<boolean> {
|
||
try {
|
||
// 验证微信session_key是否有效
|
||
// 通常通过调用微信API验证
|
||
return true;
|
||
} catch (error) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async revokeToken(token: string): Promise<void> {
|
||
// 微信平台令牌撤销逻辑
|
||
// 清除本地存储的session_key
|
||
}
|
||
|
||
/**
|
||
* 使用code获取微信授权信息
|
||
*/
|
||
private async getWechatAuth(code: string): Promise<WechatAuthResponse> {
|
||
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);
|
||
}
|
||
}
|