# docker-proxy **Repository Path**: zhReimu/docker-proxy ## Basic Information - **Project Name**: docker-proxy - **Description**: docker-proxy - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-28 - **Last Updated**: 2026-10-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Docker Proxy ## 无缓存的轻量版本 仓库同时提供独立的 `docker-proxy-lite`,只把请求转发到指定 Registry。它不加载下面介绍的管理后台、数据库或存储系统,也不使用 Distribution;镜像边下载边回传,不保存镜像、不创建临时文件、不缓存认证 Token。 ### 构建与启动 仅需 Go 1.27 和 Task,不需要 Node 或 npm: ```bash task build-lite ./dist/docker-proxy-lite --type dockerhub ``` Windows 对应程序为 `dist/docker-proxy-lite.exe`。跨平台构建使用 `task build-lite-all`,输出 Linux、macOS 的 amd64/arm64 及 Windows amd64 版本。 通过网络代理访问上游: ```bash ./dist/docker-proxy-lite --listen :8081 --type dockerhub --proxy http://127.0.0.1:7890 # SOCKS5 也可使用,例如 --proxy socks5://127.0.0.1:1080 ``` 参数: | 参数 | 默认值 | 用途 | | --- | --- | --- | | `--listen` | `:8081` | HTTP 监听地址 | | `--type` | 未设置 | 预置的上游类型,与 `--target` 至少填写一项 | | `--target` | 使用类型的预置地址 | 自定义 HTTP/HTTPS Registry 地址,可覆盖 `--type` 地址,不接受内嵌账号密码 | | `--proxy` | 直连 | HTTP、HTTPS、SOCKS5/SOCKS5H 网络代理,支持代理账号密码 | | `--trust-proxy` | `false` | 信任入口代理覆盖的 `X-Forwarded-Host`、`X-Forwarded-Proto` | | `--version` | `false` | 输出版本并退出 | 只有显式指定 `--proxy` 才使用网络代理,不读取 `HTTP_PROXY`、`HTTPS_PROXY` 等环境变量。Registry 请求、认证请求和重定向下载均使用同一个代理配置。TLS 证书按系统信任链校验。 代码通过 `upstreamType` 枚举预置以下地址,不需要额外配置文件: | `--type` | 目标地址 | | --- | --- | | `dockerhub` | `https://registry-1.docker.io` | | `ghcr` | [`https://ghcr.io`](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) | | `quay` | [`https://quay.io`](https://docs.quay.io/guides/pushpull.html) | | `gcr` | [`https://gcr.io`](https://docs.cloud.google.com/artifact-registry/docs/transition/gcr-repositories) | | `k8s` | [`https://registry.k8s.io`](https://kubernetes.io/releases/download/) | | `mcr` | [`https://mcr.microsoft.com`](https://github.com/microsoft/containerregistry/blob/main/docs/mcr-endpoints-guidance.md) | | `ecr-public` | [`https://public.ecr.aws`](https://docs.aws.amazon.com/AmazonECR/latest/public/public-registries.html) | 例如 `--type ghcr` 转发到 GHCR,`--target https://registry.internal` 使用自建 Registry;`--type dockerhub --target https://hub-mirror.example.com` 使用自定义地址并启用 Docker Hub 名称补全。GCR 类型使用仍受 Artifact Registry 支持的 `gcr.io` 地址;区域仓库可通过 `--target` 覆盖。 ### 拉取与认证 ```bash docker pull localhost:8081/nginx:latest docker pull localhost:8081/library/nginx:latest docker pull localhost:8081/myuser/myimage:latest docker login localhost:8081 ``` 选择 `--type dockerhub` 或目标为 Docker Hub 域名时,单段镜像名自动补全 `library/`,并兼容对应认证 scope;已有命名空间保持原样。`https://docker.io` 和 `https://registry.hub.docker.com` 会使用实际 Registry 地址 `registry-1.docker.io`。其他类型不进行名称补全。 程序转发所有 HTTP 方法,支持上传、分片续传、Range 请求,访问权限由目标 Registry 判断。Token 仍由上游签发,登录凭据只转给 Registry 和它指定的认证服务,跨域镜像下载不携带 Registry 的 Authorization 或 Cookie。 服务把认证和重定向地址改写为本地签名路径,客户端无需直接连接认证域名或镜像 CDN。签名密钥只在进程内保存,服务重启后旧地址失效,需要重新发起拉取或上传。部署多个副本时需将同一客户端的请求路由到同一实例。 ### 反向代理与 HTTPS 程序提供 HTTP 入口,外部 HTTPS 可由 Nginx 等反向代理终止。不需要配置公开 URL;直接访问时使用请求 Host,经过反向代理时加上 `--trust-proxy`: ```nginx location / { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; proxy_request_buffering off; proxy_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; client_max_body_size 0; } ``` 反向代理需覆盖这些转发头,且禁止外部客户端绕过它直连轻量服务。使用 HTTP 连接非本机地址时,Docker 通常需要配置 `insecure-registries`;正式部署建议使用 HTTPS。 服务自身没有额外的访问鉴权,访问者拥有目标 Registry 允许的权限,应通过防火墙或入口代理限制访问。日志不记录凭据或签名下载 URL。 ### 独立容器 ```bash docker build -f Dockerfile.lite -t docker-proxy-lite . docker run --rm -p 8081:8081 docker-proxy-lite --type dockerhub ``` 镜像不构建前端,以非 root 用户运行并包含 CA 证书,无需数据卷。容器中的 `127.0.0.1` 指向容器自身,使用 `--proxy` 时应填写容器可访问的代理地址。 ## 完整版本 Docker Proxy 是一个使用 Go 构建的 Docker Registry 代理服务,内置一个以 React + Vite 实现的中文管理后台。前端构建产物会嵌入到 Go 二进制中,因此启动单个服务后即可同时提供以下入口: - `/admin`:后台管理界面 - `/api/admin/*`:后台管理 API - `/token`:Registry Bearer Token 签发入口 - `/v2/*`:Docker Registry 入口 - `/healthz`:健康检查 - `/version`:版本信息 整个系统的运行时配置存放在 SQLite 中。服务启动时会自动迁移数据库、引导默认资源、装配当前 Registry 运行时,并把构建后的管理后台一起对外提供。 ## 核心能力 - Registry 代理与 Bearer 鉴权:`/v2/*` 走标准 Docker Registry Bearer challenge 流程,`/token` 负责签发 Ed25519 JWT - 嵌入式管理后台:React 单页应用构建后嵌入 Go 二进制,通过 `/admin` 和 `/api/admin/*` 提供运维控制面 - 启动引导:首次启动自动初始化管理员账号、默认存储后端、默认上游、系统内置的 Docker Hub 代理和基础系统设置 - 存储后端管理:支持 `filesystem`、`s3` 与 `webdav` 三类后端,允许切换活跃后端并在进程内热重载;`webdav` 使用远端存储保存最终数据,并用本地暂存目录承载上传中的 `_uploads` - 上游与网络:管理远端上游仓库、HTTP/SOCKS5 代理,并支持为上游批量绑定代理;同时内置一个仅供 Docker Hub 使用的系统代理策略 - Host 路由:按 `priority ASC, id ASC` 顺序使用正则表达式匹配 Host,并将请求路由到目标上游 - 镜像管理:支持镜像索引重建、仓库与 Tag 检索、从上游拉取、重打 Tag、删除 Tag、删除 Manifest、批量删仓库、标记仓库私有性 - 运行时观测:查看初始化状态、当前版本、Go 运行时指标、活跃存储后端、系统设置和健康检查 - 日志输出:支持通过 flag 或环境变量设置日志级别,也支持额外写入 JSON 格式日志文件 ## 技术栈 - 后端:Go `1.27.0`、Chi、GORM、`glebarez/sqlite`、`distribution/distribution` - 前端:React `19`、TypeScript `6`、Vite `8`、React Router、Tailwind CSS `4` - 鉴权与安全:Basic Auth、Docker Registry Token、Ed25519、JWKS - 构建与交付:`go-task`、`npm`、Docker 多阶段构建、Go `embed.FS` ## 目录结构 - `cmd/server`:程序入口,负责参数解析、依赖装配与 HTTP 服务启动 - `internal/application`:应用服务层,编排引导、后台管理、鉴权、镜像操作与 Registry 相关用例 - `internal/domain`:领域模型与核心业务类型 - `internal/infrastructure/http`:HTTP 路由、认证中间件、嵌入式后台页面与接口适配 - `internal/infrastructure/persistence`:SQLite 持久化、系统设置、镜像索引与访问策略 - `internal/infrastructure/distribution`:Docker Distribution handler 装配、上游联邦中间件与热重载 - `internal/infrastructure/registryauth`:Registry Token 运行时、JWK/JWKS、签发与校验 - `internal/infrastructure/storage`:存储驱动工厂,当前支持 `filesystem`、`s3` 与 `webdav` - `web`:管理后台前端源码;`web/dist` 为生成物,由 [`web/embed.go`](./web/embed.go) 嵌入 ## 运行要求 - Go `1.27.0` 或更高版本 - Node.js `26` - `npm` - `go-task` - Docker(仅在使用容器构建/运行时需要) npm 随 Node.js 一起安装。 ## 快速开始 首次拉起项目时建议先准备依赖: ```bash task deps task frontend-deps ``` 本地开发启动: ```bash task dev ``` 默认访问地址: - 后台管理:`http://127.0.0.1:8080/admin` - 健康检查:`http://127.0.0.1:8080/healthz` - 版本信息:`http://127.0.0.1:8080/version` - Registry 入口:`http://127.0.0.1:8080/v2/` - Token 服务:`http://127.0.0.1:8080/token` 说明: - `task dev` 会先构建一次嵌入式前端,再以 `go run ./cmd/server --db data/docker-proxy.db` 启动后端 - 如果需要前端热更新,请配合 [`web/README.md`](./web/README.md) 使用 `npm --prefix web run dev` - 服务本身不做 TLS 终止;生产环境通常需要放在 HTTPS 反向代理之后,或为 Docker 客户端显式配置可信的 HTTP Registry ## 常用命令 - `task dev`:构建前端并以开发模式运行服务 - `task build`:下载依赖、构建前端并编译当前平台产物到 `dist/` - `task run`:基于构建产物运行服务 - `task test`:执行前端构建、前端 lint 与 `go test -v ./...` - `task fmt`:运行 `go fmt ./...` - `task clean`:清理 Go 构建缓存与 `dist/` - `task build-all`:循环构建 Linux / macOS 的 amd64、arm64,以及 Windows amd64 产物 - `task build-cross TARGET_OS=linux TARGET_ARCH=arm64`:构建指定平台产物 - `task docker-build`:基于仓库内 `Dockerfile` 构建容器镜像 - `task docker-run`:按示例参数启动容器 如果只调试前端,可直接使用: ```bash npm --prefix web run dev npm --prefix web run build npm --prefix web run lint ``` ## CLI 与环境变量 ### CLI 参数 当前二进制支持以下参数: - `--listen` / `-l`:监听地址,默认 `:8080` - `--db` / `-d`:SQLite 数据库路径,默认 `data/docker-proxy.db` - `--log-level`:日志等级,支持 `debug`、`info`、`warn`、`error` - `--log-file`:日志文件路径;配置后标准错误输出继续使用文本日志,文件额外写入 JSON 日志 - `--version` / `-v`:输出版本信息并退出 示例: ```bash ./dist/docker-proxy --listen :8080 --db data/docker-proxy.db --log-level debug ``` Windows 下通常为: ```powershell .\dist\docker-proxy.exe --listen :8080 --db data/docker-proxy.db --log-level debug ``` ### 环境变量 - `BOOTSTRAP_ADMIN_USERNAME`:首次初始化时的管理员用户名 - `BOOTSTRAP_ADMIN_PASSWORD`:首次初始化时的管理员密码 - `BOOTSTRAP_STORAGE_ROOT`:默认 `filesystem` 存储根目录,默认 `data/registry` - `BOOTSTRAP_HOST_ROUTES_JSON`:首次初始化时导入的 Host 路由 JSON 数组 - `DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM`:Registry Token 服务使用的 Ed25519 私钥,兼容 PKCS8 PEM 与 base64 编码的 32 字节 seed - `DOCKER_PROXY_LOG_LEVEL`:默认日志等级;当 `--log-level` 也存在时,flag 优先 约束说明: - `BOOTSTRAP_ADMIN_USERNAME` 和 `BOOTSTRAP_ADMIN_PASSWORD` 必须同时提供,不能只设置其中一个 - 两者都不设置时,首次初始化会回落到默认管理员 `admin/admin` - `BOOTSTRAP_HOST_ROUTES_JSON` 仅在 Host 路由表为空时参与初始化 - `DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM` 如未设置,服务会生成进程内临时密钥;服务重启后旧 Bearer Token 会失效 - `DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM` 必须是 PKCS8 PEM 编码的 Ed25519 私钥,或 base64 编码的 32 字节 Ed25519 seed,否则服务会启动失败 - `DOCKER_PROXY_LOG_LEVEL` 支持 `debug`、`info`、`warn`、`error` 示例: ```bash BOOTSTRAP_ADMIN_USERNAME=admin \ BOOTSTRAP_ADMIN_PASSWORD=strong-password \ BOOTSTRAP_STORAGE_ROOT=data/registry \ DOCKER_PROXY_LOG_LEVEL=info \ ./dist/docker-proxy --listen :8080 --db data/docker-proxy.db ``` #### 生成 Registry Token 私钥 虽然环境变量名保留为 `DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM`,但当前支持两种格式: 1. PKCS8 PEM 编码的 Ed25519 私钥 2. base64 编码的 32 字节 Ed25519 seed PEM 方式适合挂载文件或 Secret 文件: ```bash openssl genpkey -algorithm Ed25519 -out registry-token-key.pem export DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM="$(cat registry-token-key.pem)" ``` ```powershell openssl genpkey -algorithm Ed25519 -out registry-token-key.pem $env:DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM = Get-Content -Raw -Encoding UTF8 .\registry-token-key.pem ``` base64 seed 是单行字符串,更适合直接写环境变量、Windows `set` 或 CI Secret: ```bash openssl rand -base64 32 export DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM='上一步输出的单行 seed' ``` ```cmd set DOCKER_PROXY_REGISTRY_TOKEN_PRIVATE_KEY_PEM=上一步输出的单行 seed ``` 使用 base64 seed 时,服务会在启动时把它扩展成完整的 Ed25519 私钥;只要各实例使用同一个 seed,就会得到同一把签名密钥。 Host 路由初始化示例: ```bash BOOTSTRAP_HOST_ROUTES_JSON='[ {"name":"dockerhub-route","priority":10,"host_regex":"^docker\\.example\\.com$","upstream":"dockerhub"}, {"name":"ghcr-route","priority":20,"host_regex":"^ghcr\\.example\\.com$","upstream":"ghcr"} ]' ``` 其中 `upstream` 需要引用数据库中存在的上游名称;首次启动默认会创建 `dockerhub` 和 `ghcr` 两个上游。 ## 默认初始化行为 当数据库为空或相关表尚未初始化时,服务会自动完成以下动作: - 创建默认管理员账号:`admin/admin` - 创建默认活跃存储后端:`default-filesystem` - 默认 `filesystem` 根目录为 `data/registry`,可被 `BOOTSTRAP_STORAGE_ROOT` 覆盖 - 创建系统内置代理:`dockerhub-builtin` - 创建两个默认启用的上游: - `dockerhub` -> `https://registry-1.docker.io` - `ghcr` -> `https://ghcr.io` - 写入基础系统设置: - `schema.version` - `bootstrap.completed` - `storage.reload_required` - 如提供 `BOOTSTRAP_HOST_ROUTES_JSON`,会在 Host 路由表为空时导入初始规则 说明: - `dockerhub-builtin` 只会在启动时自动创建,不会自动改写任何现有上游绑定 - 该代理是系统内置资源,不能删除、不能改名或改类型,但可以在后台编辑完整的 Caddy JSON 配置 - 保存 `dockerhub-builtin` 配置后只会更新数据库并标记 `storage.reload_required=true`,仍需手动重载运行时或重启服务后生效 - 该代理仅允许绑定到 Docker Hub 上游(`docker.io`、`index.docker.io`、`registry.docker.io`、`registry-1.docker.io`) 默认管理员仅适合本地开发。正式环境请在第一次启动前显式设置自己的初始化凭据。 ## 请求入口与鉴权 ### 管理后台 - `/admin` 与 `/api/admin/*` 使用 Basic Auth - 仅 `admin` 角色可以访问管理后台 - 没有独立登录页,浏览器会直接触发 Basic Auth 认证流程 - 当前会话信息可通过 `/api/admin/users/me` 获取 ### Registry 与 Token - `/v2/*` 只接受 Bearer Token,不支持直接对 `/v2/*` 发送 Basic Auth 完成访问 - `/token` 要求 `service=docker-proxy-registry` - `/token` 可以匿名访问,也可以携带 Basic Auth;携带凭证时允许 `admin` 与 `registry` 角色申请完整 scope - Token 由服务端使用 Ed25519 签发,默认有效期为 300 秒 - 匿名请求只能拿到公开仓库 `pull` scope 的交集裁剪结果 - 被标记为私有的镜像仓库需要先登录并获取合法 scope 才能访问 - `registry:catalog:*` 仅对已登录的 `admin` / `registry` 角色开放 匿名拉取公开仓库时,请求过程大致如下: ```text GET /v2//manifests/ -> 401 WWW-Authenticate: Bearer realm="https:///token",service="docker-proxy-registry",scope="repository::pull" GET /token?service=docker-proxy-registry&scope=repository::pull -> 200 { "token": "...", "access_token": "...", ... } GET /v2//manifests/ Authorization: Bearer -> 200 ``` 登录后可使用 Docker CLI: ```bash docker login docker pull /: ``` ## 管理后台 API 概览 后台 API 主要分为以下几组: - 启动与运行时:`/bootstrap/status`、`/system-info`、`/runtime`、`/dashboard` - 系统配置:`/settings` - 存储管理:`/storage-backends`、`/storage-backends/reload` - 网络资源:`/proxies`、`/upstreams`、`/upstreams/bulk-proxy` - 路由与账号:`/host-routes`、`/users`、`/users/me` - 镜像管理:`/images/repositories`、`/images/tags`、`/images/manifests/impact`、`/images/pull`、`/images/index/rebuild` 前端与这些接口的契约位于 [`web/src/domain`](./web/src/domain) 与 [`web/src/infrastructure/http/admin`](./web/src/infrastructure/http/admin)。 ## S3 存储后端 `s3` 后端使用 `minio-go/v7` 访问 AWS S3 和兼容服务,沿用原有存储配置和对象路径,无需迁移数据。 - `region` / `bucket`:必填,区域与已有存储桶名称;服务不会自动创建存储桶 - `regionendpoint`:可选,自定义端点,支持主机名或不含路径的 HTTP(S) URL;URL 的协议决定是否使用 HTTPS - `accesskey` / `secretkey`:可选,需同时配置;留空时依次使用 AWS 环境变量、共享凭据文件和 IAM 凭据 - `secure`:可选,默认 `true`;端点未指定协议时控制是否使用 HTTPS - `v4auth`:可选,默认 `true`;设为 `false` 使用 V2 签名 - `forcepathstyle`:可选,默认 `false`;设为 `true` 强制使用路径形式访问存储桶 上传直接使用 S3 分片接口,支持跨请求续传,不将完整镜像层暂存到本地。下载重定向使用有效期为 20 分钟的 GET/HEAD 签名 URL。配置修改后,需要在后台重载存储或重启服务。 ## WebDAV 存储后端 `webdav` 后端通过管理后台的存储后端表单进行配置,当前支持以下配置键: - `baseurl`:必填,WebDAV 服务地址,必须是绝对 `http/https` URL - `rootdirectory`:可选,远端路径前缀,默认 `/` - `username` / `password`:可选,Basic Auth 凭据 - `headers`:可选,自定义请求 Header;如果显式提供 `Authorization`,会覆盖由用户名密码生成的 Basic Auth - `insecureskipverify`:可选,默认 `false`;启用后会跳过 TLS 证书校验 - `spooldirectory`:可选,默认 `data/webdav-spool`;用于保存上传中的 `_uploads` 与 hash state 实现约束说明: - 最终 Registry 数据会落到远端 WebDAV - 上传中的 `_uploads`、断点续传状态和哈希状态文件会写到本地 `spooldirectory` - 激活或修改 `webdav` 配置后,仍然需要通过后台“重载存储”或重启服务使运行时生效 ## Registry 运行时与热重载 服务启动时会按以下流程装配 Registry 运行时: 1. 打开 SQLite 数据库并执行自动迁移 2. 读取引导环境变量,必要时写入默认管理员、默认存储、默认上游和初始 Host 路由 3. 初始化 Registry Token 运行时,准备签名密钥与 JWKS 4. 从数据库读取活跃存储后端、上游、Host 路由和系统设置 5. 构建当前存储驱动并创建 Docker Distribution handler 6. 编译 Host 路由正则并绑定联邦中间件 7. 将 `/token` 与 `/v2/*` 接到同一套 Token runtime 8. 启动 HTTP 服务并暴露嵌入式后台页面 以下变更会把系统设置 `storage.reload_required` 置为 `true`: - 存储后端新增、更新、删除 - 代理新增、更新、删除 - 上游新增、更新、删除、批量应用代理 - Host 路由新增、更新、删除 - 系统设置更新 - 首次初始化时发生的运行时配置写入 手动重载入口: ```http POST /api/admin/storage-backends/reload ``` 行为说明: - 重载成功后会在当前进程内重新构建 Registry 运行时 - 成功后会清除 `storage.reload_required` - 成功后会把镜像索引状态标记为过期,等待重新建立索引 - 如果重载失败,旧运行时保持不变,`storage.reload_required` 会继续保持为 `true` - 服务重启时也会按数据库中的最新配置重新装配运行时 - 如把上游切换到 `dockerhub-builtin`,仍需按现有流程执行“重载存储”或重启服务,新的 Registry 运行时才会生效 ## 后端实现概览 项目后端按 DDD 风格分层组织: - `internal/domain` - `iam`:账号、角色与权限模型 - `settings`:存储后端、代理、上游、Host 路由与系统设置 - `registry`:Host 归一化与上游解析结果等核心类型 - `internal/application` - `bootstrap`:读取环境变量并完成首次引导 - `iam`:密码校验、Basic Auth 鉴权、最后登录时间更新 - `admin`:后台 API 用例、镜像管理、隐私策略、索引重建 - `registry`:Host 路由解析器与上游客户端抽象 - `internal/infrastructure` - `persistence`:SQLite 表模型、查询与事务 - `distribution`:Distribution handler、联邦中间件与热重载 - `http`:管理后台 API、Registry 入口、认证中间件与嵌入式 UI - `registryauth`:Registry Token 运行时与签名 - `storage`:Distribution 存储驱动工厂 其中几个关键点: - 配置不是写在静态 YAML 中,而是以数据库记录的形式驱动运行时 - 镜像索引与仓库私有性也落在 SQLite 中,便于后台分页检索和权限裁剪 - 前端并不是独立部署应用,而是 `web/dist` 嵌入服务后随二进制一起发布 ## 容器构建 仓库内 `Dockerfile` 使用多阶段构建: 1. 基于 Node.js 26 构建前端 2. 基于 Go 1.27 编译 Linux `amd64` 二进制 3. 使用 distroless 运行时镜像发布最终产物 镜像默认暴露 `8080` 端口,入口命令为: ```text /app/docker-proxy ``` ## 开发约束 - 优先使用 `task` 统一执行依赖安装、构建、测试与 Docker 命令 - 不要手工修改 `web/dist` - 后台前端的部署根路径固定为 `/admin` - 前端产物通过 [`web/embed.go`](./web/embed.go) 嵌入到 Go 服务 - 新的领域规则应放在 `internal/domain` 与 `internal/application`,不要在 `internal/infrastructure` 中直接堆业务逻辑