feat(server): Go 模块骨架 + 工具链 [tsk_L2k2VrEujof8]

新建 server/ Go 模块:
- go.mod: module github.com/wangjia/pangolin/server, Go 1.22
  依赖: go-chi/chi/v5, google/uuid
- tools.go: //go:build tools 锁定 oapi-codegen / golang-migrate 版本
- cmd/server/main.go: chi router + GET /healthz → {"status":"ok"},监听 :8080
- cmd/migrate/main.go: 占位入口(实逻辑归 1e)
- internal/{config,store,apierr,idgen,auth,codes,devices,nodes,usage,admin}/doc.go
  各包职责说明
- Makefile: build/test/vet/lint/generate/migrate-up/migrate-down
- .golangci.yml: govet/errcheck/staticcheck/revive/gofmt 基线
- README.md: 定位 + 目录树 + make 入口说明
- setup.sh: 首次 go mod tidy 引导脚本(go.sum 需运行后生成)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-13 01:28:37 +08:00
parent a642bf16a2
commit 1629385c70
18 changed files with 225 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
version: "2"
linters:
enable:
- govet
- errcheck
- staticcheck
- revive
- gofmt
linters-settings:
revive:
rules:
- name: exported
- name: var-naming
issues:
exclude-rules:
# tools.go is build-tag-only, ignore unused import warnings
- path: tools\.go
linters:
- revive
+30
View File
@@ -0,0 +1,30 @@
.PHONY: build test vet lint generate migrate-up migrate-down
# ── build ──────────────────────────────────────────────────────────────────────
build:
go build ./...
# ── test ───────────────────────────────────────────────────────────────────────
test:
go test ./...
# ── vet ────────────────────────────────────────────────────────────────────────
vet:
go vet ./...
# ── lint ───────────────────────────────────────────────────────────────────────
lint:
golangci-lint run ./...
# ── generate ───────────────────────────────────────────────────────────────────
# Populated in task 1d: runs oapi-codegen against openapi.yaml
generate:
@echo "generate: not yet configured (see task 1d)"
# ── migrate-up / migrate-down ──────────────────────────────────────────────────
# Populated in task 1e: runs golang-migrate against the Postgres DSN
migrate-up:
@echo "migrate-up: not yet configured (see task 1e)"
migrate-down:
@echo "migrate-down: not yet configured (see task 1e)"
+47
View File
@@ -0,0 +1,47 @@
# Pangolin Server
Go 后端控制面,负责账户、套餐、激活码、设备与节点目录管理。架构详见 `../design/server/ARCHITECTURE.md`
## 目录结构
```
server/
├── cmd/
│ ├── server/ # HTTP 服务入口(chi router/healthz/v1 挂载归 1d
│ └── migrate/ # 数据库迁移 CLI 入口(实现归 1e)
├── internal/
│ ├── config/ # 配置加载(env + 文件,归 1e)
│ ├── store/ # 数据库访问层 Postgres(归 1e
│ ├── apierr/ # 统一错误体 {code, message_zh, message_en}(归 1f
│ ├── idgen/ # UUID + Crockford Base32 激活码(归 1f
│ ├── auth/ # 验证码、argon2id、JWT RS256(归 1c/1d
│ ├── codes/ # 激活码批次、兑换、审计(归 1d)
│ ├── devices/ # 设备管理(归 1d)
│ ├── nodes/ # 节点目录、connect/disconnect(归 1d
│ ├── usage/ # 用量统计(归 1d)
│ └── admin/ # 内部管理端(归 1d)
├── Makefile
├── go.mod
├── go.sum
├── tools.go # //go:build tools — 锁定 oapi-codegen / migrate 版本
└── .golangci.yml
```
## 常用命令
```bash
make build # go build ./...
make test # go test ./...
make vet # go vet ./...
make lint # golangci-lint run ./...
make generate # oapi-codegen 代码生成(1d 填充)
make migrate-up # 执行迁移(1e 填充)
make migrate-down # 回滚迁移(1e 填充)
# 快速启动(默认监听 :8080
go run ./cmd/server
# 指定地址
go run ./cmd/server -addr :9090
# 或通过环境变量
ADDR=:9090 go run ./cmd/server
```
+14
View File
@@ -0,0 +1,14 @@
package main
import (
"fmt"
"os"
)
func main() {
fmt.Fprintln(os.Stdout, "pangolin-migrate: database migration tool")
fmt.Fprintln(os.Stdout, "Usage: migrate [-up|-down] [-steps N]")
fmt.Fprintln(os.Stdout, "")
fmt.Fprintln(os.Stdout, " (implementation in task 1e)")
os.Exit(0)
}
+40
View File
@@ -0,0 +1,40 @@
package main
import (
"encoding/json"
"flag"
"log"
"net/http"
"os"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
addr := flag.String("addr", "", "listen address (default :8080, overridden by ADDR env)")
flag.Parse()
if *addr == "" {
if v := os.Getenv("ADDR"); v != "" {
*addr = v
} else {
*addr = ":8080"
}
}
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
log.Printf("pangolin server listening on %s", *addr)
if err := http.ListenAndServe(*addr, r); err != nil {
log.Fatalf("server error: %v", err)
}
}
+8
View File
@@ -0,0 +1,8 @@
module github.com/wangjia/pangolin/server
go 1.22
require (
github.com/go-chi/chi/v5 v5.2.1
github.com/google/uuid v1.6.0
)
+5
View File
@@ -0,0 +1,5 @@
// Package admin exposes internal management endpoints: code-batch generation,
// node CRUD, user lookup/ban, and audit-log queries. The admin router is
// mounted on a separate listener (or sub-path) protected by IP allowlist
// and two-factor authentication — it is never exposed on the public API port.
package admin
+5
View File
@@ -0,0 +1,5 @@
// Package apierr defines the canonical error response shape used across all
// v1 API handlers: {code, message_zh, message_en}. It provides constructor
// helpers for common HTTP error categories (400/401/403/404/409/429/500)
// and a middleware that serialises *APIError values to JSON automatically.
package apierr
+5
View File
@@ -0,0 +1,5 @@
// Package auth handles all authentication concerns: email verification codes
// (Redis-backed, rate-limited), password hashing (argon2id), JWT issuance
// (RS256, access 15 min + refresh 30 d), and the HTTP middleware that
// validates bearer tokens and injects claims into request context.
package auth
+5
View File
@@ -0,0 +1,5 @@
// Package codes manages activation code lifecycle: batch generation,
// webhook ingestion from card-selling stores, idempotent redemption
// (subscription extension), and fraud controls (per-user failure
// rate-limiting). All redemptions are recorded in the audit log.
package codes
+4
View File
@@ -0,0 +1,4 @@
// Package config loads and validates server configuration from environment
// variables and optional config files. It provides a single Config struct
// consumed by all other packages at startup.
package config
+5
View File
@@ -0,0 +1,5 @@
// Package devices manages user devices (name, platform, WireGuard public key).
// It enforces per-plan device-count limits and coordinates with the nodes
// package to revoke WireGuard peers when a device is removed or a
// subscription expires.
package devices
+5
View File
@@ -0,0 +1,5 @@
// Package idgen generates application-level identifiers.
// For most entities it wraps github.com/google/uuid (v7 time-ordered UUIDs).
// For activation codes it produces 16-character Crockford Base32 strings
// with a check digit, suitable for display and human entry.
package idgen
+5
View File
@@ -0,0 +1,5 @@
// Package nodes owns the node catalogue (region, tier, status, weight) and
// the connect/disconnect flow. It communicates with node agents over mTLS
// gRPC to add and remove WireGuard peers, and exposes a versioned catalogue
// endpoint with 304 support so clients can cache node lists efficiently.
package nodes
+6
View File
@@ -0,0 +1,6 @@
// Package store provides the database access layer for Pangolin.
// It wraps Postgres (via database/sql) and exposes typed repository
// interfaces for each domain entity: users, devices, plans, subscriptions,
// codes, nodes, usage, and audit log. Migrations are managed separately
// by the migrate command.
package store
+5
View File
@@ -0,0 +1,5 @@
// Package usage records per-user daily traffic and session minutes in
// usage_daily. It enforces free-plan daily caps and tracks ad-unlock
// timestamps. No destination addresses or DNS queries are stored —
// only aggregate byte counts and minute counts, per the no-log policy.
package usage
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# Run once after cloning to download dependencies and generate go.sum.
set -euo pipefail
cd "$(dirname "$0")"
go mod tidy
echo "setup complete — run 'make build' to verify"
+8
View File
@@ -0,0 +1,8 @@
//go:build tools
package tools
import (
_ "github.com/golang-migrate/migrate/v4/cmd/migrate"
_ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen"
)