mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 23:16:37 +08:00
压缩历史至 95081aff
This commit is contained in:
@@ -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 = "未经授权访问"
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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必填"
|
||||
)
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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 = "更新用户状态失败"
|
||||
)
|
||||
@@ -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())
|
||||
}
|
||||
Reference in New Issue
Block a user