小火机器人
基于飞书平台的高性能机器人应用,使用 TypeScript 和 Next.js 实现。通过二维码识别与权限验证,自动将用户分配到选手群或观众群。
项目概述
小火机器人是一个智能化的群组管理系统,用户只需发送二维码图片,机器人即可自动识别、验证权限并将用户添加到对应群组。该系统通过多级图像处理确保高识别率,并实现结果缓存机制以应对高并发场景。
技术架构
核心技术栈
- TypeScript:类型安全的开发体验
- Next.js:全栈 Web 框架
- @zxing/library:二维码识别
- sharp:高性能图像处理
- @larksuiteoapi/node-sdk:飞书 API 集成
- node-cache:内存缓存
- axios:HTTP 请求
- winston:日志管理
核心功能实现
1. 二维码识别与处理
使用 ZXing 库结合 Sharp 实现高识别率的二维码解析:
import { BrowserMultiFormatReader } from '@zxing/library';
import sharp from 'sharp';
class QRCodeParser {
private reader: BrowserMultiFormatReader;
constructor() {
this.reader = new BrowserMultiFormatReader();
}
async parseQRCode(imageBuffer: Buffer): Promise<string> {
try {
// 尝试直接识别
const result = await this.tryParse(imageBuffer);
if (result) return result;
// 增强对比度后识别
const enhanced = await this.enhanceContrast(imageBuffer);
const result2 = await this.tryParse(enhanced);
if (result2) return result2;
// 颜色反转后识别
const inverted = await this.invertColors(imageBuffer);
const result3 = await this.tryParse(inverted);
if (result3) return result3;
throw new Error('无法识别二维码');
} catch (error) {
throw new Error(`二维码解析失败: ${error.message}`);
}
}
private async tryParse(buffer: Buffer): Promise<string | null> {
try {
const image = await sharp(buffer)
.png()
.toBuffer();
const result = await this.reader.decodeFromBuffer(image);
return result?.getText() || null;
} catch {
return null;
}
}
private async enhanceContrast(buffer: Buffer): Promise<Buffer> {
return await sharp(buffer)
.normalize()
.linear(1.5, -(128 * 1.5) + 128)
.toBuffer();
}
private async invertColors(buffer: Buffer): Promise<Buffer> {
return await sharp(buffer)
.negate()
.toBuffer();
}
}
2. 飞书 API 集成
封装飞书 API 客户端,实现消息接收与发送:
import * as lark from '@larksuiteoapi/node-sdk';
class FeishuClient {
private client: lark.Client;
constructor(appId: string, appSecret: string) {
this.client = new lark.Client({
appId,
appSecret,
appType: lark.AppType.SelfBuild,
});
}
async getImageContent(messageId: string, fileKey: string): Promise<Buffer> {
const response = await this.client.im.messageResource.get({
path: {
message_id: messageId,
file_key: fileKey,
},
});
return Buffer.from(response.file);
}
async sendMessage(chatId: string, content: string): Promise<void> {
await this.client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: chatId,
msg_type: 'text',
content: JSON.stringify({ text: content }),
},
});
}
async addUserToChat(chatId: string, userId: string): Promise<void> {
await this.client.im.chatMembers.create({
path: {
chat_id: chatId,
},
data: {
id_list: [userId],
},
});
}
}
3. 权限验证系统
与外部 API 对接,验证用户权限:
import axios from 'axios';
interface VerificationResult {
isValid: boolean;
userType: 'player' | 'audience';
userName?: string;
}
class VerificationClient {
private apiUrl: string;
private apiToken: string;
constructor(apiUrl: string, apiToken: string) {
this.apiUrl = apiUrl;
this.apiToken = apiToken;
}
async verifyQRCode(uuid: string): Promise<VerificationResult> {
try {
const response = await axios.post(
`${this.apiUrl}/verify`,
{ uuid },
{
headers: {
'Authorization': `Bearer ${this.apiToken}`,
'Content-Type': 'application/json',
},
timeout: 5000,
}
);
return {
isValid: response.data.valid,
userType: response.data.type,
userName: response.data.name,
};
} catch (error) {
throw new Error(`验证失败: ${error.message}`);
}
}
}
4. 缓存机制
实现结果缓存,提升高并发性能:
import NodeCache from 'node-cache';
class CacheManager {
private cache: NodeCache;
constructor() {
this.cache = new NodeCache({
stdTTL: 55,
checkperiod: 60,
useClones: false,
});
}
get(key: string): any {
return this.cache.get(key);
}
set(key: string, value: any, ttl?: number): boolean {
return this.cache.set(key, value, ttl || 55);
}
del(key: string): number {
return this.cache.del(key);
}
flush(): void {
this.cache.flushAll();
}
getStats() {
return this.cache.getStats();
}
}
const cache = new CacheManager();
async function verifyWithCache(
uuid: string,
verificationClient: VerificationClient
): Promise<VerificationResult> {
const cacheKey = `verify:${uuid}`;
const cached = cache.get(cacheKey);
if (cached) {
return cached;
}
const result = await verificationClient.verifyQRCode(uuid);
cache.set(cacheKey, result);
return result;
}
5. 事件处理流程
实现完整的消息事件处理流程:
class ChatEventHandler {
private feishuClient: FeishuClient;
private qrParser: QRCodeParser;
private verificationClient: VerificationClient;
constructor(
feishuClient: FeishuClient,
qrParser: QRCodeParser,
verificationClient: VerificationClient
) {
this.feishuClient = feishuClient;
this.qrParser = qrParser;
this.verificationClient = verificationClient;
}
async handleImageMessage(event: any): Promise<void> {
const { message_id, chat_id, sender, content } = event;
const userId = sender.sender_id.user_id;
try {
await this.feishuClient.sendMessage(
chat_id,
'正在处理您的二维码图片...'
);
const imageContent = JSON.parse(content);
const fileKey = imageContent.file_key;
const imageBuffer = await this.feishuClient.getImageContent(
message_id,
fileKey
);
const uuid = await this.qrParser.parseQRCode(imageBuffer);
await this.feishuClient.sendMessage(
chat_id,
'正在验证您的权限...'
);
const result = await verifyWithCache(uuid, this.verificationClient);
if (!result.isValid) {
await this.feishuClient.sendMessage(
chat_id,
'抱歉,验证失败。请确保您使用的是有效的二维码。'
);
return;
}
const targetChatId = this.getTargetChatId(result.userType);
await this.feishuClient.addUserToChat(targetChatId, userId);
const groupName = result.userType === 'player' ? '选手群' : '观众群';
await this.feishuClient.sendMessage(
chat_id,
`恭喜!您已成功加入${groupName}。`
);
} catch (error) {
await this.feishuClient.sendMessage(
chat_id,
`处理失败: ${error.message}`
);
}
}
private getTargetChatId(userType: 'player' | 'audience'): string {
const config = {
player: process.env.PLAYER_CHAT_ID,
audience: process.env.AUDIENCE_CHAT_ID,
};
return config[userType] || '';
}
}
6. Webhook 端点实现
使用 Next.js API Routes 实现飞书事件订阅:
import type { NextApiRequest, NextApiResponse } from 'next';
import { createEventHandler } from '@/lib/feishu/events';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { type, challenge, event } = req.body;
if (type === 'url_verification') {
return res.status(200).json({ challenge });
}
if (type === 'event_callback') {
const eventHandler = createEventHandler();
try {
await eventHandler.handle(event);
return res.status(200).json({ success: true });
} catch (error) {
return res.status(500).json({ error: error.message });
}
}
return res.status(400).json({ error: 'Unknown event type' });
}
性能优化
1. 图像处理优化
通过 Sharp 实现高性能图像处理:
async function optimizeImage(buffer: Buffer): Promise<Buffer> {
return await sharp(buffer)
.resize(800, 800, {
fit: 'inside',
withoutEnlargement: true,
})
.png({ quality: 90 })
.toBuffer();
}
2. 并发控制
实现请求队列,避免过载:
class RequestQueue {
private queue: Array<() => Promise<any>> = [];
private running = 0;
private maxConcurrent = 10;
async add<T>(task: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
}
});
this.process();
});
}
private async process(): Promise<void> {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.running++;
const task = this.queue.shift();
if (task) {
await task();
}
this.running--;
this.process();
}
}
技术挑战与解决方案
挑战 1:二维码识别率不稳定
解决方案:多级图像处理(对比度增强、颜色反转),确保高识别率
挑战 2:高并发场景性能瓶颈
解决方案:结果缓存机制 + 请求队列,提升系统吞吐量
挑战 3:外部 API 调用延迟
解决方案:缓存验证结果,减少重复请求
挑战 4:用户体验优化
解决方案:实时反馈处理进度,提升用户感知
项目成果
- 实现二维码自动识别与权限验证
- 支持自动群组分配,简化用户操作
- 通过缓存机制提升高并发性能
- 完整的日志记录与错误处理
技术启示
- 图像处理需多级优化:单一算法无法覆盖所有场景
- 缓存是性能关键:合理的缓存策略显著提升系统性能
- 用户体验需精心设计:实时反馈让用户感知更好
- 错误处理要完善:外部依赖可能失败,需要优雅降级