build: ship a single fullstack binary and docker image
This commit is contained in:
1 parent
7ec666fa0e
commit
8842cabe78
7 files changed
+207
-108
No files matched your search
@@ -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/
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"]
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user