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:
copilot-swe-agent[bot] 2026-03-25 13:05:31 +00:00
parent e860b9963b
commit 403fc62e84
4 changed files with 52 additions and 7 deletions

View File

@ -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);

View File

@ -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,
}, },

View File

@ -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) {

View File

@ -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);