canglan da26dd5f69 fix: anonymous ticket submit 500 - undefined rgu param
Root cause: after UID internalization, anonymous form no longer
sends reporter_game_uid, so req.body.reporter_game_uid was
undefined. The countRow query (outside try/catch) passed undefined
to mysql2 which throws 'Bind parameters must not contain undefined'
- escaping to the global error middleware -> 500 '服务器内部错误'
for ALL anonymous ticket types (appeal/report/suggestion).

Fix: default rgn/rgu to '' when absent.
2026-08-22 12:21:58 +08:00

MC Report System

游戏玩家投诉/建议/申诉系统。Web 端 + Paper 插件双端提交MySQL 存储JWT 鉴权,三级角色。

功能模块

模块 说明
举报 指定目标玩家或事项举报,可上传截图/视频≤50MB支持投诉管理员仅服主可见
建议 提交改进建议,仅服主处理
申诉 封禁申诉 + 结果申诉(对处理结果不满可提交一次,关联原工单)
审批流 待处理→接单→处理中→待补充→已解决/已驳回→关闭7天自动关闭
接单制 管理员一人一单,可转单(带原因),处理备注(玩家不可见)
投票 分组/子服管理,定时/不定时投票,盲投制,管理员 1.5 票权
更新内容 Issues 风格,分状态(待定/计划中/已完成),讨论区,按时间/热度排序,分组筛选
通知 SMTP 邮件 + WebhookDiscord/企微/QQ/飞书SSRF 防护),邮件模板自定义
日志 邮件日志 + 系统日志Webhook 失败/服务器错误),后台可视化排查
导出 CSVUTF-8 BOM导出工单/被举报人统计/操作日志(仅服主)
外部 API ID+Secret → SESSION 鉴权;工单提交/追踪、封禁拉取/新增、按子服过滤(机器人/插件对接)
找回密码 邮箱验证1小时有效重置链接
多来源身份 来源动态管理(后台增删),一个账号可绑定多个来源身份,无默认来源
安装向导 Web 页面配置 MySQL 连接 + 创建服主账号(完成后无需重启)

API 文档

  • 完整接口文档:docs/API.md(认证/工单/封禁/用户/设置/通知/投票/更新/导出/外部 API)
  • 第三方外部 API(机器人/插件):docs/EXTERNAL-API.md(ID+Secret 换取 SESSION、提交工单、进度追踪、封禁拉取、按子服拉数据)
  • MC 插件对接示例:docs/PLUGIN-GUIDE.md(Java 实现:换取 SESSION、提交举报、同步封禁)

快速开始

npm install
node backend/server.js
# → 打开 http://localhost:3100/#/install

两步安装:填 MySQL 连接信息 → 建服主账号 → 重启生效。

默认端口

3100,可通过 PORT=8080 环境变量修改。

技术栈

技术
后端 Node.js + Express + mysql2
前端 Vanilla JS SPAHash路由+ Inter 字体
数据库 MySQL ≥ 5.7 / MariaDB ≥ 10.2
认证 Web: JWT256 位随机密钥72h 有效期);外部 API: ID+Secret → SESSION24h单会话
安全 Helmet 7 + Rate Limit分路由限速+ XSS 过滤 + CSP + SSRF 防护
验证码 SVG 混淆图片svg-captcha4 位字符 + 噪点 + 颜色变形)
文件上传 Multer + MIME + 魔数字节校验UUID 命名web 外存储
许可证 AGPLv3

角色权限矩阵

操作 玩家 管理员 服主
提交工单
查看工单 仅自己 待处理 + 自己接的 全部
查看详情 仅自己 待处理 + 自己接的 全部
接单认领 举报/申诉 全部
更新处理备注 自己接的 全部
变更状态 自己接的 全部
转单 自己接的 全部
回复工单 自己的
用户管理
邮件模板
通知配置
系统设置
导出数据
投票/更新管理
工单追加(未接单) 自己的

审批流状态

待处理(pending) ──接单──→ 处理中(processing) ──→ 已解决(resolved)
    │                          │                    ├── 已驳回(rejected)
    │                    待补充(awaiting_info)       └── 7天自动关闭(closed)
    │                          │
    └── 结果申诉 ──→ 申诉中(appealing) ──服主处理──→ 原工单关闭
  • closed 为终态,仅服主可操作
  • 结果申诉仅一次,关联原工单,仅服主可见

注册来源与多身份

来源在后台「来源管理」页动态维护(可增删/停用/排序,不设默认)。初始预置:

来源 标识 说明
网易端 netease 网易端身份
皮肤站 skin 皮肤站身份

