Project Docs

onlyWeb 个人求职与作品展示系统 · architecture.md

在线查看上传的 Markdown 项目文档。

onlyWeb 系统架构文档

目录

1. 架构目标

onlyWeb 的架构目标是:

  • 小型化:适合个人维护,不引入不必要的复杂系统。
  • Docker 化:支持本地、服务器和 NAS 等环境部署。
  • 数据驱动:页面内容来自数据库,而不是硬编码页面。
  • 可扩展:后续可以加入访问统计、文件存储、备份和权限增强。

2. 推荐技术栈

当前采用方案 A:完整 Docker 化。

推荐组合:

txt
Web 框架:Next.js
语言:TypeScript
数据库:SQLite
数据访问:better-sqlite3
样式:全局 CSS
认证:自定义管理员登录
文档转换:LibreOffice Writer,用于 Word 转 PDF 预览
部署:Docker Compose

当前正式实现使用 Next.js App Router;数据库模型和产品结构保持轻量,避免引入复杂 CMS。

3. 当前前端 Mock 原型

当前原型代码独立放在:

txt
webMock/

用途:

  • 验证页面风格。
  • 验证前台信息架构。
  • 验证后台管理范围。
  • 验证定制页的管理员预览和 HR 专属访问流程。

当前原型不连接数据库,数据来自:

txt
webMock/src/mockData.ts

webMock/ 的 Mock 登录状态暂存在浏览器 localStorage。正式程序已使用服务端认证和 HTTP-only Cookie 会话。

4. 系统组件

txt
Browser
  ↓
Web App
  ↓
SQLite

MVP Docker 服务:

txt
app
SQLite 文件数据库

后续增强服务:

txt
caddy / nginx
backup
minio

5. 模块划分

5.1 Public Frontend

负责公开页面展示。

包含:

  • 首页
  • 关于我
  • 通用简历
  • 作品列表
  • 作品详情
  • 定制简历页

5.2 Admin Dashboard

负责内容管理。

包含:

  • 登录
  • 个人资料管理
  • 作品管理
  • 经历管理
  • 技能管理
  • 定制简历页管理
  • HR 专属分享链接生成与复制
  • 管理员预览与 HR 视图切换

5.3 Server Layer

负责业务逻辑和数据访问。

包含:

  • 认证与会话
  • 数据校验
  • 数据库读写
  • 文件上传,后续

5.4 Database

使用 SQLite 文件数据库存储结构化数据。

核心数据:

  • 管理员账号
  • 个人资料
  • 作品
  • 经历
  • 技能
  • 定制简历页

6. 建议目录结构

txt
onlyWeb/
  webMock/
    src/
      main.tsx
      mockData.ts
      styles.css
    package.json
    vite.config.ts

  src/
    routes/
      index.tsx
      about.tsx
      resume.tsx
      projects/
      for/
      admin/
    components/
      public/
      admin/
      ui/
    db/
      schema.sql
      sqlite.ts
    server/
      auth.ts
      validators.ts
    lib/
      slug.ts
      dates.ts
    styles/

  public/
    uploads/

  docs/
    prd.md
    architecture.md
    roadmap.md

  docker-compose.yml
  Dockerfile
  .env.example
  README.md

实际目录会根据框架约定微调。

7. 数据模型草案

7.1 admin_users

管理员账号。

ts
{
  id: string
  email: string
  passwordHash: string
  createdAt: Date
  updatedAt: Date
}

7.2 profiles

个人资料。MVP 可以只维护一条记录。

ts
{
  id: string
  name: string
  title: string
  bio: string
  email: string
  phone?: string
  location?: string
  githubUrl?: string
  linkedinUrl?: string
  websiteUrl?: string
  avatarUrl?: string
  createdAt: Date
  updatedAt: Date
}

7.3 projects

作品。

ts
{
  id: string
  title: string
  slug: string
  summary: string
  description: string
  coverImageUrl?: string
  techStack: string[]
  role: string
  highlights: string[]
  demoUrl?: string
  githubUrl?: string
  docsUrl?: string
  isFeatured: boolean
  isPublished: boolean
  createdAt: Date
  updatedAt: Date
}

7.4 experiences

经历。

ts
{
  id: string
  company: string
  role: string
  startDate: string
  endDate?: string
  summary: string
  highlights: string[]
  skills: string[]
  isPublished: boolean
  createdAt: Date
  updatedAt: Date
}

7.5 skills

技能。

ts
{
  id: string
  name: string
  category: string
  level?: number
  sortOrder: number
  createdAt: Date
  updatedAt: Date
}

7.6 resume_pages

定制简历页。SQLite MVP 中直接用 JSON 数组字段保存关联 ID,减少个人小系统的表复杂度。

ts
{
  id: string
  companyName: string
  companySlug: string
  positionName: string
  positionSlug: string
  headline: string
  intro: string
  motivation?: string
  shareToken: string
  projectIds: string[]
  experienceIds: string[]
  skillIds: string[]
  isPublished: boolean
  createdAt: Date
  updatedAt: Date
}

8. 路由设计

