feat: 添加JWT认证系统和Zulip用户管理服务

- 新增JWT认证守卫(JwtAuthGuard)和当前用户装饰器(CurrentUser)
- 添加JWT使用示例和完整的认证流程文档
- 实现Zulip用户管理服务,支持用户查询、验证和管理
- 实现Zulip用户注册服务,支持新用户创建和注册流程
- 添加完整的单元测试覆盖
- 新增真实环境测试脚本,验证Zulip API集成
- 更新.gitignore,排除.kiro目录

主要功能:
- JWT令牌验证和用户信息提取
- 用户存在性检查和信息获取
- Zulip API集成和错误处理
- 完整的测试覆盖和文档
This commit is contained in:
moyin
2026-01-06 15:17:05 +08:00
parent f335b72f6d
commit 470b0b8dbf
10 changed files with 2369 additions and 0 deletions

2
.gitignore vendored
View File

@@ -44,3 +44,5 @@ coverage/
# Redis数据文件本地开发用 # Redis数据文件本地开发用
redis-data/ redis-data/
.kiro/

View File

@@ -0,0 +1,39 @@
/**
* 当前用户装饰器
*
* 功能描述:
* - 从请求上下文中提取当前认证用户信息
* - 简化控制器中获取用户信息的操作
*
* 使用示例:
* ```typescript
* @Get('profile')
* @UseGuards(JwtAuthGuard)
* getProfile(@CurrentUser() user: JwtPayload) {
* return { user };
* }
* ```
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-01-05
*/
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { AuthenticatedRequest, JwtPayload } from '../guards/jwt-auth.guard';
/**
* 当前用户装饰器
*
* @param data 可选的属性名,用于获取用户对象的特定属性
* @param ctx 执行上下文
* @returns 用户信息或用户的特定属性
*/
export const CurrentUser = createParamDecorator(
(data: keyof JwtPayload | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest<AuthenticatedRequest>();
const user = request.user;
return data ? user?.[data] : user;
},
);

View File

@@ -0,0 +1,128 @@
/**
* JWT 使用示例
*
* 展示如何在控制器中使用 JWT 认证守卫和当前用户装饰器
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-01-05
*/
import { Controller, Get, UseGuards, Post, Body } from '@nestjs/common';
import { JwtAuthGuard, JwtPayload } from '../guards/jwt-auth.guard';
import { CurrentUser } from '../decorators/current-user.decorator';
/**
* 示例控制器 - 展示 JWT 认证的使用方法
*/
@Controller('example')
export class ExampleController {
/**
* 公开接口 - 无需认证
*/
@Get('public')
getPublicData() {
return {
message: '这是一个公开接口,无需认证',
timestamp: new Date().toISOString(),
};
}
/**
* 受保护的接口 - 需要 JWT 认证
*
* 请求头示例:
* Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
*/
@Get('protected')
@UseGuards(JwtAuthGuard)
getProtectedData(@CurrentUser() user: JwtPayload) {
return {
message: '这是一个受保护的接口,需要有效的 JWT 令牌',
user: {
id: user.sub,
username: user.username,
role: user.role,
},
timestamp: new Date().toISOString(),
};
}
/**
* 获取当前用户信息
*/
@Get('profile')
@UseGuards(JwtAuthGuard)
getUserProfile(@CurrentUser() user: JwtPayload) {
return {
profile: {
userId: user.sub,
username: user.username,
role: user.role,
tokenIssuedAt: new Date(user.iat * 1000).toISOString(),
tokenExpiresAt: new Date(user.exp * 1000).toISOString(),
},
};
}
/**
* 获取用户的特定属性
*/
@Get('username')
@UseGuards(JwtAuthGuard)
getUsername(@CurrentUser('username') username: string) {
return {
username,
message: `你好,${username}`,
};
}
/**
* 需要特定角色的接口
*/
@Post('admin-only')
@UseGuards(JwtAuthGuard)
adminOnlyAction(@CurrentUser() user: JwtPayload, @Body() data: any) {
// 检查用户角色
if (user.role !== 1) { // 假设 1 是管理员角色
return {
success: false,
message: '权限不足,仅管理员可访问',
};
}
return {
success: true,
message: '管理员操作执行成功',
data,
operator: user.username,
};
}
}
/**
* 使用说明:
*
* 1. 首先调用登录接口获取 JWT 令牌:
* POST /auth/login
* {
* "identifier": "username",
* "password": "password"
* }
*
* 2. 从响应中获取 access_token
*
* 3. 在后续请求中添加 Authorization 头:
* Authorization: Bearer <access_token>
*
* 4. 访问受保护的接口:
* GET /example/protected
* GET /example/profile
* GET /example/username
* POST /example/admin-only
*
* 错误处理:
* - 401 Unauthorized: 令牌缺失或无效
* - 403 Forbidden: 令牌有效但权限不足
*/

View File

@@ -0,0 +1,83 @@
/**
* JWT 认证守卫
*
* 功能描述:
* - 验证请求中的 JWT 令牌
* - 提取用户信息并添加到请求上下文
* - 保护需要认证的路由
*
* @author kiro-ai
* @version 1.0.0
* @since 2025-01-05
*/
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
Logger,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
/**
* JWT 载荷接口
*/
export interface JwtPayload {
sub: string; // 用户ID
username: string;
role: number;
iat: number; // 签发时间
exp: number; // 过期时间
}
/**
* 扩展的请求接口,包含用户信息
*/
export interface AuthenticatedRequest extends Request {
user: JwtPayload;
}
@Injectable()
export class JwtAuthGuard implements CanActivate {
private readonly logger = new Logger(JwtAuthGuard.name);
constructor(private readonly jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
const token = this.extractTokenFromHeader(request);
if (!token) {
this.logger.warn('访问被拒绝:缺少认证令牌');
throw new UnauthorizedException('缺少认证令牌');
}
try {
// 验证并解码 JWT 令牌
const payload = await this.jwtService.verifyAsync<JwtPayload>(token);
// 将用户信息添加到请求对象
(request as AuthenticatedRequest).user = payload;
this.logger.log(`用户认证成功: ${payload.username} (ID: ${payload.sub})`);
return true;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : '未知错误';
this.logger.warn(`JWT 令牌验证失败: ${errorMessage}`);
throw new UnauthorizedException('无效的认证令牌');
}
}
/**
* 从请求头中提取 JWT 令牌
*
* @param request 请求对象
* @returns JWT 令牌或 undefined
*/
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}

View File

