feat: 初始化平台总后台与核心API
This commit is contained in:
10
backend/internal/platform/idn_account.go
Normal file
10
backend/internal/platform/idn_account.go
Normal file
@@ -0,0 +1,10 @@
|
||||
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"`
|
||||
}
|
||||
11
backend/internal/platform/org_delivery_point.go
Normal file
11
backend/internal/platform/org_delivery_point.go
Normal file
@@ -0,0 +1,11 @@
|
||||
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"`
|
||||
}
|
||||
15
backend/internal/platform/org_gas_station.go
Normal file
15
backend/internal/platform/org_gas_station.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// 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"`
|
||||
}
|
||||
11
backend/internal/platform/org_service_person.go
Normal file
11
backend/internal/platform/org_service_person.go
Normal file
@@ -0,0 +1,11 @@
|
||||
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"`
|
||||
}
|
||||
181
backend/internal/platform/repository.go
Normal file
181
backend/internal/platform/repository.go
Normal file
@@ -0,0 +1,181 @@
|
||||
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
|
||||
}
|
||||
13
backend/internal/platform/saf_event.go
Normal file
13
backend/internal/platform/saf_event.go
Normal file
@@ -0,0 +1,13 @@
|
||||
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"`
|
||||
}
|
||||
Reference in New Issue
Block a user