feat():learning后台管理项目初始化

This commit is contained in:
yuj
2025-12-04 16:23:46 +08:00
parent 39886d50d2
commit 88e048f4d1
154 changed files with 28966 additions and 6 deletions

View File

@@ -0,0 +1,73 @@
package handlers
import (
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
)
// AuditLogHandler 审计日志处理器
type AuditLogHandler struct {
service services.AuditLogService
response *utils.Response
}
// NewAuditLogHandler 创建审计日志处理器
func NewAuditLogHandler(service services.AuditLogService) *AuditLogHandler {
return &AuditLogHandler{
service: service,
response: utils.NewResponse(),
}
}
// List 获取审计日志列表
func (h *AuditLogHandler) List(c *gin.Context) {
var req models.AuditLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 设置默认值
if req.Page < 1 {
req.Page = 1
}
if req.Size < 1 {
req.Size = 20
}
if req.SortBy == "" {
req.SortBy = "operation_time"
}
if req.SortOrder == "" {
req.SortOrder = "desc"
}
result, err := h.service.List(&req)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, result)
}
// GetByID 获取审计日志详情
func (h *AuditLogHandler) GetByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
log, err := h.service.GetByID(uint(id))
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, log)
}

View File

@@ -0,0 +1,633 @@
package handlers
import (
"reflect"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
)
type FinanceHandler struct {
response *utils.Response
}
func NewFinanceHandler() *FinanceHandler {
return &FinanceHandler{response: utils.NewResponse()}
}
func getPageParams(c *gin.Context) (int, int) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
if page < 1 {
page = 1
}
if size < 1 {
size = 20
}
offset := (page - 1) * size
return offset, size
}
func (h *FinanceHandler) ListSandboxRecords(c *gin.Context) {
offset, size := getPageParams(c)
// 兼容多种参数命名user/user_idproject/project_id
userID := c.DefaultQuery("user_id", c.Query("user"))
project := c.DefaultQuery("project", c.Query("project_id"))
start := c.Query("start")
end := c.Query("end")
res, err := services.ListSandboxRecords(offset, size, userID, project, start, end)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, res.List, res.Total, offset/size+1, size)
}
func (h *FinanceHandler) ListTokenUsages(c *gin.Context) {
offset, size := getPageParams(c)
userID := c.DefaultQuery("user_id", c.Query("user"))
project := c.DefaultQuery("project", c.Query("project_id"))
start := c.Query("start") // 对于token使用按day进行过滤
end := c.Query("end")
res, err := services.ListTokenUsages(offset, size, userID, project, start, end)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, res.List, res.Total, offset/size+1, size)
}
func (h *FinanceHandler) ListMCPUsages(c *gin.Context) {
offset, size := getPageParams(c)
userID := c.DefaultQuery("user_id", c.Query("user"))
project := c.DefaultQuery("project", c.Query("project_id"))
start := c.Query("start")
end := c.Query("end")
res, err := services.ListMCPUsages(offset, size, userID, project, start, end)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, res.List, res.Total, offset/size+1, size)
}
func (h *FinanceHandler) ListTransactionLogs(c *gin.Context) {
offset, size := getPageParams(c)
userID := c.Query("user_id")
orderID := c.Query("order_id")
txType := c.Query("type")
status := c.Query("status")
start := c.Query("start")
end := c.Query("end")
res, err := services.ListTransactionLogs(offset, size, userID, orderID, txType, status, start, end)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, res.List, res.Total, offset/size+1, size)
}
func (h *FinanceHandler) ListPaymentRecords(c *gin.Context) {
offset, size := getPageParams(c)
userID := c.Query("user_id")
orderID := c.Query("order_id")
paypalOrderID := c.Query("paypal_order_id")
status := c.Query("status")
refundStatus := c.Query("refund_status")
payerEmail := c.Query("payer_email")
start := c.Query("start")
end := c.Query("end")
res, err := services.ListPaymentRecords(offset, size, userID, orderID, paypalOrderID, status, refundStatus, payerEmail, start, end)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, res.List, res.Total, offset/size+1, size)
}
func (h *FinanceHandler) RefundPaymentRecord(c *gin.Context) {
var req struct {
OrderID string `json:"order_id"`
PayPalCaptureID string `json:"paypal_capture_id"`
Amount *int64 `json:"amount"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err := services.RefundPaymentRecord(req.OrderID, req.PayPalCaptureID, req.Amount)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "退款请求已提交"})
}
func (h *FinanceHandler) ListMcpAccountRechargeRecords(c *gin.Context) {
offset, size := getPageParams(c)
provider := c.Query("provider")
account := c.Query("account")
start := c.Query("start")
end := c.Query("end")
res, err := services.ListMcpAccountRechargeRecords(offset, size, provider, account, start, end)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, res.List, res.Total, offset/size+1, size)
}
func (h *FinanceHandler) CreateMcpAccountRechargeRecord(c *gin.Context) {
var req struct {
ProviderID string `json:"provider_id" binding:"required"`
Amount float64 `json:"amount" binding:"required,gt=0"`
RechargeDate string `json:"recharge_date" binding:"required"`
Remark string `json:"remark"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 获取当前操作人信息
var operatorID interface{}
operatorName := "系统管理员"
if user, exists := c.Get("user"); exists {
if userInfo, ok := user.(map[string]interface{}); ok {
// 尝试从userInfo中获取sub字段可能是UUID格式
if sub, ok := userInfo["sub"].(string); ok {
operatorID = sub
}
if name, ok := userInfo["name"].(string); ok {
operatorName = name
} else if email, ok := userInfo["email"].(string); ok {
operatorName = email
}
}
}
// 如果user中没有sub尝试从user_id获取
if operatorID == nil {
operatorID, _ = c.Get("user_id")
}
err := services.CreateMcpAccountRechargeRecord(
req.ProviderID,
req.Amount,
req.RechargeDate,
operatorID,
operatorName,
req.Remark,
)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "创建成功"})
}
func (h *FinanceHandler) UpdateMcpAccountRechargeRecord(c *gin.Context) {
id := c.Param("id")
var req struct {
Amount *float64 `json:"amount"`
RechargeDate *string `json:"recharge_date"`
Remark *string `json:"remark"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err := services.UpdateMcpAccountRechargeRecord(id, req.Amount, req.RechargeDate, req.Remark)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "更新成功"})
}
func (h *FinanceHandler) DeleteMcpAccountRechargeRecord(c *gin.Context) {
id := c.Param("id")
err := services.DeleteMcpAccountRechargeRecord(id)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "删除成功"})
}
func (h *FinanceHandler) GetMcpProviderAccounts(c *gin.Context) {
status := c.Query("status")
var isUsed *bool
if v := c.Query("is_used"); v != "" {
if v == "true" {
val := true
isUsed = &val
} else if v == "false" {
val := false
isUsed = &val
}
}
list, err := services.GetMcpProviderAccounts(status, isUsed)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, list)
}
func (h *FinanceHandler) GetMcpAccountBalances(c *gin.Context) {
list, err := services.GetMcpAccountBalances()
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, list)
}
func (h *FinanceHandler) GetMcpAccountBalanceHistory(c *gin.Context) {
providerID := c.Param("provider_id")
start := c.Query("start")
end := c.Query("end")
list, err := services.GetMcpAccountBalanceHistory(providerID, start, end)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, list)
}
// ========== 模型账号充值记录和余额管理 ==========
func (h *FinanceHandler) ListModelAccountRechargeRecords(c *gin.Context) {
offset, size := getPageParams(c)
provider := c.Query("provider")
modelName := c.Query("model_name")
start := c.Query("start")
end := c.Query("end")
res, err := services.ListModelAccountRechargeRecords(offset, size, provider, modelName, start, end)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, res.List, res.Total, offset/size+1, size)
}
func (h *FinanceHandler) CreateModelAccountRechargeRecord(c *gin.Context) {
var req struct {
Account string `json:"account" binding:"required"`
Amount float64 `json:"amount" binding:"required,gt=0"`
RechargeDate string `json:"recharge_date" binding:"required"`
Remark string `json:"remark"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 获取当前操作人信息
var operatorID interface{}
operatorName := "系统管理员"
// 先尝试从 user_id 获取
if userID, exists := c.Get("user_id"); exists {
operatorID = userID
}
// 从 user 信息中获取名称
if user, exists := c.Get("user"); exists {
// user 可能是 *models.UserInfo 类型
if userInfo, ok := user.(map[string]interface{}); ok {
if name, ok := userInfo["name"].(string); ok && name != "" {
operatorName = name
} else if email, ok := userInfo["email"].(string); ok && email != "" {
operatorName = email
}
} else {
// 尝试使用反射获取结构体字段
userVal := reflect.ValueOf(user)
if userVal.Kind() == reflect.Ptr {
userVal = userVal.Elem()
}
if userVal.Kind() == reflect.Struct {
if nameField := userVal.FieldByName("Name"); nameField.IsValid() && nameField.Kind() == reflect.String {
if name := nameField.String(); name != "" {
operatorName = name
}
}
if operatorName == "系统管理员" {
if emailField := userVal.FieldByName("Email"); emailField.IsValid() && emailField.Kind() == reflect.String {
if email := emailField.String(); email != "" {
operatorName = email
}
}
}
}
}
}
err := services.CreateModelAccountRechargeRecord(
req.Account,
req.Amount,
req.RechargeDate,
operatorID,
operatorName,
req.Remark,
)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "创建成功"})
}
func (h *FinanceHandler) UpdateModelAccountRechargeRecord(c *gin.Context) {
id := c.Param("id")
var req struct {
Amount *float64 `json:"amount"`
RechargeDate *string `json:"recharge_date"`
Remark *string `json:"remark"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err := services.UpdateModelAccountRechargeRecord(id, req.Amount, req.RechargeDate, req.Remark)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "更新成功"})
}
func (h *FinanceHandler) DeleteModelAccountRechargeRecord(c *gin.Context) {
id := c.Param("id")
err := services.DeleteModelAccountRechargeRecord(id)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "删除成功"})
}
func (h *FinanceHandler) GetModelConfigAccounts(c *gin.Context) {
var enabled *bool
if v := c.Query("enabled"); v != "" {
if v == "true" {
val := true
enabled = &val
} else if v == "false" {
val := false
enabled = &val
}
}
list, err := services.GetModelConfigAccounts(enabled)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, list)
}
func (h *FinanceHandler) GetModelAccountBalances(c *gin.Context) {
list, err := services.GetModelAccountBalances()
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, list)
}
func (h *FinanceHandler) GetModelAccountBalanceHistory(c *gin.Context) {
account := c.Param("account")
start := c.Query("start")
end := c.Query("end")
list, err := services.GetModelAccountBalanceHistory(account, start, end)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, list)
}
func (h *FinanceHandler) AdjustMcpAccountBalance(c *gin.Context) {
providerID := c.Param("provider_id")
var req struct {
Balance float64 `json:"balance" binding:"required"`
Remark string `json:"remark"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
operatorName := "系统管理员"
if user, exists := c.Get("user"); exists {
if userInfo, ok := user.(map[string]interface{}); ok {
if name, ok := userInfo["name"].(string); ok && name != "" {
operatorName = name
} else if email, ok := userInfo["email"].(string); ok && email != "" {
operatorName = email
}
} else {
userVal := reflect.ValueOf(user)
if userVal.Kind() == reflect.Ptr {
userVal = userVal.Elem()
}
if userVal.Kind() == reflect.Struct {
if nameField := userVal.FieldByName("Name"); nameField.IsValid() && nameField.Kind() == reflect.String {
if name := nameField.String(); name != "" {
operatorName = name
}
}
if operatorName == "系统管理员" {
if emailField := userVal.FieldByName("Email"); emailField.IsValid() && emailField.Kind() == reflect.String {
if email := emailField.String(); email != "" {
operatorName = email
}
}
}
}
}
}
if err := services.AdjustMcpAccountBalance(providerID, req.Balance, operatorName, req.Remark); err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "更新成功"})
}
func (h *FinanceHandler) AdjustModelAccountBalance(c *gin.Context) {
account := c.Param("account")
var req struct {
Balance float64 `json:"balance" binding:"required"`
Remark string `json:"remark"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
operatorName := "系统管理员"
if user, exists := c.Get("user"); exists {
if userInfo, ok := user.(map[string]interface{}); ok {
if name, ok := userInfo["name"].(string); ok && name != "" {
operatorName = name
} else if email, ok := userInfo["email"].(string); ok && email != "" {
operatorName = email
}
} else {
userVal := reflect.ValueOf(user)
if userVal.Kind() == reflect.Ptr {
userVal = userVal.Elem()
}
if userVal.Kind() == reflect.Struct {
if nameField := userVal.FieldByName("Name"); nameField.IsValid() && nameField.Kind() == reflect.String {
if name := nameField.String(); name != "" {
operatorName = name
}
}
if operatorName == "系统管理员" {
if emailField := userVal.FieldByName("Email"); emailField.IsValid() && emailField.Kind() == reflect.String {
if email := emailField.String(); email != "" {
operatorName = email
}
}
}
}
}
}
if err := services.AdjustModelAccountBalance(account, req.Balance, operatorName, req.Remark); err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "更新成功"})
}
func (h *FinanceHandler) CreateModelAccountBalance(c *gin.Context) {
var req struct {
Account string `json:"account" binding:"required"`
Balance float64 `json:"balance" binding:"required"`
Remark string `json:"remark"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
operatorName := "系统管理员"
if user, exists := c.Get("user"); exists {
if userInfo, ok := user.(map[string]interface{}); ok {
if name, ok := userInfo["name"].(string); ok && name != "" {
operatorName = name
} else if email, ok := userInfo["email"].(string); ok && email != "" {
operatorName = email
}
} else {
userVal := reflect.ValueOf(user)
if userVal.Kind() == reflect.Ptr {
userVal = userVal.Elem()
}
if userVal.Kind() == reflect.Struct {
if nameField := userVal.FieldByName("Name"); nameField.IsValid() && nameField.Kind() == reflect.String {
if name := nameField.String(); name != "" {
operatorName = name
}
}
if operatorName == "系统管理员" {
if emailField := userVal.FieldByName("Email"); emailField.IsValid() && emailField.Kind() == reflect.String {
if email := emailField.String(); email != "" {
operatorName = email
}
}
}
}
}
}
if err := services.CreateModelAccountBalanceRecord(req.Account, req.Balance, operatorName, req.Remark); err != nil {
if strings.Contains(err.Error(), "已存在") {
h.response.BadRequest(c, err.Error())
} else {
h.response.InternalServerError(c, err.Error())
}
return
}
h.response.Success(c, gin.H{"message": "创建成功"})
}
func (h *FinanceHandler) CreateMcpAccountBalance(c *gin.Context) {
var req struct {
ProviderID string `json:"provider_id" binding:"required"`
Balance float64 `json:"balance" binding:"required"`
Remark string `json:"remark"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
operatorName := "系统管理员"
if user, exists := c.Get("user"); exists {
if userInfo, ok := user.(map[string]interface{}); ok {
if name, ok := userInfo["name"].(string); ok && name != "" {
operatorName = name
} else if email, ok := userInfo["email"].(string); ok && email != "" {
operatorName = email
}
} else {
userVal := reflect.ValueOf(user)
if userVal.Kind() == reflect.Ptr {
userVal = userVal.Elem()
}
if userVal.Kind() == reflect.Struct {
if nameField := userVal.FieldByName("Name"); nameField.IsValid() && nameField.Kind() == reflect.String {
if name := nameField.String(); name != "" {
operatorName = name
}
}
if operatorName == "系统管理员" {
if emailField := userVal.FieldByName("Email"); emailField.IsValid() && emailField.Kind() == reflect.String {
if email := emailField.String(); email != "" {
operatorName = email
}
}
}
}
}
}
if err := services.CreateMcpAccountBalanceRecord(req.ProviderID, req.Balance, operatorName, req.Remark); err != nil {
if strings.Contains(err.Error(), "已存在") {
h.response.BadRequest(c, err.Error())
} else {
h.response.InternalServerError(c, err.Error())
}
return
}
h.response.Success(c, gin.H{"message": "创建成功"})
}

View File

@@ -0,0 +1,245 @@
package handlers
import (
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
)
type GoalfyMaxUserHandler struct {
service services.GoalfyMaxUserService
response *utils.Response
}
func NewGoalfyMaxUserHandler(s services.GoalfyMaxUserService) *GoalfyMaxUserHandler {
return &GoalfyMaxUserHandler{service: s, response: utils.NewResponse()}
}
func (h *GoalfyMaxUserHandler) List(c *gin.Context) {
var req models.GoalfyMaxUserListRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.response.ValidateError(c, err)
return
}
users, total, err := h.service.List(&req)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"users": users, "total": total, "page": req.Page, "size": req.Size})
}
func (h *GoalfyMaxUserHandler) GetByID(c *gin.Context) {
idStr := c.Param("id")
id64, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
user, err := h.service.GetByID(uint(id64))
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, user)
}
func (h *GoalfyMaxUserHandler) Create(c *gin.Context) {
var req models.GoalfyMaxUserCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
user, err := h.service.Create(&req)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, user)
}
func (h *GoalfyMaxUserHandler) Update(c *gin.Context) {
idStr := c.Param("id")
id64, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
var req models.GoalfyMaxUserUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
user, err := h.service.Update(uint(id64), &req)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, user)
}
func (h *GoalfyMaxUserHandler) Delete(c *gin.Context) {
idStr := c.Param("id")
id64, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
if err := h.service.Delete(uint(id64)); err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "删除成功"})
}
func (h *GoalfyMaxUserHandler) Ban(c *gin.Context) {
idStr := c.Param("id")
id64, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
var req models.GoalfyMaxUserBanRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 从上下文获取当前管理员ID
adminID := 0
if userID, exists := c.Get("user_id"); exists {
switch v := userID.(type) {
case int:
adminID = v
case uint:
adminID = int(v)
case string:
if parsedID, err := strconv.Atoi(v); err == nil {
adminID = parsedID
}
}
}
if err := h.service.Ban(uint(id64), &req, adminID); err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "封禁成功"})
}
func (h *GoalfyMaxUserHandler) Unban(c *gin.Context) {
idStr := c.Param("id")
id64, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
if err := h.service.Unban(uint(id64)); err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "解封成功"})
}
func (h *GoalfyMaxUserHandler) AddBalance(c *gin.Context) {
idStr := c.Param("id")
id64, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
var req models.GoalfyMaxUserAddBalanceRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 从上下文获取当前管理员信息
operatorID := 0
operatorEmail := "system@goalfy.com"
if userID, exists := c.Get("user_id"); exists {
switch v := userID.(type) {
case int:
operatorID = v
case uint:
operatorID = int(v)
case string:
if parsedID, err := strconv.Atoi(v); err == nil {
operatorID = parsedID
}
}
}
// 尝试获取用户邮箱从userInfo中获取
if userInfo, exists := c.Get("user"); exists {
if user, ok := userInfo.(*models.UserInfo); ok && user != nil {
if user.Email != "" {
operatorEmail = user.Email
} else if user.PreferredUsername != "" {
operatorEmail = user.PreferredUsername + "@goalfy.com"
}
}
}
// 获取IP地址和UserAgent
ipAddress := c.ClientIP()
userAgent := c.Request.UserAgent()
if err := h.service.AddBalance(uint(id64), &req, operatorID, operatorEmail, ipAddress, userAgent); err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "增加余额成功"})
}
func (h *GoalfyMaxUserHandler) DeductBalance(c *gin.Context) {
idStr := c.Param("id")
id64, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
var req models.GoalfyMaxUserAddBalanceRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 从上下文获取当前管理员信息
operatorID := 0
operatorEmail := "system@goalfy.com"
if userID, exists := c.Get("user_id"); exists {
switch v := userID.(type) {
case int:
operatorID = v
case uint:
operatorID = int(v)
case string:
if parsedID, err := strconv.Atoi(v); err == nil {
operatorID = parsedID
}
}
}
// 尝试获取用户邮箱从userInfo中获取
if userInfo, exists := c.Get("user"); exists {
if user, ok := userInfo.(*models.UserInfo); ok && user != nil {
if user.Email != "" {
operatorEmail = user.Email
} else if user.PreferredUsername != "" {
operatorEmail = user.PreferredUsername + "@goalfy.com"
}
}
}
// 获取IP地址和UserAgent
ipAddress := c.ClientIP()
userAgent := c.Request.UserAgent()
if err := h.service.DeductBalance(uint(id64), &req, operatorID, operatorEmail, ipAddress, userAgent); err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "减少余额成功"})
}

View File

@@ -0,0 +1,254 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
)
type InviteCodeApplicationHandler struct {
service *services.InviteCodeApplicationService
}
// NewInviteCodeApplicationHandler 创建邀请码申请处理器
func NewInviteCodeApplicationHandler(db *gorm.DB) *InviteCodeApplicationHandler {
return &InviteCodeApplicationHandler{
service: services.NewInviteCodeApplicationService(db),
}
}
// SubmitApplication 提交邀请码申请(公开接口,官网使用)
func (h *InviteCodeApplicationHandler) SubmitApplication(c *gin.Context) {
var req models.InviteCodeApplicationCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "请求参数无效",
})
return
}
application, err := h.service.SubmitApplication(&req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "申请已提交我们将在1-2个工作日内处理您的申请",
"data": application,
})
}
// GetApplicationList 获取申请列表(后台管理接口,需要权限)
func (h *InviteCodeApplicationHandler) GetApplicationList(c *gin.Context) {
var req models.InviteCodeApplicationListRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "请求参数无效",
})
return
}
// 设置默认值
if req.Page <= 0 {
req.Page = 1
}
if req.Size <= 0 {
req.Size = 20
}
response, err := h.service.GetApplicationList(&req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "获取申请列表失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": response,
})
}
// GetStatistics 获取申请统计(后台管理接口,需要权限)
func (h *InviteCodeApplicationHandler) GetStatistics(c *gin.Context) {
stats, err := h.service.GetStatistics()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "获取统计信息失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": stats,
})
}
// ApproveApplication 审批通过申请(后台管理接口,需要权限)
func (h *InviteCodeApplicationHandler) ApproveApplication(c *gin.Context) {
var req models.InviteCodeApplicationApproveRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "请求参数无效",
})
return
}
// 从上下文获取操作人信息(需要在中间件中设置)
approvedBy := c.GetString("username")
if approvedBy == "" {
approvedBy = "admin"
}
if err := h.service.ApproveApplication(&req, approvedBy); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "审批通过成功",
})
}
// RejectApplication 审批拒绝申请(后台管理接口,需要权限)
func (h *InviteCodeApplicationHandler) RejectApplication(c *gin.Context) {
var req models.InviteCodeApplicationRejectRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "请求参数无效",
})
return
}
// 从上下文获取操作人信息(需要在中间件中设置)
approvedBy := c.GetString("username")
if approvedBy == "" {
approvedBy = "admin"
}
if err := h.service.RejectApplication(&req, approvedBy); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "审批拒绝成功",
})
}
// BatchApproveApplications 批量审批通过(后台管理接口,需要权限)
func (h *InviteCodeApplicationHandler) BatchApproveApplications(c *gin.Context) {
var req models.InviteCodeApplicationBatchApproveRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "请求参数无效",
})
return
}
// 从上下文获取操作人信息(需要在中间件中设置)
approvedBy := c.GetString("username")
if approvedBy == "" {
approvedBy = "admin"
}
if err := h.service.BatchApproveApplications(&req, approvedBy); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "批量审批通过成功",
})
}
// BatchRejectApplications 批量审批拒绝(后台管理接口,需要权限)
func (h *InviteCodeApplicationHandler) BatchRejectApplications(c *gin.Context) {
var req models.InviteCodeApplicationBatchRejectRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "请求参数无效",
})
return
}
// 从上下文获取操作人信息(需要在中间件中设置)
approvedBy := c.GetString("username")
if approvedBy == "" {
approvedBy = "admin"
}
if err := h.service.BatchRejectApplications(&req, approvedBy); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "批量审批拒绝成功",
})
}
// GetPendingCount 获取待处理申请数量(后台管理接口,需要权限)
func (h *InviteCodeApplicationHandler) GetPendingCount(c *gin.Context) {
count, err := h.service.GetPendingApplicationsCount()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "获取待处理数量失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": gin.H{
"count": count,
},
})
}
// RegisterRoutes 注册路由
func (h *InviteCodeApplicationHandler) RegisterRoutes(router *gin.RouterGroup, authMiddleware gin.HandlerFunc) {
// 公开接口(官网提交申请)
public := router.Group("/public")
{
public.POST("/invite-code/apply", h.SubmitApplication)
}
// 需要认证的接口(后台管理)
protected := router.Group("/invite-code/applications")
protected.Use(authMiddleware)
{
protected.GET("", h.GetApplicationList)
protected.GET("/statistics", h.GetStatistics)
protected.GET("/pending-count", h.GetPendingCount)
protected.POST("/approve", h.ApproveApplication)
protected.POST("/reject", h.RejectApplication)
protected.POST("/batch-approve", h.BatchApproveApplications)
protected.POST("/batch-reject", h.BatchRejectApplications)
}
}

View File

@@ -0,0 +1,458 @@
package handlers
import (
"net/http"
"regexp"
"strconv"
"strings"
"goalfymax-admin/internal/config"
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"github.com/gin-gonic/gin"
)
type InviteCodeHandler struct {
service services.InviteCodeService
userLevelService services.UserLevelConfigService
}
func NewInviteCodeHandler(service services.InviteCodeService, userLevelService services.UserLevelConfigService) *InviteCodeHandler {
return &InviteCodeHandler{
service: service,
userLevelService: userLevelService,
}
}
// GetInviteCodeList 获取邀请码列表
func (h *InviteCodeHandler) GetInviteCodeList(c *gin.Context) {
var req models.InviteCodeListRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
"data": nil,
})
return
}
// 设置默认分页参数
if req.Page <= 0 {
req.Page = 1
}
if req.Size <= 0 {
req.Size = 20
}
response, err := h.service.List(&req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": err.Error(),
"data": nil,
})
return
}
// enrich invite_url
base := config.GetConfig().SSO.SSOServerURL
base = strings.TrimRight(base, "/")
enriched := make([]gin.H, 0, len(response.List))
for _, item := range response.List {
// 获取用户等级信息
var userLevelName string
if item.UserLevelID != nil {
if level, err := h.userLevelService.GetByID(*item.UserLevelID); err == nil {
userLevelName = level.LevelName
}
}
enriched = append(enriched, gin.H{
"id": item.ID,
"code": item.Code,
"is_used": item.IsUsed,
"client_id": item.ClientID,
"client_label": config.GetClientValue(item.ClientID),
"email": item.Email,
"user_level_id": item.UserLevelID,
"user_level_name": userLevelName,
"expires_at": item.ExpiresAt,
"created_at": item.CreatedAt,
"invite_url": base + "/invite/" + item.Code,
})
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "获取成功",
"data": gin.H{
"list": enriched,
"total": response.Total,
},
})
}
// GetInviteCodeDetail 获取邀请码详情
func (h *InviteCodeHandler) GetInviteCodeDetail(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "ID格式错误",
"data": nil,
})
return
}
inviteCode, err := h.service.GetByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"message": "邀请码不存在",
"data": nil,
})
return
}
base := config.GetConfig().SSO.SSOServerURL
base = strings.TrimRight(base, "/")
// 获取用户等级信息
var userLevelName string
if inviteCode.UserLevelID != nil {
if level, err := h.userLevelService.GetByID(*inviteCode.UserLevelID); err == nil {
userLevelName = level.LevelName
}
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "获取成功",
"data": gin.H{
"id": inviteCode.ID,
"code": inviteCode.Code,
"is_used": inviteCode.IsUsed,
"client_id": inviteCode.ClientID,
"client_label": config.GetClientValue(inviteCode.ClientID),
"email": inviteCode.Email,
"user_level_id": inviteCode.UserLevelID,
"user_level_name": userLevelName,
"expires_at": inviteCode.ExpiresAt,
"created_at": inviteCode.CreatedAt,
"invite_url": base + "/invite/" + inviteCode.Code,
},
})
}
// validateEmail 验证邮箱格式
func validateEmail(email string) bool {
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
return emailRegex.MatchString(email)
}
// CreateInviteCode 创建邀请码(支持设置过期时间和邮箱列表)
func (h *InviteCodeHandler) CreateInviteCode(c *gin.Context) {
var req models.InviteCodeCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误:" + err.Error(),
"data": nil,
})
return
}
// 验证邮箱格式
if len(req.Emails) > 0 {
for _, email := range req.Emails {
if email != "" && !validateEmail(email) {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "邮箱格式不正确: " + email,
"data": nil,
})
return
}
}
}
// 验证用户等级ID是否存在且启用
if req.UserLevelID != nil {
userLevel, err := h.userLevelService.GetByID(*req.UserLevelID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "用户等级不存在",
"data": nil,
})
return
}
if userLevel.Status != 1 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "用户等级已禁用",
"data": nil,
})
return
}
}
inviteCodes, err := h.service.Create(&req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": err.Error(),
"data": nil,
})
return
}
base := config.GetConfig().SSO.SSOServerURL
base = strings.TrimRight(base, "/")
// 如果只创建了一个邀请码,返回单个对象(向后兼容)
if len(inviteCodes) == 1 {
inviteCode := inviteCodes[0]
var userLevelName string
if inviteCode.UserLevelID != nil {
if level, err := h.userLevelService.GetByID(*inviteCode.UserLevelID); err == nil {
userLevelName = level.LevelName
}
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "创建成功",
"data": gin.H{
"id": inviteCode.ID,
"code": inviteCode.Code,
"is_used": inviteCode.IsUsed,
"client_id": inviteCode.ClientID,
"client_label": config.GetClientValue(inviteCode.ClientID),
"email": inviteCode.Email,
"user_level_id": inviteCode.UserLevelID,
"user_level_name": userLevelName,
"expires_at": inviteCode.ExpiresAt,
"created_at": inviteCode.CreatedAt,
"invite_url": base + "/invite/" + inviteCode.Code,
},
})
return
}
// 如果创建了多个邀请码,返回数组
enriched := make([]gin.H, 0, len(inviteCodes))
for _, inviteCode := range inviteCodes {
var userLevelName string
if inviteCode.UserLevelID != nil {
if level, err := h.userLevelService.GetByID(*inviteCode.UserLevelID); err == nil {
userLevelName = level.LevelName
}
}
enriched = append(enriched, gin.H{
"id": inviteCode.ID,
"code": inviteCode.Code,
"is_used": inviteCode.IsUsed,
"client_id": inviteCode.ClientID,
"client_label": config.GetClientValue(inviteCode.ClientID),
"email": inviteCode.Email,
"user_level_id": inviteCode.UserLevelID,
"user_level_name": userLevelName,
"expires_at": inviteCode.ExpiresAt,
"created_at": inviteCode.CreatedAt,
"invite_url": base + "/invite/" + inviteCode.Code,
})
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "创建成功",
"data": enriched,
})
}
// UpdateInviteCode 更新邀请码(支持更新过期时间)
func (h *InviteCodeHandler) UpdateInviteCode(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "ID格式错误",
"data": nil,
})
return
}
var req models.InviteCodeUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
"data": nil,
})
return
}
inviteCode, err := h.service.Update(uint(id), &req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": err.Error(),
"data": nil,
})
return
}
base := config.GetConfig().SSO.SSOServerURL
base = strings.TrimRight(base, "/")
// 获取用户等级信息
var userLevelName string
if inviteCode.UserLevelID != nil {
if level, err := h.userLevelService.GetByID(*inviteCode.UserLevelID); err == nil {
userLevelName = level.LevelName
}
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "更新成功",
"data": gin.H{
"id": inviteCode.ID,
"code": inviteCode.Code,
"is_used": inviteCode.IsUsed,
"client_id": inviteCode.ClientID,
"client_label": config.GetClientValue(inviteCode.ClientID),
"email": inviteCode.Email,
"user_level_id": inviteCode.UserLevelID,
"user_level_name": userLevelName,
"expires_at": inviteCode.ExpiresAt,
"created_at": inviteCode.CreatedAt,
"invite_url": base + "/invite/" + inviteCode.Code,
},
})
}
// DeleteInviteCode 删除邀请码
func (h *InviteCodeHandler) DeleteInviteCode(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "ID格式错误",
"data": nil,
})
return
}
err = h.service.Delete(uint(id))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": err.Error(),
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "删除成功",
"data": nil,
})
}
// GetInviteCodeStatistics 获取统计信息
func (h *InviteCodeHandler) GetInviteCodeStatistics(c *gin.Context) {
stats, err := h.service.GetStatistics()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": err.Error(),
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "获取成功",
"data": stats,
})
}
// MarkInviteCodeAsUsed 标记邀请码为已使用
func (h *InviteCodeHandler) MarkInviteCodeAsUsed(c *gin.Context) {
var req struct {
Code string `json:"code" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
"data": nil,
})
return
}
err := h.service.MarkAsUsed(req.Code)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": err.Error(),
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "标记成功",
"data": nil,
})
}
// ValidateInviteCode 验证邀请码是否有效
func (h *InviteCodeHandler) ValidateInviteCode(c *gin.Context) {
var req struct {
Code string `json:"code" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
"data": nil,
})
return
}
err := h.service.ValidateInviteCode(req.Code)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": err.Error(),
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "邀请码有效",
"data": nil,
})
}
// GetClientOptions 获取客户端选项列表
func (h *InviteCodeHandler) GetClientOptions(c *gin.Context) {
options := config.GetClientOptions()
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "获取成功",
"data": options,
})
}

View File

@@ -0,0 +1,186 @@
package handlers
import (
"net/http"
"strconv"
"goalfymax-admin/internal/storage"
"goalfymax-admin/pkg/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type MCPProvider struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
Provider string `json:"provider"`
BaseURL string `json:"base_url"`
Auth *string `json:"auth"`
Account *string `json:"account"`
PriceType string `json:"price_type"`
Price float64 `json:"price"`
FloatingRatio float64 `json:"floating_ratio"`
IsUsed bool `json:"is_used"`
Status string `json:"status"`
Description *string `json:"description"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type MCPProviderHandler struct {
response *utils.Response
}
func NewMCPProviderHandler() *MCPProviderHandler {
return &MCPProviderHandler{response: utils.NewResponse()}
}
func (h *MCPProviderHandler) List(c *gin.Context) {
db := storage.GetPG()
if db == nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "PostgreSQL未初始化"})
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "10"))
if page <= 0 {
page = 1
}
if size <= 0 {
size = 10
}
offset := (page - 1) * size
q := db.Table("mcp_providers")
if v := c.Query("provider"); v != "" {
q = q.Where("provider ILIKE ?", "%"+v+"%")
}
if v := c.Query("status"); v != "" {
q = q.Where("status = ?", v)
}
if v := c.Query("is_used"); v != "" {
if v == "true" {
q = q.Where("is_used = ?", true)
} else if v == "false" {
q = q.Where("is_used = ?", false)
}
}
var total int64
if err := q.Count(&total).Error; err != nil {
h.response.InternalServerError(c, err.Error())
return
}
var list []MCPProvider
if err := q.Order("created_at DESC").Offset(offset).Limit(size).Find(&list).Error; err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, list, total, page, size)
}
func (h *MCPProviderHandler) GetByID(c *gin.Context) {
db := storage.GetPG()
id, err := uuid.Parse(c.Param("id"))
if err != nil {
h.response.BadRequest(c, "无效ID")
return
}
var m MCPProvider
if err := db.Table("mcp_providers").Where("id = ?", id).First(&m).Error; err != nil {
h.response.NotFound(c, "记录不存在")
return
}
h.response.Success(c, m)
}
func (h *MCPProviderHandler) Create(c *gin.Context) {
db := storage.GetPG()
var req map[string]any
if err := c.ShouldBindJSON(&req); err != nil {
h.response.BadRequest(c, err.Error())
return
}
if err := db.Table("mcp_providers").Create(req).Error; err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "创建成功")
}
func (h *MCPProviderHandler) Update(c *gin.Context) {
db := storage.GetPG()
id, err := uuid.Parse(c.Param("id"))
if err != nil {
h.response.BadRequest(c, "无效ID")
return
}
var req map[string]any
if err := c.ShouldBindJSON(&req); err != nil {
h.response.BadRequest(c, err.Error())
return
}
delete(req, "id")
if err := db.Table("mcp_providers").Where("id = ?", id).Updates(req).Error; err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "更新成功")
}
func (h *MCPProviderHandler) Delete(c *gin.Context) {
db := storage.GetPG()
id, err := uuid.Parse(c.Param("id"))
if err != nil {
h.response.BadRequest(c, "无效ID")
return
}
if err := db.Table("mcp_providers").Where("id = ?", id).Delete(nil).Error; err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "删除成功")
}
func (h *MCPProviderHandler) UpdateStatus(c *gin.Context) {
db := storage.GetPG()
id, err := uuid.Parse(c.Param("id"))
if err != nil {
h.response.BadRequest(c, "无效ID")
return
}
var body struct {
Status string `json:"status" binding:"required"`
}
if err := c.ShouldBindJSON(&body); err != nil {
h.response.BadRequest(c, err.Error())
return
}
if err := db.Table("mcp_providers").Where("id = ?", id).Update("status", body.Status).Error; err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "状态更新成功")
}
func (h *MCPProviderHandler) UpdateIsUsed(c *gin.Context) {
db := storage.GetPG()
id, err := uuid.Parse(c.Param("id"))
if err != nil {
h.response.BadRequest(c, "无效ID")
return
}
var body struct {
IsUsed bool `json:"is_used" binding:"required"`
}
if err := c.ShouldBindJSON(&body); err != nil {
h.response.BadRequest(c, err.Error())
return
}
if err := db.Table("mcp_providers").Where("id = ?", id).Update("is_used", body.IsUsed).Error; err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "使用状态更新成功")
}

