commit a306eee7d5be2ab35b51b7456394b8459c28f76f Author: admin Date: Thu Aug 13 21:24:16 2026 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0c62ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# 环境与密钥 +website/.env +.env + +# 运行时数据(申请记录/授权文件/资源包) +website/data/ + +# Python +__pycache__/ +*.pyc +*.pyo +.venv/ +venv/ + +# 系统 +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..3248b52 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Easy Data Collector 聚合站 + +`sincos.cc` 单站架构:博客(首页)+ ResLib(License 申请系统)+ 子页板块。 + +## 目录 + +``` +website/ FastAPI 后端(博客/ResLib/申请/管理后台,端口 8003) +blog/ 博客构建脚本 + 文章(markdown) +deploy/ 部署文档 +``` + +## 快速开始 + +```bash +pip install -r website/requirements.txt +cd website +uvicorn main:app --host 0.0.0.0 --port 8003 +``` + +管理后台密码:环境变量 `RESLIB_ADMIN_PASSWORD` 或 `/opt/website/.env` 文件。 + +## 写博客 + +`blog/posts/*.md`(front matter: title/date/tags)→ `python -X utf8 blog/build.py` +生成结果输出到 `website/static/`(home.html / about.html / blog/)。 + +部署详见 `deploy/DEPLOY.md`。 diff --git a/blog/build.py b/blog/build.py new file mode 100644 index 0000000..a3e1e83 --- /dev/null +++ b/blog/build.py @@ -0,0 +1,272 @@ +# -*- coding: utf-8 -*- +""" +博客构建脚本:blog/posts/*.md -> website/static/blog/(列表 + 文章页)+ website/static/home.html(首页) + +front matter 格式: +--- +title: 文章标题 +date: 2026-08-13 +tags: 工业软件, ResLib +--- +""" +import re +import sys +from datetime import datetime +from pathlib import Path + +import markdown + +BASE = Path(__file__).resolve().parent +POSTS_DIR = BASE / "posts" +OUT_BLOG = BASE.parent / "website" / "static" / "blog" +OUT_HOME = BASE.parent / "website" / "static" / "home.html" +OUT_ABOUT = BASE.parent / "website" / "static" / "about.html" + +SITE_NAME = "Easy Data Collector" +SEO_NAME = "江北的渔夫的空间" + +NAV = [ + ("/", "博客"), + ("/reslib/", "ResLib"), + ("/reslib/apply", "申请 License"), + ("/reslib/status", "查询 / 下载"), + ("/reslib/package", "资源包获取"), + ("https://ec.sincos.cc", "Engineer Craft"), + ("https://dt.sincos.cc", "Digital Twin"), + ("https://neural.sincos.cc", "Neural Research"), + ("https://git.sincos.cc", "Gitea Project"), +] + + +def page_header(active: str, title: str = "") -> str: + seo_title = f"{title} - {SEO_NAME}" if title else SEO_NAME + links = "\n".join( + f' {label}' + for href, label in NAV + ) + return f""" + + + + +{seo_title} + + + + + + + + + +
""" + + +def page_footer() -> str: + return f"""
+ + + + + +""" + + +def parse_post(path: Path) -> dict: + text = path.read_text(encoding="utf-8") + m = re.match(r"^---\s*\n(.*?)\n---\s*\n?(.*)$", text, re.S) + front, body = ("", text) + if m: + front, body = m.group(1), m.group(2) + meta = {} + for line in front.splitlines(): + if ":" in line: + k, _, v = line.partition(":") + meta[k.strip()] = v.strip() + html = markdown.markdown( + body, + extensions=["extra", "toc", "sane_lists", "attr_list", "admonition"], + ) + return { + "slug": path.stem, + "title": meta.get("title", path.stem), + "date": meta.get("date", ""), + "tags": [t.strip() for t in meta.get("tags", "").split(",") if t.strip()], + "html": html, + } + + +def summary(html: str, length: int = 120) -> str: + text = re.sub(r"<[^>]+>", "", html) + text = re.sub(r"\s+", " ", text).strip() + if len(text) > length: + text = text[:length] + "…" + return text + + +def fmt_date(s: str) -> str: + try: + return datetime.strptime(s, "%Y-%m-%d").strftime("%Y年%m月%d日") + except ValueError: + return s + + +def build(): + posts = [parse_post(p) for p in sorted(POSTS_DIR.glob("*.md"), reverse=True)] + posts.sort(key=lambda p: p["date"], reverse=True) + + OUT_BLOG.mkdir(parents=True, exist_ok=True) + + # ---------- 博客列表页 ---------- + cards = [] + for p in posts: + tags = "".join(f'{t}' for t in p["tags"]) + cards.append(f""" + + +

{p['title']}

+

{summary(p['html'])}

+
""") + if not cards: + cards.append('

暂无文章,敬请期待。

') + + blog_list = f"""{page_header("/blog/")} +
+
+

博客

+

工业软件、标准件库与数字化实践记录。

+
+{''.join(cards)} +
+
+
+{page_footer()}""" + (OUT_BLOG / "index.html").write_text(blog_list, encoding="utf-8") + + # ---------- 文章详情页 ---------- + for p in posts: + tags = "".join(f'{t}' for t in p["tags"]) + html = f"""{page_header(p['slug'], p['title'])} +
+
+ +

{p['title']}

+ +
+{p['html']} +
+
+
+{page_footer()}""" + (OUT_BLOG / p["slug"]).mkdir(parents=True, exist_ok=True) + (OUT_BLOG / p["slug"] / "index.html").write_text(html, encoding="utf-8") + + # ---------- 首页(博客首页) ---------- + recent = "".join(f""" + + +

{p['title']}

+

{summary(p['html'], 90)}

+
""" for p in posts[:3]) + if not recent: + recent = '

暂无文章,敬请期待。

' + + home = f"""{page_header("/")} +
+ +
+

INDUSTRIAL SOFTWARE · STANDARD PARTS · DIGITAL PRACTICE

+

从数学出发

+

记录工程与代码 —— 工业软件 · 标准件库 · 数字化实践,Easy Data Collector 旗下各平台的技术博客。

+ +
+
+ +
+ +
+ +
+
+

最新文章

+
+{recent} +
+
+ 查看全部文章 +
+
+
+{page_footer()}""" + OUT_HOME.write_text(home, encoding="utf-8") + + # ---------- 关于页 ---------- + about = f"""{page_header("/about/")} +
+
+

关于

+
+

SinCos

+

工业软件方向开发者。

+

正在做

+
    +
  • ResLib:资源中心管理器 —— Creo 7.0+ 标准零部件管理库(Pro/TOOLKIT DLL 插件),支持 License 在线申请与审批
  • +
  • Engineer Craft:工程实践与工艺设计平台
  • +
  • Digital Twin:数字孪生平台(three.js + Python + MQTT/EMQX)
  • +
  • Neural Research:神经网络与算法研究
  • +
  • Gitea Project:代码托管与版本管理
  • +
+

联系方式

