init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
131
webapp-back/admin/common.go
Normal file
131
webapp-back/admin/common.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gothinkster/golang-gin-realworld-example-app/common"
|
||||
"github.com/gothinkster/golang-gin-realworld-example-app/middleware"
|
||||
"github.com/gothinkster/golang-gin-realworld-example-app/models"
|
||||
)
|
||||
|
||||
// PaginationParams holds parsed ProTable pagination query params
|
||||
type PaginationParams struct {
|
||||
Current int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// ParsePagination reads ?current=N&pageSize=M from the request
|
||||
func ParsePagination(c *gin.Context) PaginationParams {
|
||||
current, _ := strconv.Atoi(c.DefaultQuery("current", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
|
||||
if current < 1 {
|
||||
current = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
return PaginationParams{Current: current, PageSize: pageSize}
|
||||
}
|
||||
|
||||
// Offset returns the DB offset for GORM queries
|
||||
func (p PaginationParams) Offset() int {
|
||||
return (p.Current - 1) * p.PageSize
|
||||
}
|
||||
|
||||
// OK responds with {success: true, data: ...}
|
||||
func OK(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": data})
|
||||
}
|
||||
|
||||
// OKList responds with {success: true, data: [...], total: N} — used by ProTable
|
||||
func OKList(c *gin.Context, data interface{}, total int64) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": data, "total": total})
|
||||
}
|
||||
|
||||
// Fail responds with {success: false, message: ...}
|
||||
func Fail(c *gin.Context, status int, msg string) {
|
||||
c.JSON(status, gin.H{"success": false, "message": msg})
|
||||
}
|
||||
|
||||
// flexTimeLayouts is the set of time formats accepted from the frontend.
|
||||
var flexTimeLayouts = []string{
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02T15:04",
|
||||
"2006-01-02 15:04",
|
||||
"2006-01-02",
|
||||
}
|
||||
|
||||
// BindJSONFlexTime works like c.ShouldBindJSON but also accepts non-RFC3339
|
||||
// time strings such as "2026-02-28 00:00:00" that Ant Design's DatePicker sends.
|
||||
// It reads the raw body, converts any date-like strings to RFC3339, then unmarshals.
|
||||
func BindJSONFlexTime(c *gin.Context, obj interface{}) error {
|
||||
body, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal(body, &raw); err != nil {
|
||||
return json.Unmarshal(body, obj) // fallback: let normal errors surface
|
||||
}
|
||||
|
||||
for key, val := range raw {
|
||||
var s string
|
||||
if err := json.Unmarshal(val, &s); err != nil {
|
||||
continue // not a string, skip
|
||||
}
|
||||
for _, layout := range flexTimeLayouts {
|
||||
if t, err := time.Parse(layout, s); err == nil {
|
||||
converted, _ := json.Marshal(t) // produces RFC3339
|
||||
raw[key] = converted
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fixed, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(fixed, obj)
|
||||
}
|
||||
|
||||
// LogOp writes a record to operation_logs table
|
||||
func LogOp(c *gin.Context, opType, targetType, action string, targetID *uint, payload interface{}) {
|
||||
db := common.GetDB()
|
||||
user, _ := middleware.GetCurrentUser(c)
|
||||
|
||||
var userID *uint
|
||||
if user != nil {
|
||||
uid := uint(user.UserID)
|
||||
userID = &uid
|
||||
}
|
||||
|
||||
var changesStr string
|
||||
if payload != nil {
|
||||
b, _ := json.Marshal(payload)
|
||||
changesStr = string(b)
|
||||
}
|
||||
|
||||
db.Create(&models.OperationLog{
|
||||
UserID: userID,
|
||||
OperationType: opType,
|
||||
TargetType: targetType,
|
||||
Action: action,
|
||||
TargetID: targetID,
|
||||
Changes: changesStr,
|
||||
IPAddress: c.ClientIP(),
|
||||
UserAgent: c.GetHeader("User-Agent"),
|
||||
Status: "success",
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user