View File

@@ -0,0 +1,134 @@
package handlers
import (
"strconv"
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"github.com/gin-gonic/gin"
)
// MessagePushHandler 消息推送处理器
type MessagePushHandler struct {
service services.MessagePushService
response *utils.Response
}
// NewMessagePushHandler 创建消息推送处理器
func NewMessagePushHandler(service services.MessagePushService) *MessagePushHandler {
return &MessagePushHandler{
service: service,
response: utils.NewResponse(),
}
}
// SendMessage 发送消息
func (h *MessagePushHandler) SendMessage(c *gin.Context) {
var req models.MessagePushRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 获取当前用户信息
userID, exists := c.Get("user_id")
if !exists {
h.response.Unauthorized(c, "无法获取用户信息")
return
}
// 处理用户ID类型
var senderID int
switch v := userID.(type) {
case int:
senderID = v
case uint:
senderID = int(v)
case string:
var err error
senderID, err = strconv.Atoi(v)
if err != nil {
h.response.InternalServerError(c, "用户ID格式错误")
return
}
default:
h.response.InternalServerError(c, "用户ID格式错误")
return
}
// 获取发送人姓名(从用户信息中获取)
senderName := "系统管理员"
if user, exists := c.Get("user"); exists {
if userInfo, ok := user.(map[string]interface{}); ok {
if name, ok := userInfo["name"].(string); ok {
senderName = name
}
}
}
result, err := h.service.SendMessage(c.Request.Context(), &req, senderID, senderName)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, result)
}
// GetPushLogs 获取推送记录列表
func (h *MessagePushHandler) GetPushLogs(c *gin.Context) {
var req models.MessagePushListRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.response.ValidateError(c, err)
return
}
result, err := h.service.GetPushLogs(c.Request.Context(), &req)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, result)
}
// GetPushLogByID 根据ID获取推送记录
func (h *MessagePushHandler) GetPushLogByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
h.response.BadRequest(c, "无效的推送记录ID")
return
}
log, err := h.service.GetPushLogByID(c.Request.Context(), id)
if err != nil {
if err.Error() == "推送记录不存在" {
h.response.NotFound(c, "推送记录不存在")
return
}
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, log)
}
// SearchUsers 搜索用户
func (h *MessagePushHandler) SearchUsers(c *gin.Context) {
var req models.UserSearchRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.response.ValidateError(c, err)
return
}
result, err := h.service.SearchUsers(c.Request.Context(), &req)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, result)
}