@@ -0,0 +1,388 @@
/**
* Zulip用户管理服务测试
*
* 功能描述:
* - 测试UserManagementService的核心功能
* - 测试用户查询和验证逻辑
* - 测试错误处理和边界情况
*
* @author angjustinl
* @version 1.0.0
* @since 2025-01-06
*/
import { Test, TestingModule } from '@nestjs/testing';
import { UserManagementService, UserQueryRequest, UserValidationRequest } from './user_management.service';
import { IZulipConfigService } from '../interfaces/zulip-core.interfaces';
// 模拟fetch
global.fetch = jest.fn();
describe('UserManagementService', () => {
let service: UserManagementService;
let mockConfigService: jest.Mocked<IZulipConfigService>;
let mockFetch: jest.MockedFunction<typeof fetch>;
beforeEach(async () => {
// 重置fetch模拟
mockFetch = fetch as jest.MockedFunction<typeof fetch>;
mockFetch.mockClear();
// 创建模拟的配置服务
mockConfigService = {
getZulipConfig: jest.fn().mockReturnValue({
zulipServerUrl: 'https://test.zulip.com',
zulipBotEmail: 'bot@test.com',
zulipBotApiKey: 'test-api-key',
}),
getMapIdByStream: jest.fn(),
getStreamByMap: jest.fn(),
getMapConfig: jest.fn(),
hasMap: jest.fn(),
getAllMapIds: jest.fn(),
getMapConfigByStream: jest.fn(),
getAllStreams: jest.fn(),
hasStream: jest.fn(),
findObjectByTopic: jest.fn(),
getObjectsInMap: jest.fn(),
getTopicByObject: jest.fn(),
findNearbyObject: jest.fn(),
reloadConfig: jest.fn(),
validateConfig: jest.fn(),
getAllMapConfigs: jest.fn(),
getConfigStats: jest.fn(),
getConfigFilePath: jest.fn(),
configFileExists: jest.fn(),
enableConfigWatcher: jest.fn(),
disableConfigWatcher: jest.fn(),
isConfigWatcherEnabled: jest.fn(),
getFullConfiguration: jest.fn(),
updateConfigValue: jest.fn(),
exportMapConfig: jest.fn(),
} as jest.Mocked<IZulipConfigService>;
const module: TestingModule = await Test.createTestingModule({
providers: [
UserManagementService,
{
provide: 'ZULIP_CONFIG_SERVICE',
useValue: mockConfigService,
},
],
}).compile();
service = module.get<UserManagementService>(UserManagementService);
});
it('应该正确初始化服务', () => {
expect(service).toBeDefined();
});
describe('checkUserExists - 检查用户是否存在', () => {
it('应该正确检查存在的用户', async () => {
// 模拟API响应
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
members: [
{
user_id: 1,
email: 'test@example.com',
full_name: 'Test User',
is_active: true,
is_admin: false,
is_bot: false,
},
],
}),
} as Response);
const result = await service.checkUserExists('test@example.com');
expect(result).toBe(true);
expect(mockFetch).toHaveBeenCalledWith(
'https://test.zulip.com/api/v1/users',
expect.objectContaining({
method: 'GET',
headers: expect.objectContaining({
'Authorization': expect.stringContaining('Basic'),
}),
})
);
});
it('应该正确检查不存在的用户', async () => {
// 模拟API响应
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
members: [
{
user_id: 1,
email: 'other@example.com',
full_name: 'Other User',
is_active: true,
is_admin: false,
is_bot: false,
},
],
}),
} as Response);
const result = await service.checkUserExists('test@example.com');
expect(result).toBe(false);
});
it('应该处理无效邮箱', async () => {
const result = await service.checkUserExists('invalid-email');
expect(result).toBe(false);
expect(mockFetch).not.toHaveBeenCalled();
});
it('应该处理API调用失败', async () => {
// 模拟API失败
mockFetch.mockResolvedValueOnce({
ok: false,
status: 500,
statusText: 'Internal Server Error',
} as Response);
const result = await service.checkUserExists('test@example.com');
expect(result).toBe(false);
});
it('应该处理网络异常', async () => {
// 模拟网络异常
mockFetch.mockRejectedValueOnce(new Error('Network error'));
const result = await service.checkUserExists('test@example.com');
expect(result).toBe(false);
});
});
describe('getUserInfo - 获取用户信息', () => {
it('应该成功获取用户信息', async () => {
const request: UserQueryRequest = {
email: 'test@example.com',
};
// 模拟API响应
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
members: [
{
user_id: 1,
email: 'test@example.com',
full_name: 'Test User',
is_active: true,
is_admin: false,
is_bot: false,
},
],
}),
} as Response);
const result = await service.getUserInfo(request);
expect(result.success).toBe(true);
expect(result.userId).toBe(1);
expect(result.email).toBe('test@example.com');
expect(result.fullName).toBe('Test User');
expect(result.isActive).toBe(true);
expect(result.isAdmin).toBe(false);
expect(result.isBot).toBe(false);
});
it('应该处理用户不存在的情况', async () => {
const request: UserQueryRequest = {
email: 'nonexistent@example.com',
};
// 模拟API响应
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
members: [],
}),
} as Response);
const result = await service.getUserInfo(request);
expect(result.success).toBe(false);
expect(result.error).toBe('用户不存在');
});
it('应该拒绝无效邮箱', async () => {
const request: UserQueryRequest = {
email: 'invalid-email',
};
const result = await service.getUserInfo(request);
expect(result.success).toBe(false);
expect(result.error).toBe('邮箱格式无效');
expect(mockFetch).not.toHaveBeenCalled();
});
});
describe('validateUserCredentials - 验证用户凭据', () => {
it('应该成功验证有效的API Key', async () => {
const request: UserValidationRequest = {
email: 'test@example.com',
apiKey: 'valid-api-key',
};
// 模拟API Key验证响应第一个调用
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({}),
} as Response);
// 模拟用户列表API响应第二个调用
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
members: [
{
user_id: 1,
email: 'test@example.com',
full_name: 'Test User',
is_active: true,
is_admin: false,
is_bot: false,
},
],
}),
} as Response);
const result = await service.validateUserCredentials(request);
expect(result.success).toBe(true);
expect(result.isValid).toBe(true);
expect(result.userId).toBe(1);
});
it('应该拒绝无效的API Key', async () => {
const request: UserValidationRequest = {
email: 'test@example.com',
apiKey: 'invalid-api-key',
};
// 模拟API Key验证失败第一个调用
mockFetch.mockResolvedValueOnce({
ok: false,
status: 401,
statusText: 'Unauthorized',
} as Response);
const result = await service.validateUserCredentials(request);
expect(result.success).toBe(true);
expect(result.isValid).toBe(false);
});
it('应该拒绝空的API Key', async () => {
const request: UserValidationRequest = {
email: 'test@example.com',
apiKey: '',
};
const result = await service.validateUserCredentials(request);
expect(result.success).toBe(false);
expect(result.error).toBe('API Key不能为空');
expect(mockFetch).not.toHaveBeenCalled();
});
it('应该拒绝无效邮箱', async () => {
const request: UserValidationRequest = {
email: 'invalid-email',
apiKey: 'some-api-key',
};
const result = await service.validateUserCredentials(request);
expect(result.success).toBe(false);
expect(result.error).toBe('邮箱格式无效');
expect(mockFetch).not.toHaveBeenCalled();
});
});
describe('getAllUsers - 获取所有用户', () => {
it('应该成功获取用户列表', async () => {
// 模拟API响应
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
members: [
{
user_id: 1,
email: 'user1@example.com',
full_name: 'User One',
is_active: true,
is_admin: false,
is_bot: false,
},
{
user_id: 2,
email: 'user2@example.com',
full_name: 'User Two',
is_active: true,
is_admin: true,
is_bot: false,
},
],
}),
} as Response);
const result = await service.getAllUsers();
expect(result.success).toBe(true);
expect(result.users).toHaveLength(2);
expect(result.totalCount).toBe(2);
expect(result.users?.[0]).toEqual({
userId: 1,
email: 'user1@example.com',
fullName: 'User One',
isActive: true,
isAdmin: false,
isBot: false,
});
});
it('应该处理空用户列表', async () => {
// 模拟API响应
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
members: [],
}),
} as Response);
const result = await service.getAllUsers();
expect(result.success).toBe(true);
expect(result.users).toHaveLength(0);
expect(result.totalCount).toBe(0);
});
it('应该处理API调用失败', async () => {
// 模拟API失败
mockFetch.mockResolvedValueOnce({
ok: false,
status: 403,
statusText: 'Forbidden',
} as Response);
const result = await service.getAllUsers();
expect(result.success).toBe(false);
expect(result.error).toContain('API调用失败');
});
});
});

View File

