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:
@@ -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
|
||||
@@ -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)"
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
)
|
||||
Reference in New Issue
Block a user