feat: 完善平台总后台模块
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
.PHONY: build run cli migrate lint tidy
|
||||
.PHONY: build run cli lint tidy
|
||||
|
||||
build:
|
||||
go build -o build/platform-api ./cmd/main/main.go
|
||||
@@ -10,9 +10,6 @@ run:
|
||||
cli:
|
||||
go run ./cmd/cli/main.go $(ARGS)
|
||||
|
||||
migrate:
|
||||
go run ./cmd/cli/main.go migrate
|
||||
|
||||
lint:
|
||||
go vet ./...
|
||||
go fmt ./...
|
||||
|
||||
@@ -12,8 +12,8 @@ $env:HEQI_PLATFORM_ROOT_PASSWORD="请设置不少于12位的root初始密码"
|
||||
go run ./cmd/main/main.go
|
||||
```
|
||||
|
||||
应用启动和 `go run ./cmd/cli/main.go migrate` 都会在事务内幂等创建平台 `root` 账号。账号名固定为 `root`;优先使用 `HEQI_PLATFORM_ROOT_PASSWORD`,未设置时仅使用开发环境默认值。root 首次登录后必须通过 `PUT /heqi/v1/auth/password` 修改密码。
|
||||
应用启动时会在事务内幂等创建平台 `root` 账号。账号名固定为 `root`;优先使用 `HEQI_PLATFORM_ROOT_PASSWORD`,未设置时仅使用开发环境默认值。已登录账户可通过 `PUT /heqi/v1/auth/password` 修改密码。
|
||||
|
||||
匿名接口为 `POST /heqi/v1/auth/login`;其余平台接口经 `middleware.JwtAuth(true)` 保护。请求头 `Authorization` 直接传递 JWT 原始值,不使用 `Bearer` 前缀。
|
||||
|
||||
UUID V7 主键、模型中文注释和 PostgreSQL 变更记录以 `../migrations` 为准。
|
||||
UUID V7 主键、模型中文注释与表结构以 `internal/models` 为准;应用启动时由 GORM 自动同步模型结构。
|
||||
|
||||
@@ -1,35 +1,19 @@
|
||||
// 平台 API 的数据库迁移与版本命令行工具。
|
||||
// 平台 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/initdb"
|
||||
_ "git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
)
|
||||
|
||||
const serviceKey = "heqi"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("usage: platform-cli <version|migrate>")
|
||||
fmt.Println("usage: platform-cli <version>")
|
||||
return
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "version":
|
||||
fmt.Println("platform-cli 0.1.0")
|
||||
case "migrate":
|
||||
config.New(serviceKey)
|
||||
impl.NewImpl()
|
||||
if err := initdb.New(impl.DBService); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("platform database auto migrate completed")
|
||||
default:
|
||||
if os.Args[1] != "version" {
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("platform-cli 0.1.0")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import "gorm.io/gorm"
|
||||
// New 在同一事务中初始化平台基础数据。
|
||||
func New(database *gorm.DB) error {
|
||||
return database.Transaction(func(tx *gorm.DB) error {
|
||||
if err := InitPlatformAccess(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
return InitPlatformRoot(tx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,12 +15,50 @@ const (
|
||||
// PlatformRootPassword 是仅用于首次启动的初始密码,首次登录后必须修改。
|
||||
PlatformRootPassword = "Heqi@Root2026"
|
||||
// PlatformRootRoleCode 表示根账号的平台角色。
|
||||
PlatformRootRoleCode = "platform_root"
|
||||
PlatformRootRoleCode = "root"
|
||||
)
|
||||
|
||||
// InitPlatformAccess 幂等初始化 root 角色、菜单和 root 的全菜单授权。
|
||||
func InitPlatformAccess(database *gorm.DB) error {
|
||||
rootRole := models.PlatformRole{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1},
|
||||
RoleCode: PlatformRootRoleCode,
|
||||
Name: "系统管理员",
|
||||
DataScope: "global",
|
||||
IsSystem: true,
|
||||
}
|
||||
if err := database.Where("role_code = ?", rootRole.RoleCode).FirstOrCreate(&rootRole).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
menus := []models.PlatformMenu{
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "dashboard", Name: "工作台", Icon: "icon-dashboard", Path: "/dashboard", SortNo: 10},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "gas", Name: "可燃气体站管理", Icon: "icon-fire", Path: "/gas/basic", SortNo: 20},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "delivery", Name: "配送管理", Icon: "icon-car", Path: "/delivery/basic", SortNo: 30},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "staff", Name: "服务人员", Icon: "icon-user", Path: "/staff/list", SortNo: 40},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "user", Name: "业主客户", Icon: "icon-user-group", Path: "/user/list", SortNo: 50},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "ec", Name: "电商管理", Icon: "icon-shopping", Path: "/ec/product", SortNo: 60},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "finance", Name: "财务管理", Icon: "icon-safe", Path: "/finance/payment", SortNo: 70},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "wallet", Name: "钱包中心", Icon: "icon-wallet", Path: "/wallet/list", SortNo: 80},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "report", Name: "统计报表", Icon: "icon-bar-chart", Path: "/report/list", SortNo: 90},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "platform", Name: "平台配置", Icon: "icon-settings", Path: "/platform/account", SortNo: 100},
|
||||
}
|
||||
for index := range menus {
|
||||
menu := menus[index]
|
||||
if err := database.Where("menu_code = ?", menu.MenuCode).FirstOrCreate(&menu).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
relation := models.PlatformRoleMenuRelation{PlatformRoleID: rootRole.ID, PlatformMenuID: menu.ID}
|
||||
if err := database.Where("platform_role_id = ? AND platform_menu_id = ?", rootRole.ID, menu.ID).FirstOrCreate(&relation).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitPlatformRoot 幂等创建平台总后台 root 账号。
|
||||
func InitPlatformRoot(database *gorm.DB) error {
|
||||
var account models.IdnAccount
|
||||
var account models.PlatfromAccount
|
||||
err := database.Where("username = ?", PlatformRootUsername).First(&account).Error
|
||||
if err == nil {
|
||||
return nil
|
||||
@@ -34,16 +72,13 @@ func InitPlatformRoot(database *gorm.DB) error {
|
||||
return err
|
||||
}
|
||||
|
||||
account = models.IdnAccount{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled"},
|
||||
Username: PlatformRootUsername,
|
||||
DisplayName: "平台根管理员",
|
||||
PasswordHash: string(passwordHash),
|
||||
RoleCode: PlatformRootRoleCode,
|
||||
MustChangePassword: true,
|
||||
Phone: "",
|
||||
AccountType: "operator",
|
||||
ServiceArea: "全国",
|
||||
account = models.PlatfromAccount{
|
||||
Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled"},
|
||||
Username: PlatformRootUsername,
|
||||
DisplayName: "平台根管理员",
|
||||
PasswordHash: string(passwordHash),
|
||||
PlatformRoleCode: PlatformRootRoleCode,
|
||||
Phone: "",
|
||||
}
|
||||
return database.Create(&account).Error
|
||||
}
|
||||
|
||||
@@ -23,12 +23,11 @@ type LoginRequest struct {
|
||||
|
||||
// LoginReply 是后台登录成功后的访问凭证与账号状态。
|
||||
type LoginReply struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
Identity string `json:"identity"`
|
||||
DisplayName string `json:"display_name"`
|
||||
RoleCode string `json:"role_code"`
|
||||
MustChangePassword bool `json:"must_change_password"`
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
Identity string `json:"identity"`
|
||||
DisplayName string `json:"display_name"`
|
||||
RoleCode string `json:"role_code"`
|
||||
}
|
||||
|
||||
// Login 校验平台账号密码并签发 BSM JWT。
|
||||
@@ -39,7 +38,7 @@ func Login(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var account models.IdnAccount
|
||||
var account models.PlatfromAccount
|
||||
err := impl.DBService.Where("username = ?", strings.TrimSpace(request.Username)).First(&account).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
@@ -60,23 +59,22 @@ func Login(ctx *gin.Context) {
|
||||
|
||||
accessToken, err := token.New(env.Runtime.JwtSecretKey).GenerateJwt(
|
||||
0,
|
||||
account.Identity.String(),
|
||||
account.Identity,
|
||||
"platform_admin",
|
||||
account.RoleCode,
|
||||
account.PlatformRoleCode,
|
||||
map[string]string{"username": account.Username, "display_name": account.DisplayName},
|
||||
map[string]string{"must_change_password": boolText(account.MustChangePassword)},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, LoginReply{
|
||||
AccessToken: accessToken,
|
||||
TokenType: "JWT",
|
||||
Identity: account.Identity.String(),
|
||||
DisplayName: account.DisplayName,
|
||||
RoleCode: account.RoleCode,
|
||||
MustChangePassword: account.MustChangePassword,
|
||||
AccessToken: accessToken,
|
||||
TokenType: "JWT",
|
||||
Identity: account.Identity,
|
||||
DisplayName: account.DisplayName,
|
||||
RoleCode: account.PlatformRoleCode,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -87,14 +85,14 @@ func CurrentProfile(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
var account models.IdnAccount
|
||||
var account models.PlatfromAccount
|
||||
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{
|
||||
"identity": account.Identity.String(), "username": account.Username, "display_name": account.DisplayName,
|
||||
"role_code": account.RoleCode, "must_change_password": account.MustChangePassword, "mfa_enabled": account.MFAEnabled,
|
||||
"identity": account.Identity, "username": account.Username, "display_name": account.DisplayName,
|
||||
"avatar": account.Avatar, "role_code": account.PlatformRoleCode,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,7 +114,7 @@ func ChangePassword(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
var account models.IdnAccount
|
||||
var account models.PlatfromAccount
|
||||
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
||||
return
|
||||
@@ -130,17 +128,9 @@ func ChangePassword(ctx *gin.Context) {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Model(&account).Updates(map[string]any{"password_hash": string(passwordHash), "must_change_password": false}).Error; err != nil {
|
||||
if err := impl.DBService.Model(&account).Update("password_hash", string(passwordHash)).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"changed": true})
|
||||
}
|
||||
|
||||
// boolText 将布尔值转换为 JWT 扩展字段约定的字符串。
|
||||
func boolText(value bool) string {
|
||||
if value {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"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/impl"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PingHello 返回匿名健康状态。
|
||||
@@ -14,7 +18,7 @@ func PingHello(ctx *gin.Context) {
|
||||
infra.Response.Success(ctx, gin.H{"service": "platform-api", "status": "ok"})
|
||||
}
|
||||
|
||||
// DashboardOverview 返回组织与安全运营的首期概览数据。
|
||||
// DashboardOverview 返回平台总后台的运营概览数据。
|
||||
func DashboardOverview(ctx *gin.Context) {
|
||||
overview, err := models.GetDashboardOverview()
|
||||
if err != nil {
|
||||
@@ -24,131 +28,344 @@ func DashboardOverview(ctx *gin.Context) {
|
||||
infra.Response.Success(ctx, overview)
|
||||
}
|
||||
|
||||
// CreateOrgGasStationRequest 是创建 org_gas_station 的请求体。
|
||||
type CreateOrgGasStationRequest 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"`
|
||||
// ListGasBasic 查询可燃气体站分页列表。
|
||||
func ListGasBasic(ctx *gin.Context) { listPage[models.GasBasic](ctx) }
|
||||
|
||||
// GetGasBasic 查询一个可燃气体站。
|
||||
func GetGasBasic(ctx *gin.Context) { getByIdentity[models.GasBasic](ctx) }
|
||||
|
||||
// CreateGasBasic 创建可燃气体站档案。
|
||||
func CreateGasBasic(ctx *gin.Context) {
|
||||
var request models.GasBasic
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil || request.Code == "" || request.Name == "" {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
request.Entity = newEntity("draft")
|
||||
if err := impl.DBService.Create(&request).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, request)
|
||||
}
|
||||
|
||||
// OrgGasStationListReply 是 org_gas_station 的标准分页响应。
|
||||
type OrgGasStationListReply struct {
|
||||
Total int64 `json:"total"`
|
||||
List []models.OrgGasStation `json:"list"`
|
||||
}
|
||||
|
||||
// CreateOrgGasStation 创建待审核气站并由数据库层保证唯一编码。
|
||||
func CreateOrgGasStation(ctx *gin.Context) {
|
||||
var request CreateOrgGasStationRequest
|
||||
// UpdateGasBasic 更新可燃气体站基础资料。
|
||||
func UpdateGasBasic(ctx *gin.Context) {
|
||||
var request struct {
|
||||
Name string `json:"name" binding:"required,max=128"`
|
||||
CreditCode string `json:"credit_code" binding:"max=64"`
|
||||
Principal string `json:"principal" binding:"max=64"`
|
||||
Address string `json:"address" binding:"max=255"`
|
||||
Longitude string `json:"longitude" binding:"max=32"`
|
||||
Latitude string `json:"latitude" binding:"max=32"`
|
||||
}
|
||||
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 {
|
||||
updateByIdentity(ctx, &models.GasBasic{}, gin.H{"name": request.Name, "credit_code": request.CreditCode, "principal": request.Principal, "address": request.Address, "longitude": request.Longitude, "latitude": request.Latitude})
|
||||
}
|
||||
|
||||
// ListDeliveryBasic 查询配送点分页列表。
|
||||
func ListDeliveryBasic(ctx *gin.Context) { listPage[models.DeliveryBasic](ctx) }
|
||||
|
||||
// GetDeliveryBasic 查询一个配送点。
|
||||
func GetDeliveryBasic(ctx *gin.Context) { getByIdentity[models.DeliveryBasic](ctx) }
|
||||
|
||||
// CreateDeliveryBasic 创建配送点档案。
|
||||
func CreateDeliveryBasic(ctx *gin.Context) {
|
||||
var request models.DeliveryBasic
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil || request.DeliveryCode == "" || request.Name == "" {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
request.Entity = newEntity("draft")
|
||||
if err := impl.DBService.Create(&request).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, request)
|
||||
}
|
||||
|
||||
// UpdateDeliveryBasic 更新配送点基础资料。
|
||||
func UpdateDeliveryBasic(ctx *gin.Context) {
|
||||
var request struct {
|
||||
GasBasicID uint64 `json:"gas_basic_id"`
|
||||
Name string `json:"name" binding:"required,max=128"`
|
||||
Principal string `json:"principal" binding:"max=64"`
|
||||
Address string `json:"address" binding:"max=255"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
updateByIdentity(ctx, &models.DeliveryBasic{}, gin.H{"gas_basic_id": request.GasBasicID, "name": request.Name, "principal": request.Principal, "address": request.Address})
|
||||
}
|
||||
|
||||
// ListStaff 查询服务人员分页列表。
|
||||
func ListStaff(ctx *gin.Context) { listPage[models.StaffAccount](ctx) }
|
||||
|
||||
// GetStaff 查询一个服务人员档案。
|
||||
func GetStaff(ctx *gin.Context) { getByIdentity[models.StaffAccount](ctx) }
|
||||
|
||||
// CreateStaff 创建服务人员档案。
|
||||
func CreateStaff(ctx *gin.Context) {
|
||||
var request models.StaffAccount
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil || request.Name == "" {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
request.Entity = newEntity("draft")
|
||||
if request.WorkStatus == "" {
|
||||
request.WorkStatus = "off_duty"
|
||||
}
|
||||
if err := impl.DBService.Create(&request).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, request)
|
||||
}
|
||||
|
||||
// UpdateStaff 更新服务人员档案。
|
||||
func UpdateStaff(ctx *gin.Context) {
|
||||
var request struct {
|
||||
Name string `json:"name" binding:"required,max=64"`
|
||||
Phone string `json:"phone" binding:"max=32"`
|
||||
Avatar string `json:"avatar" binding:"max=512"`
|
||||
RoleCode string `json:"role_code" binding:"max=64"`
|
||||
GasBasicID uint64 `json:"gas_basic_id"`
|
||||
DeliveryBasicID uint64 `json:"delivery_basic_id"`
|
||||
WorkStatus string `json:"work_status" binding:"max=32"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
updateByIdentity(ctx, &models.StaffAccount{}, gin.H{"name": request.Name, "phone": request.Phone, "avatar": request.Avatar, "role_code": request.RoleCode, "gas_basic_id": request.GasBasicID, "delivery_basic_id": request.DeliveryBasicID, "work_status": request.WorkStatus})
|
||||
}
|
||||
|
||||
// ListUser 查询业主客户分页列表。
|
||||
func ListUser(ctx *gin.Context) { listPage[models.UserAccount](ctx) }
|
||||
|
||||
// GetUser 查询一个业主客户档案。
|
||||
func GetUser(ctx *gin.Context) { getByIdentity[models.UserAccount](ctx) }
|
||||
|
||||
// CreateUser 创建业主客户档案。
|
||||
func CreateUser(ctx *gin.Context) {
|
||||
var request models.UserAccount
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil || request.Name == "" {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
request.Entity = newEntity("enabled")
|
||||
if err := impl.DBService.Create(&request).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, request)
|
||||
}
|
||||
|
||||
// UpdateUser 更新业主客户档案。
|
||||
func UpdateUser(ctx *gin.Context) {
|
||||
var request struct {
|
||||
Name string `json:"name" binding:"required,max=64"`
|
||||
Phone string `json:"phone" binding:"max=32"`
|
||||
Avatar string `json:"avatar" binding:"max=512"`
|
||||
RealName string `json:"real_name" binding:"max=64"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
updateByIdentity(ctx, &models.UserAccount{}, gin.H{"name": request.Name, "phone": request.Phone, "avatar": request.Avatar, "real_name": request.RealName})
|
||||
}
|
||||
|
||||
// ListPlatformRole 查询平台角色分页列表。
|
||||
func ListPlatformRole(ctx *gin.Context) { listPage[models.PlatformRole](ctx) }
|
||||
|
||||
// GetPlatformRole 查询一个平台角色。
|
||||
func GetPlatformRole(ctx *gin.Context) { getByIdentity[models.PlatformRole](ctx) }
|
||||
|
||||
// CreatePlatformRole 创建非内置平台角色。
|
||||
func CreatePlatformRole(ctx *gin.Context) {
|
||||
var request models.PlatformRole
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil || request.RoleCode == "" || request.Name == "" || request.RoleCode == "root" {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
request.Entity = newEntity("enabled")
|
||||
request.IsSystem = false
|
||||
if request.DataScope == "" {
|
||||
request.DataScope = "global"
|
||||
}
|
||||
if err := impl.DBService.Create(&request).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, request)
|
||||
}
|
||||
|
||||
// UpdatePlatformRole 更新非内置平台角色。
|
||||
func UpdatePlatformRole(ctx *gin.Context) {
|
||||
var request struct {
|
||||
Name string `json:"name" binding:"required,max=64"`
|
||||
DataScope string `json:"data_scope" binding:"required,max=32"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
var role models.PlatformRole
|
||||
if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil {
|
||||
respondRecordError(ctx, err)
|
||||
return
|
||||
}
|
||||
if role.IsSystem {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Model(&role).Updates(gin.H{"name": request.Name, "data_scope": request.DataScope}).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, role)
|
||||
}
|
||||
|
||||
// UpdatePlatformRoleStatus 更新非内置平台角色状态,root 等系统角色始终受保护。
|
||||
func UpdatePlatformRoleStatus(ctx *gin.Context) {
|
||||
var request struct {
|
||||
Status string `json:"status" binding:"required,max=32"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
var role models.PlatformRole
|
||||
if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil {
|
||||
respondRecordError(ctx, err)
|
||||
return
|
||||
}
|
||||
if role.IsSystem {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Model(&role).Update("status", request.Status).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
// ArchivePlatformRole 归档非内置平台角色,root 等系统角色始终受保护。
|
||||
func ArchivePlatformRole(ctx *gin.Context) {
|
||||
var role models.PlatformRole
|
||||
if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&role).Error; err != nil {
|
||||
respondRecordError(ctx, err)
|
||||
return
|
||||
}
|
||||
if role.IsSystem {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if err := impl.DBService.Model(&role).Update("status", "archived").Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
// ListPlatformMenu 返回菜单树构建所需的有序菜单列表。
|
||||
func ListPlatformMenu(ctx *gin.Context) {
|
||||
var list []models.PlatformMenu
|
||||
if err := impl.DBService.Order("sort_no asc, id asc").Find(&list).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"total": len(list), "list": list})
|
||||
}
|
||||
|
||||
// UpdateRecordStatus 更新主表状态,停用和归档均保留历史记录。
|
||||
func UpdateRecordStatus(ctx *gin.Context, model any) {
|
||||
var request struct {
|
||||
Status string `json:"status" binding:"required,max=32"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
updateByIdentity(ctx, model, gin.H{"status": request.Status})
|
||||
}
|
||||
|
||||
// ArchiveRecord 通过 archived 状态实现逻辑删除,不物理删除主数据。
|
||||
func ArchiveRecord(ctx *gin.Context, model any) {
|
||||
updateByIdentity(ctx, model, gin.H{"status": "archived"})
|
||||
}
|
||||
|
||||
// ListPlatfromAccount 查询平台账号列表,手机号在展示层脱敏。
|
||||
func ListPlatfromAccount(ctx *gin.Context) {
|
||||
page, size := pageSize(ctx)
|
||||
list, total, err := models.ListPlatfromAccount(page, size)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
views := make([]gin.H, 0, len(list))
|
||||
for _, item := range list {
|
||||
views = append(views, gin.H{"identity": item.Identity, "username": item.Username, "display_name": item.DisplayName, "avatar": item.Avatar, "phone_masked": maskPhone(item.Phone), "platform_role_code": item.PlatformRoleCode, "status": item.Status})
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"total": total, "list": views})
|
||||
}
|
||||
|
||||
func newEntity(status string) models.Entity {
|
||||
return models.Entity{Identity: models.NewIdentity(), Status: status, Version: 1}
|
||||
}
|
||||
|
||||
func listPage[T any](ctx *gin.Context) {
|
||||
page, size := pageSize(ctx)
|
||||
var list []T
|
||||
var total int64
|
||||
databaseQuery := impl.DBService.Model(new(T))
|
||||
if err := databaseQuery.Count(&total).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := databaseQuery.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"total": total, "list": list})
|
||||
}
|
||||
|
||||
func getByIdentity[T any](ctx *gin.Context) {
|
||||
var data T
|
||||
if err := impl.DBService.Where("identity = ?", ctx.Param("identity")).First(&data).Error; err != nil {
|
||||
respondRecordError(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, data)
|
||||
}
|
||||
|
||||
// ListOrgGasStation 查询气站分页列表。
|
||||
func ListOrgGasStation(ctx *gin.Context) {
|
||||
page, size := pageSize(ctx)
|
||||
list, total, err := models.ListOrgGasStation(page, size)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
func updateByIdentity(ctx *gin.Context, model any, values map[string]any) {
|
||||
result := impl.DBService.Model(model).Where("identity = ?", ctx.Param("identity")).Updates(values)
|
||||
if result.Error != nil {
|
||||
infra.Response.Error(ctx, result.Error)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, OrgGasStationListReply{Total: total, List: list})
|
||||
}
|
||||
|
||||
// OrgDeliveryPointListReply 是 org_delivery_point 的标准分页响应。
|
||||
type OrgDeliveryPointListReply struct {
|
||||
Total int64 `json:"total"`
|
||||
List []models.OrgDeliveryPoint `json:"list"`
|
||||
}
|
||||
|
||||
// ListOrgDeliveryPoint 查询配送点分页列表。
|
||||
func ListOrgDeliveryPoint(ctx *gin.Context) {
|
||||
page, size := pageSize(ctx)
|
||||
list, total, err := models.ListOrgDeliveryPoint(page, size)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
if result.RowsAffected == 0 {
|
||||
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, OrgDeliveryPointListReply{Total: total, List: list})
|
||||
infra.Response.Success(ctx, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
// OrgServicePersonListReply 是 org_service_person 的标准分页响应。
|
||||
type OrgServicePersonListReply struct {
|
||||
Total int64 `json:"total"`
|
||||
List []models.OrgServicePerson `json:"list"`
|
||||
}
|
||||
|
||||
// ListOrgServicePerson 查询服务人员分页列表。
|
||||
func ListOrgServicePerson(ctx *gin.Context) {
|
||||
page, size := pageSize(ctx)
|
||||
list, total, err := models.ListOrgServicePerson(page, size)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
func respondRecordError(ctx *gin.Context, err error) {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, OrgServicePersonListReply{Total: total, List: list})
|
||||
infra.Response.Error(ctx, err)
|
||||
}
|
||||
|
||||
// IdnAccountView 是 idn_account 的最小必要输出,手机号始终脱敏。
|
||||
type IdnAccountView struct {
|
||||
Identity string `json:"identity"`
|
||||
PhoneMasked string `json:"phone_masked"`
|
||||
AccountType string `json:"account_type"`
|
||||
Status string `json:"status"`
|
||||
ServiceArea string `json:"service_area"`
|
||||
}
|
||||
|
||||
// IdnAccountListReply 是 idn_account 的标准分页响应。
|
||||
type IdnAccountListReply struct {
|
||||
Total int64 `json:"total"`
|
||||
List []IdnAccountView `json:"list"`
|
||||
}
|
||||
|
||||
// ListIdnAccount 查询普通用户账户列表。
|
||||
func ListIdnAccount(ctx *gin.Context) {
|
||||
page, size := pageSize(ctx)
|
||||
list, total, err := models.ListIdnAccount(page, size)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
views := make([]IdnAccountView, 0, len(list))
|
||||
for _, item := range list {
|
||||
views = append(views, IdnAccountView{Identity: item.Identity.String(), PhoneMasked: maskPhone(item.Phone), AccountType: item.AccountType, Status: item.Status, ServiceArea: item.ServiceArea})
|
||||
}
|
||||
infra.Response.Success(ctx, IdnAccountListReply{Total: total, List: views})
|
||||
}
|
||||
|
||||
// SafEventListReply 是 saf_event 的标准分页响应。
|
||||
type SafEventListReply struct {
|
||||
Total int64 `json:"total"`
|
||||
List []models.SafEvent `json:"list"`
|
||||
}
|
||||
|
||||
// ListSafEvent 查询安全事件列表。
|
||||
func ListSafEvent(ctx *gin.Context) {
|
||||
page, size := pageSize(ctx)
|
||||
list, total, err := models.ListSafEvent(page, size)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, SafEventListReply{Total: total, List: list})
|
||||
}
|
||||
|
||||
// pageSize 统一约束分页参数,避免各接口出现不同边界。
|
||||
func pageSize(ctx *gin.Context) (int, int) {
|
||||
page := utils.String2Int(ctx.DefaultQuery("page", "1"))
|
||||
size := utils.String2Int(ctx.DefaultQuery("size", "20"))
|
||||
@@ -161,7 +378,6 @@ func pageSize(ctx *gin.Context) (int, int) {
|
||||
return page, size
|
||||
}
|
||||
|
||||
// maskPhone 遵循敏感数据最小展示原则。
|
||||
func maskPhone(phone string) string {
|
||||
if len(phone) < 7 {
|
||||
return "***"
|
||||
|
||||
87
backend/api/internal/logic/upload/upload.go
Normal file
87
backend/api/internal/logic/upload/upload.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Package upload 提供平台总后台的受控文件上传服务。
|
||||
package upload
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/errcode"
|
||||
"git.apinb.com/bsm-sdk/core/infra"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const maxUploadSize int64 = 10 << 20
|
||||
|
||||
var allowedExtensions = map[string]struct{}{
|
||||
".jpg": {}, ".jpeg": {}, ".png": {}, ".webp": {}, ".pdf": {},
|
||||
}
|
||||
|
||||
// UploadFileReply 是文件上传完成后返回的受控资源标识。
|
||||
type UploadFileReply struct {
|
||||
URI string `json:"uri"` // 资源访问标识,后续可由对象存储适配层解析
|
||||
OriginalName string `json:"original_name"` // 原始文件名,仅用于展示
|
||||
ContentType string `json:"content_type"` // 客户端声明的媒体类型
|
||||
Size int64 `json:"size"` // 文件字节数
|
||||
}
|
||||
|
||||
// UploadFile 将允许类型的文件保存至本地 Mock 存储,不直接暴露绝对磁盘路径。
|
||||
func UploadFile(ctx *gin.Context) {
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, maxUploadSize)
|
||||
fileHeader, err := ctx.FormFile("file")
|
||||
if err != nil || fileHeader == nil || fileHeader.Size <= 0 || fileHeader.Size > maxUploadSize {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
|
||||
extension := strings.ToLower(filepath.Ext(fileHeader.Filename))
|
||||
if _, allowed := allowedExtensions[extension]; !allowed {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
datePath := time.Now().Format("2006/01/02")
|
||||
filename := models.NewIdentity() + extension
|
||||
directory := filepath.Join(uploadRoot(), filepath.FromSlash(datePath))
|
||||
if err := os.MkdirAll(directory, 0o750); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
target, err := os.OpenFile(filepath.Join(directory, filename), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o640)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
defer target.Close()
|
||||
if _, err := io.Copy(target, file); err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
infra.Response.Success(ctx, UploadFileReply{
|
||||
URI: "/uploads/" + datePath + "/" + filename,
|
||||
OriginalName: fileHeader.Filename,
|
||||
ContentType: fileHeader.Header.Get("Content-Type"),
|
||||
Size: fileHeader.Size,
|
||||
})
|
||||
}
|
||||
|
||||
// uploadRoot 返回本地 Mock 存储根目录;生产环境可通过环境变量映射到受控挂载目录。
|
||||
func uploadRoot() string {
|
||||
if directory := strings.TrimSpace(os.Getenv("HEQI_UPLOAD_DIR")); directory != "" {
|
||||
return directory
|
||||
}
|
||||
return filepath.Join("runtime", "uploads")
|
||||
}
|
||||
15
backend/api/internal/models/aud_approval.go
Normal file
15
backend/api/internal/models/aud_approval.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// AudApproval 对应 aud_approval,保存审批流与复核意见。
|
||||
type AudApproval struct {
|
||||
Entity
|
||||
BusinessType string `gorm:"column:business_type;type:varchar(64);not null" json:"business_type"`
|
||||
BusinessIdentity string `gorm:"column:business_identity;type:varchar(36);not null;index" json:"business_identity"`
|
||||
ApplicantIdentity string `gorm:"column:applicant_identity;type:varchar(36);not null;index" json:"applicant_identity"`
|
||||
Opinion string `gorm:"column:opinion;type:text;not null;default:''" json:"opinion"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&AudApproval{}) }
|
||||
func (table *AudApproval) TableName() string { return "aud_approval" }
|
||||
19
backend/api/internal/models/aud_export_log.go
Normal file
19
backend/api/internal/models/aud_export_log.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AudExportLog 对应 aud_export_log,保存敏感导出审计。
|
||||
type AudExportLog struct {
|
||||
Entity
|
||||
ApplicantIdentity string `gorm:"column:applicant_identity;type:varchar(36);not null;index" json:"applicant_identity"`
|
||||
Purpose string `gorm:"column:purpose;type:varchar(255);not null" json:"purpose"`
|
||||
FieldScope string `gorm:"column:field_scope;type:jsonb;not null;default:'{}'" json:"field_scope"`
|
||||
ApprovedAt *time.Time `gorm:"column:approved_at;type:timestamptz" json:"approved_at"`
|
||||
FileURI string `gorm:"column:file_uri;type:varchar(512);not null;default:''" json:"file_uri"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&AudExportLog{}) }
|
||||
func (table *AudExportLog) TableName() string { return "aud_export_log" }
|
||||
17
backend/api/internal/models/aud_operation_log.go
Normal file
17
backend/api/internal/models/aud_operation_log.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// AudOperationLog 对应 aud_operation_log,保存不可变操作审计。
|
||||
type AudOperationLog struct {
|
||||
Entity
|
||||
OperatorIdentity string `gorm:"column:operator_identity;type:varchar(36);not null;index" json:"operator_identity"`
|
||||
Action string `gorm:"column:action;type:varchar(64);not null" json:"action"`
|
||||
ObjectType string `gorm:"column:object_type;type:varchar(64);not null" json:"object_type"`
|
||||
ObjectIdentity string `gorm:"column:object_identity;type:varchar(36);not null;index" json:"object_identity"`
|
||||
BeforeData string `gorm:"column:before_data;type:jsonb;not null;default:'{}'" json:"before_data"`
|
||||
AfterData string `gorm:"column:after_data;type:jsonb;not null;default:'{}'" json:"after_data"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&AudOperationLog{}) }
|
||||
func (table *AudOperationLog) TableName() string { return "aud_operation_log" }
|
||||
16
backend/api/internal/models/cnt_content.go
Normal file
16
backend/api/internal/models/cnt_content.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// CntContent 对应 cnt_content,保存公告与协议内容。
|
||||
type CntContent struct {
|
||||
Entity
|
||||
ContentType string `gorm:"column:content_type;type:varchar(32);not null" json:"content_type"`
|
||||
Title string `gorm:"column:title;type:varchar(256);not null" json:"title"`
|
||||
Body string `gorm:"column:body;type:text;not null;default:''" json:"body"`
|
||||
VersionNo int `gorm:"column:version_no;not null;default:1" json:"version_no"`
|
||||
PublishStatus string `gorm:"column:publish_status;type:varchar(32);not null;default:'draft'" json:"publish_status"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&CntContent{}) }
|
||||
func (table *CntContent) TableName() string { return "cnt_content" }
|
||||
15
backend/api/internal/models/cs_ticket.go
Normal file
15
backend/api/internal/models/cs_ticket.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// CsTicket 对应 cs_ticket,保存客服工单。
|
||||
type CsTicket struct {
|
||||
Entity
|
||||
TicketNo string `gorm:"column:ticket_no;type:varchar(64);not null;uniqueIndex" json:"ticket_no"`
|
||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"`
|
||||
Category string `gorm:"column:category;type:varchar(64);not null" json:"category"`
|
||||
Priority string `gorm:"column:priority;type:varchar(16);not null;default:'normal'" json:"priority"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&CsTicket{}) }
|
||||
func (table *CsTicket) TableName() string { return "cs_ticket" }
|
||||
16
backend/api/internal/models/delivery_account.go
Normal file
16
backend/api/internal/models/delivery_account.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// DeliveryAccount 对应 delivery_account,保存配送点登录账户。
|
||||
type DeliveryAccount struct {
|
||||
Entity
|
||||
DeliveryBasicID uint64 `gorm:"column:delivery_basic_id;not null;index" json:"delivery_basic_id"`
|
||||
Username string `gorm:"column:username;type:varchar(64);not null;uniqueIndex" json:"username"`
|
||||
DisplayName string `gorm:"column:display_name;type:varchar(64);not null;default:''" json:"display_name"`
|
||||
PasswordHash string `gorm:"column:password_hash;type:varchar(255);not null" json:"-"`
|
||||
RoleCode string `gorm:"column:role_code;type:varchar(64);not null" json:"role_code"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&DeliveryAccount{}) }
|
||||
func (table *DeliveryAccount) TableName() string { return "delivery_account" }
|
||||
18
backend/api/internal/models/delivery_basic.go
Normal file
18
backend/api/internal/models/delivery_basic.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// DeliveryBasic 对应 delivery_basic,保存配送点主档案。
|
||||
type DeliveryBasic struct {
|
||||
Entity
|
||||
DeliveryCode string `gorm:"column:delivery_code;type:varchar(32);not null;uniqueIndex" json:"delivery_code"` // 配送点编码
|
||||
GasBasicID uint64 `gorm:"column:gas_basic_id;not null;default:0;index" json:"gas_basic_id"` // 所属可燃气体站自增主键,0 表示平台直属
|
||||
Name string `gorm:"column:name;type:varchar(128);not null" json:"name"` // 配送点名称
|
||||
Principal string `gorm:"column:principal;type:varchar(64);not null;default:''" json:"principal"` // 负责人
|
||||
Address string `gorm:"column:address;type:varchar(255);not null;default:''" json:"address"` // 配送点地址
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&DeliveryBasic{}) }
|
||||
|
||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||
func (table *DeliveryBasic) TableName() string { return "delivery_basic" }
|
||||
14
backend/api/internal/models/delivery_task.go
Normal file
14
backend/api/internal/models/delivery_task.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// DeliveryTask 对应 delivery_task,保存配送履约任务。
|
||||
type DeliveryTask struct {
|
||||
Entity
|
||||
EcOrderID uint64 `gorm:"column:ec_order_id;not null;index" json:"ec_order_id"`
|
||||
StaffAccountID uint64 `gorm:"column:staff_account_id;not null;default:0;index" json:"staff_account_id"`
|
||||
DeliveryPointID uint64 `gorm:"column:delivery_point_id;not null;index" json:"delivery_point_id"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&DeliveryTask{}) }
|
||||
func (table *DeliveryTask) TableName() string { return "delivery_task" }
|
||||
17
backend/api/internal/models/delivery_track.go
Normal file
17
backend/api/internal/models/delivery_track.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DeliveryTrack 对应 delivery_track,保存配送轨迹摘要。
|
||||
type DeliveryTrack struct {
|
||||
Entity
|
||||
DeliveryTaskID uint64 `gorm:"column:delivery_task_id;not null;index" json:"delivery_task_id"`
|
||||
StartedAt *time.Time `gorm:"column:started_at;type:timestamptz" json:"started_at"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;type:timestamptz" json:"completed_at"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&DeliveryTrack{}) }
|
||||
func (table *DeliveryTrack) TableName() string { return "delivery_track" }
|
||||
19
backend/api/internal/models/delivery_track_point.go
Normal file
19
backend/api/internal/models/delivery_track_point.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DeliveryTrackPoint 对应 delivery_track_point,保存配送节点和位置。
|
||||
type DeliveryTrackPoint struct {
|
||||
Entity
|
||||
DeliveryTrackID uint64 `gorm:"column:delivery_track_id;not null;index" json:"delivery_track_id"`
|
||||
PointType string `gorm:"column:point_type;type:varchar(32);not null" json:"point_type"`
|
||||
OccurredAt time.Time `gorm:"column:occurred_at;type:timestamptz;not null" json:"occurred_at"`
|
||||
Longitude string `gorm:"column:longitude;type:varchar(32);not null;default:''" json:"longitude"`
|
||||
Latitude string `gorm:"column:latitude;type:varchar(32);not null;default:''" json:"latitude"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&DeliveryTrackPoint{}) }
|
||||
func (table *DeliveryTrackPoint) TableName() string { return "delivery_track_point" }
|
||||
18
backend/api/internal/models/dev_device_binding.go
Normal file
18
backend/api/internal/models/dev_device_binding.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DevDeviceBinding 对应 dev_device_binding,保存设备授权绑定。
|
||||
type DevDeviceBinding struct {
|
||||
Entity
|
||||
SmartCylinderValveID uint64 `gorm:"column:smart_cylinder_valve_id;not null;index" json:"smart_cylinder_valve_id"`
|
||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"`
|
||||
EffectiveAt time.Time `gorm:"column:effective_at;type:timestamptz;not null" json:"effective_at"`
|
||||
ExpiredAt *time.Time `gorm:"column:expired_at;type:timestamptz" json:"expired_at"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&DevDeviceBinding{}) }
|
||||
func (table *DevDeviceBinding) TableName() string { return "dev_device_binding" }
|
||||
15
backend/api/internal/models/dev_smart_cylinder_valve.go
Normal file
15
backend/api/internal/models/dev_smart_cylinder_valve.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// DevSmartCylinderValve 对应 dev_smart_cylinder_valve,保存智能瓶阀档案。
|
||||
type DevSmartCylinderValve struct {
|
||||
Entity
|
||||
DeviceNo string `gorm:"column:device_no;type:varchar(64);not null;uniqueIndex" json:"device_no"`
|
||||
Model string `gorm:"column:model;type:varchar(64);not null;default:''" json:"model"`
|
||||
OnlineStatus string `gorm:"column:online_status;type:varchar(32);not null;default:'offline'" json:"online_status"`
|
||||
OwnerIdentity string `gorm:"column:owner_identity;type:varchar(36);not null;default:'';index" json:"owner_identity"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&DevSmartCylinderValve{}) }
|
||||
func (table *DevSmartCylinderValve) TableName() string { return "dev_smart_cylinder_valve" }
|
||||
18
backend/api/internal/models/dev_telemetry.go
Normal file
18
backend/api/internal/models/dev_telemetry.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DevTelemetry 对应 dev_telemetry,保存设备遥测摘要。
|
||||
type DevTelemetry struct {
|
||||
Entity
|
||||
SmartCylinderValveIdentity string `gorm:"column:smart_cylinder_valve_identity;type:varchar(36);not null;index" json:"smart_cylinder_valve_identity"`
|
||||
ReportedAt time.Time `gorm:"column:reported_at;type:timestamptz;not null;index" json:"reported_at"`
|
||||
Payload string `gorm:"column:payload;type:jsonb;not null;default:'{}'" json:"payload"`
|
||||
QualityFlag string `gorm:"column:quality_flag;type:varchar(32);not null;default:'normal'" json:"quality_flag"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&DevTelemetry{}) }
|
||||
func (table *DevTelemetry) TableName() string { return "dev_telemetry" }
|
||||
15
backend/api/internal/models/ec_cart.go
Normal file
15
backend/api/internal/models/ec_cart.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// EcCart 对应 ec_cart,保存用户购物车明细。
|
||||
type EcCart struct {
|
||||
Entity
|
||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"`
|
||||
EcProductID uint64 `gorm:"column:ec_product_id;not null;index" json:"ec_product_id"`
|
||||
Quantity int `gorm:"column:quantity;not null;default:1" json:"quantity"`
|
||||
Selected bool `gorm:"column:selected;not null;default:true" json:"selected"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&EcCart{}) }
|
||||
func (table *EcCart) TableName() string { return "ec_cart" }
|
||||
14
backend/api/internal/models/ec_category.go
Normal file
14
backend/api/internal/models/ec_category.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// EcCategory 对应 ec_category,保存商品分类树。
|
||||
type EcCategory struct {
|
||||
Entity
|
||||
ParentID uint64 `gorm:"column:parent_id;not null;default:0;index" json:"parent_id"`
|
||||
Name string `gorm:"column:name;type:varchar(128);not null" json:"name"`
|
||||
SortNo int `gorm:"column:sort_no;not null;default:0" json:"sort_no"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&EcCategory{}) }
|
||||
func (table *EcCategory) TableName() string { return "ec_category" }
|
||||
16
backend/api/internal/models/ec_order.go
Normal file
16
backend/api/internal/models/ec_order.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// EcOrder 对应 ec_order,保存电商订单与组织快照。
|
||||
type EcOrder struct {
|
||||
Entity
|
||||
OrderNo string `gorm:"column:order_no;type:varchar(64);not null;uniqueIndex" json:"order_no"`
|
||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"`
|
||||
GasStationID uint64 `gorm:"column:gas_station_id;not null;default:0;index" json:"gas_station_id"`
|
||||
DeliveryPointID uint64 `gorm:"column:delivery_point_id;not null;default:0;index" json:"delivery_point_id"`
|
||||
TotalAmount int64 `gorm:"column:total_amount;not null;default:0" json:"total_amount"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&EcOrder{}) }
|
||||
func (table *EcOrder) TableName() string { return "ec_order" }
|
||||
16
backend/api/internal/models/ec_order_item.go
Normal file
16
backend/api/internal/models/ec_order_item.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// EcOrderItem 对应 ec_order_item,保存订单商品快照。
|
||||
type EcOrderItem struct {
|
||||
Entity
|
||||
EcOrderID uint64 `gorm:"column:ec_order_id;not null;index" json:"ec_order_id"`
|
||||
EcProductID uint64 `gorm:"column:ec_product_id;not null;index" json:"ec_product_id"`
|
||||
ProductSnapshot string `gorm:"column:product_snapshot;type:jsonb;not null;default:'{}'" json:"product_snapshot"`
|
||||
Quantity int `gorm:"column:quantity;not null;default:1" json:"quantity"`
|
||||
SaleAmount int64 `gorm:"column:sale_amount;not null;default:0" json:"sale_amount"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&EcOrderItem{}) }
|
||||
func (table *EcOrderItem) TableName() string { return "ec_order_item" }
|
||||
16
backend/api/internal/models/ec_product.go
Normal file
16
backend/api/internal/models/ec_product.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// EcProduct 对应 ec_product,保存可燃气体商品与服务。
|
||||
type EcProduct struct {
|
||||
Entity
|
||||
EcCategoryID uint64 `gorm:"column:ec_category_id;not null;index" json:"ec_category_id"`
|
||||
ProductCode string `gorm:"column:product_code;type:varchar(64);not null;uniqueIndex" json:"product_code"`
|
||||
Name string `gorm:"column:name;type:varchar(128);not null" json:"name"`
|
||||
PriceAmount int64 `gorm:"column:price_amount;not null;default:0" json:"price_amount"`
|
||||
StockQuantity int `gorm:"column:stock_quantity;not null;default:0" json:"stock_quantity"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&EcProduct{}) }
|
||||
func (table *EcProduct) TableName() string { return "ec_product" }
|
||||
15
backend/api/internal/models/ec_product_attribute.go
Normal file
15
backend/api/internal/models/ec_product_attribute.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// EcProductAttribute 对应 ec_product_attribute,保存商品属性。
|
||||
type EcProductAttribute struct {
|
||||
Entity
|
||||
EcProductID uint64 `gorm:"column:ec_product_id;not null;index" json:"ec_product_id"`
|
||||
Name string `gorm:"column:name;type:varchar(64);not null" json:"name"`
|
||||
Value string `gorm:"column:value;type:varchar(255);not null" json:"value"`
|
||||
SortNo int `gorm:"column:sort_no;not null;default:0" json:"sort_no"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&EcProductAttribute{}) }
|
||||
func (table *EcProductAttribute) TableName() string { return "ec_product_attribute" }
|
||||
15
backend/api/internal/models/ec_product_image.go
Normal file
15
backend/api/internal/models/ec_product_image.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// EcProductImage 对应 ec_product_image,保存商品受控图片资源。
|
||||
type EcProductImage struct {
|
||||
Entity
|
||||
EcProductID uint64 `gorm:"column:ec_product_id;not null;index" json:"ec_product_id"`
|
||||
ImageURI string `gorm:"column:image_uri;type:varchar(512);not null" json:"image_uri"`
|
||||
SortNo int `gorm:"column:sort_no;not null;default:0" json:"sort_no"`
|
||||
IsCover bool `gorm:"column:is_cover;not null;default:false" json:"is_cover"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&EcProductImage{}) }
|
||||
func (table *EcProductImage) TableName() string { return "ec_product_image" }
|
||||
16
backend/api/internal/models/ec_review.go
Normal file
16
backend/api/internal/models/ec_review.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// EcReview 对应 ec_review,保存商品评论与审核状态。
|
||||
type EcReview struct {
|
||||
Entity
|
||||
EcOrderID uint64 `gorm:"column:ec_order_id;not null;index" json:"ec_order_id"`
|
||||
EcProductID uint64 `gorm:"column:ec_product_id;not null;index" json:"ec_product_id"`
|
||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"`
|
||||
Score int `gorm:"column:score;not null;default:5" json:"score"`
|
||||
Content string `gorm:"column:content;type:text;not null;default:''" json:"content"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&EcReview{}) }
|
||||
func (table *EcReview) TableName() string { return "ec_review" }
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package models 定义与数据库表同名的领域模型和数据访问方法。
|
||||
// Package models 定义与数据表同名的领域模型和数据访问方法。
|
||||
package models
|
||||
|
||||
import (
|
||||
@@ -7,22 +7,21 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Entity 是所有主表共享字段;identity 必须由应用生成 UUID V7,禁止自增主键。
|
||||
// Entity 是所有主表共享字段。id 是数据库自增主键,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"` // 乐观锁版本
|
||||
ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 数据库自增主键
|
||||
Identity string `gorm:"column:identity;type:varchar(36);not null;uniqueIndex" 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"` // 更新时间
|
||||
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 {
|
||||
// NewIdentity 生成时间有序的 UUID V7 字符串,生成失败属于不可恢复的运行时错误。
|
||||
func NewIdentity() string {
|
||||
identity, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return identity
|
||||
return identity.String()
|
||||
}
|
||||
|
||||
18
backend/api/internal/models/fin_payment.go
Normal file
18
backend/api/internal/models/fin_payment.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FinPayment 对应 fin_payment,保存支付与退款记录。
|
||||
type FinPayment struct {
|
||||
Entity
|
||||
EcOrderID uint64 `gorm:"column:ec_order_id;not null;index" json:"ec_order_id"`
|
||||
Channel string `gorm:"column:channel;type:varchar(32);not null" json:"channel"`
|
||||
Amount int64 `gorm:"column:amount;not null;default:0" json:"amount"`
|
||||
PaidAt *time.Time `gorm:"column:paid_at;type:timestamptz" json:"paid_at"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&FinPayment{}) }
|
||||
func (table *FinPayment) TableName() string { return "fin_payment" }
|
||||
17
backend/api/internal/models/fin_reconciliation.go
Normal file
17
backend/api/internal/models/fin_reconciliation.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FinReconciliation 对应 fin_reconciliation,保存渠道对账记录。
|
||||
type FinReconciliation struct {
|
||||
Entity
|
||||
Channel string `gorm:"column:channel;type:varchar(32);not null;index" json:"channel"`
|
||||
BillDate time.Time `gorm:"column:bill_date;type:date;not null" json:"bill_date"`
|
||||
DifferenceAmount int64 `gorm:"column:difference_amount;not null;default:0" json:"difference_amount"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&FinReconciliation{}) }
|
||||
func (table *FinReconciliation) TableName() string { return "fin_reconciliation" }
|
||||
19
backend/api/internal/models/fin_settlement.go
Normal file
19
backend/api/internal/models/fin_settlement.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FinSettlement 对应 fin_settlement,保存结算单。
|
||||
type FinSettlement struct {
|
||||
Entity
|
||||
SettlementNo string `gorm:"column:settlement_no;type:varchar(64);not null;uniqueIndex" json:"settlement_no"`
|
||||
SubjectType string `gorm:"column:subject_type;type:varchar(32);not null" json:"subject_type"`
|
||||
SubjectID uint64 `gorm:"column:subject_id;not null;index" json:"subject_id"`
|
||||
PeriodStart time.Time `gorm:"column:period_start;type:timestamptz;not null" json:"period_start"`
|
||||
PeriodEnd time.Time `gorm:"column:period_end;type:timestamptz;not null" json:"period_end"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&FinSettlement{}) }
|
||||
func (table *FinSettlement) TableName() string { return "fin_settlement" }
|
||||
16
backend/api/internal/models/gas_account.go
Normal file
16
backend/api/internal/models/gas_account.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// GasAccount 对应 gas_account,保存可燃气体站登录账户。
|
||||
type GasAccount struct {
|
||||
Entity
|
||||
GasBasicID uint64 `gorm:"column:gas_basic_id;not null;index" json:"gas_basic_id"` // 可燃气体站主键
|
||||
Username string `gorm:"column:username;type:varchar(64);not null;uniqueIndex" json:"username"` // 登录名称
|
||||
DisplayName string `gorm:"column:display_name;type:varchar(64);not null;default:''" json:"display_name"` // 展示名称
|
||||
PasswordHash string `gorm:"column:password_hash;type:varchar(255);not null" json:"-"` // 密码哈希
|
||||
RoleCode string `gorm:"column:role_code;type:varchar(64);not null" json:"role_code"` // 角色编码
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&GasAccount{}) }
|
||||
func (table *GasAccount) TableName() string { return "gas_account" }
|
||||
20
backend/api/internal/models/gas_basic.go
Normal file
20
backend/api/internal/models/gas_basic.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// GasBasic 对应 gas_basic,保存可燃气体站的主体主档案。
|
||||
type GasBasic struct {
|
||||
Entity
|
||||
Code string `gorm:"column:code;type:varchar(32);not null;uniqueIndex" json:"code"` // 站点编码
|
||||
Name string `gorm:"column:name;type:varchar(128);not null" json:"name"` // 站点名称
|
||||
CreditCode string `gorm:"column:credit_code;type:varchar(64);not null;default:''" json:"credit_code"` // 统一社会信用代码
|
||||
Principal string `gorm:"column:principal;type:varchar(64);not null;default:''" json:"principal"` // 负责人
|
||||
Address string `gorm:"column:address;type:varchar(255);not null;default:''" json:"address"` // 站点地址
|
||||
Longitude string `gorm:"column:longitude;type:varchar(32);not null;default:''" json:"longitude"` // 经度
|
||||
Latitude string `gorm:"column:latitude;type:varchar(32);not null;default:''" json:"latitude"` // 纬度
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&GasBasic{}) }
|
||||
|
||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||
func (table *GasBasic) TableName() string { return "gas_basic" }
|
||||
@@ -1,22 +0,0 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// IdnAccount 对应 idn_account,表示用户或服务人员身份账户。
|
||||
type IdnAccount struct {
|
||||
Entity
|
||||
Username string `gorm:"column:username;type:varchar(64);uniqueIndex" json:"username"` // 登录用户名。
|
||||
DisplayName string `gorm:"column:display_name;type:varchar(64);not null;default:''" json:"display_name"` // 用户展示名称。
|
||||
PasswordHash string `gorm:"column:password_hash;type:varchar(255);not null;default:''" json:"-"` // 密码哈希值,禁止在接口中返回。
|
||||
RoleCode string `gorm:"column:role_code;type:varchar(64);not null;default:'user'" json:"role_code"` // 平台角色编码。
|
||||
MustChangePassword bool `gorm:"column:must_change_password;not null;default:false" json:"must_change_password"` // 是否必须修改初始密码。
|
||||
MFAEnabled bool `gorm:"column:mfa_enabled;not null;default:false" json:"mfa_enabled"` // 是否启用多因素认证。
|
||||
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"` // 账号类型,例如 user、operator。
|
||||
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" }
|
||||
14
backend/api/internal/models/ntf_template.go
Normal file
14
backend/api/internal/models/ntf_template.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// NtfTemplate 对应 ntf_template,保存通知模板。
|
||||
type NtfTemplate struct {
|
||||
Entity
|
||||
TemplateCode string `gorm:"column:template_code;type:varchar(64);not null;uniqueIndex" json:"template_code"`
|
||||
Channel string `gorm:"column:channel;type:varchar(32);not null" json:"channel"`
|
||||
Content string `gorm:"column:content;type:text;not null" json:"content"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&NtfTemplate{}) }
|
||||
func (table *NtfTemplate) TableName() string { return "ntf_template" }
|
||||
@@ -1,17 +0,0 @@
|
||||
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" }
|
||||
@@ -1,60 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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" }
|
||||
19
backend/api/internal/models/platform_menu.go
Normal file
19
backend/api/internal/models/platform_menu.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// PlatformMenu 对应 platform_menu,定义平台总后台的菜单树和访问路由。
|
||||
type PlatformMenu struct {
|
||||
Entity
|
||||
ParentID uint64 `gorm:"column:parent_id;not null;default:0;index" json:"parent_id"` // 父菜单自增主键,顶级菜单为 0
|
||||
MenuCode string `gorm:"column:menu_code;type:varchar(64);not null;uniqueIndex" json:"menu_code"` // 菜单编码
|
||||
Name string `gorm:"column:name;type:varchar(64);not null" json:"name"` // 菜单名称
|
||||
Icon string `gorm:"column:icon;type:varchar(64);not null;default:''" json:"icon"` // 前端图标名称
|
||||
Path string `gorm:"column:path;type:varchar(255);not null;default:''" json:"path"` // 前端路由地址
|
||||
SortNo int `gorm:"column:sort_no;not null;default:0" json:"sort_no"` // 同级排序号
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&PlatformMenu{}) }
|
||||
|
||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||
func (table *PlatformMenu) TableName() string { return "platform_menu" }
|
||||
17
backend/api/internal/models/platform_role.go
Normal file
17
backend/api/internal/models/platform_role.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// PlatformRole 对应 platform_role,定义平台总后台的数据范围与菜单权限角色。
|
||||
type PlatformRole struct {
|
||||
Entity
|
||||
RoleCode string `gorm:"column:role_code;type:varchar(64);not null;uniqueIndex" json:"role_code"` // 角色编码
|
||||
Name string `gorm:"column:name;type:varchar(64);not null" json:"name"` // 角色名称
|
||||
DataScope string `gorm:"column:data_scope;type:varchar(32);not null;default:'global'" json:"data_scope"` // 数据权限范围
|
||||
IsSystem bool `gorm:"column:is_system;not null;default:false" json:"is_system"` // 是否系统内置角色
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&PlatformRole{}) }
|
||||
|
||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||
func (table *PlatformRole) TableName() string { return "platform_role" }
|
||||
20
backend/api/internal/models/platform_role_menu_relation.go
Normal file
20
backend/api/internal/models/platform_role_menu_relation.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
)
|
||||
|
||||
// PlatformRoleMenuRelation 对应 platform_role_menu_relation,记录角色拥有的菜单权限。
|
||||
type PlatformRoleMenuRelation struct {
|
||||
ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // 数据库自增主键
|
||||
PlatformRoleID uint64 `gorm:"column:platform_role_id;not null;uniqueIndex:uk_platform_role_menu" json:"platform_role_id"` // 角色自增主键
|
||||
PlatformMenuID uint64 `gorm:"column:platform_menu_id;not null;uniqueIndex:uk_platform_role_menu" json:"platform_menu_id"` // 菜单自增主键
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null" json:"created_at"` // 创建时间
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&PlatformRoleMenuRelation{}) }
|
||||
|
||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||
func (table *PlatformRoleMenuRelation) TableName() string { return "platform_role_menu_relation" }
|
||||
19
backend/api/internal/models/platfrom_account.go
Normal file
19
backend/api/internal/models/platfrom_account.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// PlatfromAccount 对应 platfrom_account,表示平台总后台登录账号。
|
||||
type PlatfromAccount struct {
|
||||
Entity
|
||||
Username string `gorm:"column:username;type:varchar(64);uniqueIndex;not null" json:"username"` // 登录用户名
|
||||
DisplayName string `gorm:"column:display_name;type:varchar(64);not null;default:''" json:"display_name"` // 用户展示名称
|
||||
Avatar string `gorm:"column:avatar;type:varchar(512);not null;default:''" json:"avatar"` // 头像资源地址
|
||||
PasswordHash string `gorm:"column:password_hash;type:varchar(255);not null;default:''" json:"-"` // 密码哈希值
|
||||
PlatformRoleCode string `gorm:"column:platform_role_code;type:varchar(64);not null;default:'root';index" json:"platform_role_code"` // 平台角色编码
|
||||
Phone string `gorm:"column:phone;type:varchar(32);uniqueIndex;not null;default:''" json:"phone"` // 手机号
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&PlatfromAccount{}) }
|
||||
|
||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||
func (table *PlatfromAccount) TableName() string { return "platfrom_account" }
|
||||
@@ -2,28 +2,28 @@ package models
|
||||
|
||||
import "git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
|
||||
|
||||
// DashboardOverview 是平台总后台的安全与组织聚合指标。
|
||||
// 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"` // 启用普通用户数量
|
||||
GasBasicCount int64 `json:"gas_basic_count"` // 启用可燃气体站数量
|
||||
DeliveryBasicCount int64 `json:"delivery_basic_count"` // 启用配送点数量
|
||||
StaffCount int64 `json:"staff_count"` // 在岗服务人员数量
|
||||
UserCount int64 `json:"user_count"` // 启用业主客户数量
|
||||
PendingSafetyCount int64 `json:"pending_safety_count"` // 待处理安全事件数量
|
||||
}
|
||||
|
||||
// GetDashboardOverview 通过独立查询返回首期仪表盘指标。
|
||||
// GetDashboardOverview 通过独立查询返回首页概览指标。
|
||||
func GetDashboardOverview() (DashboardOverview, error) {
|
||||
var overview DashboardOverview
|
||||
if err := impl.DBService.Model(&OrgGasStation{}).Where("status = ?", "enabled").Count(&overview.GasStationCount).Error; err != nil {
|
||||
if err := impl.DBService.Model(&GasBasic{}).Where("status = ?", "enabled").Count(&overview.GasBasicCount).Error; err != nil {
|
||||
return DashboardOverview{}, err
|
||||
}
|
||||
if err := impl.DBService.Model(&OrgDeliveryPoint{}).Where("status = ?", "enabled").Count(&overview.DeliveryPointCount).Error; err != nil {
|
||||
if err := impl.DBService.Model(&DeliveryBasic{}).Where("status = ?", "enabled").Count(&overview.DeliveryBasicCount).Error; err != nil {
|
||||
return DashboardOverview{}, err
|
||||
}
|
||||
if err := impl.DBService.Model(&OrgServicePerson{}).Where("work_status = ?", "on_duty").Count(&overview.ServicePersonCount).Error; err != nil {
|
||||
if err := impl.DBService.Model(&StaffAccount{}).Where("work_status = ?", "on_duty").Count(&overview.StaffCount).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 {
|
||||
if err := impl.DBService.Model(&UserAccount{}).Where("status = ?", "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 {
|
||||
@@ -32,11 +32,11 @@ func GetDashboardOverview() (DashboardOverview, error) {
|
||||
return overview, nil
|
||||
}
|
||||
|
||||
// ListOrgDeliveryPoint 返回配送点分页列表。
|
||||
func ListOrgDeliveryPoint(page, size int) ([]OrgDeliveryPoint, int64, error) {
|
||||
var list []OrgDeliveryPoint
|
||||
// ListPlatfromAccount 返回平台账号分页列表,敏感字段由接口展示层脱敏。
|
||||
func ListPlatfromAccount(page, size int) ([]PlatfromAccount, int64, error) {
|
||||
var list []PlatfromAccount
|
||||
var total int64
|
||||
databaseQuery := impl.DBService.Model(&OrgDeliveryPoint{})
|
||||
databaseQuery := impl.DBService.Model(&PlatfromAccount{})
|
||||
if err := databaseQuery.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -45,45 +45,3 @@ func ListOrgDeliveryPoint(page, size int) ([]OrgDeliveryPoint, int64, error) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
18
backend/api/internal/models/report.go
Normal file
18
backend/api/internal/models/report.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Report 对应 report,保存统计报表档案。
|
||||
type Report struct {
|
||||
Entity
|
||||
ReportCode string `gorm:"column:report_code;type:varchar(64);not null;uniqueIndex" json:"report_code"`
|
||||
ReportType string `gorm:"column:report_type;type:varchar(32);not null" json:"report_type"`
|
||||
StatPeriod string `gorm:"column:stat_period;type:varchar(64);not null" json:"stat_period"`
|
||||
GeneratedAt time.Time `gorm:"column:generated_at;type:timestamptz;not null" json:"generated_at"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&Report{}) }
|
||||
func (table *Report) TableName() string { return "report" }
|
||||
15
backend/api/internal/models/report_item.go
Normal file
15
backend/api/internal/models/report_item.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// ReportItem 对应 report_item,保存报表维度明细。
|
||||
type ReportItem struct {
|
||||
Entity
|
||||
ReportID uint64 `gorm:"column:report_id;not null;index" json:"report_id"`
|
||||
Dimension string `gorm:"column:dimension;type:varchar(128);not null" json:"dimension"`
|
||||
MetricCode string `gorm:"column:metric_code;type:varchar(64);not null" json:"metric_code"`
|
||||
MetricValue string `gorm:"column:metric_value;type:varchar(128);not null" json:"metric_value"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&ReportItem{}) }
|
||||
func (table *ReportItem) TableName() string { return "report_item" }
|
||||
19
backend/api/internal/models/report_metric_snapshot.go
Normal file
19
backend/api/internal/models/report_metric_snapshot.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ReportMetricSnapshot 对应 report_metric_snapshot,保存指标快照。
|
||||
type ReportMetricSnapshot struct {
|
||||
Entity
|
||||
MetricCode string `gorm:"column:metric_code;type:varchar(64);not null;index" json:"metric_code"`
|
||||
ScopeType string `gorm:"column:scope_type;type:varchar(32);not null" json:"scope_type"`
|
||||
ScopeID uint64 `gorm:"column:scope_id;not null;default:0;index" json:"scope_id"`
|
||||
StatAt time.Time `gorm:"column:stat_at;type:timestamptz;not null;index" json:"stat_at"`
|
||||
MetricValue string `gorm:"column:metric_value;type:varchar(128);not null" json:"metric_value"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&ReportMetricSnapshot{}) }
|
||||
func (table *ReportMetricSnapshot) TableName() string { return "report_metric_snapshot" }
|
||||
@@ -1,18 +1,19 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SafEvent 对应 saf_event,表示需要平台跟踪处置的安全事件。
|
||||
// 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"` // 事件说明
|
||||
EventCode string `gorm:"column:event_code;type:varchar(64);not null;uniqueIndex" json:"event_code"`
|
||||
Level int `gorm:"column:level;not null;default:3" json:"level"`
|
||||
Title string `gorm:"column:title;type:varchar(256);not null;default:''" json:"title"`
|
||||
SmartCylinderValveIdentity string `gorm:"column:smart_cylinder_valve_identity;type:varchar(36);not null;default:'';index" json:"smart_cylinder_valve_identity"`
|
||||
SLAAt *time.Time `gorm:"column:sla_at;type:timestamptz" json:"sla_at"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
database.AppendMigrate(&SafEvent{})
|
||||
}
|
||||
|
||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||
func init() { database.AppendMigrate(&SafEvent{}) }
|
||||
func (table *SafEvent) TableName() string { return "saf_event" }
|
||||
|
||||
15
backend/api/internal/models/saf_event_disposal.go
Normal file
15
backend/api/internal/models/saf_event_disposal.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// SafEventDisposal 对应 saf_event_disposal,保存安全处置记录。
|
||||
type SafEventDisposal struct {
|
||||
Entity
|
||||
SafEventIdentity string `gorm:"column:saf_event_identity;type:varchar(36);not null;index" json:"saf_event_identity"`
|
||||
Action string `gorm:"column:action;type:varchar(64);not null" json:"action"`
|
||||
Reason string `gorm:"column:reason;type:text;not null;default:''" json:"reason"`
|
||||
OperatorIdentity string `gorm:"column:operator_identity;type:varchar(36);not null;default:''" json:"operator_identity"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&SafEventDisposal{}) }
|
||||
func (table *SafEventDisposal) TableName() string { return "saf_event_disposal" }
|
||||
15
backend/api/internal/models/saf_inspection.go
Normal file
15
backend/api/internal/models/saf_inspection.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// SafInspection 对应 saf_inspection,保存安检与复检记录。
|
||||
type SafInspection struct {
|
||||
Entity
|
||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"`
|
||||
StaffAccountID uint64 `gorm:"column:staff_account_id;not null;index" json:"staff_account_id"`
|
||||
Result string `gorm:"column:result;type:varchar(32);not null" json:"result"`
|
||||
EvidenceURI string `gorm:"column:evidence_uri;type:varchar(512);not null;default:''" json:"evidence_uri"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&SafInspection{}) }
|
||||
func (table *SafInspection) TableName() string { return "saf_inspection" }
|
||||
16
backend/api/internal/models/saf_rule.go
Normal file
16
backend/api/internal/models/saf_rule.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// SafRule 对应 saf_rule,保存安全规则。
|
||||
type SafRule struct {
|
||||
Entity
|
||||
RuleCode string `gorm:"column:rule_code;type:varchar(64);not null;uniqueIndex" json:"rule_code"`
|
||||
VersionNo int `gorm:"column:version_no;not null;default:1" json:"version_no"`
|
||||
Threshold string `gorm:"column:threshold;type:jsonb;not null;default:'{}'" json:"threshold"`
|
||||
Action string `gorm:"column:action;type:varchar(64);not null" json:"action"`
|
||||
GrayScope string `gorm:"column:gray_scope;type:jsonb;not null;default:'{}'" json:"gray_scope"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&SafRule{}) }
|
||||
func (table *SafRule) TableName() string { return "saf_rule" }
|
||||
22
backend/api/internal/models/staff_account.go
Normal file
22
backend/api/internal/models/staff_account.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// StaffAccount 对应 staff_account,是服务人员唯一的档案和 App 登录账户。
|
||||
type StaffAccount struct {
|
||||
Entity
|
||||
Username string `gorm:"column:username;type:varchar(64);not null;uniqueIndex" json:"username"` // 登录名称
|
||||
PasswordHash string `gorm:"column:password_hash;type:varchar(255);not null" json:"-"` // 密码哈希
|
||||
Name string `gorm:"column:name;type:varchar(64);not null" json:"name"` // 人员姓名
|
||||
Phone string `gorm:"column:phone;type:varchar(32);not null;default:'';index" json:"phone"` // 联系手机号
|
||||
Avatar string `gorm:"column:avatar;type:varchar(512);not null;default:''" json:"avatar"` // 头像资源地址
|
||||
RoleCode string `gorm:"column:role_code;type:varchar(64);not null;default:''" json:"role_code"` // 服务角色编码
|
||||
GasBasicID uint64 `gorm:"column:gas_basic_id;not null;default:0;index" json:"gas_basic_id"` // 所属可燃气体站主键
|
||||
DeliveryBasicID uint64 `gorm:"column:delivery_basic_id;not null;default:0;index" json:"delivery_basic_id"` // 所属配送点主键
|
||||
WorkStatus string `gorm:"column:work_status;type:varchar(32);not null;default:'off_duty'" json:"work_status"` // 在岗接单状态
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&StaffAccount{}) }
|
||||
|
||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||
func (table *StaffAccount) TableName() string { return "staff_account" }
|
||||
18
backend/api/internal/models/staff_credential.go
Normal file
18
backend/api/internal/models/staff_credential.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StaffCredential 对应 staff_credential,保存人员资质。
|
||||
type StaffCredential struct {
|
||||
Entity
|
||||
StaffAccountID uint64 `gorm:"column:staff_account_id;not null;index" json:"staff_account_id"`
|
||||
CredentialType string `gorm:"column:credential_type;type:varchar(64);not null" json:"credential_type"`
|
||||
CredentialNo string `gorm:"column:credential_no;type:varchar(128);not null;default:''" json:"credential_no"`
|
||||
ExpiredAt *time.Time `gorm:"column:expired_at;type:timestamptz" json:"expired_at"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&StaffCredential{}) }
|
||||
func (table *StaffCredential) TableName() string { return "staff_credential" }
|
||||
19
backend/api/internal/models/user_account.go
Normal file
19
backend/api/internal/models/user_account.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// UserAccount 对应 user_account,是业主客户唯一的档案和用户端登录账户。
|
||||
type UserAccount struct {
|
||||
Entity
|
||||
Username string `gorm:"column:username;type:varchar(64);not null;uniqueIndex" json:"username"` // 登录名称
|
||||
PasswordHash string `gorm:"column:password_hash;type:varchar(255);not null" json:"-"` // 密码哈希
|
||||
Name string `gorm:"column:name;type:varchar(64);not null" json:"name"` // 客户姓名
|
||||
Phone string `gorm:"column:phone;type:varchar(32);not null;default:'';index" json:"phone"` // 联系手机号
|
||||
Avatar string `gorm:"column:avatar;type:varchar(512);not null;default:''" json:"avatar"` // 头像资源地址
|
||||
RealName string `gorm:"column:real_name;type:varchar(64);not null;default:''" json:"real_name"` // 实名认证名称
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&UserAccount{}) }
|
||||
|
||||
// TableName 返回与模型、文件名一致的单数数据表名。
|
||||
func (table *UserAccount) TableName() string { return "user_account" }
|
||||
16
backend/api/internal/models/user_address.go
Normal file
16
backend/api/internal/models/user_address.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// UserAddress 对应 user_address,保存用户地址。
|
||||
type UserAddress struct {
|
||||
Entity
|
||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"`
|
||||
Address string `gorm:"column:address;type:varchar(255);not null" json:"address"`
|
||||
Longitude string `gorm:"column:longitude;type:varchar(32);not null;default:''" json:"longitude"`
|
||||
Latitude string `gorm:"column:latitude;type:varchar(32);not null;default:''" json:"latitude"`
|
||||
IsDefault bool `gorm:"column:is_default;not null;default:false" json:"is_default"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&UserAddress{}) }
|
||||
func (table *UserAddress) TableName() string { return "user_address" }
|
||||
15
backend/api/internal/models/user_service_relation.go
Normal file
15
backend/api/internal/models/user_service_relation.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// UserServiceRelation 对应 user_service_relation,保存用户服务归属快照。
|
||||
type UserServiceRelation struct {
|
||||
Entity
|
||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"`
|
||||
GasBasicID uint64 `gorm:"column:gas_basic_id;not null;default:0;index" json:"gas_basic_id"`
|
||||
DeliveryBasicID uint64 `gorm:"column:delivery_basic_id;not null;default:0;index" json:"delivery_basic_id"`
|
||||
StaffAccountID uint64 `gorm:"column:staff_account_id;not null;default:0;index" json:"staff_account_id"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&UserServiceRelation{}) }
|
||||
func (table *UserServiceRelation) TableName() string { return "user_service_relation" }
|
||||
15
backend/api/internal/models/wallet.go
Normal file
15
backend/api/internal/models/wallet.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// Wallet 对应 wallet,保存余额账户。
|
||||
type Wallet struct {
|
||||
Entity
|
||||
OwnerType string `gorm:"column:owner_type;type:varchar(32);not null" json:"owner_type"`
|
||||
OwnerID uint64 `gorm:"column:owner_id;not null;index" json:"owner_id"`
|
||||
BalanceAmount int64 `gorm:"column:balance_amount;not null;default:0" json:"balance_amount"`
|
||||
FrozenAmount int64 `gorm:"column:frozen_amount;not null;default:0" json:"frozen_amount"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&Wallet{}) }
|
||||
func (table *Wallet) TableName() string { return "wallet" }
|
||||
16
backend/api/internal/models/wallet_ledger.go
Normal file
16
backend/api/internal/models/wallet_ledger.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// WalletLedger 对应 wallet_ledger,保存不可变资金流水。
|
||||
type WalletLedger struct {
|
||||
Entity
|
||||
WalletID uint64 `gorm:"column:wallet_id;not null;index" json:"wallet_id"`
|
||||
Amount int64 `gorm:"column:amount;not null" json:"amount"`
|
||||
Direction string `gorm:"column:direction;type:varchar(16);not null" json:"direction"`
|
||||
BalanceAfter int64 `gorm:"column:balance_after;not null" json:"balance_after"`
|
||||
ReferenceIdentity string `gorm:"column:reference_identity;type:varchar(36);not null;default:'';index" json:"reference_identity"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&WalletLedger{}) }
|
||||
func (table *WalletLedger) TableName() string { return "wallet_ledger" }
|
||||
14
backend/api/internal/models/wallet_recharge.go
Normal file
14
backend/api/internal/models/wallet_recharge.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// WalletRecharge 对应 wallet_recharge,保存充值记录。
|
||||
type WalletRecharge struct {
|
||||
Entity
|
||||
WalletID uint64 `gorm:"column:wallet_id;not null;index" json:"wallet_id"`
|
||||
Amount int64 `gorm:"column:amount;not null" json:"amount"`
|
||||
Channel string `gorm:"column:channel;type:varchar(32);not null" json:"channel"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&WalletRecharge{}) }
|
||||
func (table *WalletRecharge) TableName() string { return "wallet_recharge" }
|
||||
14
backend/api/internal/models/wallet_withdrawal.go
Normal file
14
backend/api/internal/models/wallet_withdrawal.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// WalletWithdrawal 对应 wallet_withdrawal,保存提现记录。
|
||||
type WalletWithdrawal struct {
|
||||
Entity
|
||||
WalletID uint64 `gorm:"column:wallet_id;not null;index" json:"wallet_id"`
|
||||
Amount int64 `gorm:"column:amount;not null" json:"amount"`
|
||||
BankAccountMasked string `gorm:"column:bank_account_masked;type:varchar(128);not null;default:''" json:"bank_account_masked"`
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&WalletWithdrawal{}) }
|
||||
func (table *WalletWithdrawal) TableName() string { return "wallet_withdrawal" }
|
||||
82
backend/api/internal/routers/platform.go
Normal file
82
backend/api/internal/routers/platform.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/middleware"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/platform"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterPlatform 注册 /heqi/platform/v1 前缀下的平台总后台路由。
|
||||
func RegisterPlatform(serviceKey string, engine *gin.Engine) {
|
||||
basePath := fmt.Sprintf("/%s/platform/v1", serviceKey)
|
||||
anonymous := engine.Group(basePath)
|
||||
anonymous.GET("/ping/hello", platform.PingHello)
|
||||
anonymous.POST("/auth/login", platform.Login)
|
||||
|
||||
protected := engine.Group(basePath)
|
||||
protected.Use(middleware.JwtAuth(true))
|
||||
protected.GET("/auth/profile", platform.CurrentProfile)
|
||||
protected.PUT("/auth/password", platform.ChangePassword)
|
||||
protected.GET("/dashboard/overview", platform.DashboardOverview)
|
||||
|
||||
registerGasRoute(protected)
|
||||
registerDeliveryRoute(protected)
|
||||
registerStaffRoute(protected)
|
||||
registerUserRoute(protected)
|
||||
registerPlatformRoute(protected)
|
||||
}
|
||||
|
||||
func registerGasRoute(group *gin.RouterGroup) {
|
||||
resource := group.Group("/gas/gas_basic")
|
||||
resource.GET("", platform.ListGasBasic)
|
||||
resource.POST("", platform.CreateGasBasic)
|
||||
resource.GET("/:identity", platform.GetGasBasic)
|
||||
resource.PUT("/:identity", platform.UpdateGasBasic)
|
||||
resource.PATCH("/:identity/status", func(ctx *gin.Context) { platform.UpdateRecordStatus(ctx, &models.GasBasic{}) })
|
||||
resource.DELETE("/:identity", func(ctx *gin.Context) { platform.ArchiveRecord(ctx, &models.GasBasic{}) })
|
||||
}
|
||||
|
||||
func registerDeliveryRoute(group *gin.RouterGroup) {
|
||||
resource := group.Group("/delivery/delivery_basic")
|
||||
resource.GET("", platform.ListDeliveryBasic)
|
||||
resource.POST("", platform.CreateDeliveryBasic)
|
||||
resource.GET("/:identity", platform.GetDeliveryBasic)
|
||||
resource.PUT("/:identity", platform.UpdateDeliveryBasic)
|
||||
resource.PATCH("/:identity/status", func(ctx *gin.Context) { platform.UpdateRecordStatus(ctx, &models.DeliveryBasic{}) })
|
||||
resource.DELETE("/:identity", func(ctx *gin.Context) { platform.ArchiveRecord(ctx, &models.DeliveryBasic{}) })
|
||||
}
|
||||
|
||||
func registerStaffRoute(group *gin.RouterGroup) {
|
||||
resource := group.Group("/staff")
|
||||
resource.GET("/account", platform.ListStaff)
|
||||
resource.POST("/account", platform.CreateStaff)
|
||||
resource.GET("/:identity", platform.GetStaff)
|
||||
resource.PUT("/:identity", platform.UpdateStaff)
|
||||
resource.PATCH("/:identity/status", func(ctx *gin.Context) { platform.UpdateRecordStatus(ctx, &models.StaffAccount{}) })
|
||||
resource.DELETE("/:identity", func(ctx *gin.Context) { platform.ArchiveRecord(ctx, &models.StaffAccount{}) })
|
||||
}
|
||||
|
||||
func registerUserRoute(group *gin.RouterGroup) {
|
||||
resource := group.Group("/user")
|
||||
resource.GET("/account", platform.ListUser)
|
||||
resource.POST("/account", platform.CreateUser)
|
||||
resource.GET("/:identity", platform.GetUser)
|
||||
resource.PUT("/:identity", platform.UpdateUser)
|
||||
resource.PATCH("/:identity/status", func(ctx *gin.Context) { platform.UpdateRecordStatus(ctx, &models.UserAccount{}) })
|
||||
resource.DELETE("/:identity", func(ctx *gin.Context) { platform.ArchiveRecord(ctx, &models.UserAccount{}) })
|
||||
}
|
||||
|
||||
func registerPlatformRoute(group *gin.RouterGroup) {
|
||||
group.GET("/platform/platfrom_account", platform.ListPlatfromAccount)
|
||||
role := group.Group("/platform/platform_role")
|
||||
role.GET("", platform.ListPlatformRole)
|
||||
role.POST("", platform.CreatePlatformRole)
|
||||
role.GET("/:identity", platform.GetPlatformRole)
|
||||
role.PUT("/:identity", platform.UpdatePlatformRole)
|
||||
role.PATCH("/:identity/status", platform.UpdatePlatformRoleStatus)
|
||||
role.DELETE("/:identity", platform.ArchivePlatformRole)
|
||||
group.GET("/platform/platform_menu", platform.ListPlatformMenu)
|
||||
}
|
||||
@@ -1,33 +1,10 @@
|
||||
// Package routers 注册与 sample/server 一致的匿名和 JWT 受保护路由组。
|
||||
// Package routers 提供 API 路由注册入口。
|
||||
package routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
"git.apinb.com/bsm-sdk/core/middleware"
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/platform"
|
||||
"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", platform.PingHello)
|
||||
anonymous.POST("/auth/login", platform.Login)
|
||||
|
||||
protected := engine.Group(v1Key)
|
||||
protected.Use(middleware.JwtAuth(true))
|
||||
{
|
||||
protected.GET("/auth/profile", platform.CurrentProfile)
|
||||
protected.PUT("/auth/password", platform.ChangePassword)
|
||||
protected.GET("/dashboard/overview", platform.DashboardOverview)
|
||||
gasStationGroup := protected.Group("/organization/org_gas_station")
|
||||
gasStationGroup.POST("", platform.CreateOrgGasStation)
|
||||
gasStationGroup.GET("", platform.ListOrgGasStation)
|
||||
protected.GET("/organization/org_delivery_point", platform.ListOrgDeliveryPoint)
|
||||
protected.GET("/organization/org_service_person", platform.ListOrgServicePerson)
|
||||
protected.GET("/identity/idn_account", platform.ListIdnAccount)
|
||||
protected.GET("/safety/saf_event", platform.ListSafEvent)
|
||||
}
|
||||
// Register 注册路由。
|
||||
func Register(serviceKey string, engine *gin.Engine) {
|
||||
RegisterPlatform(serviceKey, engine)
|
||||
registerUploadRoute(serviceKey, engine)
|
||||
}
|
||||
|
||||
11
backend/api/internal/routers/upload.go
Normal file
11
backend/api/internal/routers/upload.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/upload"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// registerUploadRoute 注册已认证的文件上传接口。
|
||||
func registerUploadRoute(serviceKey string, engine *gin.Engine) {
|
||||
engine.POST("/upload/file", upload.UploadFile)
|
||||
}
|
||||
Reference in New Issue
Block a user