fix: address code review feedback - mask phone, add cache limits, cleanup expired entries
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/38d62fa6-b645-4f42-92dd-00d327337c17
This commit is contained in:
parent
e860b9963b
commit
403fc62e84
|
|
@ -5,7 +5,7 @@ const requestCounts = new Map<string, { count: number; resetTime: number }>();
|
||||||
|
|
||||||
export function rateLimiter(maxRequests: number = 10, windowMs: number = 60000) {
|
export function rateLimiter(maxRequests: number = 10, windowMs: number = 60000) {
|
||||||
return (req: Request, res: Response, next: NextFunction): void => {
|
return (req: Request, res: Response, next: NextFunction): void => {
|
||||||
const ip = req.ip || req.socket.remoteAddress || 'unknown';
|
const ip = req.ip || req.socket.remoteAddress || req.headers['x-forwarded-for']?.toString() || 'unknown';
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const record = requestCounts.get(ip);
|
const record = requestCounts.get(ip);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,13 @@ import { rateLimiter } from '../middleware/rateLimiter';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
function maskPhone(phone: string): string {
|
||||||
|
if (phone.length >= 7) {
|
||||||
|
return phone.slice(0, 3) + '****' + phone.slice(-4);
|
||||||
|
}
|
||||||
|
return '***';
|
||||||
|
}
|
||||||
|
|
||||||
// Send verification code
|
// Send verification code
|
||||||
router.post('/send-code', rateLimiter(5, 60000), async (req: Request, res: Response): Promise<void> => {
|
router.post('/send-code', rateLimiter(5, 60000), async (req: Request, res: Response): Promise<void> => {
|
||||||
const { phone } = req.body;
|
const { phone } = req.body;
|
||||||
|
|
@ -66,7 +73,7 @@ router.post('/register', rateLimiter(5, 60000), async (req: Request, res: Respon
|
||||||
id: user.id,
|
id: user.id,
|
||||||
nickname: user.nickname,
|
nickname: user.nickname,
|
||||||
role: user.role,
|
role: user.role,
|
||||||
phone: user.phone,
|
phone: maskPhone(user.phone),
|
||||||
aiCompanion: user.aiCompanion,
|
aiCompanion: user.aiCompanion,
|
||||||
creditScore: user.creditScore,
|
creditScore: user.creditScore,
|
||||||
},
|
},
|
||||||
|
|
@ -106,7 +113,7 @@ router.post('/login', rateLimiter(10, 60000), async (req: Request, res: Response
|
||||||
id: user.id,
|
id: user.id,
|
||||||
nickname: user.nickname,
|
nickname: user.nickname,
|
||||||
role: user.role,
|
role: user.role,
|
||||||
phone: user.phone,
|
phone: maskPhone(user.phone),
|
||||||
aiCompanion: user.aiCompanion,
|
aiCompanion: user.aiCompanion,
|
||||||
creditScore: user.creditScore,
|
creditScore: user.creditScore,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,34 @@ const openai = new OpenAI({
|
||||||
});
|
});
|
||||||
|
|
||||||
// Per-user conversation history (in-memory; use Redis in production)
|
// Per-user conversation history (in-memory; use Redis in production)
|
||||||
const conversationCache = new Map<string, Array<{ role: 'user' | 'assistant'; content: string }>>();
|
const MAX_CACHE_USERS = 1000;
|
||||||
|
const conversationCache = new Map<string, { messages: Array<{ role: 'user' | 'assistant'; content: string }>; lastAccess: number }>();
|
||||||
|
|
||||||
|
// Evict least-recently-used entries when cache exceeds limit
|
||||||
|
function getHistory(userId: string): Array<{ role: 'user' | 'assistant'; content: string }> {
|
||||||
|
const entry = conversationCache.get(userId);
|
||||||
|
if (entry) {
|
||||||
|
entry.lastAccess = Date.now();
|
||||||
|
return entry.messages;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHistory(userId: string, messages: Array<{ role: 'user' | 'assistant'; content: string }>): void {
|
||||||
|
if (conversationCache.size >= MAX_CACHE_USERS) {
|
||||||
|
// Evict oldest entry
|
||||||
|
let oldestKey = '';
|
||||||
|
let oldestTime = Infinity;
|
||||||
|
for (const [key, val] of conversationCache.entries()) {
|
||||||
|
if (val.lastAccess < oldestTime) {
|
||||||
|
oldestTime = val.lastAccess;
|
||||||
|
oldestKey = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldestKey) conversationCache.delete(oldestKey);
|
||||||
|
}
|
||||||
|
conversationCache.set(userId, { messages, lastAccess: Date.now() });
|
||||||
|
}
|
||||||
|
|
||||||
export async function callAI(params: {
|
export async function callAI(params: {
|
||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
|
|
@ -16,7 +43,7 @@ export async function callAI(params: {
|
||||||
const { systemPrompt, userMessage, userId } = params;
|
const { systemPrompt, userMessage, userId } = params;
|
||||||
|
|
||||||
// Get recent conversation history (last 10 turns = 20 messages)
|
// Get recent conversation history (last 10 turns = 20 messages)
|
||||||
const history = conversationCache.get(userId) || [];
|
const history = getHistory(userId);
|
||||||
|
|
||||||
const messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }> = [
|
const messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }> = [
|
||||||
{ role: 'system', content: systemPrompt },
|
{ role: 'system', content: systemPrompt },
|
||||||
|
|
@ -40,7 +67,7 @@ export async function callAI(params: {
|
||||||
{ role: 'user' as const, content: userMessage },
|
{ role: 'user' as const, content: userMessage },
|
||||||
{ role: 'assistant' as const, content: aiResponse },
|
{ role: 'assistant' as const, content: aiResponse },
|
||||||
].slice(-20);
|
].slice(-20);
|
||||||
conversationCache.set(userId, updatedHistory);
|
setHistory(userId, updatedHistory);
|
||||||
|
|
||||||
return aiResponse;
|
return aiResponse;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ export async function sendVerificationCode(phone: string): Promise<{ success: bo
|
||||||
|
|
||||||
// Check rate limit (1 code per 60 seconds per phone)
|
// Check rate limit (1 code per 60 seconds per phone)
|
||||||
const existing = codeStore.get(phone);
|
const existing = codeStore.get(phone);
|
||||||
if (existing && existing.expiresAt - Date.now() > 4 * 60 * 1000) {
|
if (existing && (existing.expiresAt - Date.now()) > 4 * 60 * 1000) {
|
||||||
|
// Code was stored less than 60 seconds ago (5min total - 4min remaining = 1min elapsed)
|
||||||
return { success: false, message: '验证码发送过于频繁,请稍后再试' };
|
return { success: false, message: '验证码发送过于频繁,请稍后再试' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,3 +58,13 @@ function storeCode(phone: string, code: string): void {
|
||||||
expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
|
expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup expired codes periodically
|
||||||
|
setInterval(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
for (const [key, value] of codeStore.entries()) {
|
||||||
|
if (now > value.expiresAt) {
|
||||||
|
codeStore.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 60000);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue