Files
whale-town-end/src/core/zulip/services/user_management.service.ts
moyin 470b0b8dbf feat: 添加JWT认证系统和Zulip用户管理服务
- 新增JWT认证守卫(JwtAuthGuard)和当前用户装饰器(CurrentUser)
- 添加JWT使用示例和完整的认证流程文档
- 实现Zulip用户管理服务,支持用户查询、验证和管理
- 实现Zulip用户注册服务,支持新用户创建和注册流程
- 添加完整的单元测试覆盖
- 新增真实环境测试脚本,验证Zulip API集成
- 更新.gitignore,排除.kiro目录

主要功能:
- JWT令牌验证和用户信息提取
- 用户存在性检查和信息获取
- Zulip API集成和错误处理
- 完整的测试覆盖和文档
2026-01-06 15:17:05 +08:00

539 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Zulip用户管理服务
*
* 功能描述:
* - 查询和验证Zulip用户信息
* - 检查用户是否存在
* - 获取用户详细信息
* - 验证用户凭据和权限
*
* 主要方法:
* - checkUserExists(): 检查用户是否存在
* - getUserInfo(): 获取用户详细信息
* - validateUserCredentials(): 验证用户凭据
* - getAllUsers(): 获取所有用户列表
*
* 使用场景:
* - 用户登录时验证用户存在性
* - 获取用户基本信息
* - 验证用户权限和状态
* - 管理员查看用户列表
*
* @author angjustinl
* @version 1.0.0
* @since 2025-01-06
*/
import { Injectable, Logger, Inject } from '@nestjs/common';
import { IZulipConfigService } from '../interfaces/zulip-core.interfaces';
/**
* Zulip API响应接口
*/
interface ZulipApiResponse {
result?: 'success' | 'error';
msg?: string;
message?: string;
}
/**
* 用户信息接口
*/
interface ZulipUser {
user_id: number;
email: string;
full_name: string;
is_active: boolean;
is_admin: boolean;
is_owner: boolean;
is_bot: boolean;
date_joined: string;
}
/**
* 用户列表响应接口
*/
interface ZulipUsersResponse extends ZulipApiResponse {
members?: ZulipUser[];
}
/**
* 用户查询请求接口
*/
export interface UserQueryRequest {
email: string;
}
/**
* 用户信息响应接口
*/
export interface UserInfoResponse {
success: boolean;
userId?: number;
email?: string;
fullName?: string;
isActive?: boolean;
isAdmin?: boolean;
isBot?: boolean;
dateJoined?: string;
error?: string;
}
/**
* 用户验证请求接口
*/
export interface UserValidationRequest {
email: string;
apiKey?: string;
}
/**
* 用户验证响应接口
*/
export interface UserValidationResponse {
success: boolean;
isValid?: boolean;
userId?: number;
error?: string;
}
/**
* 用户列表响应接口
*/
export interface UsersListResponse {
success: boolean;
users?: Array<{
userId: number;
email: string;
fullName: string;
isActive: boolean;
isAdmin: boolean;
isBot: boolean;
}>;
totalCount?: number;
error?: string;
}
/**
* Zulip用户管理服务类
*
* 职责:
* - 查询和验证Zulip用户信息
* - 检查用户是否存在于Zulip服务器
* - 获取用户详细信息和权限状态
* - 提供用户管理相关的API接口
*/
@Injectable()
export class UserManagementService {
private readonly logger = new Logger(UserManagementService.name);
constructor(
@Inject('ZULIP_CONFIG_SERVICE')
private readonly configService: IZulipConfigService,
) {
this.logger.log('UserManagementService初始化完成');
}
/**
* 检查用户是否存在
*
* 功能描述:
* 通过Zulip API检查指定邮箱的用户是否存在
*
* 业务逻辑:
* 1. 获取所有用户列表
* 2. 在列表中查找指定邮箱
* 3. 返回用户存在性结果
*
* @param email 用户邮箱
* @returns Promise<boolean> 是否存在
*/
async checkUserExists(email: string): Promise<boolean> {
const startTime = Date.now();
this.logger.log('开始检查用户是否存在', {
operation: 'checkUserExists',
email,
timestamp: new Date().toISOString(),
});
try {
// 1. 验证邮箱格式
if (!email || !this.isValidEmail(email)) {
this.logger.warn('邮箱格式无效', {
operation: 'checkUserExists',
email,
});
return false;
}
// 2. 获取用户列表
const usersResult = await this.getAllUsers();
if (!usersResult.success) {
this.logger.warn('获取用户列表失败', {
operation: 'checkUserExists',
email,
error: usersResult.error,
});
return false;
}
// 3. 检查用户是否存在
const userExists = usersResult.users?.some(user =>
user.email.toLowerCase() === email.toLowerCase()
) || false;
const duration = Date.now() - startTime;
this.logger.log('用户存在性检查完成', {
operation: 'checkUserExists',
email,
exists: userExists,
duration,
timestamp: new Date().toISOString(),
});
return userExists;
} catch (error) {
const err = error as Error;
const duration = Date.now() - startTime;
this.logger.error('检查用户存在性失败', {
operation: 'checkUserExists',
email,
error: err.message,
duration,
timestamp: new Date().toISOString(),
}, err.stack);
return false;
}
}
/**
* 获取用户详细信息
*
* 功能描述:
* 根据邮箱获取用户的详细信息
*
* @param request 用户查询请求
* @returns Promise<UserInfoResponse>
*/
async getUserInfo(request: UserQueryRequest): Promise<UserInfoResponse> {
const startTime = Date.now();
this.logger.log('开始获取用户信息', {
operation: 'getUserInfo',
email: request.email,
timestamp: new Date().toISOString(),
});
try {
// 1. 验证请求参数
if (!request.email || !this.isValidEmail(request.email)) {
return {
success: false,
error: '邮箱格式无效',
};
}
// 2. 获取用户列表
const usersResult = await this.getAllUsers();
if (!usersResult.success) {
return {
success: false,
error: usersResult.error || '获取用户列表失败',
};
}
// 3. 查找指定用户
const user = usersResult.users?.find(u =>
u.email.toLowerCase() === request.email.toLowerCase()
);
if (!user) {
return {
success: false,
error: '用户不存在',
};
}
const duration = Date.now() - startTime;
this.logger.log('用户信息获取完成', {
operation: 'getUserInfo',
email: request.email,
userId: user.userId,
duration,
timestamp: new Date().toISOString(),
});
return {
success: true,
userId: user.userId,
email: user.email,
fullName: user.fullName,
isActive: user.isActive,
isAdmin: user.isAdmin,
isBot: user.isBot,
};
} catch (error) {
const err = error as Error;
const duration = Date.now() - startTime;
this.logger.error('获取用户信息失败', {
operation: 'getUserInfo',
email: request.email,
error: err.message,
duration,
timestamp: new Date().toISOString(),
}, err.stack);
return {
success: false,
error: '系统错误,请稍后重试',
};
}
}
/**
* 验证用户凭据
*
* 功能描述:
* 验证用户的API Key是否有效
*
* @param request 用户验证请求
* @returns Promise<UserValidationResponse>
*/
async validateUserCredentials(request: UserValidationRequest): Promise<UserValidationResponse> {
const startTime = Date.now();
this.logger.log('开始验证用户凭据', {
operation: 'validateUserCredentials',
email: request.email,
hasApiKey: !!request.apiKey,
timestamp: new Date().toISOString(),
});
try {
// 1. 验证请求参数
if (!request.email || !this.isValidEmail(request.email)) {
return {
success: false,
error: '邮箱格式无效',
};
}
if (!request.apiKey) {
return {
success: false,
error: 'API Key不能为空',
};
}
// 2. 使用用户的API Key测试连接
const isValid = await this.testUserApiKey(request.email, request.apiKey);
// 3. 如果API Key有效获取用户ID
let userId = undefined;
if (isValid) {
const userInfo = await this.getUserInfo({ email: request.email });
if (userInfo.success) {
userId = userInfo.userId;
}
}
const duration = Date.now() - startTime;
this.logger.log('用户凭据验证完成', {
operation: 'validateUserCredentials',
email: request.email,
isValid,
userId,
duration,
timestamp: new Date().toISOString(),
});
return {
success: true,
isValid,
userId,
};
} catch (error) {
const err = error as Error;
const duration = Date.now() - startTime;
this.logger.error('验证用户凭据失败', {
operation: 'validateUserCredentials',
email: request.email,
error: err.message,
duration,
timestamp: new Date().toISOString(),
}, err.stack);
return {
success: false,
error: '系统错误,请稍后重试',
};
}
}
/**
* 获取所有用户列表
*
* 功能描述:
* 从Zulip服务器获取所有用户的列表
*
* @returns Promise<UsersListResponse>
*/
async getAllUsers(): Promise<UsersListResponse> {
this.logger.debug('开始获取用户列表', {
operation: 'getAllUsers',
timestamp: new Date().toISOString(),
});
try {
// 获取Zulip配置
const config = this.configService.getZulipConfig();
// 构建API URL
const apiUrl = `${config.zulipServerUrl}/api/v1/users`;
// 构建认证头
const auth = Buffer.from(`${config.zulipBotEmail}:${config.zulipBotApiKey}`).toString('base64');
// 发送请求
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
this.logger.warn('获取用户列表失败', {
operation: 'getAllUsers',
status: response.status,
statusText: response.statusText,
});
return {
success: false,
error: `API调用失败: ${response.status} ${response.statusText}`,
};
}
const data: ZulipUsersResponse = await response.json();
// 转换数据格式
const users = data.members?.map(user => ({
userId: user.user_id,
email: user.email,
fullName: user.full_name,
isActive: user.is_active,
isAdmin: user.is_admin,
isBot: user.is_bot,
})) || [];
this.logger.debug('用户列表获取完成', {
operation: 'getAllUsers',
userCount: users.length,
timestamp: new Date().toISOString(),
});
return {
success: true,
users,
totalCount: users.length,
};
} catch (error) {
const err = error as Error;
this.logger.error('获取用户列表异常', {
operation: 'getAllUsers',
error: err.message,
timestamp: new Date().toISOString(),
}, err.stack);
return {
success: false,
error: '系统错误,请稍后重试',
};
}
}
/**
* 测试用户API Key是否有效
*
* 功能描述:
* 使用用户的API Key测试是否能够成功调用Zulip API
*
* @param email 用户邮箱
* @param apiKey 用户API Key
* @returns Promise<boolean> 是否有效
* @private
*/
private async testUserApiKey(email: string, apiKey: string): Promise<boolean> {
this.logger.debug('测试用户API Key', {
operation: 'testUserApiKey',
email,
});
try {
// 获取Zulip配置
const config = this.configService.getZulipConfig();
// 构建API URL - 使用获取用户自己信息的接口
const apiUrl = `${config.zulipServerUrl}/api/v1/users/me`;
// 使用用户的API Key构建认证头
const auth = Buffer.from(`${email}:${apiKey}`).toString('base64');
// 发送请求
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
});
const isValid = response.ok;
this.logger.debug('API Key测试完成', {
operation: 'testUserApiKey',
email,
isValid,
status: response.status,
});
return isValid;
} catch (error) {
const err = error as Error;
this.logger.error('测试API Key异常', {
operation: 'testUserApiKey',
email,
error: err.message,
});
return false;
}
}
/**
* 验证邮箱格式
*
* @param email 邮箱地址
* @returns boolean 是否有效
* @private
*/
private isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}