feat: 初始化平台总后台与核心API
This commit is contained in:
17
backend/internal/database/database.go
Normal file
17
backend/internal/database/database.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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)
|
||||
}
|
||||
80
backend/internal/database/migration.go
Normal file
80
backend/internal/database/migration.go
Normal file
@@ -0,0 +1,80 @@
|
||||
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)
|
||||
}
|
||||
66
backend/internal/database/seed.go
Normal file
66
backend/internal/database/seed.go
Normal file
@@ -0,0 +1,66 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user