+ +
+
+
+{page_footer()}""" + OUT_ABOUT.write_text(about, encoding="utf-8") + + print(f"构建完成:{len(posts)} 篇文章 -> {OUT_BLOG} / home.html / about.html") + + +if __name__ == "__main__": + build() diff --git a/blog/posts/2026-08-13-welcome.md b/blog/posts/2026-08-13-welcome.md new file mode 100644 index 0000000..b8ba924 --- /dev/null +++ b/blog/posts/2026-08-13-welcome.md @@ -0,0 +1,17 @@ +--- +title: 博客开张 +date: 2026-08-13 +tags: 公告, ResLib, 数字孪生 +--- + +# 博客开张 + +用统一风格搭建的聚合博客正式上线,部署在 sincos.cc。 + +本站主要记录: + +- **工业软件**:Creo 8.0 二次开发、企业级标准零部件库 ResLib 的设计与 License 体系 +- **数字化孪生**:three.js 可视化、MQTT 数据链路(EMQX)、数字孪生平台落地 +- **工程与运维**:Python、1Panel、OpenResty 反代与多站点部署实践 + +后续内容陆续更新。 diff --git a/deploy/DEPLOY.md b/deploy/DEPLOY.md new file mode 100644 index 0000000..7cecfe1 --- /dev/null +++ b/deploy/DEPLOY.md @@ -0,0 +1,195 @@ +# SinCos 聚合站部署手册(详细版) + +- 服务器:`124.220.63.9`(1Panel + OpenResty + Python) +- 域名:`sincos.cc`(腾讯云备案,已解析 A 记录 → 124.220.63.9) +- 站点形态:单站 FastAPI(:8003),根路径 = 博客首页,其余为子页面 + +## 目录结构 + +``` +website/ ← 整个目录上传到服务器 /opt/website +├─ main.py FastAPI 主程序 +├─ license.py MAC→LIC 算法 +├─ requirements.txt 依赖清单 +├─ run.sh 启动脚本 +├─ static/ 前端(首页/博客/ResLib/申请/查询/资源包/管理后台) +└─ data/ 运行时数据(自动创建) + ├─ requests/*.txt 申请记录(DokuWiki 式) + ├─ licenses/*.lic 生成的授权文件 + └─ packages/ ★ 资源包(把真实资源包 zip 放这里) +``` + +--- + +## 第一步:准备本地文件 + +1. 打包本地 `website/` 目录为 `website.zip`(只打包目录本身,不含外层) + +--- + +## 第二步:上传到服务器 + +**方式 A:1Panel 网页上传** +1Panel → 文件 → 进入 `/opt` → 上传 `website.zip` → 右键解压 → 得到 `/opt/website/` + +**方式 B:scp 命令行** +```bash +scp -r D:\黑奴编程\网站\website root@124.220.63.9:/opt/website +``` + +--- + +## 第三步:服务器安装依赖 + +SSH 登录服务器后执行: + +```bash +# 确认 python 版本(1Panel 环境一般自带 python3) +python3 --version + +# 安装依赖 +pip3 install -r /opt/website/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +若提示 pip3 不存在,用 `python3 -m pip install -r /opt/website/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple` + +--- + +## 第四步:设置管理密码并启动 + +**方式 A(推荐):1Panel 进程守护(图形化)** + +1. 1Panel → 应用商店 → 安装 **进程守护**(Process Supervisor) +2. 1Panel → 进程守护 → 创建: + - 名称:`reslib` + - 运行用户:`root` + - 运行目录:`/opt/website` + - 启动命令:`uvicorn main:app --host 0.0.0.0 --port 8003` + - 环境变量:`RESLIB_ADMIN_PASSWORD=你的强密码`(管理后台密码,务必修改) + - 开机自启:开 +3. 创建后进程状态为"运行中"即成功 + +**方式 B:1Panel Python 项目(图形化)** +1Panel → 网站 → 创建网站 → 选择 **Python 项目**: +- 运行环境:选择已装的 Python +- 项目路径:`/opt/website` +- 启动命令:`uvicorn main:app --host 0.0.0.0 --port 8003` +- 环境变量:`RESLIB_ADMIN_PASSWORD=你的强密码` + +**方式 C:临时测试** +```bash +cd /opt/website && RESLIB_ADMIN_PASSWORD="你的强密码" python3 -m uvicorn main:app --host 0.0.0.0 --port 8003 +``` + +验证服务已监听: +```bash +curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8003/ +# 应返回 200 +``` + +--- + +## 第五步:1Panel 建站(反代) + +1Panel → 网站 → 创建网站 → **反向代理**: +- 主域名:`sincos.cc` +- 其他域名:留空(不需要 www) +- 代理地址:`http://127.0.0.1:8003` +- 创建 + +然后 网站 → 选中 sincos.cc → **网站** → 确认 OpenResty 已启用(反向代理自动走 OpenResty)。 + +--- + +## 第六步:HTTPS 证书 + +1Panel → 网站 → sincos.cc → **HTTPS** → 申请证书: +- 类型:Let's Encrypt(推荐,自动续期) +- 勾选:强制 HTTPS、自动续期 +- 申请成功后站点即带 https:// + +> 备案域名证书申请无特殊要求。若 Let's Encrypt 连不上,可改用腾讯云免费证书(DNSPod 申请 → 上传证书)。 + +--- + +## 第七步:防火墙放行 + +1Panel → 安全 → 防火墙: +- 放行端口:`80`、`443`(8003 不需要对外开放,只在服务器内网访问) +- 若之前有其他站点端口(30000/10000 等)保持原状 + +--- + +## 第八步:上线验证 + +| 地址 | 预期 | +|---|---| +| `https://sincos.cc/` | 博客首页(从数学出发 + 平台卡片 + 最新文章) | +| `https://sincos.cc/blog/` | 文章列表 | +| `https://sincos.cc/reslib/` | ResLib 产品介绍 | +| `https://sincos.cc/reslib/apply` | License 申请(MAC 输入) | +| `https://sincos.cc/reslib/status` | 查询/下载 | +| `https://sincos.cc/reslib/package` | 资源包获取(仅 approved) | +| `https://sincos.cc/admin` | 管理后台(密码 = 第四步设置的) | + +--- + +## 第九步:数据目录初始化 + +```bash +mkdir -p /opt/website/data/{requests,licenses,packages} +``` + +- `packages/` 放入真实资源包文件(zip 等),文件名即展示给用户的名字 +- `requests/`、`licenses/` 由系统自动写入 + +--- + +## 日常维护 + +**写博客** +```powershell +# 本地 +# 1. 新建 blog\posts\2026-xx-xx-标题.md(front matter: title/date/tags) +# 2. 构建 +python -X utf8 blog\build.py +# 3. 将 website\static\home.html、about.html、blog\ 目录 上传覆盖到服务器 /opt/website/static/ +``` + +**备份**(每周):备份 `/opt/website/data/` 目录即可(申请记录 + 授权文件) +```bash +tar czf /root/reslib-backup-$(date +%F).tar.gz /opt/website/data +``` + +**查看日志** +```bash +# 1Panel 进程守护 → reslib → 日志 +``` + +**重启服务** +```bash +# 1Panel 进程守护 → reslib → 重启(或先停后启) +``` + +--- + +## 常见问题 + +| 问题 | 处理 | +|---|---| +| 80/443 访问超时 | 检查防火墙放行、1Panel 网站状态 | +| 反代 502 | `curl http://127.0.0.1:8003/` 检查后端是否在跑;1Panel 进程守护查看 reslib 状态 | +| 证书申请失败 | 确认 DNS A 记录解析完成且 80 端口可访问 | +| 申请提交后 txt 乱码 | 确认服务器 `python3` 默认编码 UTF-8(正常不会) | +| 忘记管理密码 | SSH 执行 `export RESLIB_ADMIN_PASSWORD=新密码` 后重启服务 | + +## 待办清单 + +- [ ] 上传 website/ 到服务器 +- [ ] 安装依赖 +- [ ] 设置 RESLIB_ADMIN_PASSWORD +- [ ] 启动服务并 curl 验证 200 +- [ ] 1Panel 建反代站 + SSL +- [ ] 防火墙放行 80/443 +- [ ] 放真实资源包到 data/packages/ +- [ ] 全站访问验证 diff --git a/website/license.py b/website/license.py new file mode 100644 index 0000000..44da022 --- /dev/null +++ b/website/license.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +""" +ResLib LIC 授权算法 +规则(见开发技术文档 V1.2 附录B): + MAC "AA-BB-CC-DD-EE-FF" -> 去横线/冒号/空格 -> "AABBCCDDEEFF" + -> 每字符映射 A=01 B=02 ... Z=26(数字 0-9 映射为 00-09) + -> 拼接后倒序 -> LIC 文件内容 +""" +import re + + +def normalize_mac(mac: str) -> str: + mac = mac.strip().upper() + mac = re.sub(r"[-:.\s]", "", mac) + if not mac or len(mac) != 12: + raise ValueError("MAC 地址无效,应为 12 位十六进制(如 AA-BB-CC-DD-EE-FF)") + if not re.fullmatch(r"[0-9A-F]{12}", mac): + raise ValueError("MAC 地址包含非法字符,仅允许 0-9、A-F") + return mac + + +def mac_to_code(mac: str) -> str: + """逐字符映射:A=01 B=02 ... Z=26,数字 0-9 -> 00-09""" + out = [] + for ch in mac: + if ch.isdigit(): + out.append(f"{int(ch):02d}") + else: + out.append(f"{ord(ch) - ord('A') + 1:02d}") + return "".join(out) + + +def generate_lic(mac: str) -> str: + """生成 LIC 文件内容(倒序)""" + norm = normalize_mac(mac) + code = mac_to_code(norm) + return code[::-1] diff --git a/website/main.py b/website/main.py new file mode 100644 index 0000000..63bc6e0 --- /dev/null +++ b/website/main.py @@ -0,0 +1,320 @@ +# -*- coding: utf-8 -*- +""" +ResLib License 申请系统后端 (FastAPI) + +存储:data/requests/.txt(DokuWiki 式纯文本) +公开:申请 / 状态查询 / 下载 license +管理:密码登录 -> 审批 -> 生成 res_center.lic +""" +import os +import re +import secrets +import time +from datetime import datetime +from pathlib import Path + +from fastapi import FastAPI, Form, HTTPException, Request, Response +from fastapi.responses import FileResponse, PlainTextResponse, RedirectResponse +from fastapi.staticfiles import StaticFiles +from pydantic import BaseModel, Field + +from license import generate_lic, normalize_mac + +BASE_DIR = Path(__file__).resolve().parent +DATA_DIR = BASE_DIR / "data" +REQ_DIR = DATA_DIR / "requests" +LIC_DIR = DATA_DIR / "licenses" +PKG_DIR = DATA_DIR / "packages" +STATIC_DIR = BASE_DIR / "static" + + +def load_admin_password() -> str: + env = os.environ.get("RESLIB_ADMIN_PASSWORD", "").strip() + if env: + return env + env_file = BASE_DIR / ".env" + if env_file.is_file(): + for line in env_file.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if line and not line.startswith("#") and "=" in line: + k, _, v = line.partition("=") + if k.strip() == "RESLIB_ADMIN_PASSWORD": + return v.strip() + return "reslib-admin" + + +ADMIN_PASSWORD = load_admin_password() +SESSION_FILE = DATA_DIR / "session.txt" + +STATUS_PENDING = "pending" +STATUS_APPROVED = "approved" +STATUS_DENIED = "denied" + +for d in (REQ_DIR, LIC_DIR, PKG_DIR): + d.mkdir(parents=True, exist_ok=True) + +app = FastAPI(title="ResLib License System", docs_url=None, redoc_url=None) + + +# --------------------------------------------------------------------------- +# 工具 +# --------------------------------------------------------------------------- + +def new_request_id() -> str: + stamp = datetime.now().strftime("%Y%m%d-%H%M%S") + return f"{stamp}-{secrets.token_hex(3).upper()}" + + +def request_path(req_id: str) -> Path: + if not re.fullmatch(r"\d{8}-\d{6}-[0-9A-F]{6}", req_id): + raise HTTPException(400, "无效的申请编号") + return REQ_DIR / f"{req_id}.txt" + + +def read_request(req_id: str) -> dict: + path = request_path(req_id) + if not path.is_file(): + raise HTTPException(404, "申请不存在") + data = {} + for line in path.read_text(encoding="utf-8").splitlines(): + if ":" in line: + k, _, v = line.partition(":") + data[k.strip()] = v.strip() + return data + + +def write_request(data: dict): + path = request_path(data["id"]) + content = "\n".join(f"{k}: {data.get(k, '')}" for k in ( + "id", "created", "status", "mac", "company", "contact", + "email", "creo", "seats", "notes", "reason", + )) + path.write_text(content + "\n", encoding="utf-8") + + +def save_lic(req_id: str, mac: str) -> Path: + lic_path = LIC_DIR / f"{req_id}.lic" + lic_path.write_text(generate_lic(mac) + "\n", encoding="ascii") + return lic_path + + +def new_session_token() -> str: + token = secrets.token_hex(24) + SESSION_FILE.write_text(token, encoding="utf-8") + return token + + +def get_session_token() -> str: + if SESSION_FILE.is_file(): + return SESSION_FILE.read_text(encoding="utf-8").strip() + return "" + + +def is_admin(request: Request) -> bool: + cookie = request.cookies.get("reslib_admin") + return bool(cookie) and secrets.compare_digest(cookie, get_session_token()) + + +def require_admin(request: Request): + if not is_admin(request): + raise HTTPException(401, "未登录或会话已失效") + + +# --------------------------------------------------------------------------- +# 公开 API +# --------------------------------------------------------------------------- + +class ApplyForm(BaseModel): + mac: str = Field(..., min_length=1) + company: str = Field(..., min_length=1) + contact: str = Field(..., min_length=1) + email: str = Field(..., min_length=1) + creo: str = "" + seats: str = "1" + notes: str = "" + + +@app.post("/api/apply") +def api_apply(form: ApplyForm): + try: + mac = normalize_mac(form.mac) + except ValueError as e: + raise HTTPException(400, str(e)) + if "@" not in form.email or "." not in form.email.split("@")[-1]: + raise HTTPException(400, "邮箱格式不正确") + + req_id = new_request_id() + data = { + "id": req_id, + "created": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "status": STATUS_PENDING, + "mac": mac, + "company": form.company.strip(), + "contact": form.contact.strip(), + "email": form.email.strip(), + "creo": form.creo.strip(), + "seats": form.seats.strip(), + "notes": form.notes.strip(), + "reason": "", + } + write_request(data) + return {"id": req_id, "status": STATUS_PENDING} + + +@app.get("/api/status/{req_id}") +def api_status(req_id: str): + data = read_request(req_id) + return { + "id": data.get("id"), + "status": data.get("status"), + "created": data.get("created"), + "reason": data.get("reason", ""), + } + + +@app.get("/api/license/{req_id}") +def api_license(req_id: str): + data = read_request(req_id) + if data.get("status") != STATUS_APPROVED: + raise HTTPException(403, "申请尚未通过审批") + lic_path = LIC_DIR / f"{req_id}.lic" + if not lic_path.is_file(): + save_lic(req_id, data["mac"]) + lic_path = LIC_DIR / f"{req_id}.lic" + return FileResponse( + lic_path, filename="res_center.lic", + media_type="application/octet-stream", + ) + + +# --------------------------------------------------------------------------- +# 资源包(仅审核通过的用户可获取) +# --------------------------------------------------------------------------- + +@app.get("/api/package/{req_id}") +def api_package_list(req_id: str): + data = read_request(req_id) + if data.get("status") != STATUS_APPROVED: + raise HTTPException(403, "资源包仅对审核通过的用户提供,请先完成 License 审批") + files = sorted(p.name for p in PKG_DIR.glob("*") if p.is_file()) + if not files: + return {"files": []} + return {"files": files} + + +@app.get("/api/package/download/{req_id}/{filename}") +def api_package_download(req_id: str, filename: str): + data = read_request(req_id) + if data.get("status") != STATUS_APPROVED: + raise HTTPException(403, "资源包仅对审核通过的用户提供") + name = os.path.basename(filename) + path = PKG_DIR / name + if not path.is_file(): + raise HTTPException(404, "资源包不存在") + return FileResponse(path, filename=name, media_type="application/octet-stream") + + +# --------------------------------------------------------------------------- +# 管理 API +# --------------------------------------------------------------------------- + +@app.post("/api/admin/login") +def admin_login(response: Response, password: str = Form(...)): + if not secrets.compare_digest(password, ADMIN_PASSWORD): + raise HTTPException(401, "密码错误") + token = new_session_token() + response.set_cookie("reslib_admin", token, httponly=True, samesite="lax", max_age=86400 * 7) + return {"ok": True} + + +@app.post("/api/admin/logout") +def admin_logout(request: Request, response: Response): + response.delete_cookie("reslib_admin") + return {"ok": True} + + +@app.get("/api/admin/list") +def admin_list(request: Request): + require_admin(request) + items = [] + for path in sorted(REQ_DIR.glob("*.txt"), reverse=True): + items.append(read_request(path.stem)) + return items + + +@app.post("/api/admin/approve/{req_id}") +def admin_approve(req_id: str, request: Request): + require_admin(request) + data = read_request(req_id) + if data.get("status") == STATUS_APPROVED: + return {"ok": True, "id": req_id} + save_lic(req_id, data["mac"]) + data["status"] = STATUS_APPROVED + data["reason"] = "" + write_request(data) + return {"ok": True, "id": req_id} + + +@app.post("/api/admin/deny/{req_id}") +def admin_deny(req_id: str, request: Request, reason: str = Form("")): + require_admin(request) + data = read_request(req_id) + data["status"] = STATUS_DENIED + data["reason"] = reason.strip()[:200] + write_request(data) + return {"ok": True, "id": req_id} + + +# --------------------------------------------------------------------------- +# 页面与静态资源 +# --------------------------------------------------------------------------- + +@app.get("/") +def index(): + return FileResponse(STATIC_DIR / "home.html") + + +@app.get("/about/") +def about_page(): + return FileResponse(STATIC_DIR / "about.html") + + +@app.get("/reslib/") +def reslib_home(): + return FileResponse(STATIC_DIR / "index.html") + + +@app.get("/reslib/apply") +def reslib_apply(): + return FileResponse(STATIC_DIR / "apply.html") + + +@app.get("/reslib/status") +def reslib_status(): + return FileResponse(STATIC_DIR / "status.html") + + +@app.get("/reslib/package") +def reslib_package(): + return FileResponse(STATIC_DIR / "package.html") + + +@app.get("/admin") +def admin_page(request: Request): + if not is_admin(request): + return RedirectResponse("/admin/login", status_code=302) + return FileResponse(STATIC_DIR / "admin.html") + + +@app.get("/admin/login") +def admin_login_page(): + return FileResponse(STATIC_DIR / "admin_login.html") + + +@app.get("/admin/logout") +def admin_logout_page(request: Request): + return RedirectResponse("/admin/login", status_code=302) + + +app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") +app.mount("/blog", StaticFiles(directory=STATIC_DIR / "blog", html=True, check_dir=False), name="blog") diff --git a/website/requirements.txt b/website/requirements.txt new file mode 100644 index 0000000..f864b33 --- /dev/null +++ b/website/requirements.txt @@ -0,0 +1,3 @@ +fastapi>=0.110 +uvicorn>=0.29 +python-multipart>=0.0.9 diff --git a/website/run.sh b/website/run.sh new file mode 100644 index 0000000..bd39808 --- /dev/null +++ b/website/run.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# ResLib License 系统启动脚本(服务器 / 1Panel Python 应用) +# 用法: +# 1. 上传 website/ 目录到服务器,如 /opt/website +# 2. 设置管理密码:export RESLIB_ADMIN_PASSWORD='你的强密码' +# 3. 执行本脚本(或配置成 systemd / 1Panel Python 应用) +cd "$(dirname "$0")" +exec uvicorn main:app --host 0.0.0.0 --port 8003 diff --git a/website/static/about.html b/website/static/about.html new file mode 100644 index 0000000..7f52f23 --- /dev/null +++ b/website/static/about.html @@ -0,0 +1,69 @@ + + + + + +江北的渔夫的空间 + + + + + + + + + +
+
+
+

关于

+
+

SinCos

+

工业软件方向开发者。

+

正在做

+
    +
  • ResLib:资源中心管理器 —— Creo 7.0+ 标准零部件管理库(Pro/TOOLKIT DLL 插件),支持 License 在线申请与审批
  • +
  • Engineer Craft:工程实践与工艺设计平台
  • +
  • Digital Twin:数字孪生平台(three.js + Python + MQTT/EMQX)
  • +
  • Neural Research:神经网络与算法研究
  • +
  • Gitea Project:代码托管与版本管理
  • +
+

联系方式

+ +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/website/static/admin.html b/website/static/admin.html new file mode 100644 index 0000000..a6d7d1b --- /dev/null +++ b/website/static/admin.html @@ -0,0 +1,233 @@ + + + + + +管理后台 - ResLib License + + + + + + + + +
+
+
+ + + + + +
+
+ + + + + + + + +
申请编号提交时间公司联系人邮箱MAC状态操作
+
+ +
+
+ + + + + + diff --git a/website/static/admin_login.html b/website/static/admin_login.html new file mode 100644 index 0000000..78f5145 --- /dev/null +++ b/website/static/admin_login.html @@ -0,0 +1,65 @@ + + + + + +管理登录 - ResLib License + + + + + +
+
+
+ ResLib +

ResLib License 管理后台

+
+
+
+ + +
+ +
+
+
+
+ + + + diff --git a/website/static/apply.html b/website/static/apply.html new file mode 100644 index 0000000..b3a74db --- /dev/null +++ b/website/static/apply.html @@ -0,0 +1,140 @@ + + + + + +申请 License - 江北的渔夫的空间 + + + + + + + + + + +
+
+

申请 License

+

提交后我们会进行人工审核,审核通过后凭申请编号下载授权文件。

+ +
+
+ + +

如何获取:cmd 运行 getmac /v,取"物理地址"列(12 位十六进制)。

+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+
+
+ + + + + + diff --git a/website/static/blog/2026-08-13-welcome/index.html b/website/static/blog/2026-08-13-welcome/index.html new file mode 100644 index 0000000..ae1d717 --- /dev/null +++ b/website/static/blog/2026-08-13-welcome/index.html @@ -0,0 +1,65 @@ + + + + + +博客开张 - 江北的渔夫的空间 + + + + + + + + + +
+
+
+ +

博客开张

+ +
+

博客开张

+

用统一风格搭建的聚合博客正式上线,部署在 sincos.cc。

+

本站主要记录:

+
    +
  • 工业软件:Creo 8.0 二次开发、企业级标准零部件库 ResLib 的设计与 License 体系
  • +
  • 数字化孪生:three.js 可视化、MQTT 数据链路(EMQX)、数字孪生平台落地
  • +
  • 工程与运维:Python、1Panel、OpenResty 反代与多站点部署实践
  • +
+

后续内容陆续更新。

+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/website/static/blog/index.html b/website/static/blog/index.html new file mode 100644 index 0000000..da2bddc --- /dev/null +++ b/website/static/blog/index.html @@ -0,0 +1,61 @@ + + + + + +江北的渔夫的空间 + + + + + + + + + +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/website/static/css/style.css b/website/static/css/style.css new file mode 100644 index 0000000..636b353 --- /dev/null +++ b/website/static/css/style.css @@ -0,0 +1,266 @@ +:root { + --blue: #005eb8; + --blue-dark: #00427f; + --blue-light: #0a7bd8; + --ink: #1c2b3a; + --ink-light: #3d5a75; + --bg-soft: #f4f8fd; + --line: #e3ecf5; + --danger: #c0392b; + --ok: #1e8449; + --radius: 10px; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: "Segoe UI", "Microsoft YaHei", "PingFang SC", sans-serif; + color: var(--ink); + background: #fff; + line-height: 1.6; +} + +.container { width: 100%; max-width: 1320px; margin: 0 auto; padding: 0 32px; } +.container.narrow { max-width: 720px; margin: 0 auto; } + +a { color: var(--blue); text-decoration: none; } +a:hover { color: var(--blue-dark); } + +/* Header */ +.site-header { + position: sticky; + top: 0; + background: rgba(255, 255, 255, 0.97); + border-bottom: 1px solid var(--line); + z-index: 100; +} +.header-inner { + display: flex; align-items: center; justify-content: space-between; + min-height: 60px; padding: 10px 0; gap: 12px; + position: relative; +} +.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; color: var(--ink); flex-shrink: 0; } +.brand img { width: 26px; height: 26px; } +.brand-name { color: var(--blue); font-size: 1.1rem; white-space: nowrap; } + +.nav-toggle { + display: none; margin-left: auto; + background: none; border: 1px solid var(--line); border-radius: 6px; + font-size: 1.3rem; line-height: 1; color: var(--blue); + padding: 6px 12px; cursor: pointer; +} + +.main-nav { display: flex; gap: 20px; align-items: center; flex-wrap: nowrap; } +.main-nav a { color: var(--ink-light); font-size: 0.93rem; font-weight: 500; white-space: nowrap; } +.main-nav a:hover, .main-nav a.active { color: var(--blue); } + +@media (max-width: 1280px) { + .main-nav { gap: 14px; } + .main-nav a { font-size: 0.88rem; } +} + +@media (max-width: 1080px) { + .nav-toggle { display: block; } + .main-nav { + display: none; position: absolute; top: 100%; left: 0; right: 0; + background: #fff; border-bottom: 1px solid var(--line); + flex-direction: column; align-items: stretch; gap: 0; + padding: 8px 0; box-shadow: 0 12px 24px rgba(0, 30, 60, 0.10); + } + .main-nav.open { display: flex; } + .main-nav a { padding: 12px 32px; font-size: 0.95rem; } + .main-nav a:hover { background: var(--bg-soft); } +} + +/* Buttons */ +.btn { + display: inline-block; padding: 11px 24px; border-radius: 6px; + font-weight: 600; font-size: 0.95rem; border: 1px solid transparent; + cursor: pointer; font-family: inherit; text-align: center; + transition: background 0.15s ease, color 0.15s ease; +} +.btn-primary { background: var(--blue); color: #fff; } +.btn-primary:hover { background: var(--blue-dark); color: #fff; } +.btn-ghost { background: transparent; color: var(--blue); border-color: var(--blue); } +.btn-ghost:hover { background: #e8f2ff; } +.btn-danger { background: #fff; color: var(--danger); border-color: var(--danger); } +.btn-danger:hover { background: #fdf0ee; } +.btn-block { width: 100%; } +.btn-sm { padding: 7px 14px; font-size: 0.85rem; } +.btn[disabled] { opacity: 0.5; cursor: not-allowed; } + +/* Hero */ +.hero { + background: linear-gradient(135deg, #003766 0%, #005eb8 55%, #0a7bd8 100%); + color: #fff; padding: 76px 0 84px; position: relative; overflow: hidden; +} +.hero { max-width: none; } +.hero::before, .hero::after { + content: ""; position: absolute; border-radius: 50%; + background: radial-gradient(circle, rgba(255,255,255,0.12) 0%, transparent 70%); +} +.hero::before { width: 460px; height: 460px; right: -130px; top: -150px; } +.hero::after { width: 320px; height: 320px; left: -100px; bottom: -140px; } +.hero .container { position: relative; } +.hero h1 { font-size: 2.4rem; font-weight: 700; line-height: 1.2; } +.hero-eyebrow { + font-size: 0.9rem; letter-spacing: 0.18em; + color: #bcd9f5; margin-bottom: 18px; +} +.hero-sub { max-width: 640px; margin: 16px 0 28px; color: #e8f2ff; font-size: 1.05rem; } +.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; } + +/* 欧拉恒等式装饰 */ +.hero-euler { + position: absolute; right: 18%; top: 50%; + transform: translateY(-50%); + font-family: Georgia, "Times New Roman", serif; + font-style: italic; + font-size: clamp(1.6rem, 4.5vw, 4rem); + color: rgba(255, 255, 255, 0.14); + pointer-events: none; user-select: none; + white-space: nowrap; letter-spacing: 0.02em; +} +.hero-euler sup { font-size: 0.6em; } +.btn-light { background: #fff; color: var(--blue); } +.btn-light:hover { background: #e8f2ff; color: var(--blue-dark); } +.btn-outline-light { background: transparent; color: #fff; border-color: rgba(255,255,255,0.7); } +.btn-outline-light:hover { background: rgba(255,255,255,0.12); } + +/* Sections & cards */ +.section { padding: 64px 0; } +.section-alt { background: var(--bg-soft); } +.section-title { + font-size: 1.55rem; color: var(--blue-dark); + border-left: 4px solid var(--blue); padding-left: 14px; margin-bottom: 10px; +} +.section-desc { color: var(--ink-light); margin-bottom: 26px; } + +.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin-top: 26px; } +.card { + background: #fff; border: 1px solid var(--line); border-radius: var(--radius); + padding: 26px 24px; transition: transform 0.15s ease, box-shadow 0.15s ease; +} +.card:hover { transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0,94,184,0.10); } +.card .icon { font-size: 1.7rem; color: var(--blue); margin-bottom: 12px; } +.card h3 { font-size: 1.05rem; color: var(--blue-dark); margin-bottom: 8px; } +.card p { font-size: 0.93rem; color: var(--ink-light); } + +/* Forms */ +.form-card { + background: #fff; border: 1px solid var(--line); border-radius: var(--radius); + padding: 30px 28px; margin-top: 26px; +} +.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } +.form-group { margin-bottom: 16px; } +.form-group label { display: block; font-size: 0.88rem; font-weight: 600; margin-bottom: 6px; } +.form-group input, .form-group select, .form-group textarea { + width: 100%; padding: 10px 13px; border: 1px solid #cfdcea; border-radius: 6px; + font-size: 0.93rem; font-family: inherit; background: #fbfdff; color: var(--ink); +} +.form-group input:focus, .form-group select:focus, .form-group textarea:focus { + outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(0,94,184,0.12); +} +.form-hint { font-size: 0.82rem; color: var(--ink-light); margin-top: 4px; } +.form-tip { margin-top: 14px; font-size: 0.88rem; text-align: center; } + +/* Result / alerts */ +.alert { padding: 12px 16px; border-radius: 6px; font-size: 0.92rem; margin-top: 14px; display: none; } +.alert.show { display: block; } +.alert-ok { background: #e9f7ef; color: var(--ok); border: 1px solid #b8e6c8; } +.alert-err { background: #fdf0ee; color: var(--danger); border: 1px solid #f2c4be; } + +.badge { display: inline-block; padding: 2px 12px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; } +.badge-pending { background: #fef3dc; color: #9a6700; } +.badge-approved { background: #e9f7ef; color: var(--ok); } +.badge-denied { background: #fdf0ee; color: var(--danger); } + +/* Admin table */ +.toolbar { display: flex; justify-content: space-between; align-items: center; margin: 24px 0 14px; flex-wrap: wrap; gap: 10px; } +.table-wrap { overflow-x: auto; } +table.admin { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; min-width: 720px; } +table.admin th, table.admin td { padding: 11px 14px; text-align: left; font-size: 0.9rem; border-bottom: 1px solid var(--line); } +table.admin th { background: var(--bg-soft); color: var(--blue-dark); font-weight: 600; white-space: nowrap; } +table.admin tr:hover td { background: #f7fbff; } +table.admin .actions { white-space: nowrap; } + +/* Footer */ +.site-footer { background: var(--blue-dark); color: #d6e6f7; padding: 36px 0 24px; } +.footer-inner { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; font-size: 0.86rem; } +.site-footer a { color: #a9c6e3; } +.site-footer a:hover { color: #fff; } + +.mono { font-family: Consolas, monospace; letter-spacing: 0.04em; } + +/* Blog */ +.breadcrumb { font-size: 0.85rem; color: var(--ink-light); margin-bottom: 14px; } +.breadcrumb a { color: var(--ink-light); } +.breadcrumb a:hover { color: var(--blue); } + +.post-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 18px; margin-top: 26px; } +.post-card { + display: block; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); + padding: 22px 24px; transition: transform 0.15s ease, box-shadow 0.15s ease; color: var(--ink); +} +.post-card:hover { transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0, 94, 184, 0.10); color: var(--ink); } +.post-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; } +.post-date { font-size: 0.8rem; color: var(--ink-light); } +.post-title { font-size: 1.08rem; color: var(--blue-dark); margin-bottom: 6px; } +.post-excerpt { font-size: 0.9rem; color: var(--ink-light); line-height: 1.55; } + +.tag { + display: inline-block; font-size: 0.74rem; color: var(--blue); + background: #e8f2ff; border-radius: 20px; padding: 1px 10px; +} + +/* Article */ +.article { line-height: 1.8; font-size: 0.98rem; color: var(--ink); margin-top: 20px; } +.article h1, .article h2, .article h3, .article h4 { + color: var(--blue-dark); margin: 1.6em 0 0.6em; line-height: 1.4; +} +.article h1 { font-size: 1.6rem; border-bottom: 1px solid var(--line); padding-bottom: 0.3em; } +.article h2 { font-size: 1.3rem; } +.article h3 { font-size: 1.1rem; } +.article p { margin: 0.8em 0; } +.article ul, .article ol { padding-left: 1.5em; margin: 0.8em 0; } +.article li { margin: 0.3em 0; } +.article code { + background: #eef4fb; color: #00518f; border-radius: 4px; + padding: 1px 6px; font-family: Consolas, monospace; font-size: 0.9em; +} +.article pre { + background: #0d2b4a; color: #d8ecff; border-radius: 8px; + padding: 16px 18px; overflow-x: auto; margin: 1em 0; +} +.article pre code { background: none; color: inherit; padding: 0; } +.article blockquote { + border-left: 4px solid var(--blue); background: var(--bg-soft); + padding: 10px 16px; margin: 1em 0; color: var(--ink-light); border-radius: 0 6px 6px 0; +} +.article table { width: 100%; border-collapse: collapse; margin: 1em 0; font-size: 0.92rem; } +.article th, .article td { border: 1px solid var(--line); padding: 8px 12px; text-align: left; } +.article th { background: var(--bg-soft); color: var(--blue-dark); } +.article img { max-width: 100%; border-radius: 8px; } +.article a { text-decoration: underline; } + +/* Card as link */ +.card-link { display: block; color: var(--ink); } +.card-link:hover { color: var(--ink); } + +@media (max-width: 720px) { + .post-list { grid-template-columns: 1fr; } +} + +@media (max-width: 1024px) { + .main-nav { gap: 12px 16px; } + .main-nav a { font-size: 0.88rem; } +} + +@media (max-width: 720px) { + .form-row { grid-template-columns: 1fr; } + .hero h1 { font-size: 1.8rem; } + .hero { padding: 56px 0 64px; } + .section { padding: 48px 0; } + .container { padding: 0 18px; } + .hero-actions .btn { width: 100%; } +} diff --git a/website/static/home.html b/website/static/home.html new file mode 100644 index 0000000..db42207 --- /dev/null +++ b/website/static/home.html @@ -0,0 +1,99 @@ + + + + + +江北的渔夫的空间 + + + + + + + + + +
+
+ +
+

INDUSTRIAL SOFTWARE · STANDARD PARTS · DIGITAL PRACTICE

+

从数学出发

+

记录工程与代码 —— 工业软件 · 标准件库 · 数字化实践,Easy Data Collector 旗下各平台的技术博客。

+ +
+
+ +
+ +
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/website/static/img/logo.svg b/website/static/img/logo.svg new file mode 100644 index 0000000..b02aabf --- /dev/null +++ b/website/static/img/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/website/static/index.html b/website/static/index.html new file mode 100644 index 0000000..77e4892 --- /dev/null +++ b/website/static/index.html @@ -0,0 +1,119 @@ + + + + + +江北的渔夫的空间 | Easy Data Collector + + + + + + + + + + +
+
+
+

资源中心管理器 ResLib

+

面向 Creo 7.0+ 的企业级标准零部件管理库 —— 标准件浏览、3D 预览、搜索过滤、快速装配,配套自研 LIC 授权体系。

+ +
+
+ +
+
+

核心功能

+
+
▤

标准件库浏览

TreeView 目录树 + 零件列表,直接扫描目录中的 .prt / .asm 文件,去重保留最高版本。

+
◉

3D 实时预览

OpenGL 三角面片渲染,三点光源,鼠标旋转 / 缩放,装配体递归解析。

+
⌕

全局搜索过滤

递归全库匹配文件名,支持按名称快速定位标准件。

+
⇱

快速装配

选中零件一键插入装配,或进入参数定位模式(X/Y/Z/RX/RY/RZ)精确装配。

+
☆

收藏与最近使用

favorites / recent 记录,MRU 去重,常用件一键直达。

+
⛨

LIC 授权体系

MAC 地址绑定授权,未授权时装配功能隐藏,保障企业版权。

+
✎

版本与功能定制

支持按企业需求定制产品版本与扩展功能,贴合实际业务流程开发。

+
☁

私有云部署

企业私有云部署与标准化同步服务,标准件库统一管理、多端实时同步。

+
◍

标准化降本

减少 SKU 与同类物料,提升零部件借用率与标准化统一程度,有效降低成本。

+
◈

Inventor 服务

提供 Autodesk Inventor 版本类似服务功能,多 CAD 平台标准件管理方案。

+
+
+
+ +
+
+

资源包内容

+

资源包随 License 发放,内置常用标准件库,覆盖以下品类:

+
+
▣

标准化配件

通用标准化零部件库,覆盖各行业通用件,开箱即用。

+
❖

BPE 卫生级配件

符合 BPE 规范的卫生级管件、阀门与连接件,适用于生物制药与洁净工艺。

+
✚

食品 · 医药 · 包装配件

食品、医药与包装工程常用配件库,贴合产线设计选型需求。

+
⏏

气动系统配件

气缸、阀岛、接头等气动系统常用件,助力自动化产线快速搭建。

+
+ +
+
+ +
+
+

版本与环境

+
+

适配版本

Creo Parametric 7.0 ~ 10.0(64 位),Pro/TOOLKIT DLL 插件。

+

系统要求

Windows 10 / 11 64 位,Visual C++ 2022 运行库。

+

部署形态

本地部署:DLL + protk.dat + config\lic\res_center.lic,configen.exe 一键配置。

+
+
+
+ +
+
+

License 申请流程

+

三步完成授权:

+
+

1. 提交申请

填写公司、联系人与机器 MAC 地址,提交后获得申请编号。

+

2. 人工审批

我们核对信息后确认授权,通过后自动生成授权文件。

+

3. 下载安装

凭申请编号查询状态并下载 res_center.lic,放入 config\lic\ 目录。

+
+ +
+
+
+ + + + + diff --git a/website/static/js/nav.js b/website/static/js/nav.js new file mode 100644 index 0000000..f560ddc --- /dev/null +++ b/website/static/js/nav.js @@ -0,0 +1,16 @@ +(function () { + "use strict"; + document.addEventListener("DOMContentLoaded", function () { + var toggle = document.getElementById("navToggle"); + var nav = document.getElementById("mainNav"); + if (!toggle || !nav) return; + toggle.addEventListener("click", function () { + nav.classList.toggle("open"); + }); + document.addEventListener("click", function (e) { + if (!nav.contains(e.target) && !toggle.contains(e.target)) { + nav.classList.remove("open"); + } + }); + }); +})(); diff --git a/website/static/package.html b/website/static/package.html new file mode 100644 index 0000000..6d1571d --- /dev/null +++ b/website/static/package.html @@ -0,0 +1,97 @@ + + + + + +资源包获取 - 江北的渔夫的空间 + + + + + + + + + + +
+
+

资源包获取

+

资源包(部署文件、标准件库样例、说明文档等)仅对 License 审核通过的用户提供,请凭申请编号验证身份。内含标准化配件、BPE 卫生级配件、食品 / 医药 / 包装工程常用配件与气动系统配件等标准件库。

+ +
+
+ + +
+ +
+
+
+
+ + + + + + diff --git a/website/static/status.html b/website/static/status.html new file mode 100644 index 0000000..3f3779e --- /dev/null +++ b/website/static/status.html @@ -0,0 +1,109 @@ + + + + + +查询 / 下载 License - ResLib + + + + + + + + +
+
+

查询申请状态

+

输入申请时获得的申请编号。

+ +
+
+ + +
+ +
+
+
+
+ + + + + + diff --git a/开发技术文档_V1.2.txt b/开发技术文档_V1.2.txt new file mode 100644 index 0000000..fa10a30 --- /dev/null +++ b/开发技术文档_V1.2.txt @@ -0,0 +1,115 @@ +====================================================================== + 资源中心管理器 V1.2 — 开发技术文档 + 2026-07-29 +====================================================================== + +一、项目概述 +-------------------------------------------------- +项目:资源中心管理器 (Res_center) +类型:Creo Pro/TOOLKIT DLL 插件 +功能:标准件库浏览、3D 预览、搜索过滤、快速装配、LIC 授权 +语言:C (核心) + C++ (preview.cpp) +编译器:MSVC 2022 | SDK:Creo 8.0 PTK | 平台:Win10/11 x64 | Creo 7.0+ + + +二、源文件 +-------------------------------------------------- + src/main.c (50行) DLL入口,路径初始化 + src/dialog.c (1080行) 主对话框(TreeView+Parts+Preview+FT+Search+LIC) + src/menu.c (73行) Creo命令注册(2个命令) + src/assembly.c (624行) 装配逻辑 + 参数化对话框 + src/preview.cpp (223行) OpenGL 3D 预览 + src/license.c (250行) MAC激活(源码保留,未编译) + + include/res_center.h (84行) 公共头文件 + + tools/keygen_lic.c LIC授权文件生成器(Metro UI + BGM) + tools/configen.c protk.dat自动配置器(Metro UI) + tools/build_lic.bat keygen编译脚本 + tools/build_config.bat configen编译脚本 + + +三、Creo命令(2个) +-------------------------------------------------- + RL_Ico2 资源中心 → 对话框 + 插入装配 + RL_Ico3 资源中心(参数定位) → 对话框 + 参数窗口(X/Y/Z/RX/RY/RZ) + + +四、核心技术 +-------------------------------------------------- + +【4.1】零件发现 — LoadParts() + 直接扫目录 *.prt / *.prt.* / *.asm / *.asm.* → 提取基名 → 去重保留最高版本 + +【4.2】全局搜索 — SearchDirRecursive() + 搜索框 → 递归全库匹配文件名 → 显示结果 + +【4.3】3D 预览 — OpenGL + ProPartTessellate → 三角面片 → glDrawElements + 装配体:ProSolidFeatVisit → ProAsmcompMdlGet → ProAsmcomppathInit → ProAsmcomppathTrfGet + 3点光 + 双面光,灰蓝材质,中灰背景,鼠标旋转/缩放 + +【4.4】对话框布局 + TreeView(300固定) | 按钮+搜索+零件列表(35%) | GL预览+族表+按钮(65%) + +【4.5】收藏/最近使用 + config\favorites.txt, config\recent.txt,MRU去重 + +【4.6】LIC授权 + 文件:config\lic\res_center.lic + 算法:MAC → 去横线 → A=01 B=02 ... Z=26 → 倒序 + 验证:选中零件时检查LIC → 不匹配则隐藏"插入装配",显示"LIC文件不正确" + 工具:tools\keygen_lic.exe (Metro暗色UI + poemstear.mp3背景音乐) + + +五、已知限制 +-------------------------------------------------- + 超大模型(>500万顶点) → 提示"模型过大,已取消预览" + 旧版 .prt → 部分文件 ProPartTessellate 失败 + 装配体阵列实例 → Pattern 成员待完善 + 组件替换/删除 → PTK DLL 不支持(参见附录A) + + +六、已修复 Bug +-------------------------------------------------- + 最近使用/收藏模式崩溃 → ApplyFilter 递归断环 + 文件路径双 config → FAV_FILE/RECENT_FILE 修正 + 参数对话框无确定按钮 → 英文→中文,对话框加高 + 搜索框上边框缺失 → WS_EX_CLIENTEDGE 替换 WS_BORDER + Insert 按钮中文被截 → 200×50 加宽居中 + + +七、工具说明 +-------------------------------------------------- + tools\keygen_lic.exe LIC授权文件生成器(Metro暗色UI + 背景音乐) + tools\configen.exe protk.dat自动配置器(一键生成) + 部署时放在DLL同目录运行 + + +====================================================================== + 附录A:组件替换开发记录(已暂停) +====================================================================== + 需求:装配环境选中组件 → 从资源中心选新零件 → 删旧换新 + 结论:PTK DLL 不支持装配树修改 + + 尝试方案: + ProFeatureDelete → 链接失败(库未导出) + ProFeatureSuppress → 运行时崩溃 + ProFeatureWithoptionsDelete → 运行时崩溃 + ProMacroExecute(ProCmdMdlTreeDelete) → 运行时崩溃 + ProSelect+先装后删 → 状态冲突 + + 后续:研究 Creo 原生 Replace 命令名或 OTK/同步模式 + + +====================================================================== + 附录B:LIC 授权规则 +====================================================================== + 算法: + MAC "AA-BB-CC-DD-EE-FF" → 去横线 "AABBCCDDEEFF" + → A=01 B=02 ... Z=26 → "010102020303040405050606" + → 倒序 → "606050504040303020201010" + = LIC 文件内容 + + 生成:keygen_lic.exe AA-BB-CC-DD-EE-FF res_center.lic + 部署:res_center.lic 放入 config\lic\ diff --git a/部署指南_V1.2.txt b/部署指南_V1.2.txt new file mode 100644 index 0000000..4002d0a --- /dev/null +++ b/部署指南_V1.2.txt @@ -0,0 +1,99 @@ +====================================================================== + 资源中心管理器 V1.2 — 部署指南 + Creo 7.0 / 8.0 / 9.0 / 10.0 (64位) | 2026-07-29 +====================================================================== + +一、环境要求 +-------------------------------------------------- +1. Windows 10/11 64位 +2. Creo Parametric 7.0+ (64位) +3. Visual C++ 2022 运行库 + + +二、部署文件清单 +-------------------------------------------------- + D:\ResLib\ + ├── Res_center_80.dll ← 核心 DLL(必须) + ├── protk.dat ← Creo 插件注册文件(必须) + ├── ucore68.dll ← Creo 运行时依赖(必须) + ├── udata68.dll ← Creo 运行时依赖(必须) + ├── folder.ico ← 图标 + ├── configen.exe ← protk.dat 自动配置器 + ├── config\ ← 配置文件(自动生成) + │ └── lic\ + │ └── res_center.lic ← 授权文件(必须,见第五节) + ├── text\ ← 本地化资源(必须) + │ └── usascii\ + │ └── folder.gif + └── Resource_Library\ ← ★ 标准件库(核心数据) + + +三、部署步骤 +-------------------------------------------------- + +【步骤 1】复制文件 + 将部署文件夹复制到目标电脑(如 D:\ResLib) + 路径不要有中文或空格 + +【步骤 2】生成 protk.dat + 双击 configen.exe → 点击 "GENERATE protk.dat" + 自动检测当前路径并生成正确配置 + +【步骤 3】注册到 Creo + 在 config.pro 中添加一行: + protkdat D:\ResLib\protk.dat + + config.pro 位置: + C:\Users\<用户名>\AppData\Roaming\PTC\ProENGINEER\Wildfire\.wf\.Creo\ + +【步骤 4】配置授权 + 将获得的 res_center.lic 放入 config\lic\ 目录 + 不可则"插入装配"按钮不显示,其他功能不受影响 + +【步骤 5】验证 + 启动 Creo → 出现"资源中心"按钮 → 打开对话框 → 选零件 → 3D 预览 → 插入装配 + + +四、可用命令 +-------------------------------------------------- + 资源中心 → 选零件 → "插入装配" + 资源中心(参数定位) → 选零件 → 参数窗口(X/Y/Z/RX/RY/RZ) → 精确装配 + + +五、LIC 授权文件 +-------------------------------------------------- + 位置:config\lic\res_center.lic + 生成方式:运行 tools\keygen_lic.exe,输入 MAC 地址,点击 Generate + 将生成的 res_center.lic 复制到 config\lic\ 目录 + + 授权规则: + MAC 地址 → 去除横线/空格 → A=01 B=02 ... Z=26 → 数字串倒序 → lic 内容 + + +六、环境变量(可选) +-------------------------------------------------- + RES_CENTER_DIR = D:\ResLib + 未设置时自动使用 DLL 所在目录 + + +七、常见问题 +-------------------------------------------------- +Q1: 启动提示"无法加载辅助应用程序" +A1: 检查 protk.dat 路径和 REVISION=36,或用 configen.exe 重新生成 + +Q2: 点击按钮无反应 +A2: 确保 ucore68.dll、udata68.dll 与 DLL 同目录 + +Q3: 3D 预览空白 +A3: 显卡需支持 OpenGL(Windows 自带即可) + +Q4: "插入装配"按钮不显示 +A4: 检查 config\lic\res_center.lic 是否存在且内容正确 + +Q5: 如何更新标准件库 +A5: 直接在 Resource_Library 下增删 .prt/.asm 或目录,无需维护 .mnu + + +八、版本信息 +-------------------------------------------------- +版本:V1.2 | DLL:Res_center_80.dll | 兼容:Creo 7.0~10.0