Files
happy-export/backend/routes/system.js
T
Drsakura 13d67c804f feat: 多用户权限体系、AI 助手、SKU 目录与合同清洗(开源准备)
本地长期开发成果的整体入库快照,包含后端鉴权/权限、AI 接入、合同解析、
SKU 目录等模块,以及前端相应页面与样式改版。

后端
- 新增鉴权与会话:scrypt 密码哈希、HttpOnly cookie(happy_sid)、登录失败锁定
- 新增「角色 → 权限组 → 权限点」三级权限模型与审计日志
- 新增 AI 提供商接入与预设(兼容 OpenAI 协议,内置 DeepSeek / 通义千问等)
- 新增合同解析、合同文件保留策略、客户归属与访问控制
- 新增 SKU 目录、组织品牌、新客开发、系统日志等路由

前端
- 新增登录页与用户 / 权限组 / 系统日志管理页
- 新增客户详情、客户公海、询盘详情、报价编辑器、合同清洗中心
- 新增 AI 配置、组织品牌、包装参数、SKU 选择器等组件
- 新增英文大写金额、未保存改动拦截、抽屉与骨架屏
- 新增 stage1 样式层;产品、供应商、订单等页面改版
- EmbeddedSku 页面并入现有产品体系后删除

开源准备
- 新增 AGPL-3.0 LICENSE
- README:移除未启用的 SQLCipher 加密声明(改为如实的「已知限制」)、
  补充环境变量说明、许可证由「私有项目」改为 AGPL-3.0
- 移除历史遗留的空文件 _index_backup.txt
- 文档中的项目路径更新为 happy-export
2026-09-17 14:53:02 +08:00

63 lines
1.9 KiB
JavaScript

/**
* 系统信息与版本更新检查。
* 挂载点:/api/system
*/
const express = require('express');
const updater = require('../lib/updater');
const branding = require('../lib/branding');
const router = express.Router();
// 简易缓存:GitHub 未认证请求每小时只有 60 次配额,连续点「检查更新」不该把额度刷完。
const CACHE_TTL_MS = 30 * 1000;
let cache = { key: '', at: 0, payload: null };
/**
* GET /api/system/info
* 当前版本与运行环境。轻量、不发外部请求,设置页打开时就能调。
*/
router.get('/info', (req, res) => {
try {
res.json(updater.systemInfo());
} catch (error) {
res.status(500).json({ error: error.message });
}
});
/**
* GET /api/system/check-update?repo=owner/repo&force=1
* 对比远端仓库版本与本机版本。repo 省略时用默认仓库(Drsakura/happy-export)。
*/
router.get('/check-update', async (req, res) => {
try {
const repo = updater.normalizeRepo(req.query.repo) || updater.DEFAULT_REPO;
const force = String(req.query.force || '') === '1';
if (!force && cache.payload && cache.key === repo && Date.now() - cache.at < CACHE_TTL_MS) {
return res.json({ ...cache.payload, cached: true });
}
const result = await updater.checkUpdate(repo);
cache = { key: repo, at: Date.now(), payload: result };
res.json({ ...result, cached: false });
} catch (error) {
// checkUpdate 内部已兜底,走到这里说明是意料之外的问题
res.status(500).json({ ok: false, error: error.message });
}
});
/**
* GET /api/system/branding
* 品牌与组织名。公开接口 —— 登录页在拿到会话之前就得知道系统叫什么。
* 只回显示用的名字,不含任何业务数据。
*/
router.get('/branding', (req, res) => {
try {
res.json(branding.getBranding());
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;