8.1 前台路由

txt
/                               首页
/about                          关于我
/resume                         通用简历
/projects                       作品列表
/portfolio                      独立作品集展示页,无导航,可进入作品详情
/portfolio/:slug                 独立作品集专用详情页,无全站导航
/portfolio/:slug/docs            独立作品集专用文档预览页,无全站导航
/wiki                           Wiki 知识库首页
/wiki/:slug                     Wiki 笔记详情页
/projects/:slug                 作品详情
/r/:shareToken                  HR 专属定制简历页
/projects/:slug/docs            Markdown / PDF / Word 项目文档在线预览
/projects/:slug/docs/preview    PDF 预览与 Word 转 PDF 预览接口
/projects/:slug/docs/download   原始项目文档下载接口
/uploads/profile/:fileName      个人资料图片访问接口
/contact                        联系方式

说明:

  • /r/:shareToken 是发给 HR 的访问地址。
  • 前台不提供定制页列表。
  • HR 只能访问 token 对应的单个定制页。
  • /portfolio 使用独立展示模式,不渲染全站 Header/Footer;作品卡片输出到 /portfolio/:slug 专用详情链接。/portfolio/:slug 复用作品详情内容,/portfolio/:slug/docs 复用文档预览能力,但同样不渲染全站 Header/Footer,避免访客通过右上角导航进入其他页面。后台 /admin/projects 展示该页面完整地址并支持复制。

8.2 后台路由

txt
/admin/login                    登录
/admin                          后台首页、个人资料、技能列表行编辑
/admin/projects                 新版作品管理,支持新建、编辑、软删除、普通文档/效果演示文档上传、模块展示配置;桌面端使用独立滚动双栏布局
/admin/experiences              新版经历管理,支持新建、编辑、软删除
/admin/custom-pages             新版定制页管理,支持新建、编辑、软删除
/admin/recycle-bin              回收站,查看和恢复已删除作品、经历、定制页
/for/:companySlug/:positionSlug 管理员预览定制简历页,需要登录

9. Docker 架构

MVP 预期:

yaml
services:
  app:
    build:
      context: .
      args:
        APP_PORT: ${APP_PORT:-18473}
    ports:
      - "${APP_PORT:-18473}:${APP_PORT:-18473}"
    environment:
      PORT: ${APP_PORT:-18473}
      DATABASE_PATH: ${DATABASE_PATH:-/app/data/onlyweb.db}
      APP_URL: ${APP_URL:-http://localhost:${APP_PORT:-18473}}
    volumes:
      - ./data:/app/data
      - ./public/uploads:/app/public/uploads

实际配置见 docker-compose.yml;端口通过 APP_PORT 配置,对外域名通过 APP_URL 配置。运行镜像内置 LibreOffice Writer 和中文字体,用于 .doc/.docx 转 PDF 在线预览。

10. 认证设计

MVP 使用单管理员账号。

建议:

  • 密码使用 bcrypt 或 argon2 哈希。
  • 会话使用 HTTP-only Cookie。
  • 后台路由统一校验登录态。
  • 管理员预览路由 /for/:companySlug/:positionSlug 需要登录态。
  • HR 分享路由 /r/:shareToken 不需要登录,但 token 必须足够随机且不可枚举。
  • 初始管理员可通过环境变量创建,或通过 seed 脚本创建。

11. 文件上传设计

当前使用项目目录绑定挂载。

txt
./public/uploads -> /app/public/uploads

上传文件分为:

  • 普通项目文档:public/uploads/project-docs/<projectId>/
  • 效果演示文档:public/uploads/project-docs/<projectId>/effect-demo/
  • Word 预览缓存:public/uploads/project-doc-previews/<documentId>/preview.pdf
  • 个人资料图片:public/uploads/profile/

后续可迁移到:

  • S3
  • Cloudflare R2
  • MinIO

12. 部署设计

12.1 MVP 部署

bash
docker compose up -d

12.2 生产增强

后续加入:

  • Caddy 自动 HTTPS
  • 数据库定时备份
  • 上传文件备份
  • 日志轮转
  • 健康检查

13. 架构原则

  • 不为每个公司岗位创建单独页面文件。
  • 不在前台暴露定制页列表。
  • HR 只通过专属 token 链接访问单个定制页。
  • 定制简历页必须由数据库记录驱动。
  • 后台表单尽量简单,先满足个人使用。
  • 优先使用关系型数据建模,避免早期引入复杂 CMS。

14. 当前正式程序结构

当前正式程序已开始落地:

txt
src/
  app/
    page.tsx
    about/page.tsx
    projects/page.tsx
    projects/[slug]/page.tsx
    resume/page.tsx
    contact/page.tsx
    admin/login/page.tsx
    admin/page.tsx
    for/[companySlug]/[positionSlug]/page.tsx
    r/[shareToken]/page.tsx
  components/
    AppShell.tsx
    Layout.tsx
    Shared.tsx
    admin/
    resume/
  db/
    schema.sql
    sqlite.ts
  lib/
    mockData.ts

说明:

  • src/lib/mockData.ts 仅用于首次初始化种子数据。
  • src/db/schema.sql 是正式 SQLite schema。
  • src/db/sqlite.ts 负责初始化数据库和种子数据。
  • /r/:shareToken 使用专属分享视图,不显示普通站点导航。
  • /for/:companySlug/:positionSlug 当前使用服务端 session 做管理员预览保护。

15. 软删除字段

projectsexperiencesresume_pages 增加 deleted_at 字段。

  • deleted_at IS NULL:正常内容。
  • deleted_at IS NOT NULL:已进入回收站。
  • 前台查询、定制页查询和后台管理列表默认只读取 deleted_at IS NULL 的内容。
  • 回收站读取 deleted_at IS NOT NULL 的内容,并通过恢复操作把 deleted_at 置回 NULL

16. 项目文档上传与在线预览

作品外部文档链接继续复用 projects.docs_url 字段;上传的多个作品文档保存到 project_documents 表,并通过 document_kind 区分普通项目文档和效果演示文档。上传文件由项目目录 public/uploads/ 持久化。

上传文件保存时保留原始文件名;同一作品下 project_id + file_name + document_kind 唯一,普通文档和效果演示文档互不覆盖。Markdown 文档通过 /projects/:slug/docs?doc=<documentId> 在线渲染;PDF 直接通过 /projects/:slug/docs/preview?doc=<documentId> 嵌入预览;Word .doc/.docx 由 LibreOffice 转换为 PDF 后预览。原始文档下载通过 /projects/:slug/docs/download?doc=<documentId> 返回 Content-Disposition: attachment 强制下载。已上传文档删除时设置 deleted_at,不物理删除文件。

17. HR 联系方式与个人资料图片

profiles 表通过 wechat_idwechat_qr_url 保存微信联系方式。后台个人资料页支持填写微信号、填写二维码链接或上传二维码图片。

微信二维码图片保存到 public/uploads/profile/,Docker 中通过 ./public/uploads:/app/public/uploads 绑定挂载持久化。为了兼容中文文件名和运行时上传文件,图片访问走 /uploads/profile/:fileName 动态路由读取文件并返回正确图片类型。

HR 定制页 /r/:shareToken 展示统一风格的联系方式卡片,包括邮箱、手机号、微信号、所在地和微信二维码;底部旧联系方式模块在 HR 定制页中隐藏,避免重复。

18. 作品模块展示配置

作品详情页由数据库字段控制模块是否展示,避免为不同作品硬编码页面。作品详情页使用面向非技术决策者的业务成果展示布局,后台可按作品维护展示标题、首屏说明、使用场景、核心价值、交付形式、业务痛点、解决步骤、使用效果、项目价值、个人贡献、技术实现标签和管理者关注点。管理员登录后访问作品详情页会看到“编辑页面”按钮;点击后进入页面原位编辑模式,可直接修改业务成果展示、个人贡献、技术实现标签,并可在资料入口上传普通项目文档或效果演示文档;普通访客不显示该入口。页面在配置为空时才回退到作品摘要、详情、亮点或效果文档生成的短摘要;完整 Markdown、PDF、Word 文档仍通过文档详情页查看。

projects 表中的模块开关包括:

  • show_description:项目介绍
  • show_role:我的职责
  • show_effect_demo:效果演示
  • show_highlights:项目亮点
  • show_tech_stack:技术栈
  • show_links:相关链接/文档

后台 /admin/projects 的“模块展示”区域维护这些开关;已有作品默认全部展示。

19. Wiki 知识库架构

Wiki 模块通过 WIKI_HOST_VAULT_PATH 将宿主机 Obsidian vault 挂载到容器内 WIKI_VAULT_PATH。服务端读取 Markdown 文件,生成文件树、双链、标签、反向链接和局部关系图谱数据;左侧文件树目录默认收起,可点击展开或收起;后台 /admin/wiki 可以点击“知识库根目录路径”后用弹窗浏览服务器目录并选择 vault 路径,通过下拉选项维护公开状态、忽略目录,并上传 Markdown 文档到 vault;上传目标目录从已扫描到的知识库目录中选择。

图谱前端使用浏览器端 SVG 力导向模拟实现,不依赖外部图谱服务。节点支持拖拽,节点标签支持点击跳转到对应笔记。默认 WIKI_PUBLIC=false,访问 /wiki 需要管理员登录;设置为 true 后可公开访问。后台保存的 Wiki 配置写入 app_settings 表,优先级高于环境变量;WIKI_EXCLUDE_DIRS 保存为空字符串表示不忽略任何目录,而不是回退到默认值。Docker 部署时通过 WIKI_HOST_BROWSE_ROOTWIKI_CONTAINER_BROWSE_ROOT 将宿主机可浏览目录以读写方式映射到容器内,解决 LinuxOS 中 /root/onlyweb/wiki/vault 这类宿主机路径无法直接从容器访问的问题;.env 存在且可写时会同步更新 Wiki 配置。Wiki 扫描包含系统目录保护:拒绝扫描 //proc/sys/dev/run/boot/tmp,并跳过符号链接。