一个面向小规模用户(约 20 人)的轻量反馈+任务收集系统,已从原 CloudBase 架构迁移为本地/云服务器可运行版本。
- 前端:纯静态
HTML/CSS/JS - 后端:
Node.js + Express - 数据库:
SQLite / MySQL / PostgreSQL - 反向代理:
Nginx / IIS / Caddy - 进程托管:
PM2
本项目强调低资源占用、低维护成本和可控部署,适合 2C4G/4C4G 服务器并行运行多个小项目。
Browser
-> Reverse Proxy (Nginx / IIS / Caddy)
-> Express app (API + static files)
-> Relational DB (SQLite / MySQL / PostgreSQL)
- 入口页:
/(public/index.html) - 反馈页:
/feedback/(public/feedback/index.html) - WorkTask 页:
/worktask/(public/worktask/index.html) - 管理页:
/admin/(public/admin/index.html) - API:
/api/*
- 提交反馈(类型、标题、内容、联系方式)
- 提交 WorkTask/任务(类型、优先级、期望时间、标签)
- 主页“当前处理进展”展示栏(后台可控制是否显示)
- 前端长度限制与字数统计
- 提交按钮防重复点击
- 成功/失败提示
- 管理员账号登录(会话 Cookie)
- 统一管理面板切换板块(反馈管理 / WorkTask 管理)
- 本人任务录入(管理侧直接创建任务)
- 按状态与关键词筛选
- 分页查看反馈列表
- 修改状态:
new/reviewed/resolved/notplanned - 删除反馈
- WorkTask 安排(负责人 + 计划时间)
- 反馈/WorkTask 的“主页显示”开关
- CSV 导出(支持筛选后的全量导出)
- 统计卡片(总量 + 各状态)
- 外部 SMTP 邮件通知(新反馈 / 新任务)
- 管理员手动 SMTP 测试发信(用于联调与排错)
- 外部 Webhook 机器人通知(企业微信/飞书/Lark/钉钉/Slack/Generic)
- 管理员手动 Webhook 测试消息(用于联调与排错)
- 结构化日志与请求日志(请求ID、耗时、状态码)
.
├─ public/
│ ├─ index.html
│ ├─ theme.js
│ ├─ feedback/index.html
│ ├─ worktask/index.html
│ └─ admin/
│ ├─ index.html
│ └─ admin.js
├─ server/
│ ├─ app.js
│ ├─ auth.js
│ ├─ config.js
│ ├─ db.js
│ ├─ errors.js
│ ├─ logger.js
│ ├─ notify.js
│ ├─ validation.js
│ └─ webhook.js
├─ scripts/
│ ├─ init-admin.js
│ ├─ backup-db.js
│ ├─ backup-db-rdbms.js
│ ├─ backup-db.sh
│ └─ backup-db.ps1
├─ deploy/
│ ├─ nginx.kyanet-workstation.conf
│ ├─ iis.web.config.template
│ └─ Caddyfile.kyanet-workstation
├─ data/ # SQLite 数据目录(DB_CLIENT=sqlite 时使用)
├─ backups/ # SQLite 备份目录(DB_CLIENT=sqlite 时使用)
├─ ecosystem.config.cjs # PM2 配置
├─ .env.example
├─ tests/
└─ package.json
健康检查接口。默认仅返回基础状态与时间;当 HEALTH_EXPOSE_COUNTS=true 时额外返回业务计数。
提交反馈。
请求体:
{
"type": "Bug",
"title": "标题",
"content": "详细内容",
"contact": "联系方式",
"images": []
}提交 WorkTask/任务。
请求体:
{
"type": "WorkTask提交",
"title": "标题",
"content": "详细说明",
"contact": "联系方式",
"priority": "medium",
"expectedAt": "2026-05-01T12:00:00.000Z",
"tags": "前端,协作"
}管理员登录,成功后下发 HttpOnly Cookie。要求:
Content-Type: application/json- 请求需来自同源页面(默认要求带 Origin/Referer 头)
请求体:
{
"username": "admin",
"password": "your-password"
}注销当前会话。
检查当前登录态。
管理员触发 SMTP 测试发信。可选指定测试收件人(逗号分隔),留空则使用 SMTP_TO。
请求体:
{
"to": "[email protected],[email protected]"
}管理员触发 Webhook 测试消息。可选附加文本(用于区分测试批次)。
请求体:
{
"content": "本次用于联调企业微信群机器人"
}读取前端展示配置(例如管理页时间显示时区与语言)。
返回体示例:
{
"ok": true,
"data": {
"displayTimezone": "Asia/Shanghai",
"displayLocale": "zh-CN"
}
}读取主页“当前处理进展”展示数据。
- 反馈侧返回:
status IN (new, reviewed)且show_on_home = true - WorkTask 侧返回:
status IN (new, scheduled, in_progress)且show_on_home = true
按状态/关键词分页查询反馈列表。
请求体:
{
"status": "new",
"keyword": "关键词",
"page": 1,
"pageSize": 20
}返回体包含:items/page/pageSize/total/summary/totalPages
修改反馈状态。
请求体:
{
"id": 123,
"status": "resolved"
}删除反馈。
请求体:
{
"id": 123
}设置反馈是否在主页展示栏显示。
请求体:
{
"id": 123,
"showOnHome": true
}保存反馈的“管理员备注(后台可见)”和“对外回复(主页可见)”。
请求体:
{
"id": 123,
"adminNote": "仅管理员可见的备注",
"publicReply": "对外展示的回复内容"
}按状态/优先级/关键词分页查询 WorkTask 列表。
管理员直接创建本人任务(无需填写联系方式)。
请求体示例:
{
"type": "任务安排",
"title": "巡检与配置核对",
"content": "本周完成服务巡检和配置核查",
"priority": "medium",
"status": "scheduled",
"showOnHome": true,
"assignee": "Kyan",
"expectedAt": "2026-05-01T12:00:00.000Z",
"scheduledAt": "2026-04-30T10:00:00.000Z",
"tags": "运维,巡检",
"adminNote": "内部说明",
"publicReply": "已排期处理中"
}更新 WorkTask 状态(new/scheduled/in_progress/completed/cancelled)。
保存 WorkTask 安排(负责人、计划时间、可选状态)。
删除 WorkTask。
设置 WorkTask 是否在主页展示栏显示。
保存 WorkTask 的“管理员备注(后台可见)”和“对外回复(主页可见)”。
请求体:
{
"id": 123,
"adminNote": "排期约束与内部备注",
"publicReply": "当前处理中,预计本周完成"
}- Node.js 20+
- npm 10+
cp .env.example .env
npm install修改 .env 中至少以下项:
ADMIN_USERNAMEADMIN_PASSWORDAPP_BASE_URLDB_CLIENT(默认sqlite)- 当
DB_CLIENT=mysql|postgres时,额外配置DATABASE_URL
数据库连接示例:
# SQLite(默认)
DB_CLIENT=sqlite
DB_PATH=./data/workstation.db
# MySQL
DB_CLIENT=mysql
DATABASE_URL=mysql://user:[email protected]:3306/kyanet_workstation
# PostgreSQL
DB_CLIENT=postgres
DATABASE_URL=postgres://user:[email protected]:5432/kyanet_workstationnpm run init-adminnpm run start访问:
- 入口页:
http://127.0.0.1:3000/ - 反馈页:
http://127.0.0.1:3000/feedback/ - WorkTask 页:
http://127.0.0.1:3000/worktask/ - 管理页:
http://127.0.0.1:3000/admin/
sudo mkdir -p /var/www/kyanet-workstation
sudo chown -R $USER:$USER /var/www/kyanet-workstation
# 将项目文件同步到 /var/www/kyanet-workstation
cd /var/www/kyanet-workstation
cp .env.example .env
npm install --omit=dev
npm run init-adminnpm install -g pm2
pm2 start ecosystem.config.cjs
pm2 save
pm2 startupsudo cp deploy/nginx.kyanet-workstation.conf /etc/nginx/sites-available/kyanet-workstation.conf
sudo ln -s /etc/nginx/sites-available/kyanet-workstation.conf /etc/nginx/sites-enabled/kyanet-workstation.conf
sudo nginx -t
sudo systemctl reload nginx建议使用 Certbot 申请与续期 TLS 证书(示例域名见 deploy/nginx.kyanet-workstation.conf)。
适用于你希望直接在 Windows Server 或 Windows 本机长期运行服务。
- Windows Server 2019/2022 或 Windows 10/11
- Node.js 20+
- npm 10+
- (可选)反向代理:IIS/ARR、Caddy 或 Windows 版 Nginx
cd path\to\KyanetWorkStation
Copy-Item .env.example .env
npm install
npm run init-admin
npm run start访问:
- 用户页:
http://127.0.0.1:3000/ - 管理页:
http://127.0.0.1:3000/admin/
npm install -g pm2
pm2 start server/app.js --name kyanet-workstation --cwd path\to\KyanetWorkStation
pm2 save说明:
- Windows 下 PM2 可用于守护进程,但“开机自启”建议结合任务计划程序或
pm2-windows-startup。 - 若不使用 PM2,也可用 NSSM/WinSW 注册为 Windows 服务。
- 推荐把 80/443 交给 IIS/Caddy/Nginx,再反代到
127.0.0.1:3000。 - 若启用 HTTPS,请将
.env的APP_BASE_URL改为实际https://域名。
前置条件:
- 已安装 IIS
- 已安装 URL Rewrite 模块
- 已安装并启用 Application Request Routing (ARR)
使用方式:
- 在 IIS 站点根目录放置
deploy/iis.web.config.template内容,保存为web.config。 - 确认站点绑定了你的域名(80/443)并配置证书。
- 在 ARR 中启用
Proxy。 - 保持 Node 服务监听
127.0.0.1:3000。
模板文件:deploy/iis.web.config.template
- 复制模板:
Copy-Item .\deploy\Caddyfile.kyanet-workstation targetpath\Caddy\Caddyfile- 将
workstation.example.com改成你的域名。 - 启动 Caddy(示例):
caddy run --config targetpath\Caddy\Caddyfile- Caddy 会自动申请/续期 HTTPS 证书(确保域名已正确解析到服务器)。
模板文件:deploy/Caddyfile.kyanet-workstation
适用于你在 Windows 主机上,用 WSL Ubuntu 运行 Node + Nginx。
wsl --install -d Ubuntu进入 WSL 后安装基础依赖:
sudo apt update
sudo apt install -y curl git build-essential nginx
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejsmkdir -p ~/apps/kyanet-workstation
cd ~/apps/kyanet-workstation
# 将项目文件同步到该目录
cp .env.example .env
npm install --omit=dev
npm run init-adminnpm install -g pm2
pm2 start ecosystem.config.cjs --cwd ~/apps/kyanet-workstation
pm2 savesudo cp deploy/nginx.kyanet-workstation.conf /etc/nginx/sites-available/kyanet-workstation.conf
sudo ln -s /etc/nginx/sites-available/kyanet-workstation.conf /etc/nginx/sites-enabled/kyanet-workstation.conf
sudo nginx -t
sudo systemctl reload nginx说明:
- 若 WSL 未启用 systemd,可直接
sudo service nginx start。 - 生产推荐使用 Linux 云服务器;WSL 更适合开发、预发布或轻量自用场景。
| 变量 | 说明 | 默认值 |
|---|---|---|
NODE_ENV |
运行环境 | development |
PORT |
服务端口 | 3000 |
LISTEN_HOST |
监听地址(建议仅本机) | 127.0.0.1 |
TRUST_PROXY |
代理层数(直连开发建议 0,反代部署建议 1) |
开发 0 / 生产反代 1 |
APP_BASE_URL |
站点地址 | http://127.0.0.1:3000 |
HEALTH_EXPOSE_COUNTS |
健康检查是否暴露业务计数 | false |
ADMIN_ALLOW_HEADERLESS_MUTATION |
是否允许无 Origin/Referer 的管理写请求(调试专用) | false |
LOG_LEVEL |
应用日志级别(trace/debug/info/warn/error/fatal) |
info |
LOG_TO_FILE |
是否额外写入日志文件 | false |
LOG_DIR |
日志目录(LOG_TO_FILE=true 时生效) |
./logs |
ACCESS_LOG_ENABLED |
是否启用请求日志 | true |
ACCESS_LOG_SKIP_HEALTH |
是否跳过 /api/health 请求日志 |
true |
ACCESS_LOG_SLOW_MS |
慢请求阈值(毫秒,超过按 warn 记录) |
800 |
SESSION_COOKIE_NAME |
会话 Cookie 名 | kws_sid |
SESSION_TTL_HOURS |
会话有效期(小时) | 168 |
BCRYPT_ROUNDS |
密码哈希轮数 | 12 |
ADMIN_USERNAME |
管理员用户名(引导/脚本使用) | - |
ADMIN_PASSWORD |
管理员密码(引导/脚本使用) | - |
DB_CLIENT |
数据库类型(sqlite/mysql/postgres) |
sqlite |
DATABASE_URL |
MySQL/PostgreSQL 连接串(非 sqlite 时必填) | - |
DB_PATH |
SQLite 路径 | ./data/workstation.db |
RATE_LIMIT_SUBMIT_WINDOW_MS |
提交限流窗口 | 600000 |
RATE_LIMIT_SUBMIT_MAX |
提交限流次数 | 20 |
RATE_LIMIT_LOGIN_WINDOW_MS |
登录限流窗口 | 900000 |
RATE_LIMIT_LOGIN_MAX |
登录限流次数 | 10 |
RATE_LIMIT_ADMIN_WINDOW_MS |
管理接口限流窗口 | 60000 |
RATE_LIMIT_ADMIN_MAX |
管理接口限流次数 | 120 |
DISPLAY_TIMEZONE |
前端展示时区(管理页时间显示) | Asia/Shanghai |
DISPLAY_LOCALE |
前端展示语言区域(管理页时间显示) | zh-CN |
BACKUP_DIR |
备份目录 | ./backups |
BACKUP_RETENTION_DAYS |
备份保留天数 | 30 |
SMTP_ENABLED |
是否启用 SMTP 通知 | false |
SMTP_HOST |
SMTP 服务器地址 | - |
SMTP_PORT |
SMTP 端口 | 587 |
SMTP_SECURE |
是否使用 SSL(465 常用 true) |
false |
SMTP_REQUIRE_TLS |
是否要求 TLS | true |
SMTP_USER |
SMTP 用户名 | - |
SMTP_PASS |
SMTP 密码/授权码 | - |
SMTP_FROM |
发件人邮箱 | - |
SMTP_TO |
收件人列表(逗号分隔) | - |
SMTP_SUBJECT_PREFIX |
邮件标题前缀 | [KyanetWorkStation] |
WEBHOOK_ENABLED |
是否启用 Webhook 通知 | false |
WEBHOOK_PROVIDER |
Webhook 平台(wecom/feishu/lark/dingtalk/slack/generic) |
generic |
WEBHOOK_URLS |
Webhook 地址列表(逗号分隔) | - |
WEBHOOK_SECRET |
可选密钥(Generic 签名 / 钉钉签名) | - |
WEBHOOK_KEYWORDS |
可选安全关键词列表(逗号分隔,用于钉钉等平台的关键词校验) | - |
WEBHOOK_TIMEOUT_MS |
Webhook 请求超时(毫秒) | 5000 |
WEBHOOK_TITLE_PREFIX |
Webhook 标题前缀 | [KyanetWorkStation] |
- 在数据库服务中先创建数据库(例如
kyanet_workstation)。 - 修改
.env:
DB_CLIENT=mysql
DATABASE_URL=mysql://user:[email protected]:3306/kyanet_workstation或
DB_CLIENT=postgres
DATABASE_URL=postgres://user:[email protected]:5432/kyanet_workstation- 重新安装依赖(确保含
mysql2/pg):
npm install- 执行管理员初始化:
npm run init-admin- 启动服务,首次启动会自动建表:
npm run start说明:
scripts/backup-db.sh与scripts/backup-db.ps1仅支持DB_CLIENT=sqlite。scripts/backup-db-rdbms.js支持DB_CLIENT=mysql|postgres。- 两者均通过
scripts/backup-db.js使用 SQLite 在线备份 API 生成快照,避免 WAL 模式下直接压缩主库文件造成的不一致风险。 - MySQL/PostgreSQL 备份由脚本内部调用
mysqldump/pg_dump,需提前安装对应客户端工具。
bash scripts/backup-db.sh
# 或 npm run backup-db:linux
# 或 npm run backup-db:core提示:若你当前 .env 的 DB_CLIENT 不是 sqlite,可临时覆盖执行:
DB_CLIENT=sqlite npm run backup-db:corepowershell -ExecutionPolicy Bypass -File .\scripts\backup-db.ps1
# 或 npm run backup-db:win若当前 .env 不是 sqlite,可用:
$env:DB_CLIENT="sqlite"; npm run backup-db:core统一脚本(推荐):
npm run backup-db:rdbmsMySQL(手动 mysqldump):
mysqldump --single-transaction --quick --routines --triggers \
-h 127.0.0.1 -P 3306 -u your_user -p kyanet_workstation > backup.sqlPostgreSQL(手动 pg_dump):
pg_dump -h 127.0.0.1 -p 5432 -U your_user -d kyanet_workstation -F c -f backup.dumpcrontab -e添加:
30 3 * * * cd /var/www/kyanet-workstation && /usr/bin/bash scripts/backup-db.sh >> /var/log/kyanet-workstation-backup.log 2>&1可创建每日任务,执行:
powershell.exe -ExecutionPolicy Bypass -File E:\Workplace\Projects\KyanetWorkStation\scripts\backup-db.ps1- 停止 PM2 服务。
- 解压目标备份到
data/workstation.db。 - 启动 PM2 服务。
helmet安全头- 登录/提交/管理接口限流
- 后端输入校验(长度、枚举、分页)
- 统一错误响应结构
- 会话 Cookie:
HttpOnly + SameSite=Strict - 管理写接口来源校验(Origin/Referer/Sec-Fetch-Site)
- 管理写接口仅接受
application/json - 管理员密码使用
bcrypt哈希存储
- 运行时日志采用
pino结构化日志(JSON)。 - 每个请求会记录
requestId,并通过响应头x-request-id回传,便于排障串联。 - 默认输出到标准输出(适配 PM2 / 容器)。
- 当
LOG_TO_FILE=true时,会额外写入${LOG_DIR}/app.log。
常用查看方式:
pm2 logs kyanet-workstation慢请求:
- 当请求耗时超过
ACCESS_LOG_SLOW_MS(默认800ms)时,日志级别提升为warn。
针对 2C4G/4C4G 且同机多个小项目:
- PM2 单实例
fork模式 max_memory_restart建议250M~350M- Nginx 开启 gzip(可选)
- SQLite 保持本地磁盘 + WAL
- 日常操作目标:API P95 < 300ms
- 检查是否走 HTTPS(生产环境 Cookie
secure) - 检查 Nginx
X-Forwarded-*头是否正确 - 检查
TRUST_PROXY=1
- 先运行
npm run init-admin重置管理员密码 - 检查
.env中ADMIN_USERNAME/ADMIN_PASSWORD
- 检查 Nginx 是否把
/反代到127.0.0.1:3000 - 检查 PM2 进程是否在线
- 若
DB_CLIENT=sqlite:检查scripts/backup-db.sh执行权限与DB_PATH - 若
DB_CLIENT=mysql|postgres:优先使用npm run backup-db:rdbms,并确认系统已安装mysqldump/pg_dump - 检查 cron 或任务计划程序日志输出
- 使用任务计划程序在开机时执行
pm2 resurrect - 或改用 NSSM/WinSW 注册服务并设置自动启动
- 原因:在 Windows 与 WSL 之间复用了同一份
node_modules,原生模块二进制与当前运行平台不匹配 - 处理方式:
- 在当前运行环境内删除并重装依赖(Windows 用 Windows Node,WSL 用 Linux Node)
- 避免在
C:\与/mnt/c之间混用同一套node_modules - 如在 WSL 运行服务,建议项目目录放在 WSL Linux 文件系统(如
~/apps/...)再执行npm install
当前版本按以下范围实现:
- 保留文本+链接反馈模式
- 不包含原生图片上传
- 包含 SMTP 邮件通知(新反馈/新任务)
- 包含 Webhook 机器人通知(企业微信/飞书/Lark/钉钉/Slack/Generic)
- 单管理员账号模型