import type { ResultSetHeader, RowDataPacket } from 'mysql2'; /** * 反馈表的全部 SQL 出口。 * * SQL 与参数顺序在此固定:路由只做校验与组装,不再拼 SQL。 * 接口由 mock pool 的契约测试锁定(见 routes.test.ts)。 */ /** 只需要 query 能力的最小依赖,便于在测试中注入 mock。 */ export interface Database { query(sql: string, values?: unknown[]): Promise<[T, ...any[]]>; } // 写入时间戳一律用东八区 CST,避免依赖 MySQL/容器时区设置。 const CST_NOW = `DATE_ADD(UTC_TIMESTAMP(), INTERVAL 8 HOUR)`; // 两个列表接口的列集合刻意不同:管理列表额外返回 user_id / user_name。 const USER_LIST_COLUMNS = `id, type, module, content, contact, screenshots, status, reply_content, reply_user, reply_at, created_at`; const ADMIN_LIST_COLUMNS = `id, type, module, content, contact, screenshots, user_id, user_name, status, reply_content, reply_user, reply_at, created_at`; export interface NewFeedback { type: string; module: string | null; content: string; contact: string | null; screenshotsJson: string; userId: string | null; userName: string | null; userAgent: string | null; } export async function insertFeedback(db: Database, row: NewFeedback): Promise { const [result] = await db.query( `INSERT INTO bi_user_feedback (type, module, content, contact, screenshots, user_id, user_name, user_agent, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ${CST_NOW})`, [row.type, row.module, row.content, row.contact, row.screenshotsJson, row.userId, row.userName, row.userAgent], ); return result.insertId; } /** 当前用户的反馈历史(最多 100 条)。 */ export async function listFeedbackForUser(db: Database, userId: string): Promise { const [rows] = await db.query( `SELECT ${USER_LIST_COLUMNS} FROM bi_user_feedback WHERE user_id = ? ORDER BY created_at DESC LIMIT 100`, [userId], ); return rows; } /** 管理列表:可选状态过滤 + 条数上限。 */ export async function listFeedbackForAdmin( db: Database, options: { status: string | null; limit: number }, ): Promise { const where: string[] = ['1=1']; const params: (string | number)[] = []; if (options.status) { where.push('status = ?'); params.push(options.status); } const [rows] = await db.query( `SELECT ${ADMIN_LIST_COLUMNS} FROM bi_user_feedback WHERE ${where.join(' AND ')} ORDER BY created_at DESC LIMIT ?`, [...params, options.limit], ); return rows; } export interface FeedbackUpdate { status?: string; /** 传 undefined 表示不更新回复;传 null/'' 表示清空。 */ reply?: string | null; replyUser?: string | null; } /** 管理更新。返回 false 表示没有任何可更新字段。 */ export async function updateFeedback(db: Database, id: number, update: FeedbackUpdate): Promise { const fields: string[] = []; const params: (string | number | null)[] = []; if (update.status !== undefined) { fields.push('status = ?'); params.push(update.status); } if (update.reply !== undefined) { fields.push('reply_content = ?', 'reply_user = ?', `reply_at = ${CST_NOW}`); params.push(update.reply || null, update.replyUser ?? null); } if (fields.length === 0) return false; await db.query( `UPDATE bi_user_feedback SET ${fields.join(', ')} WHERE id = ?`, [...params, id], ); return true; }