Files
xiaocai/server/src/routes/feedback.ts
wangxiaogang 41cddcd2c1 fix: 修复 feedback 查询 LIMIT/OFFSET 参数类型错误
- 和 logs.ts 同样的问题,execute 对参数类型要求严格
- 改为直接嵌入 SQL 字符串
2026-06-08 16:24:15 +08:00

130 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Router } from 'express'
import pool from '../db/connection'
import { authMiddleware } from '../middleware/auth'
import { requireAdmin } from '../middleware/requireAdmin'
const router = Router()
/** 用户提交反馈 */
router.post('/', authMiddleware, async (req, res) => {
try {
const userId = (req as any).userId
const { type, content, contact } = req.body
if (!content || !content.trim()) {
return res.status(400).json({ code: 40001, message: '请填写反馈内容' })
}
const validTypes = ['bug', 'feature', 'ux', 'other']
const feedbackType = validTypes.includes(type) ? type : 'other'
await pool.execute(
'INSERT INTO feedbacks (user_id, type, content, contact) VALUES (?, ?, ?, ?)',
[userId, feedbackType, content.trim(), contact || '']
)
res.json({ code: 0 })
} catch (error) {
console.error('Submit feedback error:', error)
res.status(500).json({ code: 50000, message: '提交失败' })
}
})
/** 管理员获取反馈列表 */
router.get('/', requireAdmin, async (req, res) => {
try {
const page = Math.max(1, parseInt(req.query.page as string) || 1)
const pageSize = Math.min(100, Math.max(1, parseInt(req.query.pageSize as string) || 20))
const status = req.query.status as string
const offset = (page - 1) * pageSize
let where = '1=1'
const params: any[] = []
if (status && ['pending', 'processed', 'ignored'].includes(status)) {
where += ' AND f.status = ?'
params.push(status)
}
const [countResult] = await pool.execute(
`SELECT COUNT(*) as total FROM feedbacks f WHERE ${where}`,
params
)
const total = (countResult as any[])[0].total
// LIMIT/OFFSET 直接嵌入 SQLexecute 对参数类型要求严格)
const [rows] = await pool.execute(
`SELECT f.*, u.nickname, u.avatar_url
FROM feedbacks f
LEFT JOIN users u ON f.user_id = u.id
WHERE ${where}
ORDER BY f.created_at DESC
LIMIT ${pageSize} OFFSET ${offset}`,
params
)
res.json({ code: 0, data: { list: rows, total, page, pageSize } })
} catch (error) {
console.error('Get feedback list error:', error)
res.status(500).json({ code: 50000, message: '获取失败' })
}
})
/** 管理员更新反馈状态 */
router.put('/:id/status', requireAdmin, async (req, res) => {
try {
const { id } = req.params
const { status, admin_reply } = req.body
const validStatuses = ['pending', 'processed', 'ignored']
if (!validStatuses.includes(status)) {
return res.status(400).json({ code: 40001, message: '无效的状态' })
}
// 查询反馈信息(用于发送通知)
const [feedbackRows] = await pool.execute(
'SELECT user_id, content FROM feedbacks WHERE id = ?',
[id]
)
const feedback = (feedbackRows as any[])[0]
if (!feedback) {
return res.status(404).json({ code: 40400, message: '反馈不存在' })
}
await pool.execute(
'UPDATE feedbacks SET status = ?, admin_reply = ? WHERE id = ?',
[status, admin_reply || '', id]
)
// 如果有管理员回复,发送通知给反馈提交者
if (feedback && admin_reply && admin_reply.trim()) {
const statusText: Record<string, string> = {
processed: '已处理',
ignored: '已忽略',
pending: '待处理'
}
const replyPreview = admin_reply.trim().length > 50
? admin_reply.trim().slice(0, 50) + '...'
: admin_reply.trim()
await pool.execute(
`INSERT INTO notifications (user_id, type, title, content)
VALUES (?, 'personal', ?, ?)`,
[
feedback.user_id,
`您的反馈已${statusText[status] || '更新'}`,
`反馈内容:${feedback.content.slice(0, 30)}...\n\n管理员回复${replyPreview}`
]
)
}
res.json({ code: 0 })
} catch (error) {
console.error('Update feedback status error:', error)
res.status(500).json({ code: 50000, message: '更新失败' })
}
})
export default router