View File

@@ -0,0 +1,144 @@
package handlers
import (
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// PageHandler 页面处理器
type PageHandler struct {
pageService services.PageService
response *utils.Response
logger *utils.Logger
}
// NewPageHandler 创建页面处理器
func NewPageHandler(pageService services.PageService, logger *utils.Logger) *PageHandler {
return &PageHandler{
pageService: pageService,
response: utils.NewResponse(),
logger: logger,
}
}
// Create 创建页面
func (h *PageHandler) Create(c *gin.Context) {
var req models.PageCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("参数绑定失败", zap.Error(err))
h.response.ValidateError(c, err)
return
}
page, err := h.pageService.Create(&req)
if err != nil {
h.logger.Error("创建页面失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, page)
}
// GetByID 根据ID获取页面
func (h *PageHandler) GetByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.logger.Error("页面ID格式错误", zap.Error(err))
h.response.BadRequest(c, "无效的页面ID")
return
}
page, err := h.pageService.GetByID(uint(id))
if err != nil {
h.logger.Error("获取页面失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, page)
}
// Update 更新页面
func (h *PageHandler) Update(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.logger.Error("页面ID格式错误", zap.Error(err))
h.response.BadRequest(c, "无效的页面ID")
return
}
var req models.PageUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("参数绑定失败", zap.Error(err))
h.response.ValidateError(c, err)
return
}
page, err := h.pageService.Update(uint(id), &req)
if err != nil {
h.logger.Error("更新页面失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, page)
}
// Delete 删除页面
func (h *PageHandler) Delete(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.logger.Error("页面ID格式错误", zap.Error(err))
h.response.BadRequest(c, "无效的页面ID")
return
}
err = h.pageService.Delete(uint(id))
if err != nil {
h.logger.Error("删除页面失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "页面删除成功"})
}
// List 获取页面列表
func (h *PageHandler) List(c *gin.Context) {
var req models.PageListRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.logger.Error("参数绑定失败", zap.Error(err))
h.response.ValidateError(c, err)
return
}
pages, total, err := h.pageService.List(&req)
if err != nil {
h.logger.Error("获取页面列表失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
// 如果没有分页参数,直接返回页面列表
if req.Page <= 0 || req.Size <= 0 {
h.response.Success(c, pages)
return
}
// 有分页参数时返回完整的分页信息
h.response.Success(c, gin.H{
"pages": pages,
"total": total,
"page": req.Page,
"size": req.Size,
})
}

View File

@@ -0,0 +1,117 @@
package handlers
import (
"github.com/gin-gonic/gin"
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
)
// QuotaHandler 配额处理器
type QuotaHandler struct {
quotaService services.QuotaService
response *utils.Response
}
// NewQuotaHandler 创建配额处理器
func NewQuotaHandler(quotaService services.QuotaService) *QuotaHandler {
return &QuotaHandler{
quotaService: quotaService,
response: utils.NewResponse(),
}
}
// GetQuotaHistory 获取配额历史数据
func (h *QuotaHandler) GetQuotaHistory(c *gin.Context) {
var req models.QuotaHistoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 调用服务层
response, err := h.quotaService.GetQuotaHistory(&req)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
// 返回响应
if response.Success {
h.response.Success(c, response.Data)
} else {
h.response.BadRequest(c, response.Message)
}
}
// HealthCheck 健康检查
func (h *QuotaHandler) HealthCheck(c *gin.Context) {
err := h.quotaService.HealthCheck()
if err != nil {
h.response.InternalServerError(c, "配额服务健康检查失败")
return
}
h.response.Success(c, gin.H{"status": "ok", "service": "quota"})
}
// GetQuotaRules 获取配额规则列表(透传网关)
func (h *QuotaHandler) GetQuotaRules(c *gin.Context) {
resp, err := h.quotaService.GetQuotaRules()
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
if resp.Success {
// 透传 data
c.JSON(200, gin.H{
"code": 200,
"message": "操作成功",
"data": resp.Data,
})
return
}
h.response.BadRequest(c, resp.Message)
}
// CreateQuotaRule 创建配额规则(代理网关)
func (h *QuotaHandler) CreateQuotaRule(c *gin.Context) {
var body map[string]any
if err := c.ShouldBindJSON(&body); err != nil {
h.response.ValidateError(c, err)
return
}
resp, err := h.quotaService.CreateQuotaRule(body)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
c.JSON(200, gin.H{"code": 200, "message": "操作成功", "data": resp.Data})
}
// UpdateQuotaRule 更新配额规则(代理网关)
func (h *QuotaHandler) UpdateQuotaRule(c *gin.Context) {
id := c.Param("id")
var body map[string]any
if err := c.ShouldBindJSON(&body); err != nil {
h.response.ValidateError(c, err)
return
}
resp, err := h.quotaService.UpdateQuotaRule(id, body)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
c.JSON(200, gin.H{"code": 200, "message": "操作成功", "data": resp.Data})
}
// DeleteQuotaRule 删除配额规则(代理网关)
func (h *QuotaHandler) DeleteQuotaRule(c *gin.Context) {
id := c.Param("id")
resp, err := h.quotaService.DeleteQuotaRule(id)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
c.JSON(200, gin.H{"code": 200, "message": "操作成功", "data": resp.Data})
}

View File

@@ -0,0 +1,182 @@
package handlers
import (
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// RBACHandler 简化的RBAC处理器
type RBACHandler struct {
rbacService services.RBACService
response *utils.Response
logger *utils.Logger
}
// NewRBACHandler 创建RBAC处理器
func NewRBACHandler(rbacService services.RBACService, logger *utils.Logger) *RBACHandler {
return &RBACHandler{
rbacService: rbacService,
response: utils.NewResponse(),
logger: logger,
}
}
// AssignRolePagePermissions 分配角色页面权限
func (h *RBACHandler) AssignRolePagePermissions(c *gin.Context) {
var req models.RolePagePermissionAssignRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("参数绑定失败", zap.Error(err))
h.response.Error(c, 400, "参数错误")
return
}
err := h.rbacService.AssignRolePagePermissions(&req)
if err != nil {
h.logger.Error("分配角色页面权限失败", zap.Error(err))
h.response.Error(c, 500, "分配角色页面权限失败")
return
}
h.response.Success(c, "分配角色页面权限成功")
}
// RemoveRolePagePermissions 移除角色页面权限
func (h *RBACHandler) RemoveRolePagePermissions(c *gin.Context) {
roleIDStr := c.Param("id")
roleID, err := strconv.ParseUint(roleIDStr, 10, 32)
if err != nil {
h.logger.Error("角色ID格式错误", zap.Error(err))
h.response.Error(c, 400, "角色ID格式错误")
return
}
var req struct {
PageIDs []uint `json:"pageIds" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("参数绑定失败", zap.Error(err))
h.response.Error(c, 400, "参数错误")
return
}
err = h.rbacService.RemoveRolePagePermissions(uint(roleID), req.PageIDs)
if err != nil {
h.logger.Error("移除角色页面权限失败", zap.Error(err))
h.response.Error(c, 500, "移除角色页面权限失败")
return
}
h.response.Success(c, "移除角色页面权限成功")
}
// GetRolePagePermissions 获取角色页面权限
func (h *RBACHandler) GetRolePagePermissions(c *gin.Context) {
roleIDStr := c.Param("id")
roleID, err := strconv.ParseUint(roleIDStr, 10, 32)
if err != nil {
h.logger.Error("角色ID格式错误", zap.Error(err))
h.response.Error(c, 400, "角色ID格式错误")
return
}
pages, err := h.rbacService.GetRolePagePermissions(uint(roleID))
if err != nil {
h.logger.Error("获取角色页面权限失败", zap.Error(err))
h.response.Error(c, 500, "获取角色页面权限失败")
return
}
h.response.Success(c, pages)
}
// GetUserPermissions 获取用户权限
func (h *RBACHandler) GetUserPermissions(c *gin.Context) {
userIDStr := c.Param("id")
userID, err := strconv.ParseUint(userIDStr, 10, 32)
if err != nil {
h.logger.Error("用户ID格式错误", zap.Error(err))
h.response.Error(c, 400, "用户ID格式错误")
return
}
permissions, err := h.rbacService.GetUserPermissionsResponse(uint(userID))
if err != nil {
h.logger.Error("获取用户权限失败", zap.Error(err))
h.response.Error(c, 500, "获取用户权限失败")
return
}
h.response.Success(c, permissions)
}
// GetRolePermissions 获取角色权限
func (h *RBACHandler) GetRolePermissions(c *gin.Context) {
roleIDStr := c.Param("id")
roleID, err := strconv.ParseUint(roleIDStr, 10, 32)
if err != nil {
h.logger.Error("角色ID格式错误", zap.Error(err))
h.response.Error(c, 400, "角色ID格式错误")
return
}
permissions, err := h.rbacService.GetRolePagePermissionsResponse(uint(roleID))
if err != nil {
h.logger.Error("获取角色权限失败", zap.Error(err))
h.response.Error(c, 500, "获取角色权限失败")
return
}
h.response.Success(c, permissions)
}
// CheckPagePermission 检查页面权限
func (h *RBACHandler) CheckPagePermission(c *gin.Context) {
userIDStr := c.Query("user_id")
pagePath := c.Query("page_path")
userID, err := strconv.ParseUint(userIDStr, 10, 32)
if err != nil {
h.logger.Error("用户ID格式错误", zap.Error(err))
h.response.Error(c, 400, "用户ID格式错误")
return
}
hasPermission, err := h.rbacService.CheckUserPagePermission(uint(userID), pagePath)
if err != nil {
h.logger.Error("检查页面权限失败", zap.Error(err))
h.response.Error(c, 500, "检查页面权限失败")
return
}
h.response.Success(c, gin.H{
"hasPermission": hasPermission,
"pagePath": pagePath,
})
}
// GetUserAccessiblePages 获取用户可访问页面
func (h *RBACHandler) GetUserAccessiblePages(c *gin.Context) {
userIDStr := c.Param("id")
userID, err := strconv.ParseUint(userIDStr, 10, 32)
if err != nil {
h.logger.Error("用户ID格式错误", zap.Error(err))
h.response.Error(c, 400, "用户ID格式错误")
return
}
pages, err := h.rbacService.GetUserAccessiblePages(uint(userID))
if err != nil {
h.logger.Error("获取用户可访问页面失败", zap.Error(err))
h.response.Error(c, 500, "获取用户可访问页面失败")
return
}
h.response.Success(c, gin.H{
"pages": pages,
})
}

View File

@@ -0,0 +1,228 @@
package handlers
import (
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// RoleHandler 角色处理器
type RoleHandler struct {
roleService services.RoleService
rbacService services.RBACService
response *utils.Response
logger *utils.Logger
}
// NewRoleHandler 创建角色处理器
func NewRoleHandler(roleService services.RoleService, rbacService services.RBACService, logger *utils.Logger) *RoleHandler {
return &RoleHandler{
roleService: roleService,
rbacService: rbacService,
response: utils.NewResponse(),
logger: logger,
}
}
// Create 创建角色
func (h *RoleHandler) Create(c *gin.Context) {
var req models.RoleCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
role, err := h.roleService.Create(&req)
if err != nil {
h.logger.Error("创建角色失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, role)
}
// GetByID 获取角色详情
func (h *RoleHandler) GetByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的角色ID")
return
}
role, err := h.roleService.GetByID(uint(id))
if err != nil {
h.logger.Error("获取角色失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, role)
}
// Update 更新角色
func (h *RoleHandler) Update(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的角色ID")
return
}
var req models.RoleUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
role, err := h.roleService.Update(uint(id), &req)
if err != nil {
h.logger.Error("更新角色失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, role)
}
// Delete 删除角色
func (h *RoleHandler) Delete(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的角色ID")
return
}
err = h.roleService.Delete(uint(id))
if err != nil {
h.logger.Error("删除角色失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "角色删除成功"})
}
// List 获取角色列表
func (h *RoleHandler) List(c *gin.Context) {
var req models.RoleListRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.response.ValidateError(c, err)
return
}
roles, total, err := h.roleService.List(&req)
if err != nil {
h.logger.Error("获取角色列表失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
// 为每个角色获取页面权限
rolesWithPermissions := make([]gin.H, len(roles))
for i, role := range roles {
// 获取角色的页面权限
pages, err := h.rbacService.GetRolePagePermissions(role.ID)
if err != nil {
h.logger.Warn("获取角色页面权限失败", zap.Uint("roleId", role.ID), zap.Error(err))
pages = []models.Page{} // 如果获取失败,返回空数组
}
rolesWithPermissions[i] = gin.H{
"id": role.ID,
"name": role.Name,
"level": role.Level,
"description": role.Description,
"isDefault": role.IsDefault,
"createdAt": role.CreatedAt,
"updatedAt": role.UpdatedAt,
"deletedAt": role.DeletedAt,
"pages": pages, // 添加页面权限信息
}
}
h.response.Success(c, gin.H{
"roles": rolesWithPermissions,
"total": total,
"page": req.Page,
"size": req.Size,
})
}
// UpdateStatus 更新角色状态
func (h *RoleHandler) UpdateStatus(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的角色ID")
return
}
var req struct {
Status int `json:"status" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err = h.roleService.UpdateStatus(uint(id), req.Status)
if err != nil {
h.logger.Error("更新角色状态失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "角色状态更新成功"})
}
// UpdatePermissions 更新角色权限
func (h *RoleHandler) UpdatePermissions(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的角色ID")
return
}
var req models.RolePagePermissionAssignRequest
req.RoleID = uint(id)
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err = h.rbacService.AssignRolePagePermissions(&req)
if err != nil {
h.logger.Error("更新角色权限失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "角色权限更新成功"})
}
// GetRolePermissions 获取角色权限
func (h *RoleHandler) GetRolePermissions(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的角色ID")
return
}
permissions, err := h.rbacService.GetRolePagePermissions(uint(id))
if err != nil {
h.logger.Error("获取角色权限失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"permissions": permissions})
}

View File

@@ -0,0 +1,267 @@
package handlers
import (
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// SSOHandler SSO处理器
type SSOHandler struct {
ssoService services.SSOService
response *utils.Response
logger *utils.Logger
}
// NewSSOHandler 创建SSO处理器
func NewSSOHandler(ssoService services.SSOService, logger *utils.Logger) *SSOHandler {
return &SSOHandler{
ssoService: ssoService,
response: utils.NewResponse(),
logger: logger,
}
}
// HandleSSOLogin 处理SSO登录请求合并登录和回调逻辑
func (h *SSOHandler) HandleSSOLogin(c *gin.Context) {
if c.Request.Method != http.MethodPost {
h.response.BadRequest(c, "Method not allowed")
return
}
var req models.SSOCallbackRequest
if err := c.ShouldBindJSON(&req); err != nil {
// 解析失败时,走登录逻辑
h.handleLoginLogic(c, models.SSOLoginRequest{})
return
}
// 如果code为空走登录逻辑
if req.Code == "" {
h.handleLoginLogic(c, models.SSOLoginRequest{})
return
}
// 如果code不为空走回调逻辑
h.handleCallbackLogic(c, req)
}
// HandleSSOCallback 处理SSO回调
func (h *SSOHandler) HandleSSOCallback(c *gin.Context) {
if c.Request.Method != http.MethodPost {
h.response.BadRequest(c, "Method not allowed")
return
}
var req models.SSOCallbackRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 验证参数
if req.Code == "" || req.State == "" {
h.response.BadRequest(c, "Code and state are required")
return
}
// 调用服务层处理回调
response, err := h.ssoService.HandleCallback(c.Request.Context(), &req)
if err != nil {
h.logger.Error("failed to handle SSO callback", zap.Error(err))
// 返回更具体的错误信息,避免前端重复尝试
if strings.Contains(err.Error(), "password") {
h.response.BadRequest(c, "数据库表结构错误,请联系管理员")
} else {
h.response.InternalServerError(c, "SSO登录处理失败请稍后重试")
}
return
}
h.response.Success(c, response)
}
// HandleRefreshToken 处理令牌刷新
func (h *SSOHandler) HandleRefreshToken(c *gin.Context) {
if c.Request.Method != http.MethodPost {
h.response.BadRequest(c, "Method not allowed")
return
}
var req models.RefreshTokenRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
if req.RefreshToken == "" {
h.response.BadRequest(c, "Refresh token is required")
return
}
// 调用服务层刷新令牌
response, err := h.ssoService.RefreshToken(c.Request.Context(), &req)
if err != nil {
h.logger.Error("failed to refresh token", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, response)
}
// HandleLogout 处理登出请求
func (h *SSOHandler) HandleLogout(c *gin.Context) {
if c.Request.Method != http.MethodPost {
h.response.BadRequest(c, "Method not allowed")
return
}
// 从Authorization头获取访问令牌
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
h.response.Unauthorized(c, "Authorization header is required")
return
}
// 提取Bearer令牌
token := ""
if len(authHeader) > 7 && authHeader[:7] == "Bearer " {
token = authHeader[7:]
}
if token == "" {
h.response.Unauthorized(c, "Invalid authorization header")
return
}
// 调用服务层登出
response, err := h.ssoService.Logout(c.Request.Context(), token)
if err != nil {
h.logger.Error("failed to logout", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, response)
}
// HandleUserInfo 处理用户信息请求
func (h *SSOHandler) HandleUserInfo(c *gin.Context) {
if c.Request.Method != http.MethodGet {
h.response.BadRequest(c, "Method not allowed")
return
}
// 从Authorization头获取访问令牌
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
h.response.Unauthorized(c, "Authorization header is required")
return
}
// 提取Bearer令牌
token := ""
if len(authHeader) > 7 && authHeader[:7] == "Bearer " {
token = authHeader[7:]
}
if token == "" {
h.response.Unauthorized(c, "Invalid authorization header")
return
}
// 调用服务层获取用户信息
response, err := h.ssoService.GetUserInfo(c.Request.Context(), token)
if err != nil {
h.logger.Error("failed to get user info", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, response)
}
// handleLoginLogic 处理登录逻辑
func (h *SSOHandler) handleLoginLogic(c *gin.Context, req models.SSOLoginRequest) {
// 调用服务层初始化登录
response, err := h.ssoService.InitiateLogin(c.Request.Context())
if err != nil {
h.logger.Error("failed to initiate SSO login", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, response)
}
// handleCallbackLogic 处理回调逻辑
func (h *SSOHandler) handleCallbackLogic(c *gin.Context, req models.SSOCallbackRequest) {
// 验证参数
if req.State == "" {
// 参数缺失时,走登录逻辑
h.handleLoginLogic(c, models.SSOLoginRequest{})
return
}
// 调用服务层处理回调
response, err := h.ssoService.HandleCallback(c.Request.Context(), &req)
if err != nil {
h.logger.Error("failed to handle SSO callback", zap.Error(err))
// 回调失败时,走登录逻辑
h.handleLoginLogic(c, models.SSOLoginRequest{})
return
}
h.response.Success(c, response)
}
// GetOnlineUsers 获取在线用户列表
func (h *SSOHandler) GetOnlineUsers(c *gin.Context) {
users, err := h.ssoService.GetOnlineUsers(c.Request.Context())
if err != nil {
h.logger.Error("failed to get online users", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, users)
}
// GetOnlineUserCount 获取在线用户数量
func (h *SSOHandler) GetOnlineUserCount(c *gin.Context) {
count, err := h.ssoService.GetOnlineUserCount(c.Request.Context())
if err != nil {
h.logger.Error("failed to get online user count", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"count": count})
}
// BatchLogout 批量登出用户
func (h *SSOHandler) BatchLogout(c *gin.Context) {
var req struct {
UserIDs []int `json:"user_ids" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err := h.ssoService.BatchUserLogout(c.Request.Context(), req.UserIDs)
if err != nil {
h.logger.Error("failed to batch logout users", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "批量登出成功")
}

View File

@@ -0,0 +1,189 @@
package handlers
import (
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// SystemConfigHandler 系统配置处理器
type SystemConfigHandler struct {
service services.SystemConfigService
response *utils.Response
logger *utils.Logger
}
// NewSystemConfigHandler 创建系统配置处理器
func NewSystemConfigHandler(service services.SystemConfigService, logger *utils.Logger) *SystemConfigHandler {
return &SystemConfigHandler{
service: service,
response: utils.NewResponse(),
logger: logger,
}
}
// Create 创建系统配置
func (h *SystemConfigHandler) Create(c *gin.Context) {
var req models.SystemConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
config, err := h.service.Create(&req)
if err != nil {
h.logger.Error("创建系统配置失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, config)
}
// GetByID 获取系统配置详情
func (h *SystemConfigHandler) GetByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
config, err := h.service.GetByID(uint(id))
if err != nil {
h.logger.Error("获取系统配置失败", zap.Error(err))
h.response.NotFound(c, "配置不存在")
return
}
h.response.Success(c, config)
}
// GetByKey 根据配置标识获取系统配置
func (h *SystemConfigHandler) GetByKey(c *gin.Context) {
key := c.Param("key")
if key == "" {
h.response.BadRequest(c, "配置标识不能为空")
return
}
config, err := h.service.GetByKey(key)
if err != nil {
h.logger.Error("获取系统配置失败", zap.Error(err))
h.response.NotFound(c, "配置不存在")
return
}
h.response.Success(c, config)
}
// List 获取系统配置列表
func (h *SystemConfigHandler) List(c *gin.Context) {
var req models.SystemConfigListRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 设置默认值
if req.Page <= 0 {
req.Page = 1
}
if req.Size <= 0 {
req.Size = 20
}
configs, total, err := h.service.List(&req)
if err != nil {
h.logger.Error("获取系统配置列表失败", zap.Error(err))
h.response.InternalServerError(c, "获取列表失败")
return
}
h.response.Page(c, configs, total, req.Page, req.Size)
}
// Update 更新系统配置
func (h *SystemConfigHandler) Update(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
var req models.SystemConfigUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
config, err := h.service.Update(uint(id), &req)
if err != nil {
h.logger.Error("更新系统配置失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, config)
}
// Delete 删除系统配置
func (h *SystemConfigHandler) Delete(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
err = h.service.Delete(uint(id))
if err != nil {
h.logger.Error("删除系统配置失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, nil)
}
// UpdateStatus 更新状态
func (h *SystemConfigHandler) UpdateStatus(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
var req models.SystemConfigUpdateStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err = h.service.UpdateStatus(uint(id), req.Status)
if err != nil {
h.logger.Error("更新系统配置状态失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, nil)
}
// GetAll 获取所有系统配置
func (h *SystemConfigHandler) GetAll(c *gin.Context) {
configs, err := h.service.GetAll()
if err != nil {
h.logger.Error("获取所有系统配置失败", zap.Error(err))
h.response.InternalServerError(c, "获取配置失败")
return
}
h.response.Success(c, configs)
}

View File

@@ -0,0 +1,195 @@
package handlers
import (
"strconv"
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"github.com/gin-gonic/gin"
)
// UserFeedbackHandler 用户反馈处理器
type UserFeedbackHandler struct {
service *services.UserFeedbackService
response *utils.Response
}
// NewUserFeedbackHandler 创建用户反馈处理器
func NewUserFeedbackHandler(service *services.UserFeedbackService) *UserFeedbackHandler {
return &UserFeedbackHandler{
service: service,
response: utils.NewResponse(),
}
}
// List 获取用户反馈列表
func (h *UserFeedbackHandler) List(c *gin.Context) {
var req models.UserFeedbackListRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.response.ValidateError(c, err)
return
}
result, err := h.service.List(c.Request.Context(), &req)
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, result)
}
// GetByID 根据ID获取用户反馈
func (h *UserFeedbackHandler) GetByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
h.response.BadRequest(c, "无效的反馈ID")
return
}
feedbackItem, err := h.service.GetByID(c.Request.Context(), id)
if err != nil {
if err.Error() == "反馈不存在" {
h.response.NotFound(c, "反馈不存在")
return
}
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, feedbackItem)
}
// MarkHandled 标记为已处理
func (h *UserFeedbackHandler) MarkHandled(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
h.response.BadRequest(c, "无效的反馈ID")
return
}
var req models.UserFeedbackMarkRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 获取当前用户ID从JWT token中解析
userID, exists := c.Get("user_id")
if !exists {
h.response.Unauthorized(c, "无法获取用户信息")
return
}
// 处理用户ID支持多种类型
var handledBy int
switch v := userID.(type) {
case int:
handledBy = v
case uint:
handledBy = int(v)
case string:
var err error
handledBy, err = strconv.Atoi(v)
if err != nil {
h.response.InternalServerError(c, "用户ID格式错误")
return
}
default:
h.response.InternalServerError(c, "用户ID格式错误")
return
}
// 获取当前状态以确定切换后的状态
feedback, err := h.service.GetByID(c.Request.Context(), id)
if err != nil {
if err.Error() == "反馈不存在" {
h.response.NotFound(c, "反馈不存在")
return
}
h.response.InternalServerError(c, err.Error())
return
}
newStatus := 0
message := "标记为未处理成功"
if feedback.Status == 0 {
// 当前是未处理,切换为已处理
newStatus = 1
message = "标记为已处理成功"
}
err = h.service.MarkHandled(c.Request.Context(), id, handledBy, req.Note)
if err != nil {
if err.Error() == "反馈不存在" {
h.response.NotFound(c, "反馈不存在")
return
}
if err.Error() == "处理人ID无效" {
h.response.BadRequest(c, err.Error())
return
}
h.response.InternalServerError(c, err.Error())
return
}
// 返回处理结果
result := map[string]interface{}{
"id": id,
"status": newStatus,
"message": message,
}
if newStatus == 1 {
result["handled_by"] = handledBy
}
h.response.Success(c, result)
}
// Delete 删除用户反馈
func (h *UserFeedbackHandler) Delete(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
h.response.BadRequest(c, "无效的反馈ID")
return
}
err = h.service.Delete(c.Request.Context(), id)
if err != nil {
if err.Error() == "反馈不存在" {
h.response.NotFound(c, "反馈不存在")
return
}
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "删除成功"})
}
// GetStatistics 获取反馈统计信息
func (h *UserFeedbackHandler) GetStatistics(c *gin.Context) {
stats, err := h.service.GetStatistics(c.Request.Context())
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, stats)
}
// hasHTTPPrefix 判断字符串是否为 http/https URL
func hasHTTPPrefix(s string) bool {
if len(s) < 7 {
return false
}
if len(s) >= 8 && (s[:8] == "https://") {
return true
}
return len(s) >= 7 && (s[:7] == "http://")
}

View File

@@ -0,0 +1,285 @@
package handlers
import (
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// UserHandler 用户处理器
type UserHandler struct {
userService services.UserService
rbacService services.RBACService
response *utils.Response
logger *utils.Logger
}
// NewUserHandler 创建用户处理器
func NewUserHandler(userService services.UserService, rbacService services.RBACService, logger *utils.Logger) *UserHandler {
return &UserHandler{
userService: userService,
rbacService: rbacService,
response: utils.NewResponse(),
logger: logger,
}
}
// Create 创建用户
func (h *UserHandler) Create(c *gin.Context) {
var req models.UserCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
user, err := h.userService.Create(&req)
if err != nil {
h.logger.Error("创建用户失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, user)
}
// GetByID 获取用户详情
func (h *UserHandler) GetByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的用户ID")
return
}
user, err := h.userService.GetByID(uint(id))
if err != nil {
h.logger.Error("获取用户失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, user)
}
// Update 更新用户
func (h *UserHandler) Update(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的用户ID")
return
}
var req models.UserUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
user, err := h.userService.Update(uint(id), &req)
if err != nil {
h.logger.Error("更新用户失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, user)
}
// Delete 删除用户
func (h *UserHandler) Delete(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的用户ID")
return
}
err = h.userService.Delete(uint(id))
if err != nil {
h.logger.Error("删除用户失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "用户删除成功"})
}
// List 获取用户列表
func (h *UserHandler) List(c *gin.Context) {
var req models.UserListRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.response.ValidateError(c, err)
return
}
users, total, err := h.userService.ListWithRoles(&req)
if err != nil {
h.logger.Error("获取用户列表失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{
"users": users,
"total": total,
"page": req.Page,
"size": req.Size,
})
}
// UpdateStatus 更新用户状态
func (h *UserHandler) UpdateStatus(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的用户ID")
return
}
var req struct {
Status int `json:"status" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err = h.userService.UpdateStatus(uint(id), req.Status)
if err != nil {
h.logger.Error("更新用户状态失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "用户状态更新成功"})
}
// UpdateRoles 更新用户角色
func (h *UserHandler) UpdateRoles(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的用户ID")
return
}
var req models.UserRoleAssignRequest
req.UserID = uint(id)
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 现在用户只有一个角色直接更新用户的role_id
err = h.userService.UpdateRole(uint(id), req.RoleIDs[0])
if err != nil {
h.logger.Error("更新用户角色失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "用户角色更新成功"})
}
// GetUserRoles 获取用户角色
func (h *UserHandler) GetUserRoles(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的用户ID")
return
}
// 现在用户只有一个角色,直接获取用户信息
user, err := h.userService.GetByID(uint(id))
if err != nil {
h.logger.Error("获取用户信息失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
// 获取角色信息
role, err := h.rbacService.GetRoleByID(user.RoleID)
if err != nil {
h.logger.Error("获取角色信息失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"roles": []models.Role{*role}})
}
// GetUserPermissions 获取用户权限
func (h *UserHandler) GetUserPermissions(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的用户ID")
return
}
permissions, err := h.rbacService.GetUserPermissionsResponse(uint(id))
if err != nil {
h.logger.Error("获取用户权限失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"permissions": permissions})
}
// CheckUserRole 检查用户当前系统角色
// GET /admin/users/check-role/:user_id
func (h *UserHandler) CheckUserRole(c *gin.Context) {
userIDStr := c.Param("user_id")
userID, err := strconv.Atoi(userIDStr)
if err != nil {
h.response.BadRequest(c, "无效的用户ID")
return
}
isSystemAdmin, err := h.userService.CheckUserSystemRole(userID)
if err != nil {
h.logger.Error("检查用户系统角色失败", zap.Int("user_id", userID), zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
currentRole := "custom"
if isSystemAdmin {
currentRole = "sys_admin"
}
h.response.Success(c, gin.H{
"is_system_admin": isSystemAdmin,
"current_role": currentRole,
})
}
// ChangeUserSystemRole 变更用户系统角色
// POST /admin/users/change-system-role
func (h *UserHandler) ChangeUserSystemRole(c *gin.Context) {
var req struct {
UserID int `json:"user_id" binding:"required"`
SystemRole string `json:"system_role" binding:"required,oneof=sys_admin custom"`
}
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err := h.userService.ChangeUserSystemRole(req.UserID, req.SystemRole)
if err != nil {
h.logger.Error("变更用户系统角色失败", zap.Int("user_id", req.UserID), zap.String("system_role", req.SystemRole), zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, gin.H{"message": "角色变更成功"})
}

View File

@@ -0,0 +1,171 @@
package handlers
import (
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// UserLevelConfigHandler 用户等级配置处理器
type UserLevelConfigHandler struct {
service services.UserLevelConfigService
response *utils.Response
logger *utils.Logger
}
// NewUserLevelConfigHandler 创建用户等级配置处理器
func NewUserLevelConfigHandler(service services.UserLevelConfigService, logger *utils.Logger) *UserLevelConfigHandler {
return &UserLevelConfigHandler{
service: service,
response: utils.NewResponse(),
logger: logger,
}
}
// Create 创建用户等级配置
func (h *UserLevelConfigHandler) Create(c *gin.Context) {
var req models.UserLevelConfigCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
config, err := h.service.Create(&req)
if err != nil {
h.logger.Error("创建用户等级配置失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, config)
}
// GetByID 获取用户等级配置详情
func (h *UserLevelConfigHandler) GetByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
config, err := h.service.GetByID(uint(id))
if err != nil {
h.logger.Error("获取用户等级配置失败", zap.Error(err))
h.response.NotFound(c, "配置不存在")
return
}
h.response.Success(c, config)
}
// List 获取用户等级配置列表
func (h *UserLevelConfigHandler) List(c *gin.Context) {
var req models.UserLevelConfigListRequest
if err := c.ShouldBindQuery(&req); err != nil {
h.response.ValidateError(c, err)
return
}
// 设置默认值
if req.Page <= 0 {
req.Page = 1
}
if req.Size <= 0 {
req.Size = 20
}
configs, total, err := h.service.List(&req)
if err != nil {
h.logger.Error("获取用户等级配置列表失败", zap.Error(err))
h.response.InternalServerError(c, "获取列表失败")
return
}
h.response.Page(c, configs, total, req.Page, req.Size)
}
// Update 更新用户等级配置
func (h *UserLevelConfigHandler) Update(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
var req models.UserLevelConfigUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
config, err := h.service.Update(uint(id), &req)
if err != nil {
h.logger.Error("更新用户等级配置失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, config)
}
// Delete 删除用户等级配置
func (h *UserLevelConfigHandler) Delete(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
err = h.service.Delete(uint(id))
if err != nil {
h.logger.Error("删除用户等级配置失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, nil)
}
// UpdateStatus 更新状态
func (h *UserLevelConfigHandler) UpdateStatus(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
h.response.BadRequest(c, "无效的ID")
return
}
var req models.UserLevelConfigUpdateStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.ValidateError(c, err)
return
}
err = h.service.UpdateStatus(uint(id), req.Status)
if err != nil {
h.logger.Error("更新用户等级配置状态失败", zap.Error(err))
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, nil)
}
// GetAll 获取所有用户等级配置
func (h *UserLevelConfigHandler) GetAll(c *gin.Context) {
configs, err := h.service.GetAll()
if err != nil {
h.logger.Error("获取所有用户等级配置失败", zap.Error(err))
h.response.InternalServerError(c, "获取配置失败")
return
}
h.response.Success(c, configs)
}

View File

@@ -0,0 +1,97 @@
package handlers
import (
"github.com/gin-gonic/gin"
"goalfymax-admin/internal/models"
"goalfymax-admin/internal/services"
"goalfymax-admin/internal/storage"
"goalfymax-admin/pkg/utils"
"strconv"
)
type UserProjectQuotaHandler struct {
svc services.UserProjectQuotaService
resp *utils.Response
}
func NewUserProjectQuotaHandler(s services.UserProjectQuotaService) *UserProjectQuotaHandler {
return &UserProjectQuotaHandler{svc: s, resp: utils.NewResponse()}
}
func (h *UserProjectQuotaHandler) Create(c *gin.Context) {
var req models.UserProjectQuota
if err := c.ShouldBindJSON(&req); err != nil {
h.resp.ValidateError(c, err)
return
}
out, err := h.svc.Create(&req)
if err != nil {
h.resp.BadRequest(c, err.Error())
return
}
h.resp.Success(c, out)
}
func (h *UserProjectQuotaHandler) Update(c *gin.Context) {
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
var req models.UserProjectQuota
if err := c.ShouldBindJSON(&req); err != nil {
h.resp.ValidateError(c, err)
return
}
out, err := h.svc.Update(uint(id64), &req)
if err != nil {
h.resp.BadRequest(c, err.Error())
return
}
h.resp.Success(c, out)
}
func (h *UserProjectQuotaHandler) Delete(c *gin.Context) {
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := h.svc.Delete(uint(id64)); err != nil {
h.resp.BadRequest(c, err.Error())
return
}
h.resp.Success(c, gin.H{"message": "deleted"})
}
func (h *UserProjectQuotaHandler) GetByID(c *gin.Context) {
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
out, err := h.svc.GetByID(uint(id64))
if err != nil {
h.resp.NotFound(c, "not found")
return
}
h.resp.Success(c, out)
}
func (h *UserProjectQuotaHandler) List(c *gin.Context) {
var filter storage.UserProjectQuotaFilter
filter.UserID = c.Query("user_id")
if v := c.Query("enabled"); v != "" {
if v == "true" {
b := true
filter.Enabled = &b
} else if v == "false" {
b := false
filter.Enabled = &b
}
}
if p := c.Query("page"); p != "" {
if v, err := strconv.Atoi(p); err == nil {
filter.Page = v
}
}
if s := c.Query("size"); s != "" {
if v, err := strconv.Atoi(s); err == nil {
filter.Size = v
}
}
items, total, err := h.svc.List(filter)
if err != nil {
h.resp.InternalServerError(c, err.Error())
return
}
h.resp.Page(c, items, total, filter.Page, filter.Size)
}

View File

@@ -0,0 +1,170 @@
package handlers
import (
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// VendorModelPricingHandler 供应商模型价格配置处理器
type VendorModelPricingHandler struct {
db *gorm.DB
response *utils.Response
}
// NewVendorModelPricingHandler 创建处理器
func NewVendorModelPricingHandler(db *gorm.DB) *VendorModelPricingHandler {
return &VendorModelPricingHandler{
db: db,
response: utils.NewResponse(),
}
}
// VendorModelPricingResponse 供应商模型价格配置响应
type VendorModelPricingResponse struct {
ID uint `json:"id"`
Provider string `json:"provider"`
Account string `json:"account"`
ModelName string `json:"model_name"`
InputPrice float64 `json:"input_price"`
OutputPrice float64 `json:"output_price"`
CacheReadPrice float64 `json:"cache_read_price"`
CacheCreatePrice float64 `json:"cache_create_price"`
PriceRatio float64 `json:"price_ratio"`
Enabled bool `json:"enabled"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// PriceUpdateRequest 价格更新请求
type PriceUpdateRequest struct {
InputPrice *float64 `json:"input_price"`
OutputPrice *float64 `json:"output_price"`
CacheReadPrice *float64 `json:"cache_read_price"`
CacheCreatePrice *float64 `json:"cache_create_price"`
Enabled *bool `json:"enabled"`
PriceRatio *float64 `json:"price_ratio"`
}
// GetVendorModelPricing 获取供应商模型价格配置列表
func (h *VendorModelPricingHandler) GetVendorModelPricing(c *gin.Context) {
var models []VendorModelPricingResponse
// 构建查询条件
// 使用 v2 表gw_model_config_v2直接返回数据无需联查
// 为兼容前端字段类型account 字段返回空串
query := h.db.Table("gw_model_config_v2 mc").
Select("mc.id, mc.provider, '' as account, mc.model_name, " +
"mc.prompt_price as input_price, " +
"mc.output_price as output_price, " +
"mc.cache_read_price, mc.cache_create_price, " +
"mc.price_ratio, " +
"mc.enabled, mc.created_at, mc.updated_at")
// 添加筛选条件
if provider := c.Query("provider"); provider != "" {
query = query.Where("mc.provider = ?", provider)
}
if model := c.Query("model"); model != "" {
query = query.Where("mc.model_name LIKE ?", "%"+model+"%")
}
if status := c.Query("status"); status != "" {
if status == "enabled" {
query = query.Where("mc.enabled = ?", true)
} else if status == "disabled" {
query = query.Where("mc.enabled = ?", false)
}
}
// 分页
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
offset := (page - 1) * size
// 先获取总数(在应用分页之前)
var total int64
err := query.Count(&total).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
// 然后获取分页数据
err = query.Offset(offset).Limit(size).Find(&models).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, models, total, page, size)
}
// UpdateModelPricing 更新单个模型价格
func (h *VendorModelPricingHandler) UpdateModelPricing(c *gin.Context) {
id := c.Param("id")
var req PriceUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.BadRequest(c, err.Error())
return
}
// 构建更新字段v2 列)
updates := make(map[string]interface{})
if req.InputPrice != nil {
updates["prompt_price"] = *req.InputPrice
}
if req.OutputPrice != nil {
updates["output_price"] = *req.OutputPrice
}
if req.CacheReadPrice != nil {
updates["cache_read_price"] = *req.CacheReadPrice
}
if req.CacheCreatePrice != nil {
updates["cache_create_price"] = *req.CacheCreatePrice
}
if req.Enabled != nil {
updates["enabled"] = *req.Enabled
}
if req.PriceRatio != nil {
updates["price_ratio"] = *req.PriceRatio
}
// 检查模型是否存在v2 表)
var count int64
err := h.db.Table("gw_model_config_v2").Where("id = ?", id).Count(&count).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
if count == 0 {
h.response.NotFound(c, "模型不存在")
return
}
// 更新模型价格v2 表)
err = h.db.Table("gw_model_config_v2").Where("id = ?", id).Updates(updates).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "价格更新成功")
}
// GetProviders 获取供应商列表(用于筛选)
func (h *VendorModelPricingHandler) GetProviders(c *gin.Context) {
var providers []string
err := h.db.Table("gw_providers").
Select("DISTINCT name").
Where("status = ?", "active").
Find(&providers).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, providers)
}

View File

@@ -0,0 +1,403 @@
package handlers
import (
"errors"
"goalfymax-admin/pkg/utils"
"strconv"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// VmPricingHandler 虚拟机价格配置处理器
type VmPricingHandler struct {
db *gorm.DB
response *utils.Response
}
// NewVmPricingHandler 创建处理器
func NewVmPricingHandler(db *gorm.DB) *VmPricingHandler {
return &VmPricingHandler{
db: db,
response: utils.NewResponse(),
}
}
// VmSpecResponse 虚拟机规格响应
type VmSpecResponse struct {
ID uint `json:"id"`
SpecType string `json:"spec_type"`
CPUCores int `json:"cpu_cores"`
MemoryGB int `json:"memory_gb"`
Description *string `json:"description"`
CostPricePerMinute float64 `json:"cost_price_per_minute"`
MarkupRate float64 `json:"markup_rate"`
IsActive bool `json:"is_active"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// VmTemplateResponse 虚拟机模板响应
type VmTemplateResponse struct {
ID uint `json:"id"`
SpecType string `json:"spec_type"`
TemplateID string `json:"template_id"`
IsDefault bool `json:"is_default"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// VmSpecCreateRequest 规格创建请求
type VmSpecCreateRequest struct {
SpecType string `json:"spec_type" binding:"required"`
CPUCores int `json:"cpu_cores" binding:"required"`
MemoryGB int `json:"memory_gb" binding:"required"`
Description *string `json:"description"`
CostPricePerMinute float64 `json:"cost_price_per_minute" binding:"required"`
MarkupRate *float64 `json:"markup_rate"`
IsActive *bool `json:"is_active"`
}
// VmSpecUpdateRequest 规格更新请求
type VmSpecUpdateRequest struct {
CostPricePerMinute *float64 `json:"cost_price_per_minute"`
MarkupRate *float64 `json:"markup_rate"`
IsActive *bool `json:"is_active"`
}
// VmTemplateCreateRequest 模板创建请求
type VmTemplateCreateRequest struct {
SpecType string `json:"spec_type" binding:"required"`
TemplateID string `json:"template_id" binding:"required"`
IsDefault bool `json:"is_default"`
}
// VmTemplateUpdateRequest 模板更新请求
type VmTemplateUpdateRequest struct {
IsDefault *bool `json:"is_default"`
}
// GetVmSpecs 获取虚拟机规格列表
func (h *VmPricingHandler) GetVmSpecs(c *gin.Context) {
var specs []VmSpecResponse
// 构建查询条件
query := h.db.Table("sb_sandbox_specs")
// 添加筛选条件
if specType := c.Query("spec_type"); specType != "" {
query = query.Where("spec_type LIKE ?", "%"+specType+"%")
}
if status := c.Query("status"); status != "" {
if status == "active" {
query = query.Where("is_active = ?", true)
} else if status == "inactive" {
query = query.Where("is_active = ?", false)
}
}
// 分页
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "10"))
offset := (page - 1) * size
// 先获取总数
var total int64
err := query.Count(&total).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
// 然后获取分页数据
err = query.Order("created_at DESC").Offset(offset).Limit(size).Find(&specs).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, specs, total, page, size)
}
// UpdateVmSpec 更新虚拟机规格
func (h *VmPricingHandler) UpdateVmSpec(c *gin.Context) {
id := c.Param("id")
var req VmSpecUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.BadRequest(c, err.Error())
return
}
// 构建更新字段
updates := make(map[string]interface{})
if req.CostPricePerMinute != nil {
updates["cost_price_per_minute"] = *req.CostPricePerMinute
}
if req.MarkupRate != nil {
updates["markup_rate"] = *req.MarkupRate
}
if req.IsActive != nil {
updates["is_active"] = *req.IsActive
}
if len(updates) == 0 {
h.response.BadRequest(c, "没有需要更新的字段")
return
}
// 检查规格是否存在
var count int64
err := h.db.Table("sb_sandbox_specs").Where("id = ?", id).Count(&count).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
if count == 0 {
h.response.NotFound(c, "规格不存在")
return
}
// 更新规格
err = h.db.Table("sb_sandbox_specs").Where("id = ?", id).Updates(updates).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "规格更新成功")
}
// DeleteVmSpec 删除虚拟机规格
func (h *VmPricingHandler) DeleteVmSpec(c *gin.Context) {
id := c.Param("id")
// 检查规格是否存在
var count int64
err := h.db.Table("sb_sandbox_specs").Where("id = ?", id).Count(&count).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
if count == 0 {
h.response.NotFound(c, "规格不存在")
return
}
// 删除规格
err = h.db.Table("sb_sandbox_specs").Where("id = ?", id).Delete(nil).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "规格删除成功")
}
// CreateVmSpec 创建虚拟机规格
func (h *VmPricingHandler) CreateVmSpec(c *gin.Context) {
var req VmSpecCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.BadRequest(c, err.Error())
return
}
// 检查规格类型是否已存在
var count int64
err := h.db.Table("sb_sandbox_specs").Where("spec_type = ?", req.SpecType).Count(&count).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
if count > 0 {
h.response.BadRequest(c, "该配置类型已存在")
return
}
// 设置默认值
markupRate := 0.3000 // 默认30%
if req.MarkupRate != nil {
markupRate = *req.MarkupRate
}
isActive := true
if req.IsActive != nil {
isActive = *req.IsActive
}
// 创建规格
insertData := map[string]interface{}{
"spec_type": req.SpecType,
"cpu_cores": req.CPUCores,
"memory_gb": req.MemoryGB,
"cost_price_per_minute": req.CostPricePerMinute,
"markup_rate": markupRate,
"is_active": isActive,
}
if req.Description != nil {
insertData["description"] = *req.Description
}
err = h.db.Table("sb_sandbox_specs").Create(insertData).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "规格创建成功")
}
// GetVmTemplates 获取虚拟机模板列表
func (h *VmPricingHandler) GetVmTemplates(c *gin.Context) {
var templates []VmTemplateResponse
// 构建查询条件
query := h.db.Table("sb_sandbox_templates")
// 添加筛选条件
if specType := c.Query("spec_type"); specType != "" {
query = query.Where("spec_type = ?", specType)
}
// 分页
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "10"))
offset := (page - 1) * size
// 先获取总数
var total int64
err := query.Count(&total).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
// 然后获取分页数据
err = query.Order("spec_type ASC, created_at DESC").Offset(offset).Limit(size).Find(&templates).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Page(c, templates, total, page, size)
}
// CreateVmTemplate 创建虚拟机模板
func (h *VmPricingHandler) CreateVmTemplate(c *gin.Context) {
var req VmTemplateCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.response.BadRequest(c, err.Error())
return
}
// 检查规格是否存在
var specCount int64
err := h.db.Table("sb_sandbox_specs").Where("spec_type = ?", req.SpecType).Count(&specCount).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
if specCount == 0 {
h.response.BadRequest(c, "规格类型不存在")
return
}
// 如果设置为默认模板,需要先取消同规格的其他默认模板
if req.IsDefault {
err = h.db.Table("sb_sandbox_templates").
Where("spec_type = ? AND is_default = ?", req.SpecType, true).
Update("is_default", false).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
}
// 检查是否已存在相同的规格-模板组合
var existingCount int64
err = h.db.Table("sb_sandbox_templates").
Where("spec_type = ? AND template_id = ?", req.SpecType, req.TemplateID).
Count(&existingCount).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
if existingCount > 0 {
h.response.BadRequest(c, "该规格和模板的组合已存在")
return
}
// 创建模板
insertData := map[string]interface{}{
"spec_type": req.SpecType,
"template_id": req.TemplateID,
"is_default": req.IsDefault,
}
err = h.db.Table("sb_sandbox_templates").Create(insertData).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "模板创建成功")
}
// DeleteVmTemplate 删除虚拟机模板
func (h *VmPricingHandler) DeleteVmTemplate(c *gin.Context) {
id := c.Param("id")
// 检查模板是否存在
var count int64
err := h.db.Table("sb_sandbox_templates").Where("id = ?", id).Count(&count).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
if count == 0 {
h.response.NotFound(c, "模板不存在")
return
}
// 删除模板
err = h.db.Table("sb_sandbox_templates").Where("id = ?", id).Delete(nil).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "模板删除成功")
}
// SetDefaultVmTemplate 设置默认模板
func (h *VmPricingHandler) SetDefaultVmTemplate(c *gin.Context) {
id := c.Param("id")
// 获取模板信息
var template VmTemplateResponse
err := h.db.Table("sb_sandbox_templates").Where("id = ?", id).First(&template).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
h.response.NotFound(c, "模板不存在")
return
}
h.response.InternalServerError(c, err.Error())
return
}
// 取消同规格的其他默认模板
err = h.db.Table("sb_sandbox_templates").
Where("spec_type = ? AND is_default = ? AND id != ?", template.SpecType, true, id).
Update("is_default", false).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
// 设置当前模板为默认
err = h.db.Table("sb_sandbox_templates").Where("id = ?", id).Update("is_default", true).Error
if err != nil {
h.response.InternalServerError(c, err.Error())
return
}
h.response.Success(c, "默认模板设置成功")
}