Y-Link 是一套面向文创、非遗、门店和活动场景的库存管理系统,覆盖“线上预订、线下核销、出库开单、供货入库、库存追踪、客户反馈”流程。
系统包含两端:
- 管理端:管理员、运营人员、供货方使用,负责商品、库存、出入库、核销、用户、配置和审计。
- 客户端:普通用户使用,负责注册登录、商品大厅、购物车、预订下单、订单查看、个人资料和反馈。
技术栈:Vue 3、TypeScript、Element Plus、Pinia、Express、TypeORM。默认使用 SQLite,支持迁移到 MySQL。
仓库新增了独立的 apps/mobile Expo 工程和 packages/* 跨端基础骨架。Mobile 固定使用 Node.js 22.13.1;第一阶段只提供路由占位、基础 providers、SecureStore/SQLite 包装、未配置的平台能力接口与 CI,不改变现有 Vue Web、Express 后端、数据库或部署方式。
当前 Mobile 尚未接入真实 API,也没有实现正式登录会话、服务端购物车、订单、库存、退货、相机、相册、通知或 Deep Link。未运行模拟器或真机验证时,只能依据 SQLite migration 专项测试、typecheck 与 Android JS bundle export 判断工程基础,不代表设备交互、签名 APK 或发布链路可用。
主要目录:
apps/mobile:独立 Expo package,使用自己的package.json和package-lock.json;packages/api-client:传输无关 HTTP 契约与 Native/Web adapter;packages/shared-types:空白规范入口,第一阶段不迁移现有 DTO;packages/domain、packages/validation、packages/design-tokens:最小共享边界骨架。
常用命令:
npm --prefix apps/mobile ci
npm --prefix apps/mobile run dependencies:check
npm --prefix apps/mobile run test:db
npm --prefix apps/mobile run typecheck
npm --prefix apps/mobile run export:android
node ./node_modules/typescript/bin/tsc -p packages/tsconfig.json --noEmit
node --experimental-strip-types --test packages/api-client/test/*.test.tsMobile 未启用根 npm workspaces,当前也不能直接跨目录导入 packages/*。详细边界见 docs/project-context/60-移动端工程与共享基础.md。
| 图 1:用户端登录页面 | 图 2:管理端登录页面 |
|---|---|
| 图 3:用户端主页面 | 图 4:管理端主页面 |
|---|---|
| 图 5:入库管理工作台 | 图 6:产品规格配置 | 图 7:审计日志 |
|---|---|---|
| 图 8:我的订单 | 图 9:我的资料 |
|---|---|
新手优先使用 onebox 单镜像。它把前端、后端、Nginx 放在同一个容器里,1Panel 只需要创建一个容器。
部署前先准备:
- 一个可访问的服务器端口,例如
9050。 - 一个初始管理员强密码,用于
INIT_ADMIN_PASSWORD。 - 两个宿主机目录,用于保存数据库和上传文件。
推荐目录:
/opt/1panel/apps/y-link/data
/opt/1panel/apps/y-link/uploads
这两个目录建议纳入日常备份。
| 项目 | 填写 |
|---|---|
| 名称 | y-link |
| 镜像 | ghcr.io/hf-cygg/y-link-onebox:latest |
| 备用镜像 | docker.io/yemiao351/y-link-onebox:latest |
| 网络 | bridge |
| Entrypoint | /entrypoint.sh |
| Command | 留空 |
如果服务器拉取 GitHub Container Registry 慢,可改用 Docker Hub 镜像。
1Panel 操作路径通常是:容器 -> 创建容器 -> 手动输入镜像或选择已有镜像。创建时不要勾选“强制拉取镜像”,除非你明确需要重新拉取最新镜像。
| 服务器端口 | 容器端口 | 协议 | 说明 |
|---|---|---|---|
9050 |
80 |
tcp |
Web 访问入口 |
启动后访问:
- 管理端:
http://服务器IP:9050/login - 客户端:
http://服务器IP:9050/client/login - 健康检查:
http://服务器IP:9050/health
如果你使用域名和 HTTPS,在 1Panel 反向代理里把域名代理到 http://127.0.0.1:9050。
端口说明:
- 容器内固定使用
80作为 Web 入口。 - 服务器端口可以自定义,截图里的
9050 -> 80是推荐写法。 - 不需要额外暴露后端
3001,前端、API、上传文件、健康检查都会由容器内 Nginx 统一代理。
必须挂载数据目录和上传目录,否则重建容器后会丢失数据或图片。
| 本机目录 | 容器目录 | 权限 | 保存内容 |
|---|---|---|---|
/opt/1panel/apps/y-link/data |
/app/data |
读写 | SQLite 数据库 |
/opt/1panel/apps/y-link/uploads |
/app/uploads |
读写 | 商品图片、反馈附件等上传文件 |
你截图里的挂载方式是正确的:类型选“本机目录”,权限选“读写”。本机目录可以按自己的 1Panel 习惯调整,但容器目录必须保持上表一致。
目录用途:
/app/data/y-link.sqlite是默认 SQLite 数据库文件。/app/uploads保存商品图片、反馈图片、附件等上传内容。- 迁移服务器时,复制宿主机的
data和uploads两个目录即可保留业务数据。
不要把这两个目录挂载到 /tmp 这类临时目录。
至少需要手动新增一个变量:
INIT_ADMIN_PASSWORD=请改成你自己的强密码
TZ=Asia/Shanghai
# 建议配置:永久删除订单/送货单时的二次门禁密码。
PERMANENT_DELETE_PASSWORD=请改成仅管理员知晓的删除密码
INVITE_CODE_PEPPER=请生成至少32个随机字节的教师邀请码密钥要求:
- 必填,不填容器会拒绝启动。
- 不能使用
Admin@123456。 - 建议至少 8 位,并包含字母和数字。
- 只在 1Panel 环境变量里填写,不要写进公开文档或提交到仓库。
PERMANENT_DELETE_PASSWORD不影响容器启动;未配置时,系统会拒绝所有永久删除操作。修改后需重启后端容器才会生效。
可选变量:
| 变量 | 默认值 | 说明 |
|---|---|---|
INIT_ADMIN_USERNAME |
admin |
初始管理员账号 |
INIT_ADMIN_DISPLAY_NAME |
系统管理员 |
初始管理员显示名 |
PERMANENT_DELETE_PASSWORD |
空 | 永久删除订单、订单池记录和供货方送货单时的服务端密码门禁 |
TZ |
Asia/Shanghai |
容器和日志时区;国内部署建议保持此值 |
DB_TYPE |
sqlite |
默认 SQLite |
SQLITE_DB_PATH |
/app/data/y-link.sqlite |
SQLite 文件位置 |
PORT |
3001 |
容器内后端端口,通常不用改 |
首次启动后,系统会自动创建管理员账号。已有管理员时不会覆盖原账号密码。
如果容器日志显示 initialized=false,通常表示数据库里已经存在管理员,系统没有再次初始化,这是正常行为。
不建议新手修改这些变量:
| 变量 | 原因 |
|---|---|
DB_SYNC |
生产环境不建议长期启用自动同步结构 |
DB_HOST / DB_USER / DB_PASSWORD |
只有 DB_TYPE=mysql 时才需要 |
PORT |
onebox 内部 Nginx 已按默认端口代理后端 |
截图中选择“不重启”可以用于首次排错。正式使用建议改为“失败后重启”或“一直重启”。
资源限制可按服务器情况配置:
- CPU 权重:
1024可保持默认。 - CPU 限制:
0表示不限制。 - 内存限制:
0表示不限制;小服务器建议至少预留 512MB 以上。
建议:
- 首次启动排错:可先选“不重启”,方便看到真实错误日志。
- 正式使用:建议选“失败后重启”或“一直重启”。
- 如果服务器内存较小,不要同时跑太多同类容器。
正常启动会看到类似内容:
[onebox] starting backend on 127.0.0.1:3001
[onebox] starting nginx on 0.0.0.0:80
[y-link-backend] 服务启动完成
常见错误:
| 日志 | 原因 | 处理 |
|---|---|---|
INIT_ADMIN_PASSWORD is required |
未配置初始管理员密码 | 在环境变量里添加 INIT_ADMIN_PASSWORD |
refusing insecure INIT_ADMIN_PASSWORD=Admin@123456 |
使用了禁用弱密码 | 换成私有强密码 |
Welcome to nginx! |
旧容器或旧镜像残留 | 删除旧容器,重新拉取 onebox 镜像 |
| 上传图片 404 | 未挂载或未保留 /app/uploads |
增加 /app/uploads 读写挂载 |
日志时间显示 +0000 或 Z |
容器时区未配置,或仍在使用旧镜像 | 添加 TZ=Asia/Shanghai,拉取新镜像并重建容器 |
日志 IP 总是 10.255.0.1 |
容器只看到了 Docker/1Panel 的上一跳地址 | 确认 1Panel 反向代理传递 X-Forwarded-For,并使用新版镜像 |
构建时报 Docker Hub 429 Too Many Requests |
构建平台匿名拉取 Docker Hub 基础镜像被限流 | 默认 Dockerfile 已改用公开镜像源;若平台仍受限,可设置构建参数 Y_LINK_NODE_IMAGE=你的私有 Node 20 bookworm-slim 镜像 |
新版 nginx 访问日志会优先显示可信代理传来的真实用户 IP,并在同一行保留 proxy="..." xff="..." real="..." 便于排查。若 xff="-" 或为空,说明上游没有把真实 IP 传给容器,应用无法凭空还原用户 IP。
TZ=Asia/Shanghai 只负责“时区显示”,真正时钟校准必须在宿主机完成(容器共享宿主机内核时钟,默认无权限直接改系统时间)。
建议在服务器执行:
timedatectl set-timezone Asia/Shanghai
timedatectl set-ntp true
timedatectl status确认 System clock synchronized: yes 后,重启容器。
新版 onebox 启动日志会打印:
[onebox] timezone=Asia/Shanghai, now=2026-05-30 22:10:00 +0800 CST
启动成功后建议做 4 个检查:
- 打开
http://服务器IP:9050/health,能返回健康信息。 - 打开
http://服务器IP:9050/login,能看到管理端登录页。 - 用
admin和你配置的INIT_ADMIN_PASSWORD登录。 - 打开
http://服务器IP:9050/client/login,能看到客户端登录页。
升级前先备份两个目录:
/opt/1panel/apps/y-link/data
/opt/1panel/apps/y-link/uploads
升级步骤:
- 停止容器。
- 拉取最新镜像
ghcr.io/hf-cygg/y-link-onebox:latest。 - 用原来的端口、挂载和环境变量重建容器。
- 访问
/health、管理端登录页和客户端商品大厅确认可用。
只要保留 /app/data 和 /app/uploads 对应的宿主机目录,账号、单据、商品、图片都能继续使用。
回退方式:
- 停止新容器。
- 换回旧镜像标签或旧镜像 ID。
- 继续挂载原来的
data和uploads目录。 - 启动后检查登录、商品图片、订单列表。
如果升级前已经备份目录,必要时可以恢复备份目录后再启动容器。
按照你截图里的页面,可逐项核对:
| 1Panel 字段 | 推荐值 |
|---|---|
| 名称 | y-link |
| 镜像 | ghcr.io/hf-cygg/y-link-onebox:latest |
| 端口 | 服务器 9050,容器 80,协议 tcp |
| 网络 | bridge |
| 挂载 1 | 本机 /opt/1panel/apps/y-link/data -> 容器 /app/data,读写 |
| 挂载 2 | 本机 /opt/1panel/apps/y-link/uploads -> 容器 /app/uploads,读写 |
| Entrypoint | /entrypoint.sh |
| Command | 留空 |
| 环境变量 | 至少添加 INIT_ADMIN_PASSWORD=你的私有强密码 和 TZ=Asia/Shanghai;建议添加 PERMANENT_DELETE_PASSWORD=你的永久删除密码 |
| 特权模式 | 不开启 |
| 控制台交互 | 不需要开启 |
| 重启规则 | 首次排错可不重启,正式使用建议失败后重启 |
截图中只有 PATH、NODE_VERSION、YARN_VERSION 这类镜像自带变量还不够,必须额外添加 INIT_ADMIN_PASSWORD。如果需要启用永久删除订单/送货单,还应添加 PERMANENT_DELETE_PASSWORD;未配置时永久删除接口会明确拒绝。若希望容器日志与国内现实时间一致,也建议添加 TZ=Asia/Shanghai;旧容器需要重建后才会应用新镜像和新时区。
docker run -d --name y-link \
-p 9050:80 \
-e INIT_ADMIN_PASSWORD='请改成你自己的强密码' \
-e PERMANENT_DELETE_PASSWORD='请改成仅管理员知晓的删除密码' \
-e TZ=Asia/Shanghai \
-v /opt/1panel/apps/y-link/data:/app/data \
-v /opt/1panel/apps/y-link/uploads:/app/uploads \
--restart unless-stopped \
ghcr.io/hf-cygg/y-link-onebox:latest适合需要前后端分开管理的服务器:
INIT_ADMIN_PASSWORD='请改成你自己的强密码' PERMANENT_DELETE_PASSWORD='请改成仅管理员知晓的删除密码' TZ=Asia/Shanghai docker compose -f compose.cloud.yml up -d默认端口:
- 前端:
8080 - 后端:
3001
说明:
compose.cloud.yml默认使用 SQLite,并持久化/app/data和/app/uploads。- 不要只启动
y-link-frontend,单独前端镜像没有业务 API。 - 如果要使用 MySQL,请确认
DB_TYPE=mysql,并填写DB_HOST、DB_PORT、DB_USER、DB_PASSWORD、DB_NAME。
默认 SQLite 适合个人、小团队、轻量部署和快速上线。以下情况建议迁移到 MySQL:
- 多人高频同时下单、核销、入库。
- 数据量持续增长。
- 需要更标准的备份、审计、主机迁移和运维体系。
已有 SQLite 数据时,优先使用管理端“系统管理 -> 数据库迁移”功能迁移,不建议手工拼接导入。
仓库提供 compose.onebox.yml,同一套 Onebox 可分两阶段部署,不需要再启动第二套 Y-Link 应用:
# 1. 首次部署:默认只启动 Onebox + SQLite
cp .env.onebox.example .env
# 编辑 .env,至少填写 INIT_ADMIN_PASSWORD
docker compose -f compose.onebox.yml up -d ylink
# 2. 正式启用:以后再叠加同一私有网络中的 MySQL 8.4
# 先在 .env 中填写 MYSQL_PASSWORD 与 MYSQL_ROOT_PASSWORD
docker compose -f compose.onebox.yml -f compose.onebox.mysql.yml up -d mysqlMySQL 健康后,在管理端“系统管理 -> 数据库迁移”填写:host=mysql、port=3306、database=y_link、user=ylink 和 .env 中的 MYSQL_PASSWORD。自动任务会冻结写入、生成一致性 SQLite 快照、复制并校验数据、写入运行时覆盖,然后让同一个 Onebox 计划重启到 MySQL。
如果 MySQL 不是由可选叠加文件启动:安装在 Linux 宿主机时向导可填写 host.docker.internal,远程 MySQL 则填写 Onebox 容器可访问的 DNS 名称或 IP。compose.onebox.yml 已补齐 Linux 的 host-gateway 映射;无论目标在哪里,都不要把数据库公网端口开放给所有来源。
迁移前应在 .env 中一并确认 DB_POOL_SIZE、DB_CONNECT_TIMEOUT_MS、DB_ACQUIRE_TIMEOUT_MS、DB_IDLE_TIMEOUT_MS、DB_QUEUE_LIMIT 与 DB_MAX_QUERY_MS。这些参数由 Onebox 容器持续保留,切换后同一个进程会用它们建立 MySQL 连接池;单实例默认连接池为 20,多个应用副本时必须按“实例数 × 每实例连接池”计算总连接数,并给 MySQL 运维连接留出余量。
切换到 MySQL 并产生第一笔新业务写入后,旧 SQLite 已经是历史快照,系统会禁用“直接清除覆盖/回到旧 SQLite”。如需回退,必须恢复 MySQL 备份或执行受控反向迁移,避免静默丢单。
运行边界:SQLite 模式固定为单 Onebox、单应用进程、本地持久化磁盘,100 个突发下单会进入有界写队列串行完成;需要持续百人并发写、第二个应用副本,或管理员接口 GET /api/data-maintenance/database/performance 中 writeCoordinator.pendingWrites、等待超时持续升高时,应迁移到 MySQL。万人浏览仍应由 Nginx/CDN 缓存吸收,不能把所有目录请求直接压到任一数据库。
全新部署可使用 compose.mysql.yml 与 .env.docker.mysql.example。先创建 MySQL 8.4 的空库和专用账号,库字符集使用 utf8mb4;再复制环境变量模板并填写连接信息、管理员密码以及连接池参数。
全新空库第一次启动可临时设置 DB_SYNC=true 创建当前版本的完整实体结构;后端健康后必须立即改回 DB_SYNC=false 并重建容器。backend/sql/001_init_schema.sql 只代表历史基础结构,不能单独当作当前版本的完整初始化脚本。存量 MySQL 禁止开启 DB_SYNC=true,必须先备份、停止业务写入,在预发演练后按版本号顺序执行尚未应用的增量 SQL,再以 DB_SYNC=false 启动应用。
cp .env.docker.mysql.example .env.docker.mysql
# 编辑连接信息、强密码,并仅在确认目标库为空时临时设置 DB_SYNC=true
docker compose --env-file .env.docker.mysql -f compose.mysql.yml up -d backend
docker compose --env-file .env.docker.mysql -f compose.mysql.yml ps
# /health 正常后,把 DB_SYNC 改回 false,再重建并启动完整应用
docker compose --env-file .env.docker.mysql -f compose.mysql.yml up -d --force-recreate backend frontend本轮高并发升级至少包含:
034_high_concurrency_indexes.sql:订单、库存日志、通知收件箱和会话清理索引;035_o2o_idempotency_business_sequence.sql:O2O 下单幂等键与并发安全业务序列;036_notification_outbox.sql:通知事件领取/重试字段、索引与投递去重约束;该脚本会合并历史重复收件箱的已读状态、保留最早记录并删除重复行,必须先备份并停止所有旧/新应用进程及通知 Worker,同时预留 DDL 窗口。
通知 Outbox 对站内收件箱和数据库投递记录做唯一键去重;邮件、飞书等外部通道采用 at-least-once(至少一次) 交付。若第三方已经接收成功、但进程在写回 sent 状态前异常退出,恢复后可能再次投递;需要严格防重时,应同时为第三方通道配置其支持的幂等键或去重能力。
升级顺序固定为“备份 -> 停止应用和业务写入 -> 按 034、035、036 顺序执行 -> 启动新版本”。商城目录缓存复用既有 030_mall_catalog_performance_indexes.sql 与本轮 034 索引,本轮没有额外 catalog 表结构脚本。实际升级清单仍以 backend/sql/ 中“当前线上版本之后、目标版本之前”的增量脚本为准,不能只挑最后一个脚本执行。执行完成后再启动应用,并通过 /health、登录、商品查询、下单、通知重试链路验收。
相关文件:
- 工作台:业务数据看板、快捷入口。
- 出库开单:商品搜索、数量录入、金额计算、单据生成。
- 出库列表:历史单据、详情、导出、作废和删除治理。
- 入库管理:供货方送货单、扫码入库、入库核销。
- 商品管理:基础资料、标签、库存、线上展示、图片。
- O2O 核销:客户预订单查询、核销、撤回和库存流水。
- 用户中心:管理端用户、供货方用户、客户端用户。
- 系统配置:验证码、部门、客户服务、业务规则等配置。
- 审计日志:登录、权限拦截、关键业务操作追踪。
- 注册登录:用户名、手机号、邮箱等账号能力。
- 商品大厅:商品浏览、搜索、分类、加购。
- 购物车和结算:预订下单、库存占用、订单生成。
- 我的订单:订单列表、详情、状态查看、撤回。
- 我的:资料维护、修改密码、反馈与客服入口。
- 独立账号登录。
- 录入送货单。
- 查看历史送货单。
- 配合管理端完成入库核销。
- Node.js 20+
- npm
- Docker 可选,用于容器验证和 MySQL 并发验收
npm install
npm --prefix backend installnpm run local:dev常用命令:
| 命令 | 说明 |
|---|---|
npm run local:dev |
启动本地前后端联调 |
npm run local:dev:status |
查看本地服务状态 |
npm run local:dev:stop |
停止本地联调 |
npm run build |
前端类型检查和生产构建 |
npm --prefix backend run check |
后端 TypeScript 检查 |
npm --prefix backend run build |
后端构建 |
| 命令 | 说明 |
|---|---|
npm run verify:unit:functional |
单元和功能基线验证 |
npm --prefix backend run permission:regression:verify |
后端权限回归 |
npm --prefix backend run release:verify |
后端发布回归 |
npm run verify:onebox:smoke |
onebox 冒烟验证 |
npm run verify:db:concurrency |
SQLite 副本 + MySQL 临时库并发验收 |
npm run verify:performance |
性能预算验证 |
npm run verify:all |
全量质量验证 |
verify:db:concurrency 默认会通过 Docker 拉起 MySQL 8.4 临时环境。没有 Docker 时,可提供 VERIFY_DB_CONCURRENCY_MYSQL_* 连接到自备 MySQL。
Y-Link
├─ src/ 前端源码
│ ├─ api/ API 封装
│ ├─ components/ 通用组件
│ ├─ router/ 路由和菜单元信息
│ ├─ store/ Pinia 状态
│ └─ views/ 管理端和客户端页面
├─ backend/ 后端源码
│ ├─ src/config/ 环境变量、数据源、启动自检
│ ├─ src/entities/ TypeORM 实体
│ ├─ src/routes/ REST 路由
│ ├─ src/services/ 业务服务
│ ├─ sql/ 初始化和迁移 SQL
│ └─ scripts/ 后端验证脚本
├─ apps/mobile/ 独立 Expo Mobile 工程与 lockfile
├─ packages/ 尚未接入 Mobile 的跨端基础包骨架
├─ docker/
│ ├─ nginx/ Nginx 配置
│ └─ onebox/ onebox 入口脚本
├─ docs/ 使用和运维文档
├─ scripts/ 构建、验证、部署辅助脚本
├─ compose.cloud.yml 云端双容器部署
├─ compose.mysql.yml 外置 MySQL 部署
├─ Dockerfile 前端镜像
├─ Dockerfile.onebox 单镜像
└─ README.md
| 路径 | 说明 |
|---|---|
/app/data |
容器内 SQLite 数据目录,必须持久化 |
/app/uploads |
容器内上传文件目录,必须持久化 |
backend/sql |
MySQL 历史增量迁移脚本(用于给已有库补齐特定变更;全新空库请用 DB_SYNC=true 初始化,详见 .env.docker.mysql.example) |
backend/data |
本地开发 SQLite 数据目录 |
apps/mobile |
Mobile 路由占位、providers、本地与平台能力基础 |
packages |
API client、共享类型、领域、校验与设计 token 边界 |
docs |
使用指南、迁移手册、维护文档 |
- 初始管理员密码必须手动配置,系统不会内置默认弱密码。
- 不要把真实密码、数据库连接、Token 写入仓库。
- 生产环境建议使用 HTTPS 和反向代理。
- 管理端已做登录失败风控、验证码、权限校验和审计记录。
- SQLite 适合轻量部署;多人高并发和长期生产建议使用 MySQL。