多身份绑定:一个账号可绑定多个来源身份(控制台「我的身份」或 POST /api/auth/identities 管理)。提交工单时可选择使用哪个身份;user_identities 表由启动时自动迁移回填,users 表主身份字段保持不变(完全兼容旧数据)。

工单提交流程

  1. 填写标题 + 游戏名(登录后自动填充)
  2. 举报:选目标玩家 + 原因(内置选项或自定义)+ 可投诉管理员
  3. 建议:填写建议内容
  4. 申诉:填写封禁申诉理由
  5. 上传附件(可选,图片/视频 ≤50MB,最多5个)
  6. 提交 → 生成 tracking_token(Cookie 留存)

外部 API

端点 用途 认证
POST /api/external/auth/session ID+Secret 换取 SESSION x-api-client-id + x-api-secret
POST /api/external/auth/register 插件注册 Bearer SESSION
POST /api/external/tickets 提交工单(带 server 可选) Bearer SESSION
GET /api/external/tickets/track 按追踪码查进度 Bearer SESSION
GET /api/external/all-tickets 工单列表(可按子服过滤) Bearer SESSION
GET/POST /api/external/bans 拉取/新增封禁 Bearer SESSION
GET /api/external/reported-players 被举报统计 Bearer SESSION
GET /api/external/servers 服务器列表(含别名) Bearer SESSION
GET /api/external/stats 统计 Bearer SESSION

完整说明见 docs/EXTERNAL-API.md;MC 插件对接示例见 docs/PLUGIN-GUIDE.md

部署

# 环境变量
PORT=3100 CORS_ORIGIN=https://your-domain.com node backend/server.js

# PM2 持久化
pm2 start backend/server.js --name mc-report

# Nginx 反代(还需 include nginx.inc
proxy_pass http://127.0.0.1:3100;

完整部署步骤见 INSTALL.md

MC 插件对接

仓库不内置插件代码,按 docs/PLUGIN-GUIDE.md 用 Java 自行实现即可:

  • POST /api/external/auth/session:ID+Secret 换取 SESSION(自动续期)
  • POST /api/external/tickets:游戏内提交举报/建议/申诉(带子服)
  • GET /api/external/tickets/track:玩家查询处理进度(tracking_token)
  • GET/POST /api/external/bans:同步本服封禁

目录结构

├── backend/
│   ├── server.js              # Express 入口(动态加载业务路由)
│   ├── db.js                  # MySQL 连接池 + 建表 + 幂等迁移
│   ├── logger.js              # 邮件/系统日志(写库, 失败不影响主流程)
│   ├── mailer.js              # SMTP 邮件(自动记录日志)
│   ├── webhook.js             # Webhook 推送(SSRF 防护 + 10s 超时)
│   ├── middleware/
│   │   ├── auth.js            # JWT(Web 鉴权)
│   │   ├── security.js        # 限速 + XSS + API Key 校验
│   │   └── upload.js          # 文件上传(魔数校验)
│   └── routes/
│       ├── auth.js            # 注册/登录/验证/找回密码/多身份
│       ├── tickets.js         # 工单核心(状态流转)
│       ├── install.js         # 安装向导
│       ├── external.js        # 外部 API(SESSION 鉴权)
│       ├── sources.js         # 动态来源管理
│       ├── users.js           # 用户管理
│       ├── logs.js            # 日志查询
│       ├── settings.js        # 站点设置 + UUID 查询
│       ├── export.js          # CSV 导出
│       ├── polls.js           # 投票 + 服务器分组
│       ├── features.js        # 更新内容
│       ├── notifications.js   # 通知配置
│       ├── captcha.js         # SVG 验证码
│       └── uploads.js         # 附件下载
├── public/                    # 前端 SPA
├── docs/                      # 文档(API / 外部API / 插件指南)
├── nginx.inc                  # Nginx 额外配置
├── data/                      # config.json + uploads/ (git 忽略)
├── README.md
├── INSTALL.md
├── LICENSE                    # AGPLv3
└── package.json

安全

措施 实现
JWT 秘钥 256 位随机,惰性缓存,无硬编码 fallback
API Key 48 位随机,常量时间比对防时序攻击
限速 登录 8/min、注册 3/min、工单 10/min、全局 300/min
XSS sanitizeBody 递归过滤 + U.esc 前端转义
CSP Helmet + script-src-attr 'unsafe-inline'
SQL 注入 全参数化查询
文件上传 扩展名 + MIME + 魔数(仅读 256B+ UUID 命名
Webhook SSRF DNS 解析后过滤内网 IP
用户名枚举 注册/登录统一错误信息
CORS 环境变量控制 + localhost 自动放行
Description
No description provided
Readme 2.4 MiB
Languages
JavaScript 94.9%
CSS 4.6%
HTML 0.5%