Files
platforms/backend/api/internal/logic/platform/platform.go

185 lines
5.1 KiB
Go
Raw Normal View History

// Package platform 提供平台总后台的同步 HTTP 业务逻辑。
package platform
import (
2026-07-27 00:18:42 +08:00
"errors"
"reflect"
"strings"
2026-07-27 00:18:42 +08:00
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/bsm-sdk/core/utils"
2026-07-27 00:18:42 +08:00
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
2026-07-27 00:18:42 +08:00
"gorm.io/gorm"
)
2026-07-27 00:18:42 +08:00
// 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
}
updateAllowedByIdentity(ctx, model, gin.H{"status": request.Status}, []string{"status"})
}
2026-07-27 00:18:42 +08:00
// ArchiveRecord 通过 archived 状态实现逻辑删除,不物理删除主数据。
func ArchiveRecord(ctx *gin.Context, model any) {
updateAllowedByIdentity(ctx, model, archiveValues(), []string{"status"})
}
2026-07-27 00:18:42 +08:00
func newEntity(status string) models.Entity {
return models.Entity{Identity: models.NewIdentity(), Status: status, Version: 1}
}
2026-07-27 00:18:42 +08:00
func listPage[T any](ctx *gin.Context) {
page, size := pageSize(ctx)
2026-07-27 00:18:42 +08:00
var list []T
var total int64
model := new(T)
databaseQuery := applyKeywordFilter(ctx, impl.DBService.Model(model), model)
2026-07-27 00:18:42 +08:00
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
}
response, err := publicResourceResponse(list)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"total": total, "list": response})
2026-07-27 00:18:42 +08:00
}
var keywordExcludedColumns = map[string]bool{
"identity": true, "status": true, "password_hash": true,
"longitude": true, "latitude": true, "payload": true,
"before_data": true, "after_data": true,
}
func applyKeywordFilter(ctx *gin.Context, query *gorm.DB, model any) *gorm.DB {
keyword := strings.ToLower(strings.TrimSpace(ctx.Query("keyword")))
if keyword == "" {
return query
}
columns := keywordColumns(model)
if len(columns) == 0 {
return query
}
conditions := make([]string, 0, len(columns))
arguments := make([]any, 0, len(columns))
for _, column := range columns {
conditions = append(conditions, `LOWER("`+column+`") LIKE ?`)
arguments = append(arguments, "%"+keyword+"%")
}
return query.Where("("+strings.Join(conditions, " OR ")+")", arguments...)
}
func keywordColumns(model any) []string {
modelType := reflect.TypeOf(model)
for modelType.Kind() == reflect.Pointer {
modelType = modelType.Elem()
}
columns := make([]string, 0)
for index := 0; index < modelType.NumField(); index++ {
field := modelType.Field(index)
if field.Anonymous || field.Type.Kind() != reflect.String {
continue
}
column := gormColumn(field.Tag.Get("gorm"))
if column != "" && !keywordExcludedColumns[column] {
columns = append(columns, column)
}
}
return columns
}
func gormColumn(tag string) string {
for _, part := range strings.Split(tag, ";") {
if strings.HasPrefix(part, "column:") {
return strings.TrimPrefix(part, "column:")
}
}
return ""
}
2026-07-27 00:18:42 +08:00
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
}
response, err := publicResourceResponse(data)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, response)
2026-07-27 00:18:42 +08:00
}
func updateAllowedByIdentity(ctx *gin.Context, model any, values map[string]any, allowedFields []string) {
values = filterFields(values, allowedFields)
if len(values) == 0 {
infra.Response.Success(ctx, gin.H{"updated": false})
return
}
result := impl.DBService.Model(model).Where("identity = ?", ctx.Param("identity")).Updates(values)
if result.Error != nil {
infra.Response.Error(ctx, result.Error)
return
}
if result.RowsAffected == 0 {
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
return
}
infra.Response.Success(ctx, gin.H{"updated": true})
}
2026-07-27 00:18:42 +08:00
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
}
if result.RowsAffected == 0 {
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
return
}
infra.Response.Success(ctx, gin.H{"updated": true})
}
func respondRecordError(ctx *gin.Context, err error) {
if errors.Is(err, gorm.ErrRecordNotFound) {
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
return
}
infra.Response.Error(ctx, err)
}
func pageSize(ctx *gin.Context) (int, int) {
page := utils.String2Int(ctx.DefaultQuery("page", "1"))
size := utils.String2Int(ctx.DefaultQuery("size", "20"))
if page < 1 {
page = 1
}
if size < 1 || size > 100 {
size = 20
}
return page, size
}
func maskPhone(phone string) string {
if len(phone) < 7 {
return "***"
}
return phone[:3] + "****" + phone[len(phone)-4:]
}