diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 5407c10..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -# PostgreSQL 为首期唯一必需的真实依赖;请仅在本地环境文件中填写真实连接串。 -DATABASE_URL=postgres://postgres:change-me@localhost:5432/agent_dev?sslmode=disable -PORT=8080 - -# 其它外部能力在首期使用 Mock;不要在仓库提交真实 Redis、支付或 IoT 凭证。 -REDIS_URL=redis://default:change-me@127.0.0.1:6379/0 -APP_ENV=development diff --git a/backend/README.md b/backend/README.md index af2fcb3..95da4d3 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,11 +1,12 @@ -# 平台核心后端 +# 后端服务仓 -首期实现 M0 + M1 的平台总后台 API:组织、服务人员、用户、安全事件和仪表盘。PostgreSQL 保存业务事实;支付、地图、IoT、短信和 Redis 在本阶段均通过 Mock 边界预留,不连接生产外部服务。 +本目录严格采用 [sample/server](../../sample/server) 的 BSM-SDK Core 分层:每个 Go 进程均使用 `cmd`、`internal/config`、`internal/impl`、`internal/logic`、`internal/models`、`internal/routers`、`etc`、`scripts`、`test` 和 `Makefile` 约定。 -## 运行 +| 目录 | 进程与职责 | +| --- | --- | +| `api` | Gin HTTP API / BFF、同步事务、JWT 鉴权与统一响应 | +| `worker` | Redis Streams 消费、Outbox 投递、超时扫描与 Mock 外部适配 | +| `iot` | MQTT 协议适配边界、遥测/命令契约校验与 Mock 设备接入 | +| `migrations` | PostgreSQL 迁移、中文注释、回滚说明和初始化数据 | -1. 将 `.env.example` 复制为本地环境文件,设置 `DATABASE_URL`。 -2. 在 PowerShell 设置 `DATABASE_URL` 后执行 `go run ./cmd/api`。 -3. 服务启动时自动执行幂等迁移和演示数据初始化。 - -接口前缀为 `/api/v1`,例如:`GET /api/v1/dashboard/overview`。 +`sample/server` 的工程机制被直接沿用;但由于项目规范强制要求 `identity` 为 UUID V7 主键,领域模型使用自定义 `models.Entity`,不使用样例中含自增 `ID` 的 `types.Std_IICUDS`。 diff --git a/backend/api/Makefile b/backend/api/Makefile new file mode 100644 index 0000000..16465bf --- /dev/null +++ b/backend/api/Makefile @@ -0,0 +1,21 @@ +.PHONY: build run cli migrate lint tidy + +build: + go build -o build/platform-api ./cmd/main/main.go + go build -o build/platform-cli ./cmd/cli/main.go + +run: + go run ./cmd/main/main.go + +cli: + go run ./cmd/cli/main.go $(ARGS) + +migrate: + go run ./cmd/cli/main.go migrate + +lint: + go vet ./... + go fmt ./... + +tidy: + go mod tidy diff --git a/backend/api/README.md b/backend/api/README.md new file mode 100644 index 0000000..f983f1b --- /dev/null +++ b/backend/api/README.md @@ -0,0 +1,12 @@ +# Platform API + +严格沿用 `sample/server` 的 BSM-SDK Core 分层和运行方式。运行前设置: + +```powershell +$env:BSM_RuntimeMode="dev" +$env:BSM_Prefix="$(Get-Location)/etc" +$env:BSM_JwtSecretKey="仅本地使用的随机密钥" +go run ./cmd/main/main.go +``` + +`cmd/cli` 提供 `version` 与 `migrate`;受保护接口使用 `middleware.JwtAuth(true)`。UUID V7 主键及完整中文注释以 `../migrations` 为准。 diff --git a/backend/api/cmd/cli/main.go b/backend/api/cmd/cli/main.go new file mode 100644 index 0000000..b3e824e --- /dev/null +++ b/backend/api/cmd/cli/main.go @@ -0,0 +1,31 @@ +// 平台 API 的数据库迁移与版本命令行工具。 +package main + +import ( + "fmt" + "os" + + "git.apinb.com/heqiapp/platforms/backend/api/internal/config" + "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + _ "git.apinb.com/heqiapp/platforms/backend/api/internal/models" +) + +const serviceKey = "Platform" + +func main() { + if len(os.Args) < 2 { + fmt.Println("usage: platform-cli ") + return + } + switch os.Args[1] { + case "version": + fmt.Println("platform-cli 0.1.0") + case "migrate": + config.New(serviceKey) + impl.NewImpl() + fmt.Println("platform database auto migrate completed") + default: + fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1]) + os.Exit(1) + } +} diff --git a/backend/api/cmd/main/main.go b/backend/api/cmd/main/main.go new file mode 100644 index 0000000..fa300c6 --- /dev/null +++ b/backend/api/cmd/main/main.go @@ -0,0 +1,49 @@ +// 平台 HTTP API 进程入口,沿用 sample/server 的 BSM 启动规范。 +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/bsm-sdk/core/middleware" + "git.apinb.com/bsm-sdk/core/printer" + "git.apinb.com/heqiapp/platforms/backend/api/internal/config" + "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + "git.apinb.com/heqiapp/platforms/backend/api/internal/routers" + "github.com/gin-gonic/gin" +) + +const serviceKey = "Platform" + +func main() { + config.New(serviceKey) + impl.NewImpl() + + app := gin.Default() + middleware.Mode(app) + app.Use(middleware.Cors()) + app.Use(gin.Recovery()) + app.HEAD("/", infra.Health) + routers.Register(serviceKey, app) + + server := &http.Server{Addr: fmt.Sprintf(":%s", config.Spec.Port), Handler: app} + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + panic(err) + } + }() + printer.Success("[BSM - %s] Service Started At: %s", serviceKey, server.Addr) + + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + shutdownContext, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + _ = server.Shutdown(shutdownContext) +} diff --git a/backend/api/etc/platform.env.example b/backend/api/etc/platform.env.example new file mode 100644 index 0000000..a0d17e1 --- /dev/null +++ b/backend/api/etc/platform.env.example @@ -0,0 +1,4 @@ +BSM_Workspace=default +BSM_RuntimeMode=dev +BSM_Prefix=./etc +BSM_JwtSecretKey=change-me-to-a-random-string diff --git a/backend/api/etc/platform_dev.yaml b/backend/api/etc/platform_dev.yaml new file mode 100644 index 0000000..2177538 --- /dev/null +++ b/backend/api/etc/platform_dev.yaml @@ -0,0 +1,12 @@ +Service: platform +Port: 12426 + +Databases: + Driver: postgres + Source: + - host=127.0.0.1 user=postgres password=change-me dbname=agent_dev port=5432 sslmode=disable TimeZone=Asia/Shanghai + +# Redis 仅用于缓存、会话、限流和 Streams;首期外部事件适配使用 Mock。 +Cache: redis://default:change-me@127.0.0.1:6379/0 +OnMicroService: false +SecretKey: change-me-to-a-random-string diff --git a/backend/api/go.mod b/backend/api/go.mod new file mode 100644 index 0000000..089508f --- /dev/null +++ b/backend/api/go.mod @@ -0,0 +1,75 @@ +module git.apinb.com/heqiapp/platforms/backend/api + +go 1.26.1 + +require ( + git.apinb.com/bsm-sdk/core v0.2.0 + github.com/gin-gonic/gin v1.12.0 + github.com/google/uuid v1.6.0 + github.com/patrickmn/go-cache v2.1.0+incompatible + gorm.io/gorm v1.31.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/cors v1.7.7 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/redis/go-redis/v9 v9.19.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.etcd.io/etcd/api/v3 v3.6.11 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.11 // indirect + go.etcd.io/etcd/client/v3 v3.6.11 // indirect + go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/arch v0.23.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.36.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/grpc v1.81.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.0 // indirect +) diff --git a/backend/api/go.sum b/backend/api/go.sum new file mode 100644 index 0000000..96f0759 --- /dev/null +++ b/backend/api/go.sum @@ -0,0 +1,225 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.apinb.com/bsm-sdk/core v0.2.0 h1:/e9yqpsbKBrRgMiGpS3KX2O4qDLxo5V5GpPSLNGxEKw= +git.apinb.com/bsm-sdk/core v0.2.0/go.mod h1:E9T6Eboo/0Zb36BjkKbIgvFzq4fQ2Q8P/7y5zmYTI6Y= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q= +github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k= +github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +go.etcd.io/etcd/api/v3 v3.6.11 h1:XFGTgrJ8nak3kB4NgMG8t7NT+lEeuuvKQAqUHKVgkWQ= +go.etcd.io/etcd/api/v3 v3.6.11/go.mod h1:HYfTh0jyh+uFgp6gMbxJteIDYY97yMuYz85Rnw6Gy9o= +go.etcd.io/etcd/client/pkg/v3 v3.6.11 h1:e41mp315Yn3QMGPmEzCyLsMINgJXTY/dX8kM++1csxU= +go.etcd.io/etcd/client/pkg/v3 v3.6.11/go.mod h1:DysuMe/inqRyC/1tjRR6hReH/VV9Lufs27YKSKBWWJg= +go.etcd.io/etcd/client/v3 v3.6.11 h1:LAByD96VmmeuairkvdAcE0RZnrmGz/q3ceeWePo9bwc= +go.etcd.io/etcd/client/v3 v3.6.11/go.mod h1:vOTDMCo+fGPEClJqcFEFSqZ+8e7WKV7AyqJjX//HR2w= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/backend/api/internal/config/config.go b/backend/api/internal/config/config.go new file mode 100644 index 0000000..c771102 --- /dev/null +++ b/backend/api/internal/config/config.go @@ -0,0 +1,29 @@ +// Package config 沿用 sample/server 的 BSM 配置加载与校验方式。 +package config + +import ( + "net" + + "git.apinb.com/bsm-sdk/core/conf" +) + +// Spec 是 Platform API 的运行配置。 +var Spec SrvConfig + +// SrvConfig 与 sample/server 配置结构保持一致。 +type SrvConfig struct { + conf.Base `yaml:",inline"` + Databases *conf.DBConf `yaml:"Databases"` + Rpc map[string]conf.RpcConf `yaml:"Rpc"` + Apm *conf.ApmConf `yaml:"APM"` +} + +// New 初始化 BSM 配置并校验服务监听地址。 +func New(srvKey string) { + conf.New(srvKey, &Spec) + Spec.Port = conf.CheckPort(Spec.Port) + Spec.BindIP = conf.CheckIP(Spec.BindIP) + Spec.Addr = net.JoinHostPort(Spec.BindIP, Spec.Port) + conf.NotNil(Spec.Service, Spec.Cache) + conf.PrintInfo(Spec.Addr) +} diff --git a/backend/api/internal/impl/new.go b/backend/api/internal/impl/new.go new file mode 100644 index 0000000..949673f --- /dev/null +++ b/backend/api/internal/impl/new.go @@ -0,0 +1,25 @@ +// Package impl 统一创建数据库、Redis、内存缓存与日志基础设施。 +package impl + +import ( + "git.apinb.com/bsm-sdk/core/cache/redis" + "git.apinb.com/bsm-sdk/core/logger" + "git.apinb.com/bsm-sdk/core/with" + "git.apinb.com/heqiapp/platforms/backend/api/internal/config" + "github.com/patrickmn/go-cache" + "gorm.io/gorm" +) + +var ( + RedisService *redis.RedisClient + DBService *gorm.DB + MemoryService *cache.Cache +) + +// NewImpl 与 sample/server 保持一致,所有进程通过此处创建共享基础设施。 +func NewImpl() { + MemoryService = with.Memory(nil) + RedisService = with.RedisCache(config.Spec.Cache) + DBService = with.Databases(config.Spec.Databases, nil) + logger.New(nil) +} diff --git a/backend/api/internal/logic/dashboard/dashboard.go b/backend/api/internal/logic/dashboard/dashboard.go new file mode 100644 index 0000000..c5d7e73 --- /dev/null +++ b/backend/api/internal/logic/dashboard/dashboard.go @@ -0,0 +1,18 @@ +// Package dashboard 提供平台总后台聚合指标。 +package dashboard + +import ( + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// Overview 返回组织与安全运营的首期概览数据。 +func Overview(ctx *gin.Context) { + overview, err := models.GetDashboardOverview() + if err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, overview) +} diff --git a/backend/api/internal/logic/idn_account/idn_account.go b/backend/api/internal/logic/idn_account/idn_account.go new file mode 100644 index 0000000..2bae785 --- /dev/null +++ b/backend/api/internal/logic/idn_account/idn_account.go @@ -0,0 +1,54 @@ +// Package idn_account 提供用户账户最小必要查询逻辑。 +package idn_account + +import ( + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/bsm-sdk/core/utils" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// AccountView 是 idn_account 的最小必要输出,手机号始终脱敏。 +type AccountView struct { + Identity string `json:"identity"` // 用户主键 + PhoneMasked string `json:"phone_masked"` // 脱敏手机号 + AccountType string `json:"account_type"` // 账户类型 + Status string `json:"status"` // 账户状态 + ServiceArea string `json:"service_area"` // 服务区域 +} + +// ListReply 是 idn_account 的标准分页响应。 +type ListReply struct { + Total int64 `json:"total"` + List []AccountView `json:"list"` +} + +// List 查询普通用户账户列表。 +func List(ctx *gin.Context) { + page := utils.String2Int(ctx.DefaultQuery("page", "1")) + size := utils.String2Int(ctx.DefaultQuery("size", "20")) + if page < 1 { + page = 1 + } + if size < 1 || size > 100 { + size = 20 + } + list, total, err := models.ListIdnAccount(page, size) + if err != nil { + infra.Response.Error(ctx, err) + return + } + views := make([]AccountView, 0, len(list)) + for _, item := range list { + views = append(views, AccountView{Identity: item.Identity.String(), PhoneMasked: maskPhone(item.Phone), AccountType: item.AccountType, Status: item.Status, ServiceArea: item.ServiceArea}) + } + infra.Response.Success(ctx, ListReply{Total: total, List: views}) +} + +// maskPhone 遵循敏感数据最小展示原则。 +func maskPhone(phone string) string { + if len(phone) < 7 { + return "***" + } + return phone[:3] + "****" + phone[len(phone)-4:] +} diff --git a/backend/api/internal/logic/org_delivery_point/org_delivery_point.go b/backend/api/internal/logic/org_delivery_point/org_delivery_point.go new file mode 100644 index 0000000..f02dfd7 --- /dev/null +++ b/backend/api/internal/logic/org_delivery_point/org_delivery_point.go @@ -0,0 +1,38 @@ +// Package org_delivery_point 提供配送点管理查询逻辑。 +package org_delivery_point + +import ( + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/bsm-sdk/core/utils" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// ListReply 是 org_delivery_point 的标准分页响应。 +type ListReply struct { + Total int64 `json:"total"` + List []models.OrgDeliveryPoint `json:"list"` +} + +// List 查询配送点分页列表。 +func List(ctx *gin.Context) { + page, size := pageSize(ctx) + list, total, err := models.ListOrgDeliveryPoint(page, size) + if err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, ListReply{Total: total, List: list}) +} + +func pageSize(ctx *gin.Context) (int, int) { + page := utils.String2Int(ctx.DefaultQuery("page", "1")) + size := utils.String2Int(ctx.DefaultQuery("size", "20")) + if page < 1 { + page = 1 + } + if size < 1 || size > 100 { + size = 20 + } + return page, size +} diff --git a/backend/api/internal/logic/org_gas_station/org_gas_station.go b/backend/api/internal/logic/org_gas_station/org_gas_station.go new file mode 100644 index 0000000..1839c53 --- /dev/null +++ b/backend/api/internal/logic/org_gas_station/org_gas_station.go @@ -0,0 +1,60 @@ +// Package org_gas_station 提供气站管理的 HTTP 业务逻辑。 +package org_gas_station + +import ( + "git.apinb.com/bsm-sdk/core/errcode" + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/bsm-sdk/core/utils" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// CreateRequest 是创建 org_gas_station 的请求体。 +type CreateRequest struct { + StationCode string `json:"station_code" binding:"required,max=32"` + Name string `json:"name" binding:"required,max=128"` + Principal string `json:"principal" binding:"required,max=64"` + ServiceArea string `json:"service_area" binding:"required,max=128"` +} + +// ListReply 是标准分页响应。 +type ListReply struct { + Total int64 `json:"total"` + List []models.OrgGasStation `json:"list"` +} + +// Create 创建待审核气站并由数据库层保证唯一编码。 +func Create(ctx *gin.Context) { + var request CreateRequest + if err := ctx.ShouldBindJSON(&request); err != nil { + infra.Response.Error(ctx, errcode.ErrInvalidArgument) + return + } + data := models.OrgGasStation{StationCode: request.StationCode, Name: request.Name, Principal: request.Principal, ServiceArea: request.ServiceArea} + data.Identity = models.NewIdentity() + data.Status = "draft" + data.Version = 1 + if err := models.CreateOrgGasStation(&data); err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, data) +} + +// List 查询气站分页列表。 +func List(ctx *gin.Context) { + page := utils.String2Int(ctx.DefaultQuery("page", "1")) + size := utils.String2Int(ctx.DefaultQuery("size", "20")) + if page < 1 { + page = 1 + } + if size < 1 || size > 100 { + size = 20 + } + list, total, err := models.ListOrgGasStation(page, size) + if err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, ListReply{Total: total, List: list}) +} diff --git a/backend/api/internal/logic/org_service_person/org_service_person.go b/backend/api/internal/logic/org_service_person/org_service_person.go new file mode 100644 index 0000000..84aeb17 --- /dev/null +++ b/backend/api/internal/logic/org_service_person/org_service_person.go @@ -0,0 +1,33 @@ +// Package org_service_person 提供服务人员管理查询逻辑。 +package org_service_person + +import ( + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/bsm-sdk/core/utils" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// ListReply 是 org_service_person 的标准分页响应。 +type ListReply struct { + Total int64 `json:"total"` + List []models.OrgServicePerson `json:"list"` +} + +// List 查询服务人员分页列表。 +func List(ctx *gin.Context) { + page := utils.String2Int(ctx.DefaultQuery("page", "1")) + size := utils.String2Int(ctx.DefaultQuery("size", "20")) + if page < 1 { + page = 1 + } + if size < 1 || size > 100 { + size = 20 + } + list, total, err := models.ListOrgServicePerson(page, size) + if err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, ListReply{Total: total, List: list}) +} diff --git a/backend/api/internal/logic/ping/hello.go b/backend/api/internal/logic/ping/hello.go new file mode 100644 index 0000000..d4b99de --- /dev/null +++ b/backend/api/internal/logic/ping/hello.go @@ -0,0 +1,12 @@ +// Package ping 提供匿名健康检查接口。 +package ping + +import ( + "git.apinb.com/bsm-sdk/core/infra" + "github.com/gin-gonic/gin" +) + +// Hello 返回 API 进程健康状态。 +func Hello(ctx *gin.Context) { + infra.Response.Success(ctx, gin.H{"service": "platform-api", "status": "ok"}) +} diff --git a/backend/api/internal/logic/saf_event/saf_event.go b/backend/api/internal/logic/saf_event/saf_event.go new file mode 100644 index 0000000..fa17670 --- /dev/null +++ b/backend/api/internal/logic/saf_event/saf_event.go @@ -0,0 +1,33 @@ +// Package saf_event 提供安全事件查询逻辑。 +package saf_event + +import ( + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/bsm-sdk/core/utils" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// ListReply 是 saf_event 的标准分页响应。 +type ListReply struct { + Total int64 `json:"total"` + List []models.SafEvent `json:"list"` +} + +// List 查询安全事件列表。 +func List(ctx *gin.Context) { + page := utils.String2Int(ctx.DefaultQuery("page", "1")) + size := utils.String2Int(ctx.DefaultQuery("size", "20")) + if page < 1 { + page = 1 + } + if size < 1 || size > 100 { + size = 20 + } + list, total, err := models.ListSafEvent(page, size) + if err != nil { + infra.Response.Error(ctx, err) + return + } + infra.Response.Success(ctx, ListReply{Total: total, List: list}) +} diff --git a/backend/api/internal/models/entity.go b/backend/api/internal/models/entity.go new file mode 100644 index 0000000..4e277a5 --- /dev/null +++ b/backend/api/internal/models/entity.go @@ -0,0 +1,28 @@ +// Package models 定义与数据库表同名的领域模型和数据访问方法。 +package models + +import ( + "time" + + "github.com/google/uuid" +) + +// Entity 是所有主表共享字段;identity 必须由应用生成 UUID V7,禁止自增主键。 +type Entity struct { + Identity uuid.UUID `gorm:"column:identity;type:uuid;primaryKey" json:"identity"` // 主键,UUID V7 + CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null" json:"created_at"` // 创建时间 + UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null" json:"updated_at"` // 更新时间 + CreatedByIdentity *uuid.UUID `gorm:"column:created_by_identity;type:uuid" json:"created_by_identity,omitempty"` // 创建人主键 + UpdatedByIdentity *uuid.UUID `gorm:"column:updated_by_identity;type:uuid" json:"updated_by_identity,omitempty"` // 更新人主键 + Status string `gorm:"column:status;type:varchar(32);not null;default:'draft'" json:"status"` // 业务状态 + Version int `gorm:"column:version;not null;default:1" json:"version"` // 乐观锁版本 +} + +// NewIdentity 生成时间有序 UUID V7,生成失败属于不可恢复的运行时错误。 +func NewIdentity() uuid.UUID { + identity, err := uuid.NewV7() + if err != nil { + panic(err) + } + return identity +} diff --git a/backend/api/internal/models/idn_account.go b/backend/api/internal/models/idn_account.go new file mode 100644 index 0000000..0cca5b2 --- /dev/null +++ b/backend/api/internal/models/idn_account.go @@ -0,0 +1,16 @@ +package models + +import "git.apinb.com/bsm-sdk/core/database" + +// IdnAccount 对应 idn_account,表示用户或服务人员身份账户。 +type IdnAccount struct { + Entity + Phone string `gorm:"column:phone;type:varchar(32);uniqueIndex;not null" json:"phone"` // 手机号,响应时需脱敏 + AccountType string `gorm:"column:account_type;type:varchar(32);not null" json:"account_type"` // 账户类型 + ServiceArea string `gorm:"column:service_area;type:varchar(128);not null" json:"service_area"` // 服务区域 +} + +func init() { database.AppendMigrate(&IdnAccount{}) } + +// TableName 返回与模型、文件名一致的单数数据表名。 +func (table *IdnAccount) TableName() string { return "idn_account" } diff --git a/backend/api/internal/models/org_delivery_point.go b/backend/api/internal/models/org_delivery_point.go new file mode 100644 index 0000000..4a46ccb --- /dev/null +++ b/backend/api/internal/models/org_delivery_point.go @@ -0,0 +1,17 @@ +package models + +import "git.apinb.com/bsm-sdk/core/database" + +// OrgDeliveryPoint 对应 org_delivery_point,表示末端配送组织单元。 +type OrgDeliveryPoint struct { + Entity + DeliveryCode string `gorm:"column:delivery_code;type:varchar(32);uniqueIndex;not null" json:"delivery_code"` // 配送点编码 + GasStationIdentity string `gorm:"column:gas_station_identity;type:uuid" json:"gas_station_identity"` // 归属气站主键 + Name string `gorm:"column:name;type:varchar(128);not null" json:"name"` // 配送点名称 + ServiceArea string `gorm:"column:service_area;type:varchar(128);not null" json:"service_area"` // 服务区域 +} + +func init() { database.AppendMigrate(&OrgDeliveryPoint{}) } + +// TableName 返回与模型、文件名一致的单数数据表名。 +func (table *OrgDeliveryPoint) TableName() string { return "org_delivery_point" } diff --git a/backend/api/internal/models/org_gas_station.go b/backend/api/internal/models/org_gas_station.go new file mode 100644 index 0000000..66d512d --- /dev/null +++ b/backend/api/internal/models/org_gas_station.go @@ -0,0 +1,60 @@ +package models + +import ( + "errors" + + "git.apinb.com/bsm-sdk/core/database" + "git.apinb.com/bsm-sdk/core/errcode" + "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + "gorm.io/gorm" +) + +// OrgGasStation 对应 org_gas_station,表示可燃气体站经营主体。 +type OrgGasStation struct { + Entity + StationCode string `gorm:"column:station_code;type:varchar(32);uniqueIndex;not null" json:"station_code"` // 气站编码 + Name string `gorm:"column:name;type:varchar(128);not null" json:"name"` // 气站名称 + Principal string `gorm:"column:principal;type:varchar(64);not null" json:"principal"` // 负责人 + ServiceArea string `gorm:"column:service_area;type:varchar(128);not null" json:"service_area"` // 服务区域 +} + +func init() { + database.AppendMigrate(&OrgGasStation{}) +} + +// TableName 返回与模型、文件名一致的单数数据表名。 +func (table *OrgGasStation) TableName() string { return "org_gas_station" } + +// CreateOrgGasStation 创建待审核气站。 +func CreateOrgGasStation(data *OrgGasStation) error { + if err := impl.DBService.Create(data).Error; err != nil { + return errcode.ErrDB + } + return nil +} + +// ListOrgGasStation 按创建时间倒序查询气站。 +func ListOrgGasStation(page, size int) ([]OrgGasStation, int64, error) { + var list []OrgGasStation + var total int64 + databaseQuery := impl.DBService.Model(&OrgGasStation{}) + if err := databaseQuery.Count(&total).Error; err != nil { + return nil, 0, errcode.ErrDB + } + if err := databaseQuery.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil { + return nil, 0, errcode.ErrDB + } + return list, total, nil +} + +// GetOrgGasStationByIdentity 查询单个气站。 +func GetOrgGasStationByIdentity(identity string) (*OrgGasStation, error) { + var data OrgGasStation + if err := impl.DBService.Where("identity = ?", identity).First(&data).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errcode.ErrRecordNotFound + } + return nil, errcode.ErrDB + } + return &data, nil +} diff --git a/backend/api/internal/models/org_service_person.go b/backend/api/internal/models/org_service_person.go new file mode 100644 index 0000000..0662f1a --- /dev/null +++ b/backend/api/internal/models/org_service_person.go @@ -0,0 +1,20 @@ +package models + +import "git.apinb.com/bsm-sdk/core/database" + +// OrgServicePerson 对应 org_service_person,表示安装维修、安检或配送服务人员。 +type OrgServicePerson struct { + Entity + AccountIdentity string `gorm:"column:account_identity;type:uuid;uniqueIndex" json:"account_identity"` // 关联 idn_account 主键 + GasStationIdentity string `gorm:"column:gas_station_identity;type:uuid" json:"gas_station_identity"` // 归属气站主键 + DeliveryPointIdentity string `gorm:"column:delivery_point_identity;type:uuid" json:"delivery_point_identity"` // 主归属配送点主键 + Name string `gorm:"column:name;type:varchar(64);not null" json:"name"` // 服务人员姓名 + Roles string `gorm:"column:roles;type:varchar(128);not null" json:"roles"` // 可执行角色集合 + WorkStatus string `gorm:"column:work_status;type:varchar(32);not null" json:"work_status"` // 上班与接单状态 + CredentialStatus string `gorm:"column:credential_status;type:varchar(32);not null" json:"credential_status"` // 资质状态 +} + +func init() { database.AppendMigrate(&OrgServicePerson{}) } + +// TableName 返回与模型、文件名一致的单数数据表名。 +func (table *OrgServicePerson) TableName() string { return "org_service_person" } diff --git a/backend/api/internal/models/query.go b/backend/api/internal/models/query.go new file mode 100644 index 0000000..47e88f6 --- /dev/null +++ b/backend/api/internal/models/query.go @@ -0,0 +1,89 @@ +package models + +import "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + +// DashboardOverview 是平台总后台的安全与组织聚合指标。 +type DashboardOverview struct { + GasStationCount int64 `json:"gas_station_count"` // 启用气站数量 + DeliveryPointCount int64 `json:"delivery_point_count"` // 启用配送点数量 + ServicePersonCount int64 `json:"service_person_count"` // 在岗服务人员数量 + UserCount int64 `json:"user_count"` // 启用普通用户数量 + PendingSafetyCount int64 `json:"pending_safety_count"` // 待处理安全事件数量 +} + +// GetDashboardOverview 通过独立查询返回首期仪表盘指标。 +func GetDashboardOverview() (DashboardOverview, error) { + var overview DashboardOverview + if err := impl.DBService.Model(&OrgGasStation{}).Where("status = ?", "enabled").Count(&overview.GasStationCount).Error; err != nil { + return DashboardOverview{}, err + } + if err := impl.DBService.Model(&OrgDeliveryPoint{}).Where("status = ?", "enabled").Count(&overview.DeliveryPointCount).Error; err != nil { + return DashboardOverview{}, err + } + if err := impl.DBService.Model(&OrgServicePerson{}).Where("work_status = ?", "on_duty").Count(&overview.ServicePersonCount).Error; err != nil { + return DashboardOverview{}, err + } + if err := impl.DBService.Model(&IdnAccount{}).Where("account_type = ? AND status = ?", "user", "enabled").Count(&overview.UserCount).Error; err != nil { + return DashboardOverview{}, err + } + if err := impl.DBService.Model(&SafEvent{}).Where("status = ?", "pending").Count(&overview.PendingSafetyCount).Error; err != nil { + return DashboardOverview{}, err + } + return overview, nil +} + +// ListOrgDeliveryPoint 返回配送点分页列表。 +func ListOrgDeliveryPoint(page, size int) ([]OrgDeliveryPoint, int64, error) { + var list []OrgDeliveryPoint + var total int64 + databaseQuery := impl.DBService.Model(&OrgDeliveryPoint{}) + if err := databaseQuery.Count(&total).Error; err != nil { + return nil, 0, err + } + if err := databaseQuery.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil { + return nil, 0, err + } + return list, total, nil +} + +// ListOrgServicePerson 返回服务人员分页列表。 +func ListOrgServicePerson(page, size int) ([]OrgServicePerson, int64, error) { + var list []OrgServicePerson + var total int64 + databaseQuery := impl.DBService.Model(&OrgServicePerson{}) + if err := databaseQuery.Count(&total).Error; err != nil { + return nil, 0, err + } + if err := databaseQuery.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil { + return nil, 0, err + } + return list, total, nil +} + +// ListIdnAccount 返回普通用户分页列表,手机号脱敏由前端展示层处理。 +func ListIdnAccount(page, size int) ([]IdnAccount, int64, error) { + var list []IdnAccount + var total int64 + databaseQuery := impl.DBService.Model(&IdnAccount{}).Where("account_type = ?", "user") + if err := databaseQuery.Count(&total).Error; err != nil { + return nil, 0, err + } + if err := databaseQuery.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil { + return nil, 0, err + } + return list, total, nil +} + +// ListSafEvent 返回安全事件分页列表。 +func ListSafEvent(page, size int) ([]SafEvent, int64, error) { + var list []SafEvent + var total int64 + databaseQuery := impl.DBService.Model(&SafEvent{}) + if err := databaseQuery.Count(&total).Error; err != nil { + return nil, 0, err + } + if err := databaseQuery.Order("level asc, created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil { + return nil, 0, err + } + return list, total, nil +} diff --git a/backend/api/internal/models/saf_event.go b/backend/api/internal/models/saf_event.go new file mode 100644 index 0000000..f3238ff --- /dev/null +++ b/backend/api/internal/models/saf_event.go @@ -0,0 +1,18 @@ +package models + +import "git.apinb.com/bsm-sdk/core/database" + +// SafEvent 对应 saf_event,表示需要平台跟踪处置的安全事件。 +type SafEvent struct { + Entity + EventCode string `gorm:"column:event_code;type:varchar(32);uniqueIndex;not null" json:"event_code"` // 安全事件编码 + Level int `gorm:"column:level;type:integer;not null" json:"level"` // 风险等级,1 至 3 级 + Title string `gorm:"column:title;type:varchar(256);not null" json:"title"` // 事件说明 +} + +func init() { + database.AppendMigrate(&SafEvent{}) +} + +// TableName 返回与模型、文件名一致的单数数据表名。 +func (table *SafEvent) TableName() string { return "saf_event" } diff --git a/backend/api/internal/routers/register.go b/backend/api/internal/routers/register.go new file mode 100644 index 0000000..9030ce5 --- /dev/null +++ b/backend/api/internal/routers/register.go @@ -0,0 +1,36 @@ +// Package routers 注册与 sample/server 一致的匿名和 JWT 受保护路由组。 +package routers + +import ( + "fmt" + + "git.apinb.com/bsm-sdk/core/middleware" + "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/dashboard" + "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/idn_account" + "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/org_delivery_point" + "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/org_gas_station" + "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/org_service_person" + "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/ping" + "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/saf_event" + "github.com/gin-gonic/gin" +) + +// Register 注册路由,请求地址格式: /{serviceKey}/v1/{domain}/{resource}。 +func Register(srvKey string, engine *gin.Engine) { + v1Key := fmt.Sprintf("/%s/%s", srvKey, "v1") + anonymous := engine.Group(v1Key) + anonymous.GET("/ping/hello", ping.Hello) + + protected := engine.Group(v1Key) + protected.Use(middleware.JwtAuth(true)) + { + protected.GET("/dashboard/overview", dashboard.Overview) + gasStationGroup := protected.Group("/organization/org_gas_station") + gasStationGroup.POST("", org_gas_station.Create) + gasStationGroup.GET("", org_gas_station.List) + protected.GET("/organization/org_delivery_point", org_delivery_point.List) + protected.GET("/organization/org_service_person", org_service_person.List) + protected.GET("/identity/idn_account", idn_account.List) + protected.GET("/safety/saf_event", saf_event.List) + } +} diff --git a/backend/api/scripts/build.sh b/backend/api/scripts/build.sh new file mode 100644 index 0000000..cbbd232 --- /dev/null +++ b/backend/api/scripts/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -euo pipefail +mkdir -p build +GOARCH=amd64 GOOS=linux go build -o ./build/platform-api ./cmd/main/main.go +GOARCH=amd64 GOOS=linux go build -o ./build/platform-cli ./cmd/cli/main.go diff --git a/backend/api/scripts/lint.sh b/backend/api/scripts/lint.sh new file mode 100644 index 0000000..ae376da --- /dev/null +++ b/backend/api/scripts/lint.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail +go vet ./... +go fmt ./... diff --git a/backend/api/test/ping/hello.http b/backend/api/test/ping/hello.http new file mode 100644 index 0000000..7b5782c --- /dev/null +++ b/backend/api/test/ping/hello.http @@ -0,0 +1,2 @@ +### 匿名健康检查 +GET http://127.0.0.1:12426/Platform/v1/ping/hello diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go deleted file mode 100644 index 911e81f..0000000 --- a/backend/cmd/api/main.go +++ /dev/null @@ -1,58 +0,0 @@ -// 平台核心 API 的启动入口。 -package main - -import ( - "context" - "errors" - "log/slog" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "git.apinb.com/heqiapp/platforms/backend/internal/config" - "git.apinb.com/heqiapp/platforms/backend/internal/database" - platformhttp "git.apinb.com/heqiapp/platforms/backend/internal/http" -) - -func main() { - appConfig := config.Load() - logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) - - pool, err := database.Connect(context.Background(), appConfig.DatabaseURL) - if err != nil { - logger.Error("连接 PostgreSQL 失败", "error", err) - os.Exit(1) - } - defer pool.Close() - - if err := database.MigrateAndSeed(context.Background(), pool); err != nil { - logger.Error("初始化数据库失败", "error", err) - os.Exit(1) - } - - server := &http.Server{ - Addr: ":" + appConfig.Port, - Handler: platformhttp.NewRouter(pool, logger), - ReadHeaderTimeout: 5 * time.Second, - } - - go func() { - logger.Info("平台核心 API 已启动", "address", server.Addr) - if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - logger.Error("HTTP 服务异常退出", "error", err) - os.Exit(1) - } - }() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) - <-stop - - shutdownContext, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - if err := server.Shutdown(shutdownContext); err != nil { - logger.Error("HTTP 服务停止失败", "error", err) - } -} diff --git a/backend/go.mod b/backend/go.mod deleted file mode 100644 index 8341fff..0000000 --- a/backend/go.mod +++ /dev/null @@ -1,16 +0,0 @@ -module git.apinb.com/heqiapp/platforms/backend - -go 1.26.1 - -require ( - github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.10.0 -) - -require ( - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/text v0.29.0 // indirect -) diff --git a/backend/go.sum b/backend/go.sum deleted file mode 100644 index a6aa665..0000000 --- a/backend/go.sum +++ /dev/null @@ -1,28 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= -github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/go.work b/backend/go.work new file mode 100644 index 0000000..d0f4f71 --- /dev/null +++ b/backend/go.work @@ -0,0 +1,7 @@ +go 1.26.1 + +use ( + ./api + ./worker + ./iot +) diff --git a/backend/go.work.sum b/backend/go.work.sum new file mode 100644 index 0000000..edb8416 --- /dev/null +++ b/backend/go.work.sum @@ -0,0 +1,108 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.apinb.com/bsm-sdk/core v0.2.0 h1:/e9yqpsbKBrRgMiGpS3KX2O4qDLxo5V5GpPSLNGxEKw= +git.apinb.com/bsm-sdk/core v0.2.0/go.mod h1:E9T6Eboo/0Zb36BjkKbIgvFzq4fQ2Q8P/7y5zmYTI6Y= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q= +github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k= +github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.etcd.io/etcd/api/v3 v3.6.11 h1:XFGTgrJ8nak3kB4NgMG8t7NT+lEeuuvKQAqUHKVgkWQ= +go.etcd.io/etcd/api/v3 v3.6.11/go.mod h1:HYfTh0jyh+uFgp6gMbxJteIDYY97yMuYz85Rnw6Gy9o= +go.etcd.io/etcd/client/pkg/v3 v3.6.11 h1:e41mp315Yn3QMGPmEzCyLsMINgJXTY/dX8kM++1csxU= +go.etcd.io/etcd/client/pkg/v3 v3.6.11/go.mod h1:DysuMe/inqRyC/1tjRR6hReH/VV9Lufs27YKSKBWWJg= +go.etcd.io/etcd/client/v3 v3.6.11 h1:LAByD96VmmeuairkvdAcE0RZnrmGz/q3ceeWePo9bwc= +go.etcd.io/etcd/client/v3 v3.6.11/go.mod h1:vOTDMCo+fGPEClJqcFEFSqZ+8e7WKV7AyqJjX//HR2w= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go deleted file mode 100644 index 441a250..0000000 --- a/backend/internal/config/config.go +++ /dev/null @@ -1,19 +0,0 @@ -// Package config 提供运行配置,不在代码或仓库中保存真实密钥。 -package config - -import "os" - -// Config 是 API 进程的最小运行配置。 -type Config struct { - DatabaseURL string - Port string -} - -// Load 读取环境变量并提供适用于本地开发的端口默认值。 -func Load() Config { - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - return Config{DatabaseURL: os.Getenv("DATABASE_URL"), Port: port} -} diff --git a/backend/internal/database/database.go b/backend/internal/database/database.go deleted file mode 100644 index 3895e33..0000000 --- a/backend/internal/database/database.go +++ /dev/null @@ -1,17 +0,0 @@ -// Package database 负责 PostgreSQL 连接、迁移和演示数据初始化。 -package database - -import ( - "context" - "errors" - - "github.com/jackc/pgx/v5/pgxpool" -) - -// Connect 建立 PostgreSQL 连接池;数据库地址必须通过环境变量提供。 -func Connect(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) { - if databaseURL == "" { - return nil, errors.New("缺少 DATABASE_URL 环境变量") - } - return pgxpool.New(ctx, databaseURL) -} diff --git a/backend/internal/database/migration.go b/backend/internal/database/migration.go deleted file mode 100644 index 9b0f1ec..0000000 --- a/backend/internal/database/migration.go +++ /dev/null @@ -1,80 +0,0 @@ -package database - -import ( - "context" - "fmt" - - "github.com/jackc/pgx/v5/pgxpool" -) - -// MigrateAndSeed 执行幂等结构迁移,并只在空库时创建演示数据。 -func MigrateAndSeed(ctx context.Context, pool *pgxpool.Pool) error { - statements := []string{ - `CREATE TABLE IF NOT EXISTS idn_account ( - identity uuid PRIMARY KEY, phone varchar(32) NOT NULL UNIQUE, account_type varchar(32) NOT NULL, - status varchar(32) NOT NULL, service_area varchar(128) NOT NULL, created_at timestamptz NOT NULL, - updated_at timestamptz NOT NULL, version integer NOT NULL DEFAULT 1 - )`, - `CREATE TABLE IF NOT EXISTS org_gas_station ( - identity uuid PRIMARY KEY, station_code varchar(32) NOT NULL UNIQUE, name varchar(128) NOT NULL, - principal varchar(64) NOT NULL, service_area varchar(128) NOT NULL, status varchar(32) NOT NULL, - created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, version integer NOT NULL DEFAULT 1 - )`, - `CREATE TABLE IF NOT EXISTS org_delivery_point ( - identity uuid PRIMARY KEY, delivery_code varchar(32) NOT NULL UNIQUE, gas_station_identity uuid REFERENCES org_gas_station(identity), - name varchar(128) NOT NULL, service_area varchar(128) NOT NULL, status varchar(32) NOT NULL, - created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, version integer NOT NULL DEFAULT 1 - )`, - `CREATE TABLE IF NOT EXISTS org_service_person ( - identity uuid PRIMARY KEY, account_identity uuid UNIQUE REFERENCES idn_account(identity), gas_station_identity uuid REFERENCES org_gas_station(identity), - delivery_point_identity uuid REFERENCES org_delivery_point(identity), name varchar(64) NOT NULL, roles varchar(128) NOT NULL, - work_status varchar(32) NOT NULL, credential_status varchar(32) NOT NULL, created_at timestamptz NOT NULL, - updated_at timestamptz NOT NULL, version integer NOT NULL DEFAULT 1 - )`, - `CREATE TABLE IF NOT EXISTS org_user_service_relation ( - identity uuid PRIMARY KEY, account_identity uuid NOT NULL REFERENCES idn_account(identity), gas_station_identity uuid REFERENCES org_gas_station(identity), - delivery_point_identity uuid REFERENCES org_delivery_point(identity), source varchar(32) NOT NULL, status varchar(32) NOT NULL, - created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, version integer NOT NULL DEFAULT 1 - )`, - `CREATE TABLE IF NOT EXISTS dev_device ( - identity uuid PRIMARY KEY, device_code varchar(64) NOT NULL UNIQUE, account_identity uuid REFERENCES idn_account(identity), - online_status varchar(32) NOT NULL, valve_status varchar(32) NOT NULL, created_at timestamptz NOT NULL, - updated_at timestamptz NOT NULL, version integer NOT NULL DEFAULT 1 - )`, - `CREATE TABLE IF NOT EXISTS saf_event ( - identity uuid PRIMARY KEY, event_code varchar(32) NOT NULL UNIQUE, device_identity uuid REFERENCES dev_device(identity), - level integer NOT NULL CHECK (level BETWEEN 1 AND 3), title varchar(256) NOT NULL, status varchar(32) NOT NULL, - created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, version integer NOT NULL DEFAULT 1 - )`, - `CREATE TABLE IF NOT EXISTS aud_operation_log ( - identity uuid PRIMARY KEY, operator_identity uuid, action varchar(64) NOT NULL, object_type varchar(64) NOT NULL, - object_identity uuid, detail jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL - )`, - `CREATE INDEX IF NOT EXISTS idx_org_delivery_point_gas_station_identity ON org_delivery_point(gas_station_identity)`, - `CREATE INDEX IF NOT EXISTS idx_org_service_person_gas_station_identity ON org_service_person(gas_station_identity)`, - `CREATE INDEX IF NOT EXISTS idx_saf_event_status_level ON saf_event(status, level)`, - `COMMENT ON TABLE idn_account IS '身份账户主表'`, - `COMMENT ON COLUMN idn_account.identity IS '主键,应用生成的 UUID V7'`, - `COMMENT ON TABLE org_gas_station IS '可燃气体站主表'`, - `COMMENT ON COLUMN org_gas_station.identity IS '主键,应用生成的 UUID V7'`, - `COMMENT ON TABLE org_delivery_point IS '配送点主表'`, - `COMMENT ON COLUMN org_delivery_point.identity IS '主键,应用生成的 UUID V7'`, - `COMMENT ON TABLE org_service_person IS '服务人员主表'`, - `COMMENT ON COLUMN org_service_person.identity IS '主键,应用生成的 UUID V7'`, - `COMMENT ON TABLE org_user_service_relation IS '用户服务关系表'`, - `COMMENT ON COLUMN org_user_service_relation.identity IS '主键,应用生成的 UUID V7'`, - `COMMENT ON TABLE dev_device IS '智能瓶阀设备主表'`, - `COMMENT ON COLUMN dev_device.identity IS '主键,应用生成的 UUID V7'`, - `COMMENT ON TABLE saf_event IS '安全事件主表'`, - `COMMENT ON COLUMN saf_event.identity IS '主键,应用生成的 UUID V7'`, - `COMMENT ON TABLE aud_operation_log IS '不可变操作审计日志表'`, - `COMMENT ON COLUMN aud_operation_log.identity IS '主键,应用生成的 UUID V7'`, - } - - for _, statement := range statements { - if _, err := pool.Exec(ctx, statement); err != nil { - return fmt.Errorf("执行数据库迁移失败: %w", err) - } - } - return seed(ctx, pool) -} diff --git a/backend/internal/database/seed.go b/backend/internal/database/seed.go deleted file mode 100644 index 6712833..0000000 --- a/backend/internal/database/seed.go +++ /dev/null @@ -1,66 +0,0 @@ -package database - -import ( - "context" - "fmt" - "time" - - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgxpool" -) - -// seed 仅为空数据库提供可用于平台总后台演示的初始数据。 -func seed(ctx context.Context, pool *pgxpool.Pool) error { - var count int - if err := pool.QueryRow(ctx, `SELECT count(*) FROM org_gas_station`).Scan(&count); err != nil { - return err - } - if count > 0 { - return nil - } - - tx, err := pool.Begin(ctx) - if err != nil { - return err - } - defer func() { _ = tx.Rollback(ctx) }() - - now := time.Now().UTC() - stationID := newIdentity() - deliveryID := newIdentity() - userID := newIdentity() - personAccountID := newIdentity() - personID := newIdentity() - deviceID := newIdentity() - eventID := newIdentity() - - queries := []struct { - sql string - args []any - }{ - {`INSERT INTO org_gas_station (identity, station_code, name, principal, service_area, status, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$7)`, []any{stationID, "GS-1001", "浦东可燃气体站", "张敏", "浦东新区", "enabled", now}}, - {`INSERT INTO org_delivery_point (identity, delivery_code, gas_station_identity, name, service_area, status, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$7)`, []any{deliveryID, "DP-2001", stationID, "陆家嘴配送点", "陆家嘴片区", "enabled", now}}, - {`INSERT INTO idn_account (identity, phone, account_type, status, service_area, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$6)`, []any{userID, "13800000001", "user", "enabled", "浦东新区", now}}, - {`INSERT INTO idn_account (identity, phone, account_type, status, service_area, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$6)`, []any{personAccountID, "13900000001", "service_person", "enabled", "陆家嘴片区", now}}, - {`INSERT INTO org_service_person (identity, account_identity, gas_station_identity, delivery_point_identity, name, roles, work_status, credential_status, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$9)`, []any{personID, personAccountID, stationID, deliveryID, "李强", "delivery", "on_duty", "valid", now}}, - {`INSERT INTO org_user_service_relation (identity, account_identity, gas_station_identity, delivery_point_identity, source, status, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$7)`, []any{newIdentity(), userID, stationID, deliveryID, "seed", "active", now}}, - {`INSERT INTO dev_device (identity, device_code, account_identity, online_status, valve_status, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$6)`, []any{deviceID, "DEV-1001", userID, "online", "closed", now}}, - {`INSERT INTO saf_event (identity, event_code, device_identity, level, title, status, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$7)`, []any{eventID, "SAF-3001", deviceID, 1, "设备压力异常待处置", "pending", now}}, - {`INSERT INTO aud_operation_log (identity, action, object_type, object_identity, detail, created_at) VALUES ($1,$2,$3,$4,$5,$6)`, []any{newIdentity(), "seed", "org_gas_station", stationID, `{"source":"development"}`, now}}, - } - for _, query := range queries { - if _, err := tx.Exec(ctx, query.sql, query.args...); err != nil { - return fmt.Errorf("写入演示数据失败: %w", err) - } - } - return tx.Commit(ctx) -} - -// newIdentity 生成符合全局命名规范的时间有序 UUID V7 主键。 -func newIdentity() uuid.UUID { - identity, err := uuid.NewV7() - if err != nil { - panic(err) - } - return identity -} diff --git a/backend/internal/http/router.go b/backend/internal/http/router.go deleted file mode 100644 index 0132256..0000000 --- a/backend/internal/http/router.go +++ /dev/null @@ -1,107 +0,0 @@ -// Package http 提供平台总后台的 HTTP 路由和统一响应。 -package http - -import ( - "encoding/json" - "log/slog" - "net/http" - "time" - - "git.apinb.com/heqiapp/platforms/backend/internal/platform" - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgxpool" -) - -// NewRouter 注册首期平台总后台所需的只读列表、仪表盘与气站创建接口。 -func NewRouter(pool *pgxpool.Pool, logger *slog.Logger) http.Handler { - repository := platform.NewRepository(pool) - mux := http.NewServeMux() - - mux.HandleFunc("GET /healthz", func(writer http.ResponseWriter, request *http.Request) { - writeJSON(writer, http.StatusOK, map[string]any{"status": "ok"}) - }) - mux.HandleFunc("GET /api/v1/dashboard/overview", func(writer http.ResponseWriter, request *http.Request) { - respondRepository(writer, request, logger, func() (any, error) { return repository.DashboardOverview(request.Context()) }) - }) - mux.HandleFunc("GET /api/v1/org/gas-station", func(writer http.ResponseWriter, request *http.Request) { - respondRepository(writer, request, logger, func() (any, error) { return repository.ListOrgGasStation(request.Context()) }) - }) - mux.HandleFunc("POST /api/v1/org/gas-station", func(writer http.ResponseWriter, request *http.Request) { - var body struct { - StationCode string `json:"stationCode"` - Name string `json:"name"` - Principal string `json:"principal"` - ServiceArea string `json:"serviceArea"` - } - if err := json.NewDecoder(request.Body).Decode(&body); err != nil || body.StationCode == "" || body.Name == "" || body.Principal == "" || body.ServiceArea == "" { - writeError(writer, http.StatusBadRequest, "参数不完整:站点编码、名称、负责人和服务区域均为必填") - return - } - respondRepositoryWithStatus(writer, request, logger, http.StatusCreated, func() (any, error) { - return repository.CreateOrgGasStation(request.Context(), body.StationCode, body.Name, body.Principal, body.ServiceArea) - }) - }) - mux.HandleFunc("GET /api/v1/org/delivery-point", func(writer http.ResponseWriter, request *http.Request) { - respondRepository(writer, request, logger, func() (any, error) { return repository.ListOrgDeliveryPoint(request.Context()) }) - }) - mux.HandleFunc("GET /api/v1/org/service-person", func(writer http.ResponseWriter, request *http.Request) { - respondRepository(writer, request, logger, func() (any, error) { return repository.ListOrgServicePerson(request.Context()) }) - }) - mux.HandleFunc("GET /api/v1/idn/account", func(writer http.ResponseWriter, request *http.Request) { - respondRepository(writer, request, logger, func() (any, error) { return repository.ListIdnAccount(request.Context()) }) - }) - mux.HandleFunc("GET /api/v1/saf/event", func(writer http.ResponseWriter, request *http.Request) { - respondRepository(writer, request, logger, func() (any, error) { return repository.ListSafEvent(request.Context()) }) - }) - - return requestLogger(cors(mux), logger) -} - -func respondRepository(writer http.ResponseWriter, request *http.Request, logger *slog.Logger, query func() (any, error)) { - respondRepositoryWithStatus(writer, request, logger, http.StatusOK, query) -} - -func respondRepositoryWithStatus(writer http.ResponseWriter, request *http.Request, logger *slog.Logger, status int, query func() (any, error)) { - result, err := query() - if err != nil { - logger.Error("处理平台 API 请求失败", "method", request.Method, "path", request.URL.Path, "error", err) - writeError(writer, http.StatusInternalServerError, "服务暂不可用,请稍后重试") - return - } - writeJSON(writer, status, map[string]any{"data": result}) -} - -func writeJSON(writer http.ResponseWriter, status int, data any) { - writer.Header().Set("Content-Type", "application/json; charset=utf-8") - writer.WriteHeader(status) - _ = json.NewEncoder(writer).Encode(data) -} - -func writeError(writer http.ResponseWriter, status int, message string) { - writeJSON(writer, status, map[string]any{"error": message}) -} - -func cors(next http.Handler) http.Handler { - return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173") - writer.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS") - writer.Header().Set("Access-Control-Allow-Headers", "Content-Type,Idempotency-Key") - if request.Method == http.MethodOptions { - writer.WriteHeader(http.StatusNoContent) - return - } - next.ServeHTTP(writer, request) - }) -} - -func requestLogger(next http.Handler, logger *slog.Logger) http.Handler { - return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - requestID, err := uuid.NewV7() - if err == nil { - writer.Header().Set("X-Request-Id", requestID.String()) - } - startedAt := time.Now() - next.ServeHTTP(writer, request) - logger.Info("平台 API 请求完成", "method", request.Method, "path", request.URL.Path, "duration", time.Since(startedAt).String()) - }) -} diff --git a/backend/internal/platform/idn_account.go b/backend/internal/platform/idn_account.go deleted file mode 100644 index 6bb3034..0000000 --- a/backend/internal/platform/idn_account.go +++ /dev/null @@ -1,10 +0,0 @@ -package platform - -// IdnAccount 对应 idn_account,表示平台用户的最小账户视图。 -type IdnAccount struct { - Identity string `json:"identity"` - PhoneMasked string `json:"phoneMasked"` - AccountType string `json:"accountType"` - Status string `json:"status"` - ServiceArea string `json:"serviceArea"` -} diff --git a/backend/internal/platform/org_delivery_point.go b/backend/internal/platform/org_delivery_point.go deleted file mode 100644 index 7cb20e6..0000000 --- a/backend/internal/platform/org_delivery_point.go +++ /dev/null @@ -1,11 +0,0 @@ -package platform - -// OrgDeliveryPoint 对应 org_delivery_point,表示末端配送组织单元。 -type OrgDeliveryPoint struct { - Identity string `json:"identity"` - DeliveryCode string `json:"deliveryCode"` - Name string `json:"name"` - GasStationName string `json:"gasStationName"` - ServiceArea string `json:"serviceArea"` - Status string `json:"status"` -} diff --git a/backend/internal/platform/org_gas_station.go b/backend/internal/platform/org_gas_station.go deleted file mode 100644 index 900bff4..0000000 --- a/backend/internal/platform/org_gas_station.go +++ /dev/null @@ -1,15 +0,0 @@ -// Package platform 包含平台总后台的领域模型和数据访问。 -package platform - -import "time" - -// OrgGasStation 对应 org_gas_station,表示可燃气体站经营主体。 -type OrgGasStation struct { - Identity string `json:"identity"` - StationCode string `json:"stationCode"` - Name string `json:"name"` - Principal string `json:"principal"` - ServiceArea string `json:"serviceArea"` - Status string `json:"status"` - CreatedAt time.Time `json:"createdAt"` -} diff --git a/backend/internal/platform/org_service_person.go b/backend/internal/platform/org_service_person.go deleted file mode 100644 index 032c58f..0000000 --- a/backend/internal/platform/org_service_person.go +++ /dev/null @@ -1,11 +0,0 @@ -package platform - -// OrgServicePerson 对应 org_service_person,表示具备一个或多个服务角色的人员。 -type OrgServicePerson struct { - Identity string `json:"identity"` - Name string `json:"name"` - PhoneMasked string `json:"phoneMasked"` - Roles string `json:"roles"` - WorkStatus string `json:"workStatus"` - Credential string `json:"credentialStatus"` -} diff --git a/backend/internal/platform/repository.go b/backend/internal/platform/repository.go deleted file mode 100644 index a4a1746..0000000 --- a/backend/internal/platform/repository.go +++ /dev/null @@ -1,181 +0,0 @@ -package platform - -import ( - "context" - "fmt" - "time" - - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgxpool" -) - -// Repository 封装平台总后台首期需要的 PostgreSQL 查询。 -type Repository struct { - pool *pgxpool.Pool -} - -// NewRepository 创建平台领域数据访问实例。 -func NewRepository(pool *pgxpool.Pool) *Repository { - return &Repository{pool: pool} -} - -// DashboardOverview 返回平台总后台首页所需的聚合指标。 -func (repository *Repository) DashboardOverview(ctx context.Context) (map[string]int, error) { - queries := map[string]string{ - "gasStationCount": `SELECT count(*) FROM org_gas_station WHERE status = 'enabled'`, - "deliveryPointCount": `SELECT count(*) FROM org_delivery_point WHERE status = 'enabled'`, - "servicePersonCount": `SELECT count(*) FROM org_service_person WHERE work_status = 'on_duty'`, - "userCount": `SELECT count(*) FROM idn_account WHERE account_type = 'user' AND status = 'enabled'`, - "pendingSafetyCount": `SELECT count(*) FROM saf_event WHERE status = 'pending'`, - } - result := make(map[string]int, len(queries)) - for key, query := range queries { - var count int - if err := repository.pool.QueryRow(ctx, query).Scan(&count); err != nil { - return nil, err - } - result[key] = count - } - return result, nil -} - -// ListOrgGasStation 查询可燃气体站列表。 -func (repository *Repository) ListOrgGasStation(ctx context.Context) ([]OrgGasStation, error) { - rows, err := repository.pool.Query(ctx, `SELECT identity, station_code, name, principal, service_area, status, created_at FROM org_gas_station ORDER BY created_at DESC`) - if err != nil { - return nil, err - } - defer rows.Close() - - items := make([]OrgGasStation, 0) - for rows.Next() { - var item OrgGasStation - if err := rows.Scan(&item.Identity, &item.StationCode, &item.Name, &item.Principal, &item.ServiceArea, &item.Status, &item.CreatedAt); err != nil { - return nil, err - } - items = append(items, item) - } - return items, rows.Err() -} - -// CreateOrgGasStation 创建可燃气体站并写入不可变审计记录。 -func (repository *Repository) CreateOrgGasStation(ctx context.Context, stationCode, name, principal, serviceArea string) (OrgGasStation, error) { - identity, err := uuid.NewV7() - if err != nil { - return OrgGasStation{}, err - } - now := time.Now().UTC() - item := OrgGasStation{Identity: identity.String(), StationCode: stationCode, Name: name, Principal: principal, ServiceArea: serviceArea, Status: "draft", CreatedAt: now} - - tx, err := repository.pool.Begin(ctx) - if err != nil { - return OrgGasStation{}, err - } - defer func() { _ = tx.Rollback(ctx) }() - - _, err = tx.Exec(ctx, `INSERT INTO org_gas_station (identity, station_code, name, principal, service_area, status, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$7)`, identity, stationCode, name, principal, serviceArea, item.Status, now) - if err != nil { - return OrgGasStation{}, fmt.Errorf("创建气站失败: %w", err) - } - _, err = tx.Exec(ctx, `INSERT INTO aud_operation_log (identity, action, object_type, object_identity, detail, created_at) VALUES ($1,$2,$3,$4,$5,$6)`, newIdentity(), "create", "org_gas_station", identity, `{"channel":"platform_admin"}`, now) - if err != nil { - return OrgGasStation{}, err - } - if err = tx.Commit(ctx); err != nil { - return OrgGasStation{}, err - } - return item, nil -} - -// ListOrgDeliveryPoint 查询配送点及其气站归属。 -func (repository *Repository) ListOrgDeliveryPoint(ctx context.Context) ([]OrgDeliveryPoint, error) { - rows, err := repository.pool.Query(ctx, `SELECT d.identity, d.delivery_code, d.name, COALESCE(g.name, ''), d.service_area, d.status FROM org_delivery_point d LEFT JOIN org_gas_station g ON d.gas_station_identity = g.identity ORDER BY d.created_at DESC`) - if err != nil { - return nil, err - } - defer rows.Close() - items := make([]OrgDeliveryPoint, 0) - for rows.Next() { - var item OrgDeliveryPoint - if err := rows.Scan(&item.Identity, &item.DeliveryCode, &item.Name, &item.GasStationName, &item.ServiceArea, &item.Status); err != nil { - return nil, err - } - items = append(items, item) - } - return items, rows.Err() -} - -// ListOrgServicePerson 查询服务人员并在 API 层完成手机号脱敏。 -func (repository *Repository) ListOrgServicePerson(ctx context.Context) ([]OrgServicePerson, error) { - rows, err := repository.pool.Query(ctx, `SELECT p.identity, p.name, a.phone, p.roles, p.work_status, p.credential_status FROM org_service_person p JOIN idn_account a ON p.account_identity = a.identity ORDER BY p.created_at DESC`) - if err != nil { - return nil, err - } - defer rows.Close() - items := make([]OrgServicePerson, 0) - for rows.Next() { - var item OrgServicePerson - var phone string - if err := rows.Scan(&item.Identity, &item.Name, &phone, &item.Roles, &item.WorkStatus, &item.Credential); err != nil { - return nil, err - } - item.PhoneMasked = maskPhone(phone) - items = append(items, item) - } - return items, rows.Err() -} - -// ListIdnAccount 查询用户最小必要视图,不返回完整手机号。 -func (repository *Repository) ListIdnAccount(ctx context.Context) ([]IdnAccount, error) { - rows, err := repository.pool.Query(ctx, `SELECT identity, phone, account_type, status, service_area FROM idn_account WHERE account_type = 'user' ORDER BY created_at DESC`) - if err != nil { - return nil, err - } - defer rows.Close() - items := make([]IdnAccount, 0) - for rows.Next() { - var item IdnAccount - var phone string - if err := rows.Scan(&item.Identity, &phone, &item.AccountType, &item.Status, &item.ServiceArea); err != nil { - return nil, err - } - item.PhoneMasked = maskPhone(phone) - items = append(items, item) - } - return items, rows.Err() -} - -// ListSafEvent 查询安全事件列表,供平台安全运营中心使用。 -func (repository *Repository) ListSafEvent(ctx context.Context) ([]SafEvent, error) { - rows, err := repository.pool.Query(ctx, `SELECT identity, event_code, level, title, status, created_at FROM saf_event ORDER BY level ASC, created_at DESC`) - if err != nil { - return nil, err - } - defer rows.Close() - items := make([]SafEvent, 0) - for rows.Next() { - var item SafEvent - if err := rows.Scan(&item.Identity, &item.EventCode, &item.Level, &item.Title, &item.Status, &item.CreatedAt); err != nil { - return nil, err - } - items = append(items, item) - } - return items, rows.Err() -} - -// maskPhone 按最小必要原则返回脱敏手机号。 -func maskPhone(phone string) string { - if len(phone) < 7 { - return "***" - } - return phone[:3] + "****" + phone[len(phone)-4:] -} - -// newIdentity 生成审计记录使用的 UUID V7。 -func newIdentity() uuid.UUID { - identity, err := uuid.NewV7() - if err != nil { - panic(err) - } - return identity -} diff --git a/backend/internal/platform/saf_event.go b/backend/internal/platform/saf_event.go deleted file mode 100644 index 7afbd01..0000000 --- a/backend/internal/platform/saf_event.go +++ /dev/null @@ -1,13 +0,0 @@ -package platform - -import "time" - -// SafEvent 对应 saf_event,表示需要跟踪处置的安全事件。 -type SafEvent struct { - Identity string `json:"identity"` - EventCode string `json:"eventCode"` - Level int `json:"level"` - Title string `json:"title"` - Status string `json:"status"` - CreatedAt time.Time `json:"createdAt"` -} diff --git a/backend/iot/README.md b/backend/iot/README.md new file mode 100644 index 0000000..aac1e03 --- /dev/null +++ b/backend/iot/README.md @@ -0,0 +1,3 @@ +# Platform IoT + +独立 IoT 进程预留 MQTT TLS 会话、协议适配、遥测校验与命令回执边界。首期使用 Mock 适配,不连接真实设备或 MQTT Broker。 diff --git a/backend/iot/cmd/main/main.go b/backend/iot/cmd/main/main.go new file mode 100644 index 0000000..b3e9df4 --- /dev/null +++ b/backend/iot/cmd/main/main.go @@ -0,0 +1,21 @@ +// IoT 进程入口;真实 MQTT TLS 适配将在设备协议和证书契约确认后接入。 +package main + +import ( + "os" + "os/signal" + "syscall" + + "git.apinb.com/bsm-sdk/core/printer" + "git.apinb.com/heqiapp/platforms/backend/iot/internal/config" + "git.apinb.com/heqiapp/platforms/backend/iot/internal/impl" +) + +func main() { + config.New("PlatformIot") + impl.NewImpl() + printer.Info("[BSM - PlatformIot] Mock IoT adapter started; MQTT broker is not connected") + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit +} diff --git a/backend/iot/etc/platform_iot_dev.yaml b/backend/iot/etc/platform_iot_dev.yaml new file mode 100644 index 0000000..fc586f7 --- /dev/null +++ b/backend/iot/etc/platform_iot_dev.yaml @@ -0,0 +1,9 @@ +Service: platform-iot +Port: 12428 +Databases: + Driver: postgres + Source: + - host=127.0.0.1 user=postgres password=change-me dbname=agent_dev port=5432 sslmode=disable TimeZone=Asia/Shanghai +Cache: redis://default:change-me@127.0.0.1:6379/0 +OnMicroService: false +SecretKey: change-me-to-a-random-string diff --git a/backend/iot/go.mod b/backend/iot/go.mod new file mode 100644 index 0000000..3987a48 --- /dev/null +++ b/backend/iot/go.mod @@ -0,0 +1,48 @@ +module git.apinb.com/heqiapp/platforms/backend/iot + +go 1.26.1 + +require ( + git.apinb.com/bsm-sdk/core v0.2.0 + github.com/patrickmn/go-cache v2.1.0+incompatible + gorm.io/gorm v1.31.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/redis/go-redis/v9 v9.19.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + go.etcd.io/etcd/api/v3 v3.6.11 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.11 // indirect + go.etcd.io/etcd/client/v3 v3.6.11 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.36.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/grpc v1.81.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.0 // indirect +) diff --git a/backend/iot/go.sum b/backend/iot/go.sum new file mode 100644 index 0000000..3417f6d --- /dev/null +++ b/backend/iot/go.sum @@ -0,0 +1,159 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.apinb.com/bsm-sdk/core v0.2.0 h1:/e9yqpsbKBrRgMiGpS3KX2O4qDLxo5V5GpPSLNGxEKw= +git.apinb.com/bsm-sdk/core v0.2.0/go.mod h1:E9T6Eboo/0Zb36BjkKbIgvFzq4fQ2Q8P/7y5zmYTI6Y= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k= +github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +go.etcd.io/etcd/api/v3 v3.6.11 h1:XFGTgrJ8nak3kB4NgMG8t7NT+lEeuuvKQAqUHKVgkWQ= +go.etcd.io/etcd/api/v3 v3.6.11/go.mod h1:HYfTh0jyh+uFgp6gMbxJteIDYY97yMuYz85Rnw6Gy9o= +go.etcd.io/etcd/client/pkg/v3 v3.6.11 h1:e41mp315Yn3QMGPmEzCyLsMINgJXTY/dX8kM++1csxU= +go.etcd.io/etcd/client/pkg/v3 v3.6.11/go.mod h1:DysuMe/inqRyC/1tjRR6hReH/VV9Lufs27YKSKBWWJg= +go.etcd.io/etcd/client/v3 v3.6.11 h1:LAByD96VmmeuairkvdAcE0RZnrmGz/q3ceeWePo9bwc= +go.etcd.io/etcd/client/v3 v3.6.11/go.mod h1:vOTDMCo+fGPEClJqcFEFSqZ+8e7WKV7AyqJjX//HR2w= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/backend/iot/internal/config/config.go b/backend/iot/internal/config/config.go new file mode 100644 index 0000000..e3f522f --- /dev/null +++ b/backend/iot/internal/config/config.go @@ -0,0 +1,29 @@ +// Package config 沿用 sample/server 的 BSM 运行配置与地址校验。 +package config + +import ( + "net" + + "git.apinb.com/bsm-sdk/core/conf" +) + +// Spec 是 IoT 进程运行配置。 +var Spec SrvConfig + +// SrvConfig 保持与 API 进程相同的 BSM 配置结构。 +type SrvConfig struct { + conf.Base `yaml:",inline"` + Databases *conf.DBConf `yaml:"Databases"` + Rpc map[string]conf.RpcConf `yaml:"Rpc"` + Apm *conf.ApmConf `yaml:"APM"` +} + +// New 初始化 IoT 进程配置。 +func New(srvKey string) { + conf.New(srvKey, &Spec) + Spec.Port = conf.CheckPort(Spec.Port) + Spec.BindIP = conf.CheckIP(Spec.BindIP) + Spec.Addr = net.JoinHostPort(Spec.BindIP, Spec.Port) + conf.NotNil(Spec.Service, Spec.Cache) + conf.PrintInfo(Spec.Addr) +} diff --git a/backend/iot/internal/impl/new.go b/backend/iot/internal/impl/new.go new file mode 100644 index 0000000..10470ca --- /dev/null +++ b/backend/iot/internal/impl/new.go @@ -0,0 +1,25 @@ +// Package impl 沿用 sample/server 创建 IoT 进程所需的基础设施实例。 +package impl + +import ( + "git.apinb.com/bsm-sdk/core/cache/redis" + "git.apinb.com/bsm-sdk/core/logger" + "git.apinb.com/bsm-sdk/core/with" + "git.apinb.com/heqiapp/platforms/backend/iot/internal/config" + "github.com/patrickmn/go-cache" + "gorm.io/gorm" +) + +var ( + RedisService *redis.RedisClient + DBService *gorm.DB + MemoryService *cache.Cache +) + +// NewImpl 初始化 MQTT Mock 进程共用的数据库、缓存、Redis 与日志。 +func NewImpl() { + MemoryService = with.Memory(nil) + RedisService = with.RedisCache(config.Spec.Cache) + DBService = with.Databases(config.Spec.Databases, nil) + logger.New(nil) +} diff --git a/backend/migrations/0001_create_org_gas_station.sql b/backend/migrations/0001_create_org_gas_station.sql new file mode 100644 index 0000000..cf783b7 --- /dev/null +++ b/backend/migrations/0001_create_org_gas_station.sql @@ -0,0 +1,27 @@ +-- org_gas_station:可燃气体站主表。identity 由应用生成 UUID V7,禁止自增主键。 +CREATE TABLE IF NOT EXISTS org_gas_station ( + identity uuid PRIMARY KEY, + station_code varchar(32) NOT NULL UNIQUE, + name varchar(128) NOT NULL, + principal varchar(64) NOT NULL, + service_area varchar(128) NOT NULL, + created_at timestamptz NOT NULL, + updated_at timestamptz NOT NULL, + created_by_identity uuid, + updated_by_identity uuid, + status varchar(32) NOT NULL DEFAULT 'draft', + version integer NOT NULL DEFAULT 1 +); + +COMMENT ON TABLE org_gas_station IS '可燃气体站主表'; +COMMENT ON COLUMN org_gas_station.identity IS '主键,应用生成的 UUID V7'; +COMMENT ON COLUMN org_gas_station.station_code IS '气站全局唯一业务编码'; +COMMENT ON COLUMN org_gas_station.name IS '气站名称'; +COMMENT ON COLUMN org_gas_station.principal IS '气站负责人姓名'; +COMMENT ON COLUMN org_gas_station.service_area IS '气站授权服务区域'; +COMMENT ON COLUMN org_gas_station.created_at IS '记录创建时间,UTC'; +COMMENT ON COLUMN org_gas_station.updated_at IS '记录更新时间,UTC'; +COMMENT ON COLUMN org_gas_station.created_by_identity IS '创建人 identity'; +COMMENT ON COLUMN org_gas_station.updated_by_identity IS '更新人 identity'; +COMMENT ON COLUMN org_gas_station.status IS '状态:draft、enabled、frozen、archived'; +COMMENT ON COLUMN org_gas_station.version IS '乐观锁版本'; diff --git a/backend/migrations/0002_create_saf_event.sql b/backend/migrations/0002_create_saf_event.sql new file mode 100644 index 0000000..d658cbe --- /dev/null +++ b/backend/migrations/0002_create_saf_event.sql @@ -0,0 +1,28 @@ +-- saf_event:安全事件主表。事件和审计记录不允许物理删除。 +CREATE TABLE IF NOT EXISTS saf_event ( + identity uuid PRIMARY KEY, + event_code varchar(32) NOT NULL UNIQUE, + device_identity uuid, + level integer NOT NULL CHECK (level BETWEEN 1 AND 3), + title varchar(256) NOT NULL, + created_at timestamptz NOT NULL, + updated_at timestamptz NOT NULL, + created_by_identity uuid, + updated_by_identity uuid, + status varchar(32) NOT NULL DEFAULT 'pending', + version integer NOT NULL DEFAULT 1 +); + +CREATE INDEX IF NOT EXISTS idx_saf_event_status_level ON saf_event(status, level); +COMMENT ON TABLE saf_event IS '安全事件主表'; +COMMENT ON COLUMN saf_event.identity IS '主键,应用生成的 UUID V7'; +COMMENT ON COLUMN saf_event.event_code IS '安全事件全局唯一业务编码'; +COMMENT ON COLUMN saf_event.device_identity IS '关联 dev_device 的 identity'; +COMMENT ON COLUMN saf_event.level IS '风险等级:1 高风险、2 中风险、3 低风险'; +COMMENT ON COLUMN saf_event.title IS '安全事件说明'; +COMMENT ON COLUMN saf_event.created_at IS '记录创建时间,UTC'; +COMMENT ON COLUMN saf_event.updated_at IS '记录更新时间,UTC'; +COMMENT ON COLUMN saf_event.created_by_identity IS '创建人 identity'; +COMMENT ON COLUMN saf_event.updated_by_identity IS '更新人 identity'; +COMMENT ON COLUMN saf_event.status IS '状态:pending、processing、closed、overdue'; +COMMENT ON COLUMN saf_event.version IS '乐观锁版本'; diff --git a/backend/migrations/0003_create_org_delivery_point_org_service_person_idn_account.sql b/backend/migrations/0003_create_org_delivery_point_org_service_person_idn_account.sql new file mode 100644 index 0000000..8ebd425 --- /dev/null +++ b/backend/migrations/0003_create_org_delivery_point_org_service_person_idn_account.sql @@ -0,0 +1,39 @@ +-- 首期平台组织与身份主表。所有 identity 均为应用生成的 UUID V7。 +CREATE TABLE IF NOT EXISTS idn_account ( + identity uuid PRIMARY KEY, phone varchar(32) NOT NULL UNIQUE, account_type varchar(32) NOT NULL, + service_area varchar(128) NOT NULL, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, + created_by_identity uuid, updated_by_identity uuid, status varchar(32) NOT NULL DEFAULT 'enabled', version integer NOT NULL DEFAULT 1 +); +CREATE TABLE IF NOT EXISTS org_delivery_point ( + identity uuid PRIMARY KEY, delivery_code varchar(32) NOT NULL UNIQUE, gas_station_identity uuid, + name varchar(128) NOT NULL, service_area varchar(128) NOT NULL, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, + created_by_identity uuid, updated_by_identity uuid, status varchar(32) NOT NULL DEFAULT 'draft', version integer NOT NULL DEFAULT 1 +); +CREATE TABLE IF NOT EXISTS org_service_person ( + identity uuid PRIMARY KEY, account_identity uuid UNIQUE, gas_station_identity uuid, delivery_point_identity uuid, + name varchar(64) NOT NULL, roles varchar(128) NOT NULL, work_status varchar(32) NOT NULL, credential_status varchar(32) NOT NULL, + created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, created_by_identity uuid, updated_by_identity uuid, + status varchar(32) NOT NULL DEFAULT 'draft', version integer NOT NULL DEFAULT 1 +); +COMMENT ON TABLE idn_account IS '身份账户主表'; +COMMENT ON COLUMN idn_account.identity IS '主键,应用生成的 UUID V7'; +COMMENT ON COLUMN idn_account.phone IS '手机号,敏感字段,展示时脱敏'; +COMMENT ON COLUMN idn_account.account_type IS '账户类型:user、service_person、admin'; +COMMENT ON COLUMN idn_account.service_area IS '授权服务区域'; +COMMENT ON COLUMN idn_account.status IS '状态:enabled、frozen、archived'; +COMMENT ON TABLE org_delivery_point IS '配送点主表'; +COMMENT ON COLUMN org_delivery_point.identity IS '主键,应用生成的 UUID V7'; +COMMENT ON COLUMN org_delivery_point.delivery_code IS '配送点全局唯一业务编码'; +COMMENT ON COLUMN org_delivery_point.gas_station_identity IS '归属 org_gas_station 的 identity'; +COMMENT ON COLUMN org_delivery_point.name IS '配送点名称'; +COMMENT ON COLUMN org_delivery_point.service_area IS '配送服务区域'; +COMMENT ON COLUMN org_delivery_point.status IS '状态:draft、enabled、frozen、archived'; +COMMENT ON TABLE org_service_person IS '服务人员主表'; +COMMENT ON COLUMN org_service_person.identity IS '主键,应用生成的 UUID V7'; +COMMENT ON COLUMN org_service_person.account_identity IS '关联 idn_account 的 identity'; +COMMENT ON COLUMN org_service_person.gas_station_identity IS '归属 org_gas_station 的 identity'; +COMMENT ON COLUMN org_service_person.delivery_point_identity IS '主归属 org_delivery_point 的 identity'; +COMMENT ON COLUMN org_service_person.roles IS '可执行角色集合'; +COMMENT ON COLUMN org_service_person.work_status IS '上班与接单状态'; +COMMENT ON COLUMN org_service_person.credential_status IS '资质状态'; +COMMENT ON COLUMN org_service_person.status IS '状态:draft、enabled、frozen、archived'; diff --git a/backend/migrations/README.md b/backend/migrations/README.md new file mode 100644 index 0000000..7476002 --- /dev/null +++ b/backend/migrations/README.md @@ -0,0 +1,5 @@ +# PostgreSQL 迁移 + +迁移文件名遵循 `<序号>_create_<模块前缀_单数实体>.sql`。每个主表必须以应用生成的 UUID V7 `identity` 为主键,并对表、字段、索引、约束和枚举补充中文注释。 + +当前 API 的 GORM 自动迁移用于本地开发;生产发布必须先评审并执行本目录的显式 SQL 迁移,再部署新版本服务。 diff --git a/backend/worker/README.md b/backend/worker/README.md new file mode 100644 index 0000000..4c6d143 --- /dev/null +++ b/backend/worker/README.md @@ -0,0 +1,3 @@ +# Platform Worker + +独立 Worker 进程沿用 `sample/server` 的 BSM 配置与基础设施创建方式。首期只提供 Mock 事件循环,真实 Redis Streams 消费将在 Outbox、死信、幂等消费与积压监控契约确定后接入。 diff --git a/backend/worker/cmd/main/main.go b/backend/worker/cmd/main/main.go new file mode 100644 index 0000000..5cc64aa --- /dev/null +++ b/backend/worker/cmd/main/main.go @@ -0,0 +1,21 @@ +// Worker 进程入口;保留独立扩缩和 Redis Streams 消费边界。 +package main + +import ( + "os" + "os/signal" + "syscall" + + "git.apinb.com/bsm-sdk/core/printer" + "git.apinb.com/heqiapp/platforms/backend/worker/internal/config" + "git.apinb.com/heqiapp/platforms/backend/worker/internal/impl" +) + +func main() { + config.New("PlatformWorker") + impl.NewImpl() + printer.Info("[BSM - PlatformWorker] Mock worker started; Redis Streams consumer is not enabled") + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit +} diff --git a/backend/worker/etc/platform_worker_dev.yaml b/backend/worker/etc/platform_worker_dev.yaml new file mode 100644 index 0000000..0524b7a --- /dev/null +++ b/backend/worker/etc/platform_worker_dev.yaml @@ -0,0 +1,9 @@ +Service: platform-worker +Port: 12427 +Databases: + Driver: postgres + Source: + - host=127.0.0.1 user=postgres password=change-me dbname=agent_dev port=5432 sslmode=disable TimeZone=Asia/Shanghai +Cache: redis://default:change-me@127.0.0.1:6379/0 +OnMicroService: false +SecretKey: change-me-to-a-random-string diff --git a/backend/worker/go.mod b/backend/worker/go.mod new file mode 100644 index 0000000..4db8cd9 --- /dev/null +++ b/backend/worker/go.mod @@ -0,0 +1,48 @@ +module git.apinb.com/heqiapp/platforms/backend/worker + +go 1.26.1 + +require ( + git.apinb.com/bsm-sdk/core v0.2.0 + github.com/patrickmn/go-cache v2.1.0+incompatible + gorm.io/gorm v1.31.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/redis/go-redis/v9 v9.19.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + go.etcd.io/etcd/api/v3 v3.6.11 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.11 // indirect + go.etcd.io/etcd/client/v3 v3.6.11 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.36.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/grpc v1.81.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.0 // indirect +) diff --git a/backend/worker/go.sum b/backend/worker/go.sum new file mode 100644 index 0000000..3417f6d --- /dev/null +++ b/backend/worker/go.sum @@ -0,0 +1,159 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.apinb.com/bsm-sdk/core v0.2.0 h1:/e9yqpsbKBrRgMiGpS3KX2O4qDLxo5V5GpPSLNGxEKw= +git.apinb.com/bsm-sdk/core v0.2.0/go.mod h1:E9T6Eboo/0Zb36BjkKbIgvFzq4fQ2Q8P/7y5zmYTI6Y= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k= +github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +go.etcd.io/etcd/api/v3 v3.6.11 h1:XFGTgrJ8nak3kB4NgMG8t7NT+lEeuuvKQAqUHKVgkWQ= +go.etcd.io/etcd/api/v3 v3.6.11/go.mod h1:HYfTh0jyh+uFgp6gMbxJteIDYY97yMuYz85Rnw6Gy9o= +go.etcd.io/etcd/client/pkg/v3 v3.6.11 h1:e41mp315Yn3QMGPmEzCyLsMINgJXTY/dX8kM++1csxU= +go.etcd.io/etcd/client/pkg/v3 v3.6.11/go.mod h1:DysuMe/inqRyC/1tjRR6hReH/VV9Lufs27YKSKBWWJg= +go.etcd.io/etcd/client/v3 v3.6.11 h1:LAByD96VmmeuairkvdAcE0RZnrmGz/q3ceeWePo9bwc= +go.etcd.io/etcd/client/v3 v3.6.11/go.mod h1:vOTDMCo+fGPEClJqcFEFSqZ+8e7WKV7AyqJjX//HR2w= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/backend/worker/internal/config/config.go b/backend/worker/internal/config/config.go new file mode 100644 index 0000000..4123026 --- /dev/null +++ b/backend/worker/internal/config/config.go @@ -0,0 +1,29 @@ +// Package config 沿用 sample/server 的 BSM 运行配置与地址校验。 +package config + +import ( + "net" + + "git.apinb.com/bsm-sdk/core/conf" +) + +// Spec 是 Worker 运行配置。 +var Spec SrvConfig + +// SrvConfig 保持与 API 进程相同的 BSM 配置结构。 +type SrvConfig struct { + conf.Base `yaml:",inline"` + Databases *conf.DBConf `yaml:"Databases"` + Rpc map[string]conf.RpcConf `yaml:"Rpc"` + Apm *conf.ApmConf `yaml:"APM"` +} + +// New 初始化 Worker 配置。 +func New(srvKey string) { + conf.New(srvKey, &Spec) + Spec.Port = conf.CheckPort(Spec.Port) + Spec.BindIP = conf.CheckIP(Spec.BindIP) + Spec.Addr = net.JoinHostPort(Spec.BindIP, Spec.Port) + conf.NotNil(Spec.Service, Spec.Cache) + conf.PrintInfo(Spec.Addr) +} diff --git a/backend/worker/internal/impl/new.go b/backend/worker/internal/impl/new.go new file mode 100644 index 0000000..f4e75fd --- /dev/null +++ b/backend/worker/internal/impl/new.go @@ -0,0 +1,25 @@ +// Package impl 沿用 sample/server 创建 Worker 需要的基础设施实例。 +package impl + +import ( + "git.apinb.com/bsm-sdk/core/cache/redis" + "git.apinb.com/bsm-sdk/core/logger" + "git.apinb.com/bsm-sdk/core/with" + "git.apinb.com/heqiapp/platforms/backend/worker/internal/config" + "github.com/patrickmn/go-cache" + "gorm.io/gorm" +) + +var ( + RedisService *redis.RedisClient + DBService *gorm.DB + MemoryService *cache.Cache +) + +// NewImpl 初始化 Outbox 和 Mock 事件消费共用的数据库、缓存、Redis 与日志。 +func NewImpl() { + MemoryService = with.Memory(nil) + RedisService = with.RedisCache(config.Spec.Cache) + DBService = with.Databases(config.Spec.Databases, nil) + logger.New(nil) +} diff --git a/frontend/platform_admin/.env.example b/frontend/platform_admin/.env.example index 0c0e1f5..e77e566 100644 --- a/frontend/platform_admin/.env.example +++ b/frontend/platform_admin/.env.example @@ -1 +1,3 @@ -VITE_API_BASE_URL=http://localhost:8080/api/v1 +VITE_API_BASE_URL=http://localhost:12426/Platform/v1 +# 受保护 API 使用 BSM JWT;本地联调时由认证服务签发,不要提交真实令牌。 +VITE_DEV_JWT= diff --git a/frontend/platform_admin/src/api/platform.ts b/frontend/platform_admin/src/api/platform.ts index 50f0e20..49b9955 100644 --- a/frontend/platform_admin/src/api/platform.ts +++ b/frontend/platform_admin/src/api/platform.ts @@ -1,5 +1,6 @@ // 平台总后台 API 客户端,仅封装首期 M0 + M1 接口。 -const apiBaseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api/v1'; +const apiBaseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:12426/Platform/v1'; +const developmentJWT = import.meta.env.VITE_DEV_JWT?.trim(); export type OrgGasStation = { identity: string; @@ -48,23 +49,39 @@ export type SafEvent = { export type DashboardOverview = Record; +type ListReply = { total: number; list: T[] }; +type RawOrgDeliveryPoint = { identity: string; delivery_code: string; name: string; gas_station_name?: string; service_area: string; status: string }; +type RawOrgServicePerson = { identity: string; name: string; phone_masked?: string; roles: string; work_status: string; credential_status: string }; +type RawIdnAccount = { identity: string; phone_masked: string; account_type: string; status: string; service_area: string }; +type RawSafEvent = { identity: string; event_code: string; level: number; title: string; status: string; created_at: string }; + async function request(path: string, init?: RequestInit): Promise { const response = await fetch(`${apiBaseURL}${path}`, { - headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) }, + headers: { + 'Content-Type': 'application/json', + ...(developmentJWT ? { Authorization: `Bearer ${developmentJWT}` } : {}), + ...(init?.headers ?? {}), + }, ...init, }); - const payload = (await response.json()) as { data?: T; error?: string }; - if (!response.ok || payload.error) throw new Error(payload.error || '请求失败'); - return payload.data as T; + const payload = (await response.json()) as { code?: number; message?: string; details?: T }; + if (!response.ok || payload.code !== 0) throw new Error(payload.message || '请求失败'); + return payload.details as T; } export const platformAPI = { - getDashboard: () => request('/dashboard/overview'), - listOrgGasStation: () => request('/org/gas-station'), + getDashboard: async () => { + const item = await request<{ gas_station_count: number; delivery_point_count: number; service_person_count: number; user_count: number; pending_safety_count: number }>('/dashboard/overview'); + return { gasStationCount: item.gas_station_count, deliveryPointCount: item.delivery_point_count, servicePersonCount: item.service_person_count, userCount: item.user_count, pendingSafetyCount: item.pending_safety_count }; + }, + listOrgGasStation: async () => { + const reply = await request>('/organization/org_gas_station'); + return reply.list.map((item) => ({ identity: item.identity, stationCode: item.station_code, name: item.name, principal: item.principal, serviceArea: item.service_area, status: item.status, createdAt: item.created_at })); + }, createOrgGasStation: (body: Pick) => - request('/org/gas-station', { method: 'POST', body: JSON.stringify(body) }), - listOrgDeliveryPoint: () => request('/org/delivery-point'), - listOrgServicePerson: () => request('/org/service-person'), - listIdnAccount: () => request('/idn/account'), - listSafEvent: () => request('/saf/event'), + request('/organization/org_gas_station', { method: 'POST', body: JSON.stringify({ station_code: body.stationCode, name: body.name, principal: body.principal, service_area: body.serviceArea }) }), + listOrgDeliveryPoint: async () => (await request>('/organization/org_delivery_point')).list.map((item) => ({ identity: item.identity, deliveryCode: item.delivery_code, name: item.name, gasStationName: item.gas_station_name ?? '未关联', serviceArea: item.service_area, status: item.status })), + listOrgServicePerson: async () => (await request>('/organization/org_service_person')).list.map((item) => ({ identity: item.identity, name: item.name, phoneMasked: item.phone_masked ?? '***', roles: item.roles, workStatus: item.work_status, credentialStatus: item.credential_status })), + listIdnAccount: async () => (await request>('/identity/idn_account')).list.map((item) => ({ identity: item.identity, phoneMasked: item.phone_masked, accountType: item.account_type, status: item.status, serviceArea: item.service_area })), + listSafEvent: async () => (await request>('/safety/saf_event')).list.map((item) => ({ identity: item.identity, eventCode: item.event_code, level: item.level, title: item.title, status: item.status, createdAt: item.created_at })), };