@@ -0,0 +1,539 @@
/**
* 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);
}
}

View File

@@ -0,0 +1,188 @@
/**
* Zulip用户注册服务测试
*
* 功能描述:
* - 测试UserRegistrationService的核心功能
* - 测试用户注册流程和验证逻辑
* - 测试错误处理和边界情况
*
* @author angjustinl
* @version 1.0.0
* @since 2025-01-06
*/
import { Test, TestingModule } from '@nestjs/testing';
import { UserRegistrationService, UserRegistrationRequest } from './user_registration.service';
import { IZulipConfigService } from '../interfaces/zulip-core.interfaces';
describe('UserRegistrationService', () => {
let service: UserRegistrationService;
let mockConfigService: jest.Mocked<IZulipConfigService>;
beforeEach(async () => {
// 创建模拟的配置服务
mockConfigService = {
getZulipConfig: jest.fn().mockReturnValue({
zulipServerUrl: 'https://test.zulip.com',
zulipBotEmail: 'bot@test.com',
zulipBotApiKey: 'test-api-key',
}),
getMapIdByStream: jest.fn(),
getStreamByMap: jest.fn(),
getMapConfig: jest.fn(),
hasMap: jest.fn(),
getAllMapIds: jest.fn(),
getMapConfigByStream: jest.fn(),
getAllStreams: jest.fn(),
hasStream: jest.fn(),
findObjectByTopic: jest.fn(),
getObjectsInMap: jest.fn(),
getTopicByObject: jest.fn(),
findNearbyObject: jest.fn(),
reloadConfig: jest.fn(),
validateConfig: jest.fn(),
getAllMapConfigs: jest.fn(),
getConfigStats: jest.fn(),
getConfigFilePath: jest.fn(),
configFileExists: jest.fn(),
enableConfigWatcher: jest.fn(),
disableConfigWatcher: jest.fn(),
isConfigWatcherEnabled: jest.fn(),
getFullConfiguration: jest.fn(),
updateConfigValue: jest.fn(),
exportMapConfig: jest.fn(),
} as jest.Mocked<IZulipConfigService>;
const module: TestingModule = await Test.createTestingModule({
providers: [
UserRegistrationService,
{
provide: 'ZULIP_CONFIG_SERVICE',
useValue: mockConfigService,
},
],
}).compile();
service = module.get<UserRegistrationService>(UserRegistrationService);
});
it('应该正确初始化服务', () => {
expect(service).toBeDefined();
});
describe('registerUser - 用户注册', () => {
it('应该成功注册有效用户', async () => {
const request: UserRegistrationRequest = {
email: 'test@example.com',
fullName: 'Test User',
password: 'password123',
};
const result = await service.registerUser(request);
expect(result.success).toBe(true);
expect(result.email).toBe(request.email);
expect(result.userId).toBeDefined();
expect(result.apiKey).toBeDefined();
expect(result.error).toBeUndefined();
});
it('应该拒绝无效邮箱', async () => {
const request: UserRegistrationRequest = {
email: 'invalid-email',
fullName: 'Test User',
};
const result = await service.registerUser(request);
expect(result.success).toBe(false);
expect(result.error).toContain('邮箱格式无效');
});
it('应该拒绝空邮箱', async () => {
const request: UserRegistrationRequest = {
email: '',
fullName: 'Test User',
};
const result = await service.registerUser(request);
expect(result.success).toBe(false);
expect(result.error).toContain('邮箱不能为空');
});
it('应该拒绝空用户名', async () => {
const request: UserRegistrationRequest = {
email: 'test@example.com',
fullName: '',
};
const result = await service.registerUser(request);
expect(result.success).toBe(false);
expect(result.error).toContain('用户全名不能为空');
});
it('应该拒绝过短的用户名', async () => {
const request: UserRegistrationRequest = {
email: 'test@example.com',
fullName: 'A',
};
const result = await service.registerUser(request);
expect(result.success).toBe(false);
expect(result.error).toContain('用户全名至少需要2个字符');
});
it('应该拒绝过长的用户名', async () => {
const request: UserRegistrationRequest = {
email: 'test@example.com',
fullName: 'A'.repeat(101), // 101个字符
};
const result = await service.registerUser(request);
expect(result.success).toBe(false);
expect(result.error).toContain('用户全名不能超过100个字符');
});
it('应该拒绝过短的密码', async () => {
const request: UserRegistrationRequest = {
email: 'test@example.com',
fullName: 'Test User',
password: '123', // 只有3个字符
};
const result = await service.registerUser(request);
expect(result.success).toBe(false);
expect(result.error).toContain('密码至少需要6个字符');
});
it('应该接受没有密码的注册', async () => {
const request: UserRegistrationRequest = {
email: 'test@example.com',
fullName: 'Test User',
// 不提供密码
};
const result = await service.registerUser(request);
expect(result.success).toBe(true);
});
it('应该拒绝过长的短名称', async () => {
const request: UserRegistrationRequest = {
email: 'test@example.com',
fullName: 'Test User',
shortName: 'A'.repeat(51), // 51个字符
};
const result = await service.registerUser(request);
expect(result.success).toBe(false);
expect(result.error).toContain('短名称不能超过50个字符');
});
});
});

View File

