压缩历史至 95081aff

This commit is contained in:
ryan
2026-06-08 20:34:27 +08:00
commit 8a782525de
435 changed files with 71146 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package admin
const (
AdminRequired = "未经授权访问"
)
+50
View File
@@ -0,0 +1,50 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package admin
import (
"net/http"
"github.com/linux-do/credit/internal/logger"
"github.com/linux-do/credit/internal/model"
"github.com/linux-do/credit/internal/otel_trace"
"github.com/linux-do/credit/internal/util"
"github.com/gin-gonic/gin"
"github.com/linux-do/credit/internal/apps/oauth"
)
func LoginAdminRequired() gin.HandlerFunc {
return func(c *gin.Context) {
// init trace
ctx, span := otel_trace.Start(c.Request.Context(), "LoginAdminRequired")
defer span.End()
user, _ := util.GetFromContext[*model.User](c, oauth.UserObjKey)
if !user.IsAdmin {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error_msg": AdminRequired, "data": nil})
return
}
// log
logger.InfoF(ctx, "[LoginAdminRequired] %d %s", user.ID, user.Username)
// next
c.Next()
}
}
+24
View File
@@ -0,0 +1,24 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package system_config
const (
SystemConfigNotFound = "系统配置不存在"
ConfigKeyRequired = "配置键不能为空"
ConfigValueRequired = "配置值不能为空"
ConfigKeyExists = "配置键已存在"
)
@@ -0,0 +1,217 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package system_config
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/linux-do/credit/internal/db"
"github.com/linux-do/credit/internal/model"
"github.com/linux-do/credit/internal/util"
"gorm.io/gorm"
)
// CreateSystemConfigRequest 创建系统配置请求
type CreateSystemConfigRequest struct {
Key string `json:"key" binding:"required,max=64"`
Value string `json:"value" binding:"required,max=255"`
Description string `json:"description" binding:"max=255"`
}
// UpdateSystemConfigRequest 更新系统配置请求
type UpdateSystemConfigRequest struct {
Value string `json:"value" binding:"required,max=255"`
Description string `json:"description" binding:"max=255"`
}
// CreateSystemConfig 创建系统配置
// @Tags admin
// @Accept json
// @Produce json
// @Param request body CreateSystemConfigRequest true "request body"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/system-configs [post]
func CreateSystemConfig(c *gin.Context) {
var req CreateSystemConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
// 检查配置键是否已存在
var existing model.SystemConfig
if err := db.DB(c.Request.Context()).Where("key = ?", req.Key).First(&existing).Error; err == nil {
c.JSON(http.StatusBadRequest, util.Err(ConfigKeyExists))
return
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
config := model.SystemConfig{
Key: req.Key,
Value: req.Value,
Description: req.Description,
}
if err := db.DB(c.Request.Context()).Transaction(func(tx *gorm.DB) error {
// 创建配置
if err := tx.Create(&config).Error; err != nil {
return err
}
if err := db.HSetJSON(c.Request.Context(), model.SystemConfigRedisHashKey, req.Key, &config); err != nil {
return err
}
return nil
}); err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OKNil())
}
// ListSystemConfigs 获取系统配置列表
// @Tags admin
// @Produce json
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/system-configs [get]
func ListSystemConfigs(c *gin.Context) {
var configs []model.SystemConfig
if err := db.DB(c.Request.Context()).
Order("created_at DESC").
Find(&configs).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OK(configs))
}
// GetSystemConfig 获取单个系统配置
// @Tags admin
// @Produce json
// @Param key path string true "配置键"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/system-configs/{key} [get]
func GetSystemConfig(c *gin.Context) {
var config model.SystemConfig
if err := db.DB(c.Request.Context()).Where("key = ?", c.Param("key")).First(&config).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, util.Err(SystemConfigNotFound))
} else {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
}
return
}
c.JSON(http.StatusOK, util.OK(config))
}
// UpdateSystemConfig 更新系统配置
// @Tags admin
// @Accept json
// @Produce json
// @Param key path string true "配置键"
// @Param request body UpdateSystemConfigRequest true "request body"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/system-configs/{key} [put]
func UpdateSystemConfig(c *gin.Context) {
var req UpdateSystemConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
key := c.Param("key")
// 检查配置是否存在
var config model.SystemConfig
if err := db.DB(c.Request.Context()).Where("key = ?", key).First(&config).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, util.Err(SystemConfigNotFound))
} else {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
}
return
}
if err := db.DB(c.Request.Context()).Transaction(func(tx *gorm.DB) error {
// 更新配置
if err := tx.Model(&config).
Updates(map[string]interface{}{
"value": req.Value,
"description": req.Description,
}).Error; err != nil {
return err
}
if err := db.HSetJSON(c.Request.Context(), model.SystemConfigRedisHashKey, key, &config); err != nil {
return err
}
return nil
}); err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OKNil())
}
// DeleteSystemConfig 删除系统配置
// @Tags admin
// @Produce json
// @Param key path string true "配置键"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/system-configs/{key} [delete]
func DeleteSystemConfig(c *gin.Context) {
key := c.Param("key")
// 检查配置是否存在
var config model.SystemConfig
if err := db.DB(c.Request.Context()).Where("key = ?", key).First(&config).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, util.Err(SystemConfigNotFound))
} else {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
}
return
}
if err := db.DB(c.Request.Context()).Transaction(func(tx *gorm.DB) error {
// 删除配置
if err := tx.Delete(&config).Error; err != nil {
return err
}
if err := db.Redis.HDel(c.Request.Context(), db.PrefixedKey(model.SystemConfigRedisHashKey), key).Err(); err != nil {
return err
}
return nil
}); err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OKNil())
}
+24
View File
@@ -0,0 +1,24 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package task
const (
InvalidTaskType = "无效的任务类型"
InvalidTimeRange = "无效的时间范围"
TaskDispatchFailed = "任务下发失败"
UserIDRequired = "用户ID必填"
)
+120
View File
@@ -0,0 +1,120 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package task
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/hibiken/asynq"
"github.com/linux-do/credit/internal/task"
"github.com/linux-do/credit/internal/task/scheduler"
"github.com/linux-do/credit/internal/util"
)
// ListTaskTypes 获取支持的任务类型列表
// @Tags admin
// @Produce json
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/tasks/types [get]
func ListTaskTypes(c *gin.Context) {
c.JSON(http.StatusOK, util.OK(task.DispatchableTasks))
}
// DispatchTaskRequest 下发任务请求
type DispatchTaskRequest struct {
TaskType string `json:"task_type" binding:"required"`
StartTime *time.Time `json:"start_time"`
EndTime *time.Time `json:"end_time"`
UserID *uint64 `json:"user_id"`
}
// DispatchTask 下发任务
// @Tags admin
// @Accept json
// @Produce json
// @Param request body DispatchTaskRequest true "request body"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/tasks/dispatch [post]
func DispatchTask(c *gin.Context) {
var req DispatchTaskRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
meta := task.GetTaskMeta(req.TaskType)
if meta == nil {
c.JSON(http.StatusBadRequest, util.Err(InvalidTaskType))
return
}
var taskInfo *asynq.Task
var taskID string
switch req.TaskType {
case task.TaskTypeOrderSync:
if req.StartTime != nil && req.EndTime != nil {
if req.EndTime.Before(*req.StartTime) {
c.JSON(http.StatusBadRequest, util.Err(InvalidTimeRange))
return
}
payload, _ := json.Marshal(map[string]interface{}{
"start_time": req.StartTime,
"end_time": req.EndTime,
})
taskInfo = asynq.NewTask(meta.AsynqTask, payload)
taskID = fmt.Sprintf("manual_%s_%s_%s", req.TaskType,
req.StartTime.Format("20060102150405"),
req.EndTime.Format("20060102150405"))
} else {
taskInfo = asynq.NewTask(meta.AsynqTask, nil)
taskID = fmt.Sprintf("manual_%s", req.TaskType)
}
case task.TaskTypeUserGamification:
if req.UserID == nil || *req.UserID == 0 {
taskInfo = asynq.NewTask(task.UpdateUserGamificationScoresTask, nil)
taskID = fmt.Sprintf("manual_%s", req.TaskType)
} else {
payload, _ := json.Marshal(map[string]interface{}{
"user_id": *req.UserID,
})
taskInfo = asynq.NewTask(meta.AsynqTask, payload)
taskID = fmt.Sprintf("manual_%s_user_%d", req.TaskType, *req.UserID)
}
default:
taskInfo = asynq.NewTask(meta.AsynqTask, nil)
taskID = fmt.Sprintf("manual_%s", req.TaskType)
}
_, err := scheduler.AsynqClient.Enqueue(
taskInfo,
asynq.TaskID(taskID),
asynq.MaxRetry(meta.MaxRetry),
asynq.Queue(meta.Queue),
)
if err != nil {
c.JSON(http.StatusInternalServerError, util.Err(fmt.Sprintf("%s: %v", TaskDispatchFailed, err)))
return
}
c.JSON(http.StatusOK, util.OKNil())
}
+23
View File
@@ -0,0 +1,23 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package user
const (
userNotFound = "用户不存在"
cannotDisable = "不能禁用管理员用户"
updateUserFailed = "更新用户状态失败"
)
+171
View File
@@ -0,0 +1,171 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package user
import (
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/linux-do/credit/internal/db"
"github.com/linux-do/credit/internal/model"
"github.com/linux-do/credit/internal/util"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// listUsersRequest 用户列表查询请求
type listUsersRequest struct {
Page int `form:"page" binding:"min=1"`
PageSize int `form:"page_size" binding:"min=1,max=100"`
UserID *uint64 `form:"user_id" binding:"omitempty,gt=0"`
Username string `form:"username"`
}
type user struct {
ID uint64 `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
AvatarUrl string `json:"avatar_url"`
TrustLevel model.TrustLevel `json:"trust_level"`
PayScore int64 `json:"pay_score"`
TotalReceive decimal.Decimal `json:"total_receive"`
TotalPayment decimal.Decimal `json:"total_payment"`
TotalTransfer decimal.Decimal `json:"total_transfer"`
TotalCommunity decimal.Decimal `json:"total_community"`
CommunityBalance decimal.Decimal `json:"community_balance"`
AvailableBalance decimal.Decimal `json:"available_balance"`
IsActive bool `json:"is_active"`
IsAdmin bool `json:"is_admin"`
LastLoginAt time.Time `json:"last_login_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// listUsersResponse 用户列表响应
type listUsersResponse struct {
Users []user `json:"users"`
Total int64 `json:"total"`
}
// ListUsers 获取用户列表
// @Tags admin
// @Produce json
// @Param request query listUsersRequest true "查询参数"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/users [get]
func ListUsers(c *gin.Context) {
var req listUsersRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
var users []user
var total int64
query := db.DB(c.Request.Context()).Table("users")
username := strings.TrimSpace(req.Username)
if req.UserID != nil {
query = query.Where("id = ?", *req.UserID)
}
if username != "" {
query = query.Where("username LIKE ?", username+"%")
}
if err := query.Count(&total).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
offset := (req.Page - 1) * req.PageSize
if err := query.
Select("id, username, nickname, avatar_url, trust_level, pay_score, " +
"total_receive, total_payment, total_transfer, total_community, " +
"community_balance, available_balance, is_active, is_admin, " +
"last_login_at, created_at, updated_at").
Order("id DESC").
Offset(offset).
Limit(req.PageSize).
Find(&users).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OK(listUsersResponse{
Users: users,
Total: total,
}))
}
// updateUserStatusRequest 更新用户状态请求
type updateUserStatusRequest struct {
IsActive bool `json:"is_active"`
}
// UpdateUserStatus 更新用户状态(启用/禁用)
// @Tags admin
// @Produce json
// @Param id path int true "用户ID"
// @Param request body updateUserStatusRequest true "状态"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/users/{id}/status [put]
func UpdateUserStatus(c *gin.Context) {
var req updateUserStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
id := c.Param("id")
var targetUser struct {
ID uint64 `gorm:"column:id"`
IsAdmin bool `gorm:"column:is_admin"`
}
if err := db.DB(c.Request.Context()).
Table("users").
Select("id, is_admin").
Where("id = ?", id).
First(&targetUser).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, util.Err(userNotFound))
return
}
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
if !req.IsActive && targetUser.IsAdmin {
c.JSON(http.StatusForbidden, util.Err(cannotDisable))
return
}
if err := db.DB(c.Request.Context()).
Table("users").
Where("id = ?", id).
Update("is_active", req.IsActive).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(updateUserFailed))
return
}
c.JSON(http.StatusOK, util.OKNil())
}
@@ -0,0 +1,25 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package user_pay_config
const (
UserPayConfigNotFound = "支付配置不存在"
LevelRequired = "等级不能为空"
MinScoreRequired = "最小分数不能为空"
ScoreRangeInvalid = "分数范围无效:最大分数必须大于最小分数"
LevelExists = "等级已存在"
)
@@ -0,0 +1,211 @@
/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package user_pay_config
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/linux-do/credit/internal/db"
"github.com/linux-do/credit/internal/model"
"github.com/linux-do/credit/internal/util"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// CreateUserPayConfigRequest 创建支付配置请求
type CreateUserPayConfigRequest struct {
Level model.PayLevel `json:"level"`
MinScore int64 `json:"min_score"`
MaxScore *int64 `json:"max_score" binding:"omitempty,gtfield=MinScore"`
DailyLimit *int64 `json:"daily_limit"`
FeeRate decimal.Decimal `json:"fee_rate" binding:"required"`
ScoreRate decimal.Decimal `json:"score_rate" binding:"required"`
DistributeRate decimal.Decimal `json:"distribute_rate" binding:"required"`
}
// UpdateUserPayConfigRequest 更新支付配置请求
type UpdateUserPayConfigRequest struct {
MinScore int64 `json:"min_score"`
MaxScore *int64 `json:"max_score" binding:"omitempty,gtfield=MinScore"`
DailyLimit *int64 `json:"daily_limit"`
FeeRate decimal.Decimal `json:"fee_rate" binding:"required"`
ScoreRate decimal.Decimal `json:"score_rate" binding:"required"`
DistributeRate decimal.Decimal `json:"distribute_rate" binding:"required"`
}
// CreateUserPayConfig 创建支付配置
// @Tags admin
// @Accept json
// @Produce json
// @Param request body CreateUserPayConfigRequest true "request body"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/user-pay-configs [post]
func CreateUserPayConfig(c *gin.Context) {
var req CreateUserPayConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
// 验证费率和积分倍率
if err := util.ValidateRates(req.FeeRate, req.ScoreRate, req.DistributeRate); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
// 检查等级是否已存在
var existing model.UserPayConfig
if err := db.DB(c.Request.Context()).Where("level = ?", req.Level).First(&existing).Error; err == nil {
c.JSON(http.StatusBadRequest, util.Err(LevelExists))
return
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
config := model.UserPayConfig{
Level: req.Level,
MinScore: req.MinScore,
MaxScore: req.MaxScore,
DailyLimit: req.DailyLimit,
FeeRate: req.FeeRate,
ScoreRate: req.ScoreRate,
DistributeRate: req.DistributeRate,
}
if err := db.DB(c.Request.Context()).Create(&config).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OK(config))
}
// ListUserPayConfigs 获取支付配置列表
// @Tags admin
// @Produce json
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/user-pay-configs [get]
func ListUserPayConfigs(c *gin.Context) {
var configs []model.UserPayConfig
if err := db.DB(c.Request.Context()).
Order("min_score ASC").
Find(&configs).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OK(configs))
}
// GetUserPayConfig 获取单个支付配置
// @Tags admin
// @Produce json
// @Param id path string true "配置ID"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/user-pay-configs/{id} [get]
func GetUserPayConfig(c *gin.Context) {
var config model.UserPayConfig
if err := db.DB(c.Request.Context()).Where("id = ?", c.Param("id")).First(&config).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, util.Err(UserPayConfigNotFound))
} else {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
}
return
}
c.JSON(http.StatusOK, util.OK(config))
}
// UpdateUserPayConfig 更新支付配置
// @Tags admin
// @Accept json
// @Produce json
// @Param id path string true "配置ID"
// @Param request body UpdateUserPayConfigRequest true "request body"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/user-pay-configs/{id} [put]
func UpdateUserPayConfig(c *gin.Context) {
var req UpdateUserPayConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
// 验证费率和积分倍率
if err := util.ValidateRates(req.FeeRate, req.ScoreRate); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
// 检查配置是否存在
var config model.UserPayConfig
if err := db.DB(c.Request.Context()).Where("id = ?", c.Param("id")).First(&config).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, util.Err(UserPayConfigNotFound))
} else {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
}
return
}
// 更新配置
if err := db.DB(c.Request.Context()).
Model(&config).
Updates(map[string]interface{}{
"min_score": req.MinScore,
"max_score": req.MaxScore,
"fee_rate": req.FeeRate,
"score_rate": req.ScoreRate,
"daily_limit": req.DailyLimit,
"distribute_rate": req.DistributeRate,
}).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OKNil())
}
// DeleteUserPayConfig 删除支付配置
// @Tags admin
// @Produce json
// @Param id path string true "配置ID"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/admin/user-pay-configs/{id} [delete]
func DeleteUserPayConfig(c *gin.Context) {
// 检查配置是否存在
var config model.UserPayConfig
if err := db.DB(c.Request.Context()).Where("id = ?", c.Param("id")).First(&config).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, util.Err(UserPayConfigNotFound))
} else {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
}
return
}
if err := db.DB(c.Request.Context()).Delete(&config).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OKNil())
}