build: ship a single fullstack binary and docker image

This commit is contained in:
dujiao-next committed 2026-07-26 15:56:09 +08:00
1 parent 7ec666fa0e
commit 8842cabe78
7 files changed
+207 -108

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
.git
.github
.idea
.vscode
.DS_Store
# 运行时数据,不属于构建上下文
db/
uploads/
logs/
config.yml
*.db
*.log
# 构建产物:前端 dist 由镜像内的 frontend 阶段重新生成,
# internal/web/dist 由 COPY --from=frontend 填充,都不能从宿主机带进来
dist/
frontend/*/dist/
frontend/*/node_modules/
internal/web/dist/
+53 -3
View File
@@ -15,13 +15,13 @@ concurrency:
cancel-in-progress: true
jobs:
verify:
api:
name: Verify API
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout api
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
@@ -33,10 +33,11 @@ jobs:
- name: Download dependencies
run: go mod download
# 用 git ls-files 限定范围,避免 gofmt 递归遍历 frontend/*/node_modules
- name: Check formatting
shell: bash
run: |
unformatted="$(gofmt -l .)"
unformatted="$(gofmt -l $(git ls-files '*.go'))"
if [[ -n "$unformatted" ]]; then
echo "::error::The following Go files are not formatted:"
printf '%s\n' "$unformatted"
@@ -51,3 +52,52 @@ jobs:
- name: Build server
run: go build ./cmd/server
fullstack:
name: Verify fullstack build
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.34.3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24.11.1'
# 用子 shell cd 而非 `pnpm --dir`:后者不会读取子目录的 packageManager 字段,
# 在启用 corepack 的环境下会用错 pnpm 版本
- name: Install frontend dependencies
run: |
(cd frontend/admin && pnpm install --frozen-lockfile)
(cd frontend/user && pnpm install --frozen-lockfile)
# 两个 build 脚本都以 `vue-tsc -b` 开头,因此这一步同时覆盖了类型检查
- name: Build frontends
run: |
(cd frontend/admin && pnpm run build:fullstack)
(cd frontend/user && pnpm run build)
- name: Embed frontend assets
run: |
mkdir -p internal/web/dist
cp -r frontend/admin/dist internal/web/dist/admin
cp -r frontend/user/dist internal/web/dist/user
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
# 只有带 fullstack tag 才会编译 embed_fullstack.go,
# 默认构建覆盖不到,必须在这里单独验证一次完整的嵌入链路。
- name: Build fullstack binary
run: go build -tags release,fullstack ./cmd/server
+5 -39
View File
@@ -12,28 +12,14 @@ jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout api
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# ---- fullstack 模式所需:拉取同 tag 的 admin/user 源码并构建 ----
# admin/user 仓库为 public,无需 PAT。要求三仓库已用 scripts/tag-and-push.sh
# 打过同名 tag。
- name: Checkout admin (cross-repo)
uses: actions/checkout@v4
with:
repository: dujiao-next/admin
ref: ${{ github.ref_name }}
path: admin-src
- name: Checkout user (cross-repo)
uses: actions/checkout@v4
with:
repository: dujiao-next/user
ref: ${{ github.ref_name }}
path: user-src
# 前端源码已在本仓库 frontend/ 下,不再需要跨仓库 checkout。
# 实际的前端构建与嵌入由 .goreleaser.yaml 的 before hooks 完成,
# 这里只负责把 pnpm / Node 工具链准备好。
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
@@ -44,30 +30,10 @@ jobs:
with:
node-version: '24.11.1'
- name: Build admin (fullstack mode)
working-directory: admin-src
run: |
pnpm install --frozen-lockfile
pnpm run build:fullstack
- name: Build user
working-directory: user-src
run: |
pnpm install --frozen-lockfile
pnpm run build
- name: Embed frontend assets into api
run: |
mkdir -p internal/web/dist
cp -r admin-src/dist internal/web/dist/admin
cp -r user-src/dist internal/web/dist/user
ls -la internal/web/dist/admin internal/web/dist/user | head -10
# ---- Go 工具链 + GoReleaser ----
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: stable
go-version-file: go.mod
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
+7 -7
View File
@@ -7,14 +7,14 @@ uploads
*.log
.env
.DS_Store
*/node_modules/
*/dist/
node_modules/
# Added by goreleaser init:
dist/
# Fullstack 构建产物(仅 -tags fullstack 时使用,不进 git 历史)
internal/web/dist/
# 前端构建产物与依赖(frontend/{admin,user})
frontend/*/dist/
frontend/*/node_modules/
frontend/*/*.local
# CI workflow 跨仓库 checkout 的前端源码(fullstack 构建用,不进 git 历史)
admin-src/
user-src/
# Fullstack 嵌入产物:由前端构建拷贝而来,不进 git 历史
internal/web/dist/
+21 -37
View File
@@ -2,12 +2,29 @@ version: 2
project_name: dujiao-next
# 前端在这里构建并拷进 internal/web/dist/,随后由 -tags=fullstack 的 go:embed 打进二进制。
# 放在 before hooks 而不是 CI step 里,是为了让本地 `goreleaser build --snapshot`
# 与 GitHub Actions 走完全相同的路径。执行前需要有 pnpm(见 .github/workflows/release.yml)。
before:
hooks:
- go mod tidy
- cmd: pnpm install --frozen-lockfile
dir: frontend/admin
- cmd: pnpm install --frozen-lockfile
dir: frontend/user
# admin 用 fullstack 模式构建:base 为 './' 且注入 __DJ_ADMIN_BASE__ 占位符,
# 后端启动时按 web.admin_path 替换,使产物可挂载到任意自定义前缀。
- cmd: pnpm run build:fullstack
dir: frontend/admin
- cmd: pnpm run build
dir: frontend/user
- sh -c 'rm -rf internal/web/dist && mkdir -p internal/web/dist'
- sh -c 'cp -r frontend/admin/dist internal/web/dist/admin'
- sh -c 'cp -r frontend/user/dist internal/web/dist/user'
builds:
- id: api
# 单一产物:内嵌 admin + user SPA,一个二进制跑完整服务。
- id: dujiao-next
env:
- CGO_ENABLED=0
main: ./cmd/server
@@ -15,7 +32,7 @@ builds:
ldflags:
- -s -w -X github.com/dujiao-next/internal/version.Version={{.Tag}}
flags:
- "-tags=release"
- "-tags=release,fullstack"
goos:
- linux
- windows
@@ -24,28 +41,10 @@ builds:
- amd64
- arm64
# fullstack 二进制:embed admin + user SPA。仅 linux(小白单二进制部署目标)。
# CI workflow 在 GoReleaser 之前 checkout admin/user 同 tag 源码、跑前端 build、
# 把 dist 拷到 internal/web/dist/{admin,user},再由 -tags=fullstack 触发 embed。
- id: fullstack
env:
- CGO_ENABLED=0
main: ./cmd/server
binary: dujiao-server
ldflags:
- -s -w -X github.com/dujiao-next/internal/version.Version={{.Tag}}
flags:
- "-tags=release,fullstack"
goos:
- linux
goarch:
- amd64
- arm64
archives:
- id: api
- id: dujiao-next
ids:
- api
- dujiao-next
formats: [tar.gz]
files:
- ./config.yml.example
@@ -62,21 +61,6 @@ archives:
- goos: windows
formats: [zip]
- id: fullstack
ids:
- fullstack
formats: [tar.gz]
files:
- ./config.yml.example
- ./README.md
name_template: >-
dujiao-all_
{{- .Tag }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
changelog:
sort: asc
groups:
+38 -4
View File
@@ -1,6 +1,34 @@
# syntax=docker/dockerfile:1
#
# 单镜像全栈构建:admin + user 两个 SPA 编译后由 go:embed 打进同一个二进制,
# 由同一个进程在同一端口(8080)提供服务。不再需要独立的前端容器与 nginx。
FROM golang:1.26.3-alpine AS builder
# ---- 阶段 1:构建两个前端 ----
# 固定在构建机原生架构上跑(前端产物与目标架构无关),避免 buildx 多平台构建时
# 在 QEMU 模拟的 arm64 下重跑一遍 Node —— 那样构建耗时会差一个数量级。
FROM --platform=$BUILDPLATFORM node:24.11.1-alpine AS frontend
WORKDIR /src
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
RUN corepack enable
# 先只拷 manifest,让依赖安装层能在源码变动时命中缓存
COPY frontend/admin/package.json frontend/admin/pnpm-lock.yaml ./admin/
COPY frontend/user/package.json frontend/user/pnpm-lock.yaml ./user/
RUN cd admin && pnpm install --frozen-lockfile
RUN cd user && pnpm install --frozen-lockfile
COPY frontend/admin ./admin
COPY frontend/user ./user
# admin 走 fullstack 模式:注入 __DJ_ADMIN_BASE__ 占位符,
# 后端启动时按 web.admin_path 替换,使同一份产物能挂到任意自定义前缀。
RUN cd admin && pnpm run build:fullstack
RUN cd user && pnpm run build
# ---- 阶段 2:构建内嵌前端的 Go 二进制 ----
FROM --platform=$BUILDPLATFORM golang:1.26.3-alpine AS builder
ARG TARGETOS
ARG TARGETARCH
@@ -16,12 +44,18 @@ COPY go.mod go.sum ./
RUN go mod download
COPY . .
# go:embed 只能读取包目录内的文件,所以前端产物必须落到 internal/web/dist/ 下
COPY --from=frontend /src/admin/dist ./internal/web/dist/admin
COPY --from=frontend /src/user/dist ./internal/web/dist/user
RUN set -eux; \
export GOOS="$TARGETOS" GOARCH="$TARGETARCH"; \
if [ "$TARGETARCH" = "arm" ] && [ -n "$TARGETVARIANT" ]; then export GOARM="${TARGETVARIANT#v}"; fi; \
if [ "$TARGETARCH" = "amd64" ] && [ -n "$TARGETVARIANT" ]; then export GOAMD64="${TARGETVARIANT#v}"; fi; \
go build -trimpath -tags release -ldflags="-s -w -X github.com/dujiao-next/internal/version.Version=${APP_VERSION}" -o /out/dujiao-api ./cmd/server
go build -trimpath -tags release,fullstack -ldflags="-s -w -X github.com/dujiao-next/internal/version.Version=${APP_VERSION}" -o /out/dujiao-next ./cmd/server
# ---- 阶段 3:运行时 ----
FROM alpine:latest
WORKDIR /app
@@ -29,9 +63,9 @@ WORKDIR /app
RUN apk --no-cache add ca-certificates tzdata \
&& mkdir -p /app/db /app/uploads /app/logs
COPY --from=builder /out/dujiao-api /app/dujiao-api
COPY --from=builder /out/dujiao-next /app/dujiao-next
COPY config.yml.example /app/config.yml.example
EXPOSE 8080
CMD ["./dujiao-api"]
CMD ["./dujiao-next"]
+63 -18
View File
@@ -1,30 +1,75 @@
# Dujiao-Next API
# Dujiao-Next
Dujiao-Next API is the backend service for the Dujiao-Next ecosystem. It provides public APIs, user/auth APIs, order and payment workflows, and admin APIs.
Dujiao-Next is a digital goods e-commerce platform. This repository contains the complete
application: the Go backend, the customer storefront, and the admin panel.
**One binary runs everything.** Both frontends are compiled into the server binary with
`go:embed`, so a production deployment is a single process on a single port behind a single
domain — no separate frontend containers, no nginx serving static files.
## Tech Stack
- Go
- Gin
- GORM
- SQLite / PostgreSQL
- Go · Gin · GORM · SQLite / PostgreSQL
- Vue 3 · Vite · TypeScript · Tailwind CSS v4
## What This Service Does
## Repository Layout
- Serves REST APIs for user, order, and payment flows
- Handles payment callbacks/webhooks
- Supports product, fulfillment, and configuration management
## Quick Start
```bash
go mod tidy
go run cmd/server/main.go
```
├── cmd/server/ # entry point
├── internal/
│ └── web/ # frontend embedding and SPA route mounting
├── frontend/
│ ├── admin/ # admin panel
│ └── user/ # customer storefront
├── config.yml.example
├── Dockerfile # single full-stack image
└── .goreleaser.yaml
```
The default health check endpoint is:
## Quick Start (Deploy)
- `GET /health`
Download the latest `dujiao-next_*.tar.gz` from [Releases](https://github.com/dujiao-next/dujiao-next/releases):
```bash
tar -xzf dujiao-next_*.tar.gz
cp config.yml.example config.yml
# edit config.yml: set jwt.secret, user_jwt.secret, and web.admin_path
./dujiao-next
```
The storefront is served at `/` and the admin panel at `web.admin_path` (default `/admin` —
change it). Full instructions: https://dujiao-next.com/deploy/
Or with Docker:
```bash
docker run -d -p 8080:8080 -v $PWD/config.yml:/app/config.yml:ro dujiaonext/api:latest
```
## Quick Start (Develop)
Run the backend and the two frontends separately for hot reload:
```bash
go mod tidy && go run ./cmd/server # :8080 — API only, no SPAs mounted
cd frontend/user && pnpm install && pnpm run dev # :5173
cd frontend/admin && pnpm install && pnpm run dev # :5174
```
Both dev servers proxy `/api` and `/uploads` to `localhost:8080`.
## Building the Full-Stack Binary
```bash
goreleaser build --snapshot --single-target --clean
```
This builds both frontends, embeds them, and compiles with `-tags fullstack` — the same path
CI uses for releases. See [Manual Deployment](https://dujiao-next.com/deploy/manual) for the
step-by-step equivalent.
Health check endpoint: `GET /health`
## Online Documentation