@@ -0,0 +1,531 @@
/**
* Zulip用户管理服务
*
* 功能描述:
* - 查询和验证Zulip用户信息
* - 检查用户是否存在
* - 获取用户详细信息
* - 管理用户API Key如果有权限
*
* 主要方法:
* - checkUserExists(): 检查用户是否存在
* - getUserInfo(): 获取用户详细信息
* - validateUserCredentials(): 验证用户凭据
* - getUserApiKey(): 获取用户API Key需要管理员权限
*
* 使用场景:
* - 用户登录时验证用户存在性
* - 获取用户基本信息
* - 验证用户权限和状态
*
* @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 ZulipUsersResponse extends ZulipApiResponse {
members?: Array<{
email: string;
user_id: number;
full_name: string;
}>;
}
/**
* 创建用户响应接口
*/
interface ZulipCreateUserResponse extends ZulipApiResponse {
user_id?: number;
}
/**
* API Key响应接口
*/
interface ZulipApiKeyResponse extends ZulipApiResponse {
api_key?: string;
}
export interface UserRegistrationRequest {
email: string;
fullName: string;
password?: string;
shortName?: string;
}
/**
* 用户注册响应接口
*/
export interface UserRegistrationResponse {
success: boolean;
userId?: number;
email?: string;
apiKey?: string;
error?: string;
details?: any;
}
/**
* Zulip用户注册服务类
*
* 职责:
* - 处理新用户在Zulip服务器上的注册
* - 验证用户信息的有效性
* - 与Zulip API交互创建用户账户
* - 管理注册流程和错误处理
*/
@Injectable()
export class UserRegistrationService {
private readonly logger = new Logger(UserRegistrationService.name);
constructor(
@Inject('ZULIP_CONFIG_SERVICE')
private readonly configService: IZulipConfigService,
) {
this.logger.log('UserRegistrationService初始化完成');
}
/**
* 注册新用户到Zulip服务器
*
* 功能描述:
* 在Zulip服务器上创建新用户账户
*
* 业务逻辑:
* 1. 验证用户注册信息
* 2. 检查用户是否已存在
* 3. 调用Zulip API创建用户
* 4. 获取用户API Key
* 5. 返回注册结果
*
* @param request 用户注册请求数据
* @returns Promise<UserRegistrationResponse>
*/
async registerUser(request: UserRegistrationRequest): Promise<UserRegistrationResponse> {
const startTime = Date.now();
this.logger.log('开始注册Zulip用户', {
operation: 'registerUser',
email: request.email,
fullName: request.fullName,
timestamp: new Date().toISOString(),
});
try {
// 1. 验证用户注册信息
const validationResult = this.validateUserInfo(request);
if (!validationResult.valid) {
this.logger.warn('用户注册信息验证失败', {
operation: 'registerUser',
email: request.email,
errors: validationResult.errors,
});
return {
success: false,
error: validationResult.errors.join(', '),
};
}
// TODO: 实现实际的Zulip用户注册逻辑
// 这里先返回模拟结果后续步骤中实现真实的API调用
// 2. 检查用户是否已存在
const userExists = await this.checkUserExists(request.email);
if (userExists) {
this.logger.warn('用户注册失败:用户已存在', {
operation: 'registerUser',
email: request.email,
});
return {
success: false,
error: '用户已存在',
};
}
// 3. 调用Zulip API创建用户
const createResult = await this.createZulipUser(request);
if (!createResult.success) {
return {
success: false,
error: createResult.error || '创建用户失败',
};
}
// 4. 获取用户API Key如果需要
let apiKey = undefined;
if (createResult.userId) {
const apiKeyResult = await this.generateApiKey(createResult.userId, request.email);
if (apiKeyResult.success) {
apiKey = apiKeyResult.apiKey;
}
}
const duration = Date.now() - startTime;
this.logger.log('Zulip用户注册完成模拟', {
operation: 'registerUser',
email: request.email,
duration,
timestamp: new Date().toISOString(),
});
return {
success: true,
userId: createResult.userId,
email: request.email,
apiKey: apiKey,
};
} catch (error) {
const err = error as Error;
const duration = Date.now() - startTime;
this.logger.error('Zulip用户注册失败', {
operation: 'registerUser',
email: request.email,
error: err.message,
duration,
timestamp: new Date().toISOString(),
}, err.stack);
return {
success: false,
error: '注册失败,请稍后重试',
};
}
}
/**
* 验证用户注册信息
*
* 功能描述:
* 验证用户提供的注册信息是否有效
*
* @param request 用户注册请求
* @returns {valid: boolean, errors: string[]} 验证结果
* @private
*/
private validateUserInfo(request: UserRegistrationRequest): {
valid: boolean;
errors: string[];
} {
const errors: string[] = [];
// 验证邮箱
if (!request.email || !request.email.trim()) {
errors.push('邮箱不能为空');
} else if (!this.isValidEmail(request.email)) {
errors.push('邮箱格式无效');
}
// 验证全名
if (!request.fullName || !request.fullName.trim()) {
errors.push('用户全名不能为空');
} else if (request.fullName.trim().length < 2) {
errors.push('用户全名至少需要2个字符');
} else if (request.fullName.trim().length > 100) {
errors.push('用户全名不能超过100个字符');
}
// 验证密码(如果提供)
if (request.password && request.password.length < 6) {
errors.push('密码至少需要6个字符');
}
// 验证短名称(如果提供)
if (request.shortName && request.shortName.trim().length > 50) {
errors.push('短名称不能超过50个字符');
}
return {
valid: errors.length === 0,
errors,
};
}
/**
* 验证邮箱格式
*
* @param email 邮箱地址
* @returns boolean 是否有效
* @private
*/
private isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* 检查用户是否已存在
*
* 功能描述:
* 通过Zulip API检查指定邮箱的用户是否已存在
*
* @param email 用户邮箱
* @returns Promise<boolean> 是否存在
* @private
*/
private async checkUserExists(email: string): Promise<boolean> {
this.logger.debug('检查用户是否存在', {
operation: 'checkUserExists',
email,
});
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: 'checkUserExists',
status: response.status,
statusText: response.statusText,
});
return false; // 如果API调用失败假设用户不存在
}
const data: ZulipUsersResponse = await response.json();
// 检查用户是否在列表中
if (data.members && Array.isArray(data.members)) {
const userExists = data.members.some((user: any) =>
user.email && user.email.toLowerCase() === email.toLowerCase()
);
this.logger.debug('用户存在性检查完成', {
operation: 'checkUserExists',
email,
exists: userExists,
});
return userExists;
}
return false;
} catch (error) {
const err = error as Error;
this.logger.error('检查用户存在性失败', {
operation: 'checkUserExists',
email,
error: err.message,
});
// 如果检查失败,假设用户不存在,允许继续注册
return false;
}
}
/**
* 创建Zulip用户
*
* 功能描述:
* 通过Zulip API创建新用户账户
*
* @param request 用户注册请求
* @returns Promise<{success: boolean, userId?: number, error?: string}>
* @private
*/
private async createZulipUser(request: UserRegistrationRequest): Promise<{
success: boolean;
userId?: number;
error?: string;
}> {
this.logger.log('开始创建Zulip用户', {
operation: 'createZulipUser',
email: request.email,
fullName: request.fullName,
});
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 requestBody = new URLSearchParams();
requestBody.append('email', request.email);
requestBody.append('full_name', request.fullName);
if (request.password) {
requestBody.append('password', request.password);
}
if (request.shortName) {
requestBody.append('short_name', request.shortName);
}
// 发送请求
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: requestBody.toString(),
});
const data: ZulipCreateUserResponse = await response.json();
if (!response.ok) {
this.logger.warn('Zulip用户创建失败', {
operation: 'createZulipUser',
email: request.email,
status: response.status,
statusText: response.statusText,
error: data.msg || data.message,
});
return {
success: false,
error: data.msg || data.message || '创建用户失败',
};
}
this.logger.log('Zulip用户创建成功', {
operation: 'createZulipUser',
email: request.email,
userId: data.user_id,
});
return {
success: true,
userId: data.user_id,
};
} catch (error) {
const err = error as Error;
this.logger.error('创建Zulip用户异常', {
operation: 'createZulipUser',
email: request.email,
error: err.message,
}, err.stack);
return {
success: false,
error: '系统错误,请稍后重试',
};
}
}
/**
* 为用户生成API Key
*
* 功能描述:
* 为新创建的用户生成API Key用于后续的Zulip API调用
*
* @param userId 用户ID
* @param email 用户邮箱
* @returns Promise<{success: boolean, apiKey?: string, error?: string}>
* @private
*/
private async generateApiKey(userId: number, email: string): Promise<{
success: boolean;
apiKey?: string;
error?: string;
}> {
this.logger.log('开始生成用户API Key', {
operation: 'generateApiKey',
userId,
email,
});
try {
// 获取Zulip配置
const config = this.configService.getZulipConfig();
// 构建API URL
const apiUrl = `${config.zulipServerUrl}/api/v1/users/${userId}/api_key/regenerate`;
// 构建认证头
const auth = Buffer.from(`${config.zulipBotEmail}:${config.zulipBotApiKey}`).toString('base64');
// 发送请求
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
});
const data: ZulipApiKeyResponse = await response.json();
if (!response.ok) {
this.logger.warn('生成API Key失败', {
operation: 'generateApiKey',
userId,
email,
status: response.status,
statusText: response.statusText,
error: data.msg || data.message,
});
return {
success: false,
error: data.msg || data.message || '生成API Key失败',
};
}
this.logger.log('API Key生成成功', {
operation: 'generateApiKey',
userId,
email,
});
return {
success: true,
apiKey: data.api_key,
};
} catch (error) {
const err = error as Error;
this.logger.error('生成API Key异常', {
operation: 'generateApiKey',
userId,
email,
error: err.message,
}, err.stack);
return {
success: false,
error: '系统错误,请稍后重试',
};
}
}
}

