Files
SingBox-Gopanel/internal/handler/common.go
CN-JS-HuiBai 25fd919477
All checks were successful
build / build (api, amd64, linux) (push) Successful in -43s
build / build (api, arm64, linux) (push) Successful in -44s
build / build (api.exe, amd64, windows) (push) Successful in -43s
API功能性修复
2026-04-17 15:13:43 +08:00

294 lines
5.5 KiB
Go

package handler
import (
"encoding/json"
"net/http"
"sort"
"strconv"
"strings"
"time"
"xboard-go/internal/database"
"xboard-go/internal/model"
"github.com/gin-gonic/gin"
)
func Success(c *gin.Context, data any) {
c.JSON(http.StatusOK, gin.H{"data": data})
}
func SuccessMessage(c *gin.Context, message string, data any) {
c.JSON(http.StatusOK, gin.H{
"message": message,
"data": data,
})
}
func Fail(c *gin.Context, status int, message string) {
c.JSON(status, gin.H{"message": message})
}
func NotImplemented(endpoint string) gin.HandlerFunc {
return func(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{
"message": "not implemented yet",
"endpoint": endpoint,
})
}
}
func intFromAny(value any) int {
switch typed := value.(type) {
case int:
return typed
case int8:
return int(typed)
case int16:
return int(typed)
case int32:
return int(typed)
case int64:
return int(typed)
case float32:
return int(typed)
case float64:
return int(typed)
case json.Number:
parsed, _ := typed.Int64()
return int(parsed)
case string:
parsed, _ := strconv.Atoi(strings.TrimSpace(typed))
return parsed
default:
return 0
}
}
func stringFromAny(value any) string {
switch typed := value.(type) {
case string:
return typed
case json.Number:
return typed.String()
case float64:
return strconv.FormatFloat(typed, 'f', -1, 64)
case float32:
return strconv.FormatFloat(float64(typed), 'f', -1, 32)
case int:
return strconv.Itoa(typed)
case int64:
return strconv.FormatInt(typed, 10)
case bool:
if typed {
return "true"
}
return "false"
default:
return ""
}
}
func boolFromAny(value any) bool {
switch typed := value.(type) {
case bool:
return typed
case int:
return typed != 0
case int64:
return typed != 0
case float64:
return typed != 0
case string:
text := strings.TrimSpace(strings.ToLower(typed))
return text == "1" || text == "true" || text == "yes" || text == "on"
default:
return false
}
}
func intValue(value *int) any {
if value == nil {
return nil
}
return *value
}
func int64Value(value *int64) any {
if value == nil {
return nil
}
return *value
}
func stringValue(value *string) string {
if value == nil {
return ""
}
return *value
}
func unixTimeValue(value *time.Time) any {
if value == nil {
return nil
}
return value.Unix()
}
func marshalJSON(value any, fallbackEmptyArray bool) (*string, error) {
if value == nil {
if fallbackEmptyArray {
empty := "[]"
return &empty, nil
}
return nil, nil
}
if text, ok := value.(string); ok {
text = strings.TrimSpace(text)
if text == "" {
if fallbackEmptyArray {
empty := "[]"
return &empty, nil
}
return nil, nil
}
if json.Valid([]byte(text)) {
return &text, nil
}
}
payload, err := json.Marshal(value)
if err != nil {
return nil, err
}
text := string(payload)
return &text, nil
}
func sanitizePositiveIDs(ids []int) []int {
unique := make(map[int]struct{}, len(ids))
result := make([]int, 0, len(ids))
for _, id := range ids {
if id <= 0 {
continue
}
if _, exists := unique[id]; exists {
continue
}
unique[id] = struct{}{}
result = append(result, id)
}
sort.Ints(result)
return result
}
func loadServerGroupNameMap() map[int]string {
var groups []model.ServerGroup
_ = database.DB.Find(&groups).Error
result := make(map[int]string, len(groups))
for _, group := range groups {
result[group.ID] = group.Name
}
return result
}
func loadUserEmailMap(userIDs []int) map[int]string {
result := make(map[int]string)
if len(userIDs) == 0 {
return result
}
var users []model.User
if err := database.DB.Select("id", "email").Where("id IN ?", sanitizePositiveIDs(userIDs)).Find(&users).Error; err != nil {
return result
}
for _, user := range users {
result[user.ID] = user.Email
}
return result
}
func loadTicketMessageCountMap(ticketIDs []int) map[int]int64 {
result := make(map[int]int64)
if len(ticketIDs) == 0 {
return result
}
type ticketCount struct {
TicketID int
Total int64
}
var counts []ticketCount
if err := database.DB.Model(&model.TicketMessage{}).
Select("ticket_id, COUNT(*) AS total").
Where("ticket_id IN ?", sanitizePositiveIDs(ticketIDs)).
Group("ticket_id").
Scan(&counts).Error; err != nil {
return result
}
for _, item := range counts {
result[item.TicketID] = item.Total
}
return result
}
func parsePositiveInt(raw string, defaultValue int) int {
value, err := strconv.Atoi(strings.TrimSpace(raw))
if err != nil || value <= 0 {
return defaultValue
}
return value
}
func calculateLastPage(total int64, perPage int) int {
if perPage <= 0 {
return 1
}
last := int((total + int64(perPage) - 1) / int64(perPage))
if last == 0 {
return 1
}
return last
}
func formatUnixValue(value int64) string {
if value <= 0 {
return "-"
}
return time.Unix(value, 0).Format("2006-01-02 15:04:05")
}
func formatTimeValue(value *time.Time) string {
if value == nil {
return "-"
}
return value.Format("2006-01-02 15:04:05")
}
func getFetchParams(c *gin.Context) map[string]string {
params := make(map[string]string)
// 1. Get from Query parameters
for k, v := range c.Request.URL.Query() {
if len(v) > 0 {
params[k] = v[0]
}
}
// 2. Override with JSON body if applicable (for POST)
if c.Request.Method == http.MethodPost {
var body map[string]any
// Using ShouldBindJSON without erroring to allow fallback or partial data
if err := c.ShouldBindJSON(&body); err == nil {
for k, v := range body {
params[k] = stringFromAny(v)
}
}
}
return params
}