Files
xiaocai/server/src/routes/feedback.ts
wangxiaogang 860c599b7c fix: 系统检查修复 — 安全和稳定性
后端:
- config.ts 使用 requireAdmin 中间件,统一响应格式
- feedback.ts 使用 requireAdmin 中间件,移除重复权限检查
- feedback.ts 添加 affectedRows 检查(反馈不存在时返回 404)
- 全局错误处理中间件兜底未捕获异常

前端:
- request.ts 添加 15 秒请求超时设置
2026-06-08 14:48:39 +08:00

129 lines
3.9 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
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 ? OFFSET ?`,
[...params, pageSize, offset]
)
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