Skip to main content
Glama

MCPDemo - Visual SQL Chat Platform

by Ayi456
SmsService.ts8.22 kB
import * as tencentcloud from "tencentcloud-sdk-nodejs-sms"; import { executeMysqlQuery, executeRedisCommand, getRedisClient } from './database.js'; import crypto from 'crypto'; const SmsClient = tencentcloud.sms.v20210111.Client; // 短信配置接口 export interface SmsConfig { secretId: string; secretKey: string; region: string; sdkAppId: string; signName: string; templateId: string; } // 验证码信息接口 export interface VerifyCodeInfo { phone: string; code: string; createdAt: Date; expiresAt: Date; verified: boolean; attempts: number; } /** * 短信服务管理器 */ export class SmsService { private smsClient: any; private config: SmsConfig; private codeExpireMinutes: number = 15; // 验证码过期时间(分钟) private maxAttempts: number = 5; // 最大尝试次数 constructor(config?: Partial<SmsConfig>) { this.config = { secretId: config?.secretId || process.env.TENCENT_SECRET_ID || '', secretKey: config?.secretKey || process.env.TENCENT_SECRET_KEY || '', region: config?.region || process.env.TENCENT_REGION || 'ap-guangzhou', sdkAppId: config?.sdkAppId || process.env.SMS_SDK_APP_ID || '1400535378', signName: config?.signName || process.env.SMS_SIGN_NAME || '札记网络科技', templateId: config?.templateId || process.env.SMS_TEMPLATE_ID || '1106723', }; this.initClient(); } /** * 初始化腾讯云短信客户端 */ private initClient(): void { const clientConfig = { credential: { secretId: this.config.secretId, secretKey: this.config.secretKey, }, region: this.config.region, profile: { httpProfile: { endpoint: "sms.tencentcloudapi.com", }, }, }; this.smsClient = new SmsClient(clientConfig); } /** * 生成随机验证码 */ private generateCode(length: number = 6): string { const digits = '0123456789'; let code = ''; for (let i = 0; i < length; i++) { code += digits.charAt(Math.floor(Math.random() * digits.length)); } return code; } /** * 发送验证码 */ public async sendVerifyCode(phone: string, minute?: number): Promise<{ success: boolean; message: string; code?: string }> { try { // 检查手机号格式 if (!this.isValidPhoneNumber(phone)) { return { success: false, message: '无效的手机号码' }; } // 检查发送频率限制(60秒内只能发送一次) const rateLimitKey = `sms:rate:${phone}`; const isRateLimited = await executeRedisCommand(async () => { const client = getRedisClient(); const exists = await client.exists(rateLimitKey); if (exists) { return true; } await client.setEx(rateLimitKey, 60, '1'); return false; }); if (isRateLimited) { return { success: false, message: '发送过于频繁,请稍后再试' }; } // 生成验证码 const code = this.generateCode(); const expireMinutes = minute || this.codeExpireMinutes; // 调用腾讯云API发送短信 const params = { PhoneNumberSet: [`+86${phone}`], SmsSdkAppId: this.config.sdkAppId, SignName: this.config.signName, TemplateId: this.config.templateId, TemplateParamSet: [code, String(expireMinutes)], }; const response = await this.smsClient.SendSms(params); if (response.SendStatusSet && response.SendStatusSet[0].Code === "Ok") { // 保存验证码到数据库 await this.saveVerifyCode(phone, code, expireMinutes); // 同时缓存到Redis(用于快速验证) const cacheKey = `sms:code:${phone}`; await executeRedisCommand(async () => { const client = getRedisClient(); await client.setEx(cacheKey, expireMinutes * 60, code); }); console.log('验证码已发送并保存:', { phone, codeLength: code.length, expireMinutes }); return { success: true, message: '验证码发送成功', code: code // 内部始终返回验证码供系统使用,API层可以根据环境决定是否暴露 }; } else { console.error('短信发送失败:', response); return { success: false, message: '短信发送失败,请稍后再试' }; } } catch (error) { console.error('发送验证码失败:', error); return { success: false, message: '系统错误,请稍后再试' }; } } /** * 验证验证码 */ public async verifyCode(phone: string, code: string): Promise<{ success: boolean; message: string }> { try { // 先从Redis缓存中查找 const cacheKey = `sms:code:${phone}`; const cachedCode = await executeRedisCommand(async () => { const client = getRedisClient(); return await client.get(cacheKey); }); if (cachedCode === code) { // 验证成功,删除缓存 await executeRedisCommand(async () => { const client = getRedisClient(); await client.del(cacheKey); }); // 标记数据库中的验证码为已使用 await this.markCodeAsUsed(phone, code); return { success: true, message: '验证成功' }; } // 如果缓存中没有,从数据库查找 const result = await executeMysqlQuery<any[]>( `SELECT * FROM sms_codes WHERE phone = ? AND code = ? AND verified = 0 AND expires_at > NOW() AND attempts < ? ORDER BY created_at DESC LIMIT 1`, [phone, code, this.maxAttempts] ); if (result.length > 0) { // 验证成功 await this.markCodeAsUsed(phone, code); return { success: true, message: '验证成功' }; } else { // 增加尝试次数 await this.incrementAttempts(phone); return { success: false, message: '验证码错误或已过期' }; } } catch (error) { console.error('验证码验证失败:', error); return { success: false, message: '验证失败,请重试' }; } } /** * 保存验证码到数据库 */ private async saveVerifyCode(phone: string, code: string, expireMinutes: number): Promise<void> { const expiresAt = new Date(Date.now() + expireMinutes * 60 * 1000); await executeMysqlQuery( `INSERT INTO sms_codes (phone, code, expires_at, created_at, verified, attempts) VALUES (?, ?, ?, NOW(), 0, 0)`, [phone, code, expiresAt] ); } /** * 标记验证码为已使用 */ private async markCodeAsUsed(phone: string, code: string): Promise<void> { await executeMysqlQuery( `UPDATE sms_codes SET verified = 1 WHERE phone = ? AND code = ?`, [phone, code] ); } /** * 增加尝试次数 */ private async incrementAttempts(phone: string): Promise<void> { await executeMysqlQuery( `UPDATE sms_codes SET attempts = attempts + 1 WHERE phone = ? AND verified = 0 AND expires_at > NOW()`, [phone] ); } /** * 验证手机号格式 */ private isValidPhoneNumber(phone: string): boolean { // 中国手机号正则 const phoneRegex = /^1[3-9]\d{9}$/; return phoneRegex.test(phone); } /** * 清理过期验证码 */ public async cleanupExpiredCodes(): Promise<number> { try { const result = await executeMysqlQuery<any>( `DELETE FROM sms_codes WHERE expires_at < NOW() OR verified = 1` ); return result.affectedRows || 0; } catch (error) { console.error('清理过期验证码失败:', error); return 0; } } } // 导出单例 let smsServiceInstance: SmsService | null = null; export function initSmsService(config?: Partial<SmsConfig>): SmsService { if (!smsServiceInstance) { smsServiceInstance = new SmsService(config); } return smsServiceInstance; } export function getSmsService(): SmsService { if (!smsServiceInstance) { throw new Error('SmsService未初始化'); } return smsServiceInstance; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ayi456/visual-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server