diff --git a/.cursor/rules/build-scripts.mdc b/.cursor/rules/build-scripts.mdc
index 6d78454..199efaa 100644
--- a/.cursor/rules/build-scripts.mdc
+++ b/.cursor/rules/build-scripts.mdc
@@ -5,7 +5,8 @@ alwaysApply: true
# 编译与构建脚本
-Go 单二进制 + `go:embed` 前端;产物在 `dist/`。
+本分支(`rebuild/gitea-ssr`):Go 单二进制 + `go:embed` 的 **templates** 与 **public/assets**(由 `web_src` 构建);产物在 `dist/`。
+对照 SPA 仅在 `main` 分支。
## 运行编译(优先用封装命令,不要猜命令)
@@ -18,15 +19,16 @@ Go 单二进制 + `go:embed` 前端;产物在 `dist/`。
Windows 上**不要**让用户直接 `.\build.ps1`(默认 ExecutionPolicy 会拦截);应通过 `build.bat`(内部 `-ExecutionPolicy Bypass`)调用。
-常用 target:`build`(默认)、`dev`、`run`、`frontend`、`clean`、`build-all`、`build-windows`、`build-linux`、`tidy`、`help`。
+常用 target:`build`(默认)、`dev`、`run`、`web-src`、`clean`、`build-all`、`build-windows`、`build-linux`、`tidy`、`help`。
## 修改构建脚本时的约定
1. **双轨同步**:`build.ps1` 与 `Makefile` 目标与行为保持一致;改其一须同步另一份。
2. **`build.bat` 仅用 ASCII 注释**:`.bat` 会被 cmd 按 GBK 解析,UTF-8 中文注释会导致整行乱码、`powershell` 无法执行。
3. **`build.ps1` 可用 UTF-8**:由 PowerShell 执行,中文注释无妨。
-4. **构建顺序**:先 `frontend` 内 `npm run build`,再 `go build -trimpath -ldflags "-s -w -X main.version=..." -o dist/jiang13 ./cmd/jiang13`。
+4. **构建顺序**:先 `web_src` 内 `npm run build`(产出 `public/assets`),再 `go build -trimpath -ldflags "-s -w -X main.version=..." -o dist/jiang13 ./cmd/jiang13`。
5. **入口包**:`./cmd/jiang13`;Windows 产物带 `.exe`。
+6. **本分支无 `frontend/`**:勿再添加 SPA 构建步骤;需要对照 UI 时 `git checkout main`。
## 新增 target 检查清单
diff --git a/.cursor/rules/rebuild-gitea-ssr.mdc b/.cursor/rules/rebuild-gitea-ssr.mdc
index 38bd0de..8318764 100644
--- a/.cursor/rules/rebuild-gitea-ssr.mdc
+++ b/.cursor/rules/rebuild-gitea-ssr.mdc
@@ -21,9 +21,14 @@ alwaysApply: true
对齐 [Gitea](https://github.com/go-gitea/gitea) 职责划分:
- `routers/web` — HTML 页面路由
+- `routers/api` — JSON API(原 `handler/`)
+- `routers/setup.go` — 路由总装
- `templates/` — 模板
- `web_src/` → `public/assets/` — 渐进增强 CSS/JS
-- 现有 `service/`、`model/` 可复用业务逻辑
+- `models/`、`services/` — 数据与业务
+- `modules/auth`、`modules/webrender`、`modules/seo` — 横切
+
+本分支**已删除** `frontend/` 与 `embed_static/`;勿再恢复 SPA 生产回落。
细节见 [`docs/rebuild-spec/08-gitea-ssr-architecture.md`](docs/rebuild-spec/08-gitea-ssr-architecture.md)。
diff --git a/.dockerignore b/.dockerignore
index 96abb60..ac6b70b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,17 +4,17 @@
.idea
.vscode
.cursor
+.trae
# 运行时数据与本地配置
data/
app.ini
tmp-cookie.txt
-# 编译产物与前端缓存
+# 编译产物与依赖缓存
dist/
-frontend/node_modules/
-frontend/dist/
-embed_static/static/spa/
+web_src/node_modules/
+public/assets/
node_modules/
.vite/
diff --git a/.gitignore b/.gitignore
index 0f5d4c2..c5e2f31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,22 +4,28 @@
# 本地配置(保留 app.ini.example)
/app.ini
-# 前端依赖与构建缓存
+# Node / 构建缓存
/node_modules/
-/frontend/node_modules/
-/frontend/dist/
-/embed_static/static/spa/
+/web_src/node_modules/
+.vite/
+*.tsbuildinfo
-# Go 编译产物
+# Go 编译产物(统一进 dist/;勿把二进制扔在仓库根目录)
/dist/
*.exe
+/jiang13
+/jiang13-*
+!/cmd/jiang13/
# 临时文件
tmp-cookie.txt
+*-err.txt
+*-out.txt
-# 编辑器 / OS
+# 编辑器 / AI 草稿 / OS
.idea/
.vscode/
+.trae/
*.swp
Thumbs.db
.DS_Store
diff --git a/Dockerfile b/Dockerfile
index 61cedfc..d9c8a00 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-# 姜十三论坛 — 多阶段构建:Node 前端 → Go 单二进制 → Alpine 运行镜像
+# 姜十三论坛 — 多阶段构建:web_src → Go 单二进制 → Alpine 运行镜像
# 不使用 # syntax=docker/dockerfile:1,避免构建前额外拉取 docker.io/docker/dockerfile
#
# 国内网络:默认经 DaoCloud 拉取基础镜像,npm/go 走国内代理
@@ -8,14 +8,12 @@
ARG IMAGE_PREFIX=docker.m.daocloud.io/library/
ARG VERSION=dev
-# ── Stage 1: 前端构建(Vite → embed_static/static/spa)────────────────────
-FROM ${IMAGE_PREFIX}node:22-bookworm-slim AS frontend
-WORKDIR /src/frontend
-COPY frontend/package.json frontend/package-lock.json ./
-RUN npm config set registry https://registry.npmmirror.com \
- && npm ci
-COPY frontend/ ./
-RUN npm run build
+# ── Stage 1: SSR 渐进资源(web_src → public/assets)────────────────────────
+FROM ${IMAGE_PREFIX}node:22-bookworm-slim AS websrc
+WORKDIR /src/web_src
+COPY web_src/package.json ./
+COPY web_src/ ./
+RUN node build.mjs
# ── Stage 2: Go 编译(纯 Go SQLite,CGO_ENABLED=0)────────────────────────
FROM ${IMAGE_PREFIX}golang:1.26-bookworm AS builder
@@ -25,7 +23,7 @@ WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
-COPY --from=frontend /src/embed_static/static/spa ./embed_static/static/spa
+COPY --from=websrc /src/public/assets ./public/assets
RUN CGO_ENABLED=0 go build -trimpath \
-ldflags "-s -w -X main.version=${VERSION}" \
-o /out/jiang13 ./cmd/jiang13
diff --git a/LICENSE b/LICENSE
index 2ae8e80..3bbab44 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,4 @@
-MIT License
-
-Copyright (c) 2026 freefire
+Copyright (c) 2026 The Jiang13 Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +7,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Makefile b/Makefile
index 8acfd7b..c4df5dc 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
# 姜十三论坛 Jiang13 Forum - Makefile
-# Go 1.26 单二进制编译,与 Gitea 打包方式一致
+# Go 1.26 单二进制:templates SSR + web_src 渐进资源(本分支无 React SPA)
APP_NAME := jiang13
MAIN_PKG := ./cmd/jiang13
@@ -12,42 +12,39 @@ REGISTRY_IMAGE := hangzhang714128/jiang13-forum
GO := go
GOFLAGS := -trimpath
-.PHONY: all build build-windows build-linux build-darwin clean run dev tidy help frontend frontend-build web-src-build docker compose-up compose-down
+.PHONY: all build build-windows build-linux build-darwin build-all clean run dev tidy help web-src-build docker compose-up compose-down
all: build
web-src-build:
cd web_src && npm run build
-frontend-build:
- cd frontend && npm install && npm run build
-
## 编译当前平台二进制(纯 Go SQLite,无需 CGO)
-build: web-src-build frontend-build
+build: web-src-build
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(APP_NAME) $(MAIN_PKG)
@echo "✓ 编译完成: $(BUILD_DIR)/$(APP_NAME)"
-## Windows amd64(先打包前端再 embed)
-build-windows: web-src-build frontend-build
+## Windows amd64
+build-windows: web-src-build
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(APP_NAME).exe $(MAIN_PKG)
@echo "✓ Windows: $(BUILD_DIR)/$(APP_NAME).exe"
-## Linux amd64(先打包前端再 embed)
-build-linux: web-src-build frontend-build
+## Linux amd64
+build-linux: web-src-build
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(APP_NAME)-linux-amd64 $(MAIN_PKG)
@echo "✓ Linux: $(BUILD_DIR)/$(APP_NAME)-linux-amd64"
-## macOS arm64 (Apple Silicon)(先打包前端再 embed)
-build-darwin: web-src-build frontend-build
+## macOS arm64 (Apple Silicon)
+build-darwin: web-src-build
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(APP_NAME)-darwin-arm64 $(MAIN_PKG)
@echo "✓ macOS: $(BUILD_DIR)/$(APP_NAME)-darwin-arm64"
-## 跨平台全量编译(web-src + frontend 只跑一次)
-build-all: web-src-build frontend-build
+## 跨平台全量编译(web_src 只跑一次)
+build-all: web-src-build
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(APP_NAME).exe $(MAIN_PKG)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(APP_NAME)-linux-amd64 $(MAIN_PKG)
@@ -59,20 +56,13 @@ build-all: web-src-build frontend-build
tidy:
$(GO) mod tidy
-## 本地运行(仅后端,使用已 embed 的前端;数据目录与 dist 二进制一致)
-run:
+## 本地运行 SSR(先构建 web_src)
+run: web-src-build
@mkdir -p $(DEV_DATA_DIR)
- $(GO) run $(MAIN_PKG) --data $(DEV_DATA_DIR)
+ $(GO) run $(MAIN_PKG) --work-path . --data $(DEV_DATA_DIR)
-## 前端热更新开发(后端 :3000 + Vite :5173,Ctrl+C 同时退出;数据目录与 dist 二进制一致)
-dev:
- @echo "前端热更新: http://localhost:5173"
- @echo "后端 API : http://localhost:3000"
- @echo "数据目录 : $(DEV_DATA_DIR) (与 dist 二进制一致)"
- @mkdir -p $(DEV_DATA_DIR)
- @trap 'kill 0' INT; \
- $(GO) run $(MAIN_PKG) --dev --data $(DEV_DATA_DIR) & \
- cd frontend && (test -d node_modules || npm install) && npm run dev
+## 同 run(SPA 对照请 git checkout main)
+dev: run
## 清理编译产物
clean:
@@ -91,15 +81,15 @@ compose-down:
docker compose down
help:
- @echo "姜十三论坛编译命令:"
+ @echo "姜十三论坛编译命令 (rebuild/gitea-ssr):"
@echo " make web-src-build - 构建 SSR 渐进资源 (web_src)"
- @echo " make build - 编译当前平台"
+ @echo " make build - web_src + 编译当前平台"
@echo " make build-windows - 编译 Windows"
@echo " make build-linux - 编译 Linux"
@echo " make build-darwin - 编译 macOS"
@echo " make build-all - 编译全部平台"
- @echo " make run - 启动后端 SSR(:3000)"
- @echo " make dev - 后端 + 旧 SPA Vite 对照(:5173 + :3000)"
+ @echo " make run / make dev - 启动 SSR(:3000)"
@echo " make docker - 构建 Docker 镜像"
@echo " make compose-up - Docker Compose 启动"
@echo " make compose-down - Docker Compose 停止"
+ @echo " SPA 对照: git checkout main"
diff --git a/README.md b/README.md
index 6cd2576..4db81b9 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,8 @@
**能聊 · 好看 · 好装**
面向小圈子、团队与同好社群的轻量现代化论坛。
-编译为单个 Go 二进制,前端 SPA(单页应用)内嵌,内置 SQLite,拷到服务器即可运行。
+本分支(`rebuild/gitea-ssr`):Go 模板真 SSR + `web_src` 渐进增强,单二进制 + SQLite。
+对照 React SPA 请见 `main` 分支。
@@ -13,7 +14,7 @@
[](LICENSE)
[](https://hub.docker.com/r/hangzhang714128/jiang13-forum)
[](go.mod)
-[](frontend/package.json)
+[](docs/rebuild-spec/08-gitea-ssr-architecture.md)
[](#)
[在线演示](https://bbs.iioio.com/) ·
@@ -31,8 +32,8 @@
-> **演示站点:** [https://bbs.iioio.com/](https://bbs.iioio.com/)
-> 项目积极开发中。管理后台已统一为 React SPA(`/admin`),欢迎提 Issue / PR 共建。
+> **演示站点:** [https://bbs.iioio.com/](https://bbs.iioio.com/)(现网多为 `main` SPA)
+> 本分支按 [Gitea 式 SSR 规格](docs/rebuild-spec/08-gitea-ssr-architecture.md) 重构;欢迎提 Issue / PR 共建。
@@ -142,7 +143,7 @@ make build
**手动分步(全平台):**
```bash
-cd frontend && npm install && npm run build
+cd web_src && npm run build
cd .. && go build -trimpath -ldflags "-s -w" -o dist/jiang13 ./cmd/jiang13
```
@@ -325,80 +326,61 @@ C:\jiang13\jiang13.exe --service start
| 层级 | 技术 |
|------|------|
-| **后端** | Go 1.26 · Gin · GORM · SQLite |
-| **前端** | React 18 · TipTap · Radix UI · Tailwind CSS · TanStack Virtual |
-| **构建** | Vite → `go:embed` 内嵌 SPA,单二进制发布 |
+| **后端 / SSR** | Go 1.26 · Gin · GORM · SQLite · `html/template` |
+| **渐进资源** | `web_src/`(构建到 `public/assets/`,URL `/ssr-assets/`) |
+| **构建** | `web_src` → `go:embed` templates + assets,单二进制发布 |
| **认证** | bcrypt · JWT Cookie · 可选 OIDC Provider |
+| **对照 SPA** | 仅 `main` 分支(React 18 · TipTap · Vite) |
---
-## 前端开发
-
-日常改前端不需要重新完整构建,Vite 支持秒级热更新(HMR,热模块替换):
+## 本地开发(SSR)
```bat
-build.bat -Target dev
+build.bat -Target run
```
```bash
-make dev
+make run
```
-浏览器访问 `http://localhost:5173`,API 自动代理到 `http://localhost:3000`。
+浏览器访问 `http://localhost:3000`。数据目录默认 `dist/data`。
-开发后端与 `dist/jiang13` 共用数据目录 `dist/data`(SQLite、上传、JWT 密钥等),避免 dev 与 dist 运行数据不一致。
+改模板 / Go 后重启进程;改 `web_src` 后需再跑 `build.bat -Target web-src`(或完整 `build`)。
-**何时需要完整构建:**
-
-- 修改 Go 代码或要发布单二进制 → `build.bat` / `make build`
-- 更新 README 界面截图 → 见下方「更新截图」
-
-> 直接访问 `:3000` 看到的是上次 build 嵌入的前端;开发时请用 `:5173`。
-
-### 更新截图
-
-默认从演示站抓取到 `docs/screenshots/`(需本机已安装 Playwright):
-
-```bash
-npm install -D playwright
-npx playwright install chromium
-node scripts/capture-screenshots.mjs
-```
-
-| 环境变量 | 说明 | 默认 |
-|----------|------|------|
-| `J13_URL` | 抓取目标 | `https://bbs.iioio.com` |
-| `J13_POST_ID` | 详情页帖子 ID | `1` |
-| `J13_RICH_POST_ID` | 富文本展示帖 ID | `8` |
-| `J13_USER` / `J13_PASS` | 发帖页登录(可选) | `admin` / `admin123` |
-
-本地站点示例:`J13_URL=http://localhost:3000 node scripts/capture-screenshots.mjs`
+需要对照旧 SPA UI:`git checkout main` 或 `git worktree add ../jiang13-spa main`。
---
## 项目结构
```
-jiang13-forum/
-├── cmd/jiang13/ # 程序入口(含系统服务注册)
-├── config/ # app.ini 与命令行配置
-├── app.ini.example # 配置文件示例
-├── Dockerfile # 多阶段 Docker 构建
-├── docker-compose.yml # 单容器 Compose 部署
-├── docker-entrypoint.sh # 容器启动脚本(修正 /data 卷权限)
-├── .dockerignore
-├── model/ # GORM 模型与数据库迁移
-├── service/ # 业务逻辑
-├── handler/ # HTTP 处理器(前台 + 后台)
-├── middleware/ # JWT 鉴权等
-├── router/ # 路由注册
-├── embed_static/ # go:embed 内嵌的 SPA
-├── frontend/ # React 源码(Vite 构建)
-├── docs/screenshots/ # README 界面截图
-├── ROADMAP.md # 路线图与已知问题
-└── scripts/ # 开发辅助脚本(含截图)
+jiang13-forum/ # 分支 rebuild/gitea-ssr
+├── cmd/jiang13/ # 程序入口(含系统服务注册)
+├── config/ # app.ini 与命令行配置
+├── app.ini.example
+├── Dockerfile # web_src → Go → Alpine
+├── docker-compose.yml
+├── models/ # GORM 模型
+├── services/ # 业务逻辑
+├── routers/
+│ ├── setup.go # 路由总装
+│ ├── web/ # HTML SSR
+│ └── api/ # JSON API
+├── modules/
+│ ├── auth/ # JWT / 限流
+│ ├── webrender/ # 模板渲染
+│ └── seo/
+├── templates/ # Go html/template(embed)
+├── web_src/ # 渐进 CSS/JS 源码
+├── public/assets/ # web_src 构建产物(embed)
+├── docs/rebuild-spec/ # 产品规格与 SSR 架构
+├── docs/screenshots/
+└── ROADMAP.md
```
+> SPA 源码树仅存在于 `main`(`frontend/`、`embed_static/`)。
+
---
## 数据目录
@@ -424,7 +406,7 @@ data/
|------|------|
| ✅ 已可用 | 三栏布局、暗色主题、虚拟滚动、Feed 排序、楼层评论 |
| ✅ 发帖体验 | TipTap 富文本、图片上传、修订历史、回复可见等门控 |
-| ✅ 管理后台 | React SPA:仪表盘、置顶 / 精华、禁言、系统设置 |
+| ✅ 管理后台 | JSON API 已就绪;本分支管理 UI 为 SSR 占位(完整后台见 `main` SPA) |
| 📋 计划中 | 通知动态优化、邮件提醒 |
---
@@ -439,4 +421,4 @@ data/
## 许可证
-[MIT](LICENSE) — 自由使用、修改与分发。
+[MIT](LICENSE)(与 [Gitea](https://github.com/go-gitea/gitea) 相同的 Expat 文本格式)— 自由使用、修改与分发。
diff --git a/build.ps1 b/build.ps1
index 661d4c0..e1e7498 100644
--- a/build.ps1
+++ b/build.ps1
@@ -1,9 +1,10 @@
# Jiang13 Forum - Windows build script (replaces GNU Make)
# Usage: .\build.ps1
# .\build.ps1 -Target build-windows
+# Branch rebuild/gitea-ssr: Go templates SSR + web_src (no React SPA)
param(
- [ValidateSet('build', 'build-windows', 'build-linux', 'build-darwin', 'build-all', 'frontend', 'web-src', 'tidy', 'run', 'dev', 'clean', 'docker', 'compose-up', 'compose-down', 'help')]
+ [ValidateSet('build', 'build-windows', 'build-linux', 'build-darwin', 'build-all', 'web-src', 'tidy', 'run', 'dev', 'clean', 'docker', 'compose-up', 'compose-down', 'help')]
[string]$Target = 'build'
)
@@ -33,20 +34,6 @@ function Build-WebSrc {
}
}
-function Build-Frontend {
- Write-Host '[frontend] npm run build...' -ForegroundColor Cyan
- Push-Location frontend
- try {
- if (-not (Test-Path node_modules)) {
- npm install
- }
- npm run build
- if ($LASTEXITCODE -ne 0) { throw 'frontend build failed' }
- } finally {
- Pop-Location
- }
-}
-
function Build-Go([string]$OutFile, [string]$GoOS = '', [string]$GoArch = '') {
Ensure-Dir $BuildDir
if ($GoOS) { $env:GOOS = $GoOS } else { Remove-Item Env:GOOS -ErrorAction SilentlyContinue }
@@ -80,23 +67,22 @@ function Build-Go([string]$OutFile, [string]$GoOS = '', [string]$GoArch = '') {
switch ($Target) {
'help' {
- Write-Host '.\build.ps1 build current platform'
- Write-Host '.\build.ps1 -Target frontend SPA frontend only (legacy)'
- Write-Host '.\build.ps1 -Target web-src SSR progressive assets'
+ Write-Host '.\build.ps1 build current platform (web_src + go)'
+ Write-Host '.\build.ps1 -Target web-src SSR progressive assets only'
Write-Host '.\build.ps1 -Target build-windows'
Write-Host '.\build.ps1 -Target build-linux'
Write-Host '.\build.ps1 -Target build-all'
- Write-Host '.\build.ps1 -Target run backend (SSR on :3000)'
- Write-Host '.\build.ps1 -Target dev backend + Vite SPA对照'
+ Write-Host '.\build.ps1 -Target run SSR on :3000'
+ Write-Host '.\build.ps1 -Target dev same as run (SSR; SPA is on main)'
Write-Host '.\build.ps1 -Target tidy'
Write-Host '.\build.ps1 -Target clean'
- Write-Host '.\build.ps1 -Target docker build Docker image'
- Write-Host '.\build.ps1 -Target compose-up docker compose up -d --build'
- Write-Host '.\build.ps1 -Target compose-down docker compose down'
+ Write-Host '.\build.ps1 -Target docker'
+ Write-Host '.\build.ps1 -Target compose-up'
+ Write-Host '.\build.ps1 -Target compose-down'
Write-Host ''
Write-Host 'Note: Windows "make" is often Embarcadero MAKE, not GNU Make.'
+ Write-Host 'SPA reference: git checkout main (or origin/main).'
}
- 'frontend' { Build-Frontend }
'web-src' { Build-WebSrc }
'tidy' { go mod tidy }
'clean' {
@@ -105,54 +91,33 @@ switch ($Target) {
}
'run' {
Ensure-Dir $DevDataDir
- go run $MainPkg --data $DevDataDir
+ Build-WebSrc
+ go run $MainPkg --work-path . --data $DevDataDir
}
'dev' {
- $root = (Get-Location).Path
Ensure-Dir $DevDataDir
- Write-Host ''
- Write-Host '[dev] 前端开发 : http://localhost:5173 (Vite HMR)' -ForegroundColor Green
- Write-Host '[dev] 后端 API : http://localhost:3000 (Go)' -ForegroundColor Green
- Write-Host "[dev] 数据目录 : $DevDataDir (与 dist 二进制一致)" -ForegroundColor Green
- Write-Host '[dev] 提示 : 请访问 5173 端口,Vite 会自动代理 API 到 3000' -ForegroundColor Yellow
- Write-Host '[dev] 正在新窗口启动 Go 后端 (仅 API)...' -ForegroundColor Cyan
- Start-Process powershell -ArgumentList @(
- '-NoExit', '-Command',
- "Set-Location '$root'; Write-Host '[backend] Go API on :3000' -ForegroundColor Cyan; go run $MainPkg --dev --data '$DevDataDir'"
- ) | Out-Null
- Start-Sleep -Seconds 2
- Push-Location frontend
- try {
- if (-not (Test-Path node_modules)) { npm install }
- npm run dev
- } finally {
- Pop-Location
- }
+ Build-WebSrc
+ Write-Host '[dev] SSR: http://localhost:3000 (SPA 对照请 checkout main)' -ForegroundColor Green
+ go run $MainPkg --work-path . --data $DevDataDir
}
'build' {
Build-WebSrc
- Build-Frontend
Build-Go -OutFile $AppName
}
'build-windows' {
Build-WebSrc
- Build-Frontend
Build-Go -OutFile $AppName -GoOS 'windows' -GoArch 'amd64'
}
'build-linux' {
- Write-Host '[build-linux] will build web_src + SPA then go:embed' -ForegroundColor Yellow
Build-WebSrc
- Build-Frontend
Build-Go -OutFile "$AppName-linux-amd64" -GoOS 'linux' -GoArch 'amd64'
}
'build-darwin' {
Build-WebSrc
- Build-Frontend
Build-Go -OutFile "$AppName-darwin-arm64" -GoOS 'darwin' -GoArch 'arm64'
}
'build-all' {
Build-WebSrc
- Build-Frontend
Build-Go -OutFile $AppName -GoOS 'windows' -GoArch 'amd64'
Build-Go -OutFile "$AppName-linux-amd64" -GoOS 'linux' -GoArch 'amd64'
Build-Go -OutFile "$AppName-darwin-arm64" -GoOS 'darwin' -GoArch 'arm64'
@@ -166,12 +131,10 @@ switch ($Target) {
}
'compose-up' {
docker compose up -d --build
- if ($LASTEXITCODE -ne 0) { throw 'docker compose up failed' }
- Write-Host '[ok] compose started' -ForegroundColor Green
+ if ($LASTEXITCODE -ne 0) { throw 'compose up failed' }
}
'compose-down' {
docker compose down
- if ($LASTEXITCODE -ne 0) { throw 'docker compose down failed' }
- Write-Host '[ok] compose stopped' -ForegroundColor Green
+ if ($LASTEXITCODE -ne 0) { throw 'compose down failed' }
}
}
diff --git a/cmd/jiang13/main.go b/cmd/jiang13/main.go
index 1fd57d9..81f0ed2 100644
--- a/cmd/jiang13/main.go
+++ b/cmd/jiang13/main.go
@@ -1,11 +1,11 @@
-package main
+package main
import (
"fmt"
"log"
"os"
- "github.com/kardianos/service"
+ kardsvc "github.com/kardianos/service"
"git.iioio.com/freefire/jiang13-forum/config"
)
@@ -25,7 +25,7 @@ func main() {
}
prg := &program{cfg: cfg}
- svc, err := service.New(prg, svcCfg)
+ svc, err := kardsvc.New(prg, svcCfg)
if err != nil {
log.Fatalf("创建系统服务失败: %v", err)
}
diff --git a/cmd/jiang13/program.go b/cmd/jiang13/program.go
index b68391d..d10a03c 100644
--- a/cmd/jiang13/program.go
+++ b/cmd/jiang13/program.go
@@ -1,4 +1,4 @@
-package main
+package main
import (
"context"
@@ -9,11 +9,11 @@ import (
"os"
"time"
- "github.com/kardianos/service"
+ kardsvc "github.com/kardianos/service"
"git.iioio.com/freefire/jiang13-forum/config"
- "git.iioio.com/freefire/jiang13-forum/model"
- "git.iioio.com/freefire/jiang13-forum/router"
+ "git.iioio.com/freefire/jiang13-forum/models"
+ "git.iioio.com/freefire/jiang13-forum/routers"
)
const (
@@ -28,7 +28,7 @@ type program struct {
server *http.Server
}
-func (p *program) Start(s service.Service) error {
+func (p *program) Start(s kardsvc.Service) error {
if err := p.setup(); err != nil {
return err
}
@@ -40,7 +40,7 @@ func (p *program) Start(s service.Service) error {
return nil
}
-func (p *program) Stop(s service.Service) error {
+func (p *program) Stop(s kardsvc.Service) error {
log.Println("收到关机信号,正在优雅关闭...")
if p.server == nil {
return nil
@@ -63,7 +63,7 @@ func (p *program) setup() error {
return fmt.Errorf("打开日志文件失败: %w", err)
}
// 服务模式下 stdout 可能不可用,仅写文件;前台运行则双写
- if service.Interactive() {
+ if kardsvc.Interactive() {
log.SetOutput(io.MultiWriter(os.Stdout, logFile))
} else {
log.SetOutput(logFile)
@@ -75,11 +75,11 @@ func (p *program) setup() error {
log.Printf(" 版本: %s", version)
log.Println("========================================")
- if err := model.InitDB(cfg.DBPath()); err != nil {
+ if err := models.InitDB(cfg.DBPath()); err != nil {
return fmt.Errorf("数据库初始化失败: %w", err)
}
- engine, err := router.Setup(cfg)
+ engine, err := routers.Setup(cfg)
if err != nil {
return fmt.Errorf("路由初始化失败: %w", err)
}
@@ -98,9 +98,9 @@ func (p *program) setup() error {
return nil
}
-func buildServiceConfig(cfg *config.Config) (*service.Config, error) {
+func buildServiceConfig(cfg *config.Config) (*kardsvc.Config, error) {
// 服务只绑定工作目录与配置文件;端口/数据目录改 app.ini 后重启即可,无需重装服务
- return &service.Config{
+ return &kardsvc.Config{
Name: svcName,
DisplayName: svcDisplayName,
Description: svcDescription,
@@ -109,7 +109,7 @@ func buildServiceConfig(cfg *config.Config) (*service.Config, error) {
"--work-path", cfg.WorkPath,
"--config", cfg.ConfigFile,
},
- Option: service.KeyValue{
+ Option: kardsvc.KeyValue{
// systemd:异常退出后自动拉起
"Restart": "always",
// Windows:崩溃后重启
@@ -118,16 +118,16 @@ func buildServiceConfig(cfg *config.Config) (*service.Config, error) {
}, nil
}
-func runServiceControl(s service.Service, action string) error {
+func runServiceControl(s kardsvc.Service, action string) error {
if action == "status" {
st, err := s.Status()
if err != nil {
return err
}
switch st {
- case service.StatusRunning:
+ case kardsvc.StatusRunning:
fmt.Println("服务状态: 运行中 (running)")
- case service.StatusStopped:
+ case kardsvc.StatusStopped:
fmt.Println("服务状态: 已停止 (stopped)")
default:
fmt.Println("服务状态: 未知 (unknown)")
@@ -135,7 +135,7 @@ func runServiceControl(s service.Service, action string) error {
return nil
}
- if err := service.Control(s, action); err != nil {
+ if err := kardsvc.Control(s, action); err != nil {
return err
}
diff --git a/docs/rebuild-spec/02-features.md b/docs/rebuild-spec/02-features.md
index 3cc0313..d985f4f 100644
--- a/docs/rebuild-spec/02-features.md
+++ b/docs/rebuild-spec/02-features.md
@@ -3,7 +3,7 @@
> **读者**:实现与验收
> **前置**:[01-product.md](01-product.md)
> **交叉**:[05-business-rules.md](05-business-rules.md)、[06-pages-ux.md](06-pages-ux.md)
-> **源码对照**:[`frontend/src/App.tsx`](../../frontend/src/App.tsx)、[`router/router.go`](../../router/router.go)、[`README.md`](../../README.md)
+> **源码对照**:[`frontend/src/App.tsx`]((仅 main)frontend/src/App.tsx)、[`router/router.go`](../../routers/setup.go)、[`README.md`](../../README.md)
用复选框做验收;重构完成时应全部可勾选(或书面声明砍掉的功能)。
diff --git a/docs/rebuild-spec/03-data-model.md b/docs/rebuild-spec/03-data-model.md
index ed2f882..e7c7123 100644
--- a/docs/rebuild-spec/03-data-model.md
+++ b/docs/rebuild-spec/03-data-model.md
@@ -3,7 +3,7 @@
> **读者**:实现数据库与领域层的 AI
> **前置**:[01-product.md](01-product.md)
> **后续**:[04-api.md](04-api.md)、[05-business-rules.md](05-business-rules.md)
-> **源码**:[`model/models.go`](../../model/models.go)、[`model/oauth.go`](../../model/oauth.go)、[`model/gitea.go`](../../model/gitea.go)、[`model/level.go`](../../model/level.go)、[`model/db.go`](../../model/db.go)、[`model/user_view.go`](../../model/user_view.go)、[`service/settings.go`](../../service/settings.go)
+> **源码**:[`model/models.go`](../../models/models.go)、[`model/oauth.go`](../../models/oauth.go)、[`model/gitea.go`](../../models/gitea.go)、[`model/level.go`](../../models/level.go)、[`model/db.go`](../../models/db.go)、[`model/user_view.go`](../../models/user_view.go)、[`service/settings.go`](../../services/settings.go)
当前无独立 SQL migration;表由 GORM `AutoMigrate` 创建。新站可用正式 migration,但**字段语义应对齐**。
@@ -72,7 +72,7 @@ erDiagram
**非落库展示字段**:`level`(由 Exp 推导)、`badges`(附加)。
-视图结构:`UserPublic` / `UserSelf` / `UserAdmin`(见 [`model/user_view.go`](../../model/user_view.go))。
+视图结构:`UserPublic` / `UserSelf` / `UserAdmin`(见 [`model/user_view.go`](../../models/user_view.go))。
### 2.2 boards
@@ -278,7 +278,7 @@ Metric:`tenure_days` | `likes_received` | `creator_income`
## 4. 等级(Exp → Level)
-源:[`model/level.go`](../../model/level.go)
+源:[`model/level.go`](../../models/level.go)
| Level | 最低 Exp |
|-------|----------|
@@ -299,7 +299,7 @@ Metric:`tenure_days` | `likes_received` | `creator_income`
## 5. 内置自动徽章(seed)
-源:[`model/db.go`](../../model/db.go) `seedDefaultBadges`
+源:[`model/db.go`](../../models/db.go) `seedDefaultBadges`
| code | 名称 | metric | threshold |
|------|------|--------|-----------|
@@ -317,7 +317,7 @@ Metric:`tenure_days` | `likes_received` | `creator_income`
## 6. forum_settings 键与默认值
-源:[`service/settings.go`](../../service/settings.go)、[`service/permalink.go`](../../service/permalink.go)
+源:[`service/settings.go`](../../services/settings.go)、[`service/permalink.go`](../../services/permalink.go)
### 6.1 论坛限制
@@ -429,7 +429,7 @@ Metric:`tenure_days` | `likes_received` | `creator_income`
## 7. 升级兼容补丁(现网 InitDB)
-[`model/db.go`](../../model/db.go) 在 AutoMigrate 后:
+[`model/db.go`](../../models/db.go) 在 AutoMigrate 后:
- 空 `status` 的帖/评 → `published`
- 空 `post_type` → `normal`
@@ -449,4 +449,4 @@ Metric:`tenure_days` | `likes_received` | `creator_income`