196
test_zulip_registration.js Normal file
View File

@@ -0,0 +1,196 @@
/**
* Zulip用户注册真实环境测试脚本
*
* 功能描述:
* - 测试Zulip用户注册功能在真实环境下的表现
* - 验证API调用是否正常工作
* - 检查配置是否正确
*
* 使用方法:
* node test_zulip_registration.js
*
* @author angjustinl
* @version 1.0.0
* @since 2025-01-06
*/
const https = require('https');
const { URLSearchParams } = require('url');
// 配置信息
const config = {
zulipServerUrl: 'https://zulip.xinghangee.icu',
zulipBotEmail: 'angjustinl@mail.angforever.top',
zulipBotApiKey: 'lCPWCPfGh7WUHxwN56GF8oYXOpqNfGF8',
};
/**
* 检查用户是否存在
*/
async function checkUserExists(email) {
console.log(`🔍 检查用户是否存在: ${email}`);
try {
const url = `${config.zulipServerUrl}/api/v1/users`;
const auth = Buffer.from(`${config.zulipBotEmail}:${config.zulipBotApiKey}`).toString('base64');
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
console.log(`❌ 获取用户列表失败: ${response.status} ${response.statusText}`);
return false;
}
const data = await response.json();
console.log(`📊 获取到 ${data.members?.length || 0} 个用户`);
if (data.members && Array.isArray(data.members)) {
const userExists = data.members.some(user =>
user.email && user.email.toLowerCase() === email.toLowerCase()
);
console.log(`✅ 用户存在性检查完成: ${userExists ? '存在' : '不存在'}`);
return userExists;
}
return false;
} catch (error) {
console.error(`❌ 检查用户存在性失败:`, error.message);
return false;
}
}
/**
* 创建测试用户
*/
async function createTestUser(email, fullName, password) {
console.log(`🚀 开始创建用户: ${email}`);
try {
const url = `${config.zulipServerUrl}/api/v1/users`;
const auth = Buffer.from(`${config.zulipBotEmail}:${config.zulipBotApiKey}`).toString('base64');
const requestBody = new URLSearchParams();
requestBody.append('email', email);
requestBody.append('full_name', fullName);
if (password) {
requestBody.append('password', password);
}
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: requestBody.toString(),
});
const data = await response.json();
if (!response.ok) {
console.log(`❌ 用户创建失败: ${response.status} ${response.statusText}`);
console.log(`📝 错误信息: ${data.msg || data.message || '未知错误'}`);
return { success: false, error: data.msg || data.message };
}
console.log(`✅ 用户创建成功! 用户ID: ${data.user_id}`);
return { success: true, userId: data.user_id };
} catch (error) {
console.error(`❌ 创建用户异常:`, error.message);
return { success: false, error: error.message };
}
}
/**
* 测试连接
*/
async function testConnection() {
console.log('🔗 测试Zulip服务器连接...');
try {
const url = `${config.zulipServerUrl}/api/v1/server_settings`;
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
console.log(`✅ 连接成功! 服务器版本: ${data.zulip_version || '未知'}`);
return true;
} else {
console.log(`❌ 连接失败: ${response.status} ${response.statusText}`);
return false;
}
} catch (error) {
console.error(`❌ 连接异常:`, error.message);
return false;
}
}
/**
* 主测试函数
*/
async function main() {
console.log('🎯 开始Zulip用户注册测试');
console.log('=' * 50);
// 1. 测试连接
const connected = await testConnection();
if (!connected) {
console.log('❌ 无法连接到Zulip服务器测试终止');
return;
}
console.log('');
// 2. 生成测试用户信息
const timestamp = Date.now();
const testEmail = `test_user_${timestamp}@example.com`;
const testFullName = `Test User ${timestamp}`;
const testPassword = 'test123456';
console.log(`📋 测试用户信息:`);
console.log(` 邮箱: ${testEmail}`);
console.log(` 姓名: ${testFullName}`);
console.log(` 密码: ${testPassword}`);
console.log('');
// 3. 检查用户是否已存在
const userExists = await checkUserExists(testEmail);
if (userExists) {
console.log('⚠️ 用户已存在,跳过创建测试');
return;
}
console.log('');
// 4. 创建用户
const createResult = await createTestUser(testEmail, testFullName, testPassword);
console.log('');
console.log('📊 测试结果:');
if (createResult.success) {
console.log('✅ 用户注册功能正常工作');
console.log(` 新用户ID: ${createResult.userId}`);
} else {
console.log('❌ 用户注册功能存在问题');
console.log(` 错误信息: ${createResult.error}`);
}
console.log('');
console.log('🎉 测试完成');
}
// 运行测试
main().catch(error => {
console.error('💥 测试过程中发生未处理的错误:', error);
process.exit(1);
});

View File

@@ -0,0 +1,275 @@
/**
* Zulip用户管理真实环境测试脚本
*
* 功能描述:
* - 测试Zulip用户管理功能在真实环境下的表现
* - 验证用户查询、验证等API调用是否正常工作
* - 检查配置是否正确
*
* 使用方法:
* node test_zulip_user_management.js
*
* @author angjustinl
* @version 1.0.0
* @since 2025-01-06
*/
const https = require('https');
// 配置信息
const config = {
zulipServerUrl: 'https://zulip.xinghangee.icu',
zulipBotEmail: 'angjustinl@mail.angforever.top',
zulipBotApiKey: 'lCPWCPfGh7WUHxwN56GF8oYXOpqNfGF8',
};
/**
* 获取所有用户列表
*/
async function getAllUsers() {
console.log('📋 获取所有用户列表...');
try {
const url = `${config.zulipServerUrl}/api/v1/users`;
const auth = Buffer.from(`${config.zulipBotEmail}:${config.zulipBotApiKey}`).toString('base64');
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
console.log(`❌ 获取用户列表失败: ${response.status} ${response.statusText}`);
return { success: false, error: `${response.status} ${response.statusText}` };
}
const data = 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,
})) || [];
console.log(`✅ 成功获取 ${users.length} 个用户`);
// 显示前几个用户信息
console.log('👥 用户列表预览:');
users.slice(0, 5).forEach((user, index) => {
console.log(` ${index + 1}. ${user.fullName} (${user.email})`);
console.log(` ID: ${user.userId}, 活跃: ${user.isActive}, 管理员: ${user.isAdmin}, 机器人: ${user.isBot}`);
});
if (users.length > 5) {
console.log(` ... 还有 ${users.length - 5} 个用户`);
}
return { success: true, users, totalCount: users.length };
} catch (error) {
console.error(`❌ 获取用户列表异常:`, error.message);
return { success: false, error: error.message };
}
}
/**
* 检查指定用户是否存在
*/
async function checkUserExists(email) {
console.log(`🔍 检查用户是否存在: ${email}`);
try {
const usersResult = await getAllUsers();
if (!usersResult.success) {
console.log(`❌ 无法获取用户列表: ${usersResult.error}`);
return false;
}
const userExists = usersResult.users.some(user =>
user.email.toLowerCase() === email.toLowerCase()
);
console.log(`✅ 用户存在性检查完成: ${userExists ? '存在' : '不存在'}`);
return userExists;
} catch (error) {
console.error(`❌ 检查用户存在性失败:`, error.message);
return false;
}
}
/**
* 获取用户详细信息
*/
async function getUserInfo(email) {
console.log(`📝 获取用户信息: ${email}`);
try {
const usersResult = await getAllUsers();
if (!usersResult.success) {
console.log(`❌ 无法获取用户列表: ${usersResult.error}`);
return { success: false, error: usersResult.error };
}
const user = usersResult.users.find(u =>
u.email.toLowerCase() === email.toLowerCase()
);
if (!user) {
console.log(`❌ 用户不存在: ${email}`);
return { success: false, error: '用户不存在' };
}
console.log(`✅ 用户信息获取成功:`);
console.log(` 用户ID: ${user.userId}`);
console.log(` 邮箱: ${user.email}`);
console.log(` 姓名: ${user.fullName}`);
console.log(` 状态: ${user.isActive ? '活跃' : '非活跃'}`);
console.log(` 权限: ${user.isAdmin ? '管理员' : '普通用户'}`);
console.log(` 类型: ${user.isBot ? '机器人' : '真实用户'}`);
return { success: true, user };
} catch (error) {
console.error(`❌ 获取用户信息失败:`, error.message);
return { success: false, error: error.message };
}
}
/**
* 测试用户API Key
*/
async function testUserApiKey(email, apiKey) {
console.log(`🔑 测试用户API Key: ${email}`);
try {
const url = `${config.zulipServerUrl}/api/v1/users/me`;
const auth = Buffer.from(`${email}:${apiKey}`).toString('base64');
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
},
});
const isValid = response.ok;
if (isValid) {
const data = await response.json();
console.log(`✅ API Key有效! 用户信息:`);
console.log(` 用户ID: ${data.user_id}`);
console.log(` 邮箱: ${data.email}`);
console.log(` 姓名: ${data.full_name}`);
} else {
console.log(`❌ API Key无效: ${response.status} ${response.statusText}`);
}
return isValid;
} catch (error) {
console.error(`❌ 测试API Key异常:`, error.message);
return false;
}
}
/**
* 测试连接
*/
async function testConnection() {
console.log('🔗 测试Zulip服务器连接...');
try {
const url = `${config.zulipServerUrl}/api/v1/server_settings`;
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
console.log(`✅ 连接成功! 服务器信息:`);
console.log(` 版本: ${data.zulip_version || '未知'}`);
console.log(` 服务器: ${data.realm_name || '未知'}`);
return true;
} else {
console.log(`❌ 连接失败: ${response.status} ${response.statusText}`);
return false;
}
} catch (error) {
console.error(`❌ 连接异常:`, error.message);
return false;
}
}
/**
* 主测试函数
*/
async function main() {
console.log('🎯 开始Zulip用户管理测试');
console.log('='.repeat(50));
// 1. 测试连接
const connected = await testConnection();
if (!connected) {
console.log('❌ 无法连接到Zulip服务器测试终止');
return;
}
console.log('');
// 2. 获取所有用户列表
const usersResult = await getAllUsers();
if (!usersResult.success) {
console.log('❌ 无法获取用户列表,测试终止');
return;
}
console.log('');
// 3. 测试用户存在性检查
const testEmails = [
'angjustinl@mail.angforever.top', // 应该存在
'nonexistent@example.com', // 应该不存在
];
console.log('🔍 测试用户存在性检查:');
for (const email of testEmails) {
const exists = await checkUserExists(email);
console.log(` ${email}: ${exists ? '✅ 存在' : '❌ 不存在'}`);
}
console.log('');
// 4. 测试获取用户信息
console.log('📝 测试获取用户信息:');
const existingEmail = 'angjustinl@mail.angforever.top';
const userInfoResult = await getUserInfo(existingEmail);
console.log('');
// 5. 测试API Key验证如果有的话
console.log('🔑 测试API Key验证:');
const testApiKey = 'lCPWCPfGh7WUHxwN56GF8oYXOpqNfGF8'; // 这是我们的测试API Key
const apiKeyValid = await testUserApiKey(existingEmail, testApiKey);
console.log('');
console.log('📊 测试结果总结:');
console.log(`✅ 服务器连接: 正常`);
console.log(`✅ 用户列表获取: 正常 (${usersResult.totalCount} 个用户)`);
console.log(`✅ 用户存在性检查: 正常`);
console.log(`✅ 用户信息获取: ${userInfoResult.success ? '正常' : '异常'}`);
console.log(`✅ API Key验证: ${apiKeyValid ? '正常' : '异常'}`);
console.log('');
console.log('🎉 用户管理功能测试完成');
}
// 运行测试
main().catch(error => {
console.error('💥 测试过程中发生未处理的错误:', error);
process.exit(1);
});