OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

169
dev/开发文档.md Normal file
View File

@@ -0,0 +1,169 @@
# 2D / 3D 在线查看器 · 开发文档
> 更新2026-08-21。本文覆盖 `dev/` 目录下的自研查看器(不含 2D/、3D/ 里的新迪官方案例)。
## 一、项目结构
```
dev/
├── index.html ← 统一入口:落地页 + 2D/3D 数模库 + 嵌入预览弹窗
├── library.php ← 数模库元数据接口GET 读 / POST 保存)
├── library.json ← 数模库记录(上传自动收录 + 手动增删改)
├── DWGViewer/ ← 2D 图纸查看器(.dwg / .dxf
│ ├── index.html / app.js / style.css
│ ├── js/ ← source.js(解析) → flatten.js(展平) → render.js(Canvas2D) → tools.js
│ ├── libs/dxf-parser(MIT) libs/libredwg-web(GPL-3.0, 仅打开 .dwg 时动态 import)
│ ├── samples/ ← XQD601.dwg、patent.dwg、pulley.dxf、cushion.dxf
│ └── api/ ← token.php / model.php / upload.php防下载三件套
└── STEPViewer/ ← 3D 模型查看器(.step / .stp浏览器内 WASM 直读,无云服务)
├── index.html / app.js / style.css
├── libs/three.js + occt-import-js0.0.23
├── samples/ ← as1-oc-214.stp 等
└── api/ ← 同款三件套SECRET 与 DWGViewer 不同)
```
## 二、本地开发
- 服务器python http.server 脚本(`%TEMP%\nds_server_step.py` 等),**root 指向 dev/**
端口 **8920**;日志 `%TEMP%\req_log_*.txt`
- 打开 `http://127.0.0.1:8920/`(入口)或直接开 `DWGViewer/index.html?file=samples/XQD601.dwg`
- **本地没有 PHP**:查看器会自动退回直连(判断依据是接口响应的 content-type 是否为
`application/json`——python 会把 token.php 源码当静态文件 200 发回来,只看状态码会误判);
数模库只读(增删改走 library.php 会 501 并给出明确提示)
- 改完 JS 用 `node --check app.js` 做语法检查
## 三、架构要点
**2D 数据流**`source.js`(解析+归一化libredwg 全给弧度dxf-parser 混用——统一成弧度)
`flatten.js`展平成世界坐标显示列表INSERT 递归烘焙变换,**只做一次**;缩放平移只重算
屏幕坐标 → 2.9 万实体 6ms/帧)→ `render.js`Canvas2D支持旋转/三种背景/线宽)。
**3D 数据流**:拖入/`?file=` 取到 STEP 字节 → `occt-import-js` WASM 转换 → three.js 场景
MeshPhongMaterial + Ambient/2×Directional30° 法线平滑)。零件树按 mesh 名分组,
`S.meshEntryMap` 做 mesh→树行精确映射视口点选用它不要按树顺序 first-match
**入口分发**dev/index.html
- 按扩展名路由dwg/dxf → DWGViewerstep/stp → STEPVieweriframe 加载
- 本地文件postMessageviewer-ready 握手后发 viewer-file只传 ArrayBuffer 不落地)
- 路径换算优先「查看器目录内」HEAD 探测),入口目录兜底;**选中候选后要换算成
相对查看器的路径**,否则双目录 404
- 参数透传:`embed=1``bg=` 必须显式转发给查看器(入口 URL 不会自动带过去)
**嵌入模式**`?file=路径&embed=1` 隐藏顶栏3D 另强制高精度);`bg=grey|beige|black` 定背景。
嵌入地址规则:私有目录文件用**裸文件名**`?file=xxx.enc`web 目录内文件写相对路径
`samples/xxx.stp`**`../` 逃出查看器目录的路径会被令牌通道拒绝**(防目录穿越)。
## 四、防下载令牌三件套PHP 部署后自动生效)
```
查看器 fetch → api/token.php同源校验 + HMAC 5 分钟令牌)
→ api/model.php验令牌 → .enc XOR 解密 / 原文件原样 → gzip 传输)
上传POST api/upload.phpX-Filename 头;客户端 gzip → 服务端再 gzip → XOR 加密落盘)
```
- `SECRET`:同一查看器内 token/model/upload 三处一致DWG 与 STEP 的 SECRET 不同
- **私有目录**`$PRIVATE_DIR = '/volume1/stepviewer_private'`DWG 为 dwgviewer_private——
模型存 web 目录外web 里根本没有文件可下载model.php 按 `basename($f)` 在私有目录找,
**找不到时退回 web 目录**(留在 web 目录里的样例仍可按原路径用,但可直链下载)
- 落盘名STEP = 原名+8 位随机 hex+.encDWG = 纯随机 hex.enc原文件名藏加密体内
model.php 用 `X-Filename` 头带回)
- 客户端 `apiRequest`STEP`.php` 必须拼在 `?` 之前(曾经拼成
`api/token?f=xxx.stp.php` 导致 PHP 部署下令牌流程整体失效——血泪教训)
- 接口未部署自动退回直连;接口在跑但拒绝时明确报错,不悄悄回退
- 网页端无法绝对防抓包,此方案挡的是直接下载、批量爬取、链接外传猜名
## 五、数模库(首页库表)
- 记录结构:`{path, name, type(2d|3d), note, time}``library.json`(入口目录)
- **path=链接核心)只在新增时可填、之后锁定 🔒**name/type/note 可编辑;删除只删记录
- `library.php`GET 返回全量(静态 JSON 也能读,无 PHP 只读可用POST 整体保存
清洗path 拒绝 `..`、限制字符长度LOCK_EX 写文件Sec-Fetch-Site 弱校验
- **自动收录**:两个查看器 upload.php 成功后调 `add_to_library()``../../library.json`
同 path 更新、否则插最前),并 postMessage `library-updated` 让入口刷新
- 库表功能:打开(按 type 进对应查看器、复制链接embed=1 完整地址)、预览
(弹窗内 iframe 以 embed=1 直载查看器 + 复制 iframe 代码)、编辑、删除、新增、
搜索(名称/路径/备注)、类型筛选
## 六、手机适配≤768px
- 2D 图层 / 3D 模型树:桌面**默认显示**(按钮 toggle 隐藏);手机变**抽屉**
(默认隐藏,点「图层」/「结构树」弹出,抽屉内 ✕ 或点空白处收起)
- 顶栏收起 logo/文件名;底部工具栏横向滚动、图标缩小;浮动面板限宽可滚动
- 首页:库表外层 `overflow-x:auto` 横向滚动、预览弹窗全屏
- 实现要点:
- 抽屉 = `body.tree-open` 类 + `transform: translateX(-100%→0)` + transition
- **「点外部收起」的 document click 监听会把打开按钮的那次点击也当外部点击**——
按钮 handler 必须 `stopPropagation()`
- **媒体查询必须写在被覆盖规则之后**(同特异性后写生效;放前面会被基础规则吃掉)
- STEP 的 `#viewport` left 是内联样式(会压过 CSS手机态要 `style.left=''`
## 七、测试
playwright-core + 系统 Edgeheadless脚本在 `%TEMP%\nds3dtest\`
```js
const { chromium } = require('playwright-core')
const browser = await chromium.launch({
executablePath: 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
headless: true, args: ['--enable-unsafe-swiftshader', '--disable-gpu', '--ignore-certificate-errors'],
})
```
关键脚本:`embed_matrix3.js`(嵌入矩阵)、`dwg_php_sim.js` / route 拦截模拟 PHP 令牌流程、
`prod_final_all.js`(生产回归)、`mobile_test2.js`(手机行为)、`diag_*.js`(诊断)。
测试坑:
- 加载断点别用 `#fileName` 默认文本(非空,会提前 break 假失败)——等含「实体」/「加载完成」
或 errBox 出现
- 嵌入模式下顶栏隐藏,`#btnEmbed` 点不到——测嵌入面板用非嵌入模式
- playwright 点击偶报「tree 拦截指针事件」而 `elementFromPoint` 实际命中按钮——加载完
等 1.5s 再点 + fallback 坐标点击
- headless 全新加载测不出缓存问题——**线上用户复现的白屏往往是缓存混版**(见下)
## 八、⚠️ 缓存版本号(每次改文件必做)
两个查看器的 index.html 用固定 `?v=` 引用 app.js/style.css。**任何一轮修改都必须升级
版本号**当前DWG `app.js?v=2`/`style.css?v=2`STEP `app.js?v=5`/`style.css?v=5`
否则用户浏览器缓存新旧混合(旧 index.html + 新 app.js 引用新 DOM 元素 → 模块崩溃白屏)。
跨文件 DOM 引用(如 `#btnTreeClose`)一律加 null 容错。
## 九、已踩过的坑(速查)
**2D**
- 大半径圆弧包围盒按**实际弧段**算不能按整圆R=49000 的过渡弧会把图纸撑大 20 倍)
- HATCH 的 offset(DXF 45/46) 是已旋转到世界系的位移向量ANSI31 等预定义图案要内置图案表现场展开
- 文档内单位一律弧度;`$INSUNITS` 国内图纸常年默认值,只认显式公制,默认 mm
- dxf-parser 的 dist 是无扩展名相对 import + 依赖 loglevelimportmap 指到本地 shim
- libredwg 的块表在 `db.tables.BLOCK_RECORD.entries[]`,模型空间取 `*Model_Space`
- 该 wasm 构建禁用了 DXF 读写(错误码 2048alpha:false canvas 要先画一次白底
**3D**
- occt 0.0.23 的 `attributes.position/index``{array}` 包装(转 TypedArray树节点字段是
`meshes``brep_faces``{first,last,color}[]`
- 顶点颜色必须 `Color.setRGB(..., THREE.SRGBColorSpace)`,否则模型洗白
- ⚠️ 曾试 PBR(MeshStandard+RoomEnvironment+ACES) 被用户否掉(上色被洗淡),**不要再加
色调映射/环境反射**;过暗 CAD 面色(<0.3自动提亮到 0.35 保色相
- 剖切平面必须按模型包围盒中心重算固定原点切不到
**PHP/接口**
- PHP 没执行时 token.php 返回源码 200——判部署看 content-type 是否 JSON
- 群晖 Web Station PHP **按「网站门户」逐个启用**新域名不会自动继承
- 同源校验Sec-Fetch-Site 优先不受父页面 Referrer-Policy 影响跨站 iframe 嵌入可用
Referer 兜底直链/curl 两者都过不了
**许可**DWGViewer libredwg-web GPL-3.0前端分发有传染性商用需评估换服务端
dwg2dxf / ODA SDK 只需替换 `source.js` `loadDwg()`
## 十、部署(群晖 NAS Web Station生产 3d.ruiguo.wang
1. dev 内容放站点根目录3d.ruiguo.wang 根即入口本地 FTP 目录与生产双向同步
2. Web Station 网站门户 该站点 编辑 **PHP 选 PHP 8.x**不选=纯静态返回源码)
3. File Station web 目录外建 `/volume1/stepviewer_private``/volume1/dwgviewer_private`
`http` 用户组读写权限
4. 两个查看器 api/ `$SECRET` 已设为随机值同查看器三处一致
5. nginx `.htaccess` 不生效——防下载靠私有目录方案不需要 nginx 规则
6. 验证清单token.php 返回 JSON上传返回裸文件名直开 `.stp/.dwg` 已不在 web 目录
嵌入链接能打开库表增删改持久化
> 另外 `ftp.ruiguo.wang/上海新迪3D/dev/` 也是同一套文件PHP 未启用),
> 上传的加密模型在那里打不开属预期(只读回退),正式使用请走 3d.ruiguo.wang。