Initial commit: 网络验证平台
This commit is contained in:
@@ -0,0 +1,776 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupAgentAppRoutes(r *gin.RouterGroup) {
|
||||
agentApps := r.Group("/agent-apps")
|
||||
{
|
||||
agentApps.GET("", handleGetAgentApps)
|
||||
agentApps.GET("/requests", handleGetAgentRequests)
|
||||
agentApps.POST("/invite", handleInviteAgent)
|
||||
agentApps.PUT("/requests/:id/approve", handleApproveRequest)
|
||||
agentApps.PUT("/requests/:id/reject", handleRejectRequest)
|
||||
agentApps.GET("/:id", handleGetAgentAppDetail)
|
||||
agentApps.PUT("/:id", handleUpdateAgentApp)
|
||||
agentApps.PUT("/:id/card-types", handleUpdateAgentCardTypes)
|
||||
agentApps.DELETE("/:id", handleRemoveAgentApp)
|
||||
}
|
||||
}
|
||||
|
||||
func SetupAgentAppRoutesWithoutPackage(r *gin.RouterGroup) {
|
||||
agentApps := r.Group("/agent-apps")
|
||||
{
|
||||
agentApps.GET("/my-requests", handleGetMyRequests)
|
||||
agentApps.POST("/request", handleRequestAuthorization)
|
||||
agentApps.DELETE("/requests/:id", handleCancelRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func checkAgentPermission(userID uint) bool {
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if user.CurrentPackageID == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var userPackage model.UserPackage
|
||||
if err := database.DB.Where("user_id = ? AND package_id = ? AND status = ?",
|
||||
userID, user.CurrentPackageID, "active").First(&userPackage).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if userPackage.ExpiredAt != nil && userPackage.ExpiredAt.Before(time.Now()) {
|
||||
return false
|
||||
}
|
||||
|
||||
var permission model.PackagePermission
|
||||
if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&permission).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return permission.AllowAgent
|
||||
}
|
||||
|
||||
func handleGetAgentApps(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
log.Printf("[DEBUG] handleGetAgentApps called for developer %d\n", userID)
|
||||
|
||||
var myAuthorizations []model.AgentApplication
|
||||
if err := database.DB.Where("developer_id = ?", userID).
|
||||
Preload("CardTypes.CardType").
|
||||
Find(&myAuthorizations).Error; err != nil {
|
||||
response.Error(c, 500, "获取授权列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var receivedAuthorizations []model.AgentApplication
|
||||
if err := database.DB.Where("agent_id = ?", userID).
|
||||
Preload("CardTypes.CardType").
|
||||
Find(&receivedAuthorizations).Error; err != nil {
|
||||
response.Error(c, 500, "获取授权列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Found %d my authorizations and %d received authorizations for developer %d\n",
|
||||
len(myAuthorizations), len(receivedAuthorizations), userID)
|
||||
|
||||
var allAgentApps []model.AgentApplication
|
||||
allAgentApps = append(allAgentApps, myAuthorizations...)
|
||||
allAgentApps = append(allAgentApps, receivedAuthorizations...)
|
||||
|
||||
var agentIDs []uint
|
||||
var developerIDs []uint
|
||||
var applicationIDs []uint
|
||||
|
||||
for _, aa := range allAgentApps {
|
||||
agentIDs = append(agentIDs, aa.AgentID)
|
||||
developerIDs = append(developerIDs, aa.DeveloperID)
|
||||
applicationIDs = append(applicationIDs, aa.ApplicationID)
|
||||
}
|
||||
|
||||
var users []model.User
|
||||
if err := database.DB.Where("id IN ?", append(agentIDs, developerIDs...)).Find(&users).Error; err != nil {
|
||||
log.Printf("[ERROR] Failed to query users: %v\n", err)
|
||||
} else {
|
||||
log.Printf("[DEBUG] Found %d users\n", len(users))
|
||||
}
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("id IN ?", applicationIDs).Find(&applications).Error; err != nil {
|
||||
log.Printf("[ERROR] Failed to query applications: %v\n", err)
|
||||
}
|
||||
|
||||
userMap := make(map[uint]model.User)
|
||||
for _, user := range users {
|
||||
userMap[user.ID] = user
|
||||
}
|
||||
|
||||
applicationMap := make(map[uint]model.Application)
|
||||
for _, app := range applications {
|
||||
applicationMap[app.ID] = app
|
||||
}
|
||||
|
||||
type CardTypeResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
Name string `json:"name"`
|
||||
CanGenerate bool `json:"can_generate"`
|
||||
}
|
||||
|
||||
type AgentAppResponse struct {
|
||||
ID uint `json:"id"`
|
||||
AgentID uint `json:"agent_id"`
|
||||
AgentName string `json:"agent_name"`
|
||||
AgentEmail string `json:"agent_email"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
AppName string `json:"app_name"`
|
||||
Commission float64 `json:"commission"`
|
||||
Discount float64 `json:"discount"`
|
||||
Status string `json:"status"`
|
||||
CardTypes []CardTypeResponse `json:"card_types"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
IsReceived bool `json:"is_received"`
|
||||
}
|
||||
|
||||
var result []AgentAppResponse
|
||||
for _, aa := range allAgentApps {
|
||||
agentName := ""
|
||||
agentEmail := ""
|
||||
if user, exists := userMap[aa.AgentID]; exists {
|
||||
agentName = user.Username
|
||||
if user.Email != nil {
|
||||
agentEmail = *user.Email
|
||||
}
|
||||
}
|
||||
|
||||
appName := ""
|
||||
if app, exists := applicationMap[aa.ApplicationID]; exists {
|
||||
appName = app.Name
|
||||
}
|
||||
|
||||
var cardTypes []CardTypeResponse
|
||||
for _, ct := range aa.CardTypes {
|
||||
cardTypes = append(cardTypes, CardTypeResponse{
|
||||
ID: ct.ID,
|
||||
CardTypeID: ct.CardTypeID,
|
||||
Name: ct.CardType.Name,
|
||||
CanGenerate: ct.CanGenerate,
|
||||
})
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Processing AgentApp: ID=%d, AgentID=%d, ApplicationID=%d, CardTypes count=%d, IsReceived=%v\n",
|
||||
aa.ID, aa.AgentID, aa.ApplicationID, len(cardTypes), aa.AgentID == userID)
|
||||
|
||||
result = append(result, AgentAppResponse{
|
||||
ID: aa.ID,
|
||||
AgentID: aa.AgentID,
|
||||
AgentName: agentName,
|
||||
AgentEmail: agentEmail,
|
||||
ApplicationID: aa.ApplicationID,
|
||||
AppName: appName,
|
||||
Commission: aa.Commission,
|
||||
Discount: aa.Discount,
|
||||
Status: aa.Status,
|
||||
CardTypes: cardTypes,
|
||||
CreatedAt: aa.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
IsReceived: aa.AgentID == userID,
|
||||
})
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Returning %d agent apps for user %d\n", len(result), userID)
|
||||
response.Success(c, gin.H{
|
||||
"agent_apps": result,
|
||||
"total": len(result),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetAgentRequests(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
requestType := c.Query("type")
|
||||
|
||||
query := database.DB.Where("developer_id = ?", userID)
|
||||
if requestType == "invite" {
|
||||
query = query.Where("type = ?", "invite")
|
||||
} else if requestType == "request" {
|
||||
query = query.Where("type = ?", "request")
|
||||
}
|
||||
|
||||
var requests []model.AgentApplicationRequest
|
||||
if err := query.
|
||||
Preload("Agent").
|
||||
Preload("Application").
|
||||
Order("created_at DESC").
|
||||
Find(&requests).Error; err != nil {
|
||||
response.Error(c, 500, "获取申请列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
type RequestResponse struct {
|
||||
ID uint `json:"id"`
|
||||
AgentID uint `json:"agent_id"`
|
||||
AgentName string `json:"agent_name"`
|
||||
AgentEmail string `json:"agent_email"`
|
||||
DeveloperID uint `json:"developer_id"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
AppName string `json:"app_name"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
RejectReason string `json:"reject_reason"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
var result []RequestResponse
|
||||
for _, req := range requests {
|
||||
agentName := ""
|
||||
agentEmail := ""
|
||||
if req.Agent.ID != 0 {
|
||||
agentName = req.Agent.Username
|
||||
if req.Agent.Email != nil {
|
||||
agentEmail = *req.Agent.Email
|
||||
}
|
||||
}
|
||||
|
||||
appName := ""
|
||||
if req.Application.ID != 0 {
|
||||
appName = req.Application.Name
|
||||
}
|
||||
|
||||
result = append(result, RequestResponse{
|
||||
ID: req.ID,
|
||||
AgentID: req.AgentID,
|
||||
AgentName: agentName,
|
||||
AgentEmail: agentEmail,
|
||||
DeveloperID: req.DeveloperID,
|
||||
ApplicationID: req.ApplicationID,
|
||||
AppName: appName,
|
||||
Type: req.Type,
|
||||
Status: req.Status,
|
||||
Message: req.Message,
|
||||
RejectReason: req.RejectReason,
|
||||
CreatedAt: req.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"requests": result,
|
||||
"total": len(result),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetMyRequests(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
requestType := c.Query("type")
|
||||
|
||||
query := database.DB.Where("agent_id = ?", userID)
|
||||
if requestType == "invite" {
|
||||
query = query.Where("type = ?", "invite")
|
||||
} else if requestType == "request" {
|
||||
query = query.Where("type = ?", "request")
|
||||
}
|
||||
|
||||
var requests []model.AgentApplicationRequest
|
||||
if err := query.
|
||||
Preload("Developer").
|
||||
Preload("Application").
|
||||
Order("created_at DESC").
|
||||
Find(&requests).Error; err != nil {
|
||||
response.Error(c, 500, "获取申请列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
type RequestResponse struct {
|
||||
ID uint `json:"id"`
|
||||
AgentID uint `json:"agent_id"`
|
||||
DeveloperID uint `json:"developer_id"`
|
||||
DeveloperName string `json:"developer_name"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
AppName string `json:"app_name"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
RejectReason string `json:"reject_reason"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
var result []RequestResponse
|
||||
for _, req := range requests {
|
||||
developerName := ""
|
||||
if req.Developer.ID != 0 {
|
||||
developerName = req.Developer.Username
|
||||
}
|
||||
|
||||
appName := ""
|
||||
if req.Application.ID != 0 {
|
||||
appName = req.Application.Name
|
||||
}
|
||||
|
||||
result = append(result, RequestResponse{
|
||||
ID: req.ID,
|
||||
AgentID: req.AgentID,
|
||||
DeveloperID: req.DeveloperID,
|
||||
DeveloperName: developerName,
|
||||
ApplicationID: req.ApplicationID,
|
||||
AppName: appName,
|
||||
Type: req.Type,
|
||||
Status: req.Status,
|
||||
Message: req.Message,
|
||||
RejectReason: req.RejectReason,
|
||||
CreatedAt: req.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"requests": result,
|
||||
"total": len(result),
|
||||
})
|
||||
}
|
||||
|
||||
func handleInviteAgent(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
if !checkAgentPermission(userID) {
|
||||
response.Error(c, 403, "您的套餐不支持代理功能,请升级套餐")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AgentID uint `json:"agent_id"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var agent model.User
|
||||
if err := database.DB.First(&agent, req.AgentID).Error; err != nil {
|
||||
response.Error(c, 404, "开发者不存在")
|
||||
return
|
||||
}
|
||||
if agent.Role != "developer" {
|
||||
response.Error(c, 400, "该用户不是开发者")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var existing model.AgentApplication
|
||||
if err := database.DB.Where("agent_id = ? AND application_id = ?", req.AgentID, req.ApplicationID).First(&existing).Error; err == nil {
|
||||
response.Error(c, 400, "该开发者已获得此应用的授权")
|
||||
return
|
||||
}
|
||||
|
||||
request := model.AgentApplicationRequest{
|
||||
AgentID: req.AgentID,
|
||||
DeveloperID: userID,
|
||||
ApplicationID: req.ApplicationID,
|
||||
Type: "invite",
|
||||
Status: "pending",
|
||||
Message: req.Message,
|
||||
}
|
||||
if err := database.DB.Create(&request).Error; err != nil {
|
||||
fmt.Printf("创建邀请错误: %v\n", err)
|
||||
response.Error(c, 500, "创建邀请失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": request.ID,
|
||||
"agent_id": request.AgentID,
|
||||
"agent_name": agent.Username,
|
||||
"app_id": request.ApplicationID,
|
||||
"app_name": app.Name,
|
||||
"type": request.Type,
|
||||
"status": request.Status,
|
||||
})
|
||||
}
|
||||
|
||||
func handleRequestAuthorization(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
DeveloperID uint `json:"developer_id"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var developer model.User
|
||||
if err := database.DB.First(&developer, req.DeveloperID).Error; err != nil {
|
||||
response.Error(c, 404, "开发者不存在")
|
||||
return
|
||||
}
|
||||
if developer.Role != "developer" {
|
||||
response.Error(c, 400, "该用户不是开发者")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, req.DeveloperID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var existing model.AgentApplication
|
||||
if err := database.DB.Where("agent_id = ? AND application_id = ?", userID, req.ApplicationID).First(&existing).Error; err == nil {
|
||||
response.Error(c, 400, "您已获得此应用的授权")
|
||||
return
|
||||
}
|
||||
|
||||
var existingRequest model.AgentApplicationRequest
|
||||
if err := database.DB.Where("agent_id = ? AND developer_id = ? AND application_id = ? AND status = ?",
|
||||
userID, req.DeveloperID, req.ApplicationID, "pending").First(&existingRequest).Error; err == nil {
|
||||
response.Error(c, 400, "您已有待处理的申请")
|
||||
return
|
||||
}
|
||||
|
||||
request := model.AgentApplicationRequest{
|
||||
AgentID: userID,
|
||||
DeveloperID: req.DeveloperID,
|
||||
ApplicationID: req.ApplicationID,
|
||||
Type: "request",
|
||||
Status: "pending",
|
||||
Message: req.Message,
|
||||
}
|
||||
if err := database.DB.Create(&request).Error; err != nil {
|
||||
fmt.Printf("创建申请错误: %v\n", err)
|
||||
response.Error(c, 500, "创建申请失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": request.ID,
|
||||
"developer_id": request.DeveloperID,
|
||||
"developer_name": developer.Username,
|
||||
"app_id": request.ApplicationID,
|
||||
"app_name": app.Name,
|
||||
"type": request.Type,
|
||||
"status": request.Status,
|
||||
})
|
||||
}
|
||||
|
||||
func handleApproveRequest(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
requestID := c.Param("id")
|
||||
|
||||
if !checkAgentPermission(userID) {
|
||||
response.Error(c, 403, "您的套餐不支持代理功能,请升级套餐")
|
||||
return
|
||||
}
|
||||
|
||||
var req model.AgentApplicationRequest
|
||||
if err := database.DB.Where("id = ? AND developer_id = ?", requestID, userID).First(&req).Error; err != nil {
|
||||
response.Error(c, 404, "申请不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Status != "pending" {
|
||||
response.Error(c, 400, "该申请已处理")
|
||||
return
|
||||
}
|
||||
|
||||
tx := database.DB.Begin()
|
||||
|
||||
agentApp := model.AgentApplication{
|
||||
AgentID: req.AgentID,
|
||||
ApplicationID: req.ApplicationID,
|
||||
DeveloperID: userID,
|
||||
Commission: 0.1,
|
||||
Discount: 1.0,
|
||||
Status: "active",
|
||||
IsReceived: true,
|
||||
}
|
||||
if err := tx.Create(&agentApp).Error; err != nil {
|
||||
tx.Rollback()
|
||||
fmt.Printf("创建授权错误: %v\n", err)
|
||||
response.Error(c, 500, "创建授权失败")
|
||||
return
|
||||
}
|
||||
|
||||
var cardTypes []model.CardType
|
||||
database.DB.Where("application_id = ? OR application_id IS NULL", req.ApplicationID).Find(&cardTypes)
|
||||
for _, ct := range cardTypes {
|
||||
agentCardType := model.AgentApplicationCardType{
|
||||
AgentApplicationID: agentApp.ID,
|
||||
CardTypeID: ct.ID,
|
||||
CanGenerate: false,
|
||||
}
|
||||
tx.Create(&agentCardType)
|
||||
}
|
||||
|
||||
req.Status = "approved"
|
||||
if err := tx.Save(&req).Error; err != nil {
|
||||
tx.Rollback()
|
||||
response.Error(c, 500, "更新申请状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": agentApp.ID,
|
||||
"status": "approved",
|
||||
})
|
||||
}
|
||||
|
||||
func handleRejectRequest(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
requestID := c.Param("id")
|
||||
|
||||
var reqBody struct {
|
||||
RejectReason string `json:"reject_reason"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&reqBody); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var req model.AgentApplicationRequest
|
||||
if err := database.DB.Where("id = ? AND developer_id = ?", requestID, userID).First(&req).Error; err != nil {
|
||||
response.Error(c, 404, "申请不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Status != "pending" {
|
||||
response.Error(c, 400, "该申请已处理")
|
||||
return
|
||||
}
|
||||
|
||||
req.Status = "rejected"
|
||||
req.RejectReason = reqBody.RejectReason
|
||||
if err := database.DB.Save(&req).Error; err != nil {
|
||||
response.Error(c, 500, "更新申请状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": req.ID,
|
||||
"status": "rejected",
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetAgentAppDetail(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
agentAppID := c.Param("id")
|
||||
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("id = ? AND developer_id = ?", agentAppID, userID).
|
||||
Preload("CardTypes.CardType").
|
||||
First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 404, "授权记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var agent model.User
|
||||
if err := database.DB.Where("id = ?", agentApp.AgentID).First(&agent).Error; err != nil {
|
||||
log.Printf("[ERROR] Failed to query agent: %v\n", err)
|
||||
}
|
||||
|
||||
var application model.Application
|
||||
if err := database.DB.Where("id = ?", agentApp.ApplicationID).First(&application).Error; err != nil {
|
||||
log.Printf("[ERROR] Failed to query application: %v\n", err)
|
||||
}
|
||||
|
||||
type CardTypeResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Price float64 `json:"price"`
|
||||
CanGenerate bool `json:"can_generate"`
|
||||
}
|
||||
|
||||
type AgentAppDetailResponse struct {
|
||||
ID uint `json:"id"`
|
||||
AgentID uint `json:"agent_id"`
|
||||
AgentName string `json:"agent_name"`
|
||||
AgentEmail string `json:"agent_email"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
AppName string `json:"app_name"`
|
||||
Commission float64 `json:"commission"`
|
||||
Discount float64 `json:"discount"`
|
||||
Status string `json:"status"`
|
||||
CardTypes []CardTypeResponse `json:"card_types"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
var cardTypes []CardTypeResponse
|
||||
for _, ct := range agentApp.CardTypes {
|
||||
cardTypes = append(cardTypes, CardTypeResponse{
|
||||
ID: ct.CardTypeID,
|
||||
Name: ct.CardType.Name,
|
||||
Price: ct.CardType.Price,
|
||||
CanGenerate: ct.CanGenerate,
|
||||
})
|
||||
}
|
||||
|
||||
result := AgentAppDetailResponse{
|
||||
ID: agentApp.ID,
|
||||
AgentID: agentApp.AgentID,
|
||||
AgentName: agent.Username,
|
||||
ApplicationID: agentApp.ApplicationID,
|
||||
AppName: application.Name,
|
||||
Commission: agentApp.Commission,
|
||||
Discount: agentApp.Discount,
|
||||
Status: agentApp.Status,
|
||||
CardTypes: cardTypes,
|
||||
CreatedAt: agentApp.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
if agent.Email != nil {
|
||||
result.AgentEmail = *agent.Email
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
func handleUpdateAgentApp(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
agentAppID := c.Param("id")
|
||||
|
||||
var reqBody struct {
|
||||
Commission *float64 `json:"commission"`
|
||||
Discount *float64 `json:"discount"`
|
||||
Status *string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&reqBody); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("id = ? AND developer_id = ?", agentAppID, userID).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 404, "授权记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if reqBody.Commission != nil {
|
||||
updates["commission"] = *reqBody.Commission
|
||||
}
|
||||
if reqBody.Discount != nil {
|
||||
updates["discount"] = *reqBody.Discount
|
||||
}
|
||||
if reqBody.Status != nil {
|
||||
updates["status"] = *reqBody.Status
|
||||
}
|
||||
|
||||
if len(updates) > 0 {
|
||||
if err := database.DB.Model(&agentApp).Updates(updates).Error; err != nil {
|
||||
response.Error(c, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": agentApp.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateAgentCardTypes(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
agentAppID := c.Param("id")
|
||||
|
||||
var reqBody struct {
|
||||
CardTypes []struct {
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
CanGenerate bool `json:"can_generate"`
|
||||
} `json:"card_types"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&reqBody); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("id = ? AND developer_id = ?", agentAppID, userID).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 404, "授权记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
tx := database.DB.Begin()
|
||||
|
||||
if err := tx.Where("agent_application_id = ?", agentAppID).Delete(&model.AgentApplicationCardType{}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
response.Error(c, 500, "清除旧权限失败")
|
||||
return
|
||||
}
|
||||
|
||||
for _, ct := range reqBody.CardTypes {
|
||||
agentCardType := model.AgentApplicationCardType{
|
||||
AgentApplicationID: agentApp.ID,
|
||||
CardTypeID: ct.CardTypeID,
|
||||
CanGenerate: ct.CanGenerate,
|
||||
}
|
||||
if err := tx.Create(&agentCardType).Error; err != nil {
|
||||
tx.Rollback()
|
||||
response.Error(c, 500, "创建权限失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": agentApp.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func handleRemoveAgentApp(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
agentAppID := c.Param("id")
|
||||
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("id = ? AND developer_id = ?", agentAppID, userID).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 404, "授权记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&agentApp).Error; err != nil {
|
||||
response.Error(c, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": agentApp.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func handleCancelRequest(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
requestID := c.Param("id")
|
||||
|
||||
var req model.AgentApplicationRequest
|
||||
if err := database.DB.Where("id = ? AND agent_id = ? AND status = ?", requestID, userID, "pending").First(&req).Error; err != nil {
|
||||
response.Error(c, 404, "申请不存在或已处理")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&req).Error; err != nil {
|
||||
response.Error(c, 500, "取消失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": req.ID,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func SetupAgentsRoutes(r *gin.RouterGroup) {
|
||||
agents := r.Group("/agents")
|
||||
{
|
||||
agents.GET("", handleGetAgents)
|
||||
agents.GET("/tree", handleGetAgentsTree)
|
||||
agents.POST("", handleCreateAgent)
|
||||
agents.GET("/:id", handleGetAgentDetail)
|
||||
agents.PUT("/:id", handleUpdateAgent)
|
||||
agents.PUT("/:id/status", handleUpdateAgentStatus)
|
||||
agents.DELETE("/:id", handleDeleteAgent)
|
||||
agents.GET("/:id/cards", handleGetAgentCards)
|
||||
agents.PUT("/:id/cards", handleUpdateAgentCards)
|
||||
}
|
||||
}
|
||||
|
||||
type AgentTreeNode struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status string `json:"status"`
|
||||
Role string `json:"role"`
|
||||
ParentAgentID *uint `json:"parent_agent_id"`
|
||||
ParentAgentName string `json:"parent_agent_name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastLoginAt string `json:"last_login_at"`
|
||||
Balance float64 `json:"balance"`
|
||||
Commission float64 `json:"commission"`
|
||||
CanCreateAgent bool `json:"can_create_agent"`
|
||||
CardsCount int `json:"cards_count"`
|
||||
ChildAgentsCount int `json:"child_agents_count"`
|
||||
Children []AgentTreeNode `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
func handleGetAgents(c *gin.Context) {
|
||||
var users []model.User
|
||||
if err := database.DB.Where("role = ?", "developer").
|
||||
Preload("ParentAgent").
|
||||
Order("created_at DESC").
|
||||
Find(&users).Error; err != nil {
|
||||
response.Error(c, 500, "获取代理列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
type AgentResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status string `json:"status"`
|
||||
Role string `json:"role"`
|
||||
ParentAgentID *uint `json:"parent_agent_id"`
|
||||
ParentAgentName string `json:"parent_agent_name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastLoginAt string `json:"last_login_at"`
|
||||
Balance float64 `json:"balance"`
|
||||
Commission float64 `json:"commission"`
|
||||
CanCreateAgent bool `json:"can_create_agent"`
|
||||
CardsCount int `json:"cards_count"`
|
||||
ChildAgentsCount int `json:"child_agents_count"`
|
||||
}
|
||||
|
||||
var result []AgentResponse
|
||||
for _, user := range users {
|
||||
var parentAgentName string
|
||||
if user.ParentAgent != nil {
|
||||
parentAgentName = user.ParentAgent.Username
|
||||
}
|
||||
|
||||
var childAgentsCount int64
|
||||
database.DB.Model(&model.User{}).Where("parent_agent_id = ?", user.ID).Count(&childAgentsCount)
|
||||
|
||||
var cardsCount int64
|
||||
database.DB.Model(&model.Card{}).Where("creator_id = ?", user.ID).Count(&cardsCount)
|
||||
|
||||
var lastLoginAt string
|
||||
if user.LastLoginAt != nil {
|
||||
lastLoginAt = user.LastLoginAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
email := ""
|
||||
if user.Email != nil {
|
||||
email = *user.Email
|
||||
}
|
||||
|
||||
result = append(result, AgentResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: email,
|
||||
Avatar: user.Avatar,
|
||||
Status: user.Status,
|
||||
Role: user.Role,
|
||||
ParentAgentID: user.ParentAgentID,
|
||||
ParentAgentName: parentAgentName,
|
||||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
LastLoginAt: lastLoginAt,
|
||||
Balance: user.Balance,
|
||||
Commission: user.Commission,
|
||||
CanCreateAgent: user.CanCreateAgent,
|
||||
CardsCount: int(cardsCount),
|
||||
ChildAgentsCount: int(childAgentsCount),
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"agents": result,
|
||||
"total": len(result),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetAgentsTree(c *gin.Context) {
|
||||
var users []model.User
|
||||
if err := database.DB.Where("role = ?", "developer").
|
||||
Preload("ParentAgent").
|
||||
Order("created_at DESC").
|
||||
Find(&users).Error; err != nil {
|
||||
response.Error(c, 500, "获取代理列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
userMap := make(map[uint]model.User)
|
||||
for _, user := range users {
|
||||
userMap[user.ID] = user
|
||||
}
|
||||
|
||||
childrenMap := make(map[uint][]uint)
|
||||
var rootUsers []uint
|
||||
for _, user := range users {
|
||||
if user.ParentAgentID != nil {
|
||||
childrenMap[*user.ParentAgentID] = append(childrenMap[*user.ParentAgentID], user.ID)
|
||||
} else {
|
||||
rootUsers = append(rootUsers, user.ID)
|
||||
}
|
||||
}
|
||||
|
||||
var buildTree func(userID uint) AgentTreeNode
|
||||
buildTree = func(userID uint) AgentTreeNode {
|
||||
user := userMap[userID]
|
||||
|
||||
var parentAgentName string
|
||||
if user.ParentAgent != nil {
|
||||
parentAgentName = user.ParentAgent.Username
|
||||
}
|
||||
|
||||
var cardsCount int64
|
||||
database.DB.Model(&model.Card{}).Where("creator_id = ?", user.ID).Count(&cardsCount)
|
||||
|
||||
var childAgentsCount int64
|
||||
database.DB.Model(&model.User{}).Where("parent_agent_id = ?", user.ID).Count(&childAgentsCount)
|
||||
|
||||
var lastLoginAt string
|
||||
if user.LastLoginAt != nil {
|
||||
lastLoginAt = user.LastLoginAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
email := ""
|
||||
if user.Email != nil {
|
||||
email = *user.Email
|
||||
}
|
||||
|
||||
node := AgentTreeNode{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: email,
|
||||
Avatar: user.Avatar,
|
||||
Status: user.Status,
|
||||
Role: user.Role,
|
||||
ParentAgentID: user.ParentAgentID,
|
||||
ParentAgentName: parentAgentName,
|
||||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
LastLoginAt: lastLoginAt,
|
||||
Balance: user.Balance,
|
||||
Commission: user.Commission,
|
||||
CanCreateAgent: user.CanCreateAgent,
|
||||
CardsCount: int(cardsCount),
|
||||
ChildAgentsCount: int(childAgentsCount),
|
||||
}
|
||||
|
||||
if childIDs, exists := childrenMap[userID]; exists {
|
||||
for _, childID := range childIDs {
|
||||
node.Children = append(node.Children, buildTree(childID))
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
var tree []AgentTreeNode
|
||||
for _, rootID := range rootUsers {
|
||||
tree = append(tree, buildTree(rootID))
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"tree": tree,
|
||||
"total": len(users),
|
||||
})
|
||||
}
|
||||
|
||||
func handleCreateAgent(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
ParentAgentID *uint `json:"parent_agent_id"`
|
||||
Commission float64 `json:"commission"`
|
||||
CanCreateAgent bool `json:"can_create_agent"`
|
||||
Balance float64 `json:"balance"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var existingUser model.User
|
||||
if err := database.DB.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
|
||||
response.Error(c, 400, "用户名已存在")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email != "" {
|
||||
if err := database.DB.Where("email = ?", req.Email).First(&existingUser).Error; err == nil {
|
||||
response.Error(c, 400, "邮箱已被使用")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if req.ParentAgentID != nil {
|
||||
var parentAgent model.User
|
||||
if err := database.DB.Where("id = ? AND role = ?", *req.ParentAgentID, "developer").First(&parentAgent).Error; err != nil {
|
||||
response.Error(c, 404, "上级代理不存在")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "密码加密失败")
|
||||
return
|
||||
}
|
||||
|
||||
user := model.User{
|
||||
Username: req.Username,
|
||||
Password: string(hashedPassword),
|
||||
Role: "developer",
|
||||
Status: "active",
|
||||
ParentAgentID: req.ParentAgentID,
|
||||
Commission: req.Commission,
|
||||
CanCreateAgent: req.CanCreateAgent,
|
||||
Balance: req.Balance,
|
||||
}
|
||||
|
||||
if req.Email != "" {
|
||||
user.Email = &req.Email
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&user).Error; err != nil {
|
||||
response.Error(c, 500, "创建代理失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "create", "agent", &user.ID, fmt.Sprintf("创建代理: %s", user.Username), nil)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetAgentDetail(c *gin.Context) {
|
||||
agentID := c.Param("id")
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Where("id = ? AND role = ?", agentID, "developer").
|
||||
Preload("ParentAgent").
|
||||
First(&user).Error; err != nil {
|
||||
response.Error(c, 404, "代理不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var agentApps []model.AgentApplication
|
||||
database.DB.Where("agent_id = ?", user.ID).
|
||||
Preload("Application").
|
||||
Find(&agentApps)
|
||||
|
||||
var childAgents []model.User
|
||||
database.DB.Where("parent_agent_id = ?", user.ID).Find(&childAgents)
|
||||
|
||||
var cards []model.Card
|
||||
database.DB.Where("creator_id = ?", user.ID).
|
||||
Preload("Application").
|
||||
Preload("CardType").
|
||||
Find(&cards)
|
||||
|
||||
type CardResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CardKey string `json:"card_key"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
AppName string `json:"app_name"`
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
CardTypeName string `json:"card_type_name"`
|
||||
Price float64 `json:"price"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
var cardsResponse []CardResponse
|
||||
for _, card := range cards {
|
||||
appName := ""
|
||||
if card.Application != nil {
|
||||
appName = card.Application.Name
|
||||
}
|
||||
cardTypeName := ""
|
||||
if card.CardType.ID != 0 {
|
||||
cardTypeName = card.CardType.Name
|
||||
}
|
||||
cardsResponse = append(cardsResponse, CardResponse{
|
||||
ID: card.ID,
|
||||
CardKey: card.CardKey,
|
||||
ApplicationID: card.ApplicationID,
|
||||
AppName: appName,
|
||||
CardTypeID: card.CardTypeID,
|
||||
CardTypeName: cardTypeName,
|
||||
Price: card.CardType.Price,
|
||||
Status: card.Status,
|
||||
CreatedAt: card.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
var lastLoginAt string
|
||||
if user.LastLoginAt != nil {
|
||||
lastLoginAt = user.LastLoginAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
email := ""
|
||||
if user.Email != nil {
|
||||
email = *user.Email
|
||||
}
|
||||
|
||||
var parentAgentName string
|
||||
if user.ParentAgent != nil {
|
||||
parentAgentName = user.ParentAgent.Username
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"email": email,
|
||||
"avatar": user.Avatar,
|
||||
"status": user.Status,
|
||||
"role": user.Role,
|
||||
"parent_agent_id": user.ParentAgentID,
|
||||
"parent_agent_name": parentAgentName,
|
||||
"commission": user.Commission,
|
||||
"can_create_agent": user.CanCreateAgent,
|
||||
"balance": user.Balance,
|
||||
"created_at": user.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
"last_login_at": lastLoginAt,
|
||||
"cards": cardsResponse,
|
||||
"cards_count": len(cardsResponse),
|
||||
"child_agents_count": len(childAgents),
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateAgent(c *gin.Context) {
|
||||
agentID := c.Param("id")
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Where("id = ? AND role = ?", agentID, "developer").First(&user).Error; err != nil {
|
||||
response.Error(c, 404, "代理不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
ParentAgentID *uint `json:"parent_agent_id"`
|
||||
Commission *float64 `json:"commission"`
|
||||
Email *string `json:"email"`
|
||||
Password *string `json:"password"`
|
||||
CanCreateAgent *bool `json:"can_create_agent"`
|
||||
Balance *float64 `json:"balance"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.ParentAgentID != nil {
|
||||
if *req.ParentAgentID == user.ID {
|
||||
response.Error(c, 400, "不能将自己设为上级代理")
|
||||
return
|
||||
}
|
||||
var parentAgent model.User
|
||||
if err := database.DB.Where("id = ? AND role = ?", *req.ParentAgentID, "developer").First(&parentAgent).Error; err != nil {
|
||||
response.Error(c, 404, "上级代理不存在")
|
||||
return
|
||||
}
|
||||
user.ParentAgentID = req.ParentAgentID
|
||||
}
|
||||
|
||||
if req.Email != nil {
|
||||
user.Email = req.Email
|
||||
}
|
||||
if req.Commission != nil {
|
||||
user.Commission = *req.Commission
|
||||
}
|
||||
if req.Password != nil && *req.Password != "" {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "密码加密失败")
|
||||
return
|
||||
}
|
||||
user.Password = string(hashedPassword)
|
||||
}
|
||||
if req.CanCreateAgent != nil {
|
||||
user.CanCreateAgent = *req.CanCreateAgent
|
||||
}
|
||||
if req.Balance != nil {
|
||||
user.Balance = *req.Balance
|
||||
}
|
||||
|
||||
if err := database.DB.Save(&user).Error; err != nil {
|
||||
response.Error(c, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "update", "agent", &user.ID, fmt.Sprintf("更新代理: %s", user.Username), nil)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": user.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateAgentStatus(c *gin.Context) {
|
||||
agentID := c.Param("id")
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Where("id = ? AND role = ?", agentID, "developer").First(&user).Error; err != nil {
|
||||
response.Error(c, 404, "代理不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required,oneof=active inactive banned"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
user.Status = req.Status
|
||||
if err := database.DB.Save(&user).Error; err != nil {
|
||||
response.Error(c, 500, "更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "update_status", "agent", &user.ID, fmt.Sprintf("更新代理状态: %s -> %s", user.Username, user.Status), nil)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": user.ID,
|
||||
"status": user.Status,
|
||||
})
|
||||
}
|
||||
|
||||
func handleDeleteAgent(c *gin.Context) {
|
||||
agentID := c.Param("id")
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Where("id = ? AND role = ?", agentID, "developer").First(&user).Error; err != nil {
|
||||
response.Error(c, 404, "代理不存在")
|
||||
return
|
||||
}
|
||||
|
||||
database.DB.Model(&model.User{}).Where("parent_agent_id = ?", user.ID).Update("parent_agent_id", nil)
|
||||
|
||||
if err := database.DB.Delete(&user).Error; err != nil {
|
||||
response.Error(c, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "delete", "agent", &user.ID, fmt.Sprintf("删除代理: %s", user.Username), nil)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": user.ID,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetAgentCards(c *gin.Context) {
|
||||
agentID := c.Param("id")
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Where("id = ? AND role = ?", agentID, "developer").First(&user).Error; err != nil {
|
||||
response.Error(c, 404, "代理不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var cards []model.Card
|
||||
database.DB.Where("creator_id = ?", user.ID).
|
||||
Preload("Application").
|
||||
Preload("CardType").
|
||||
Order("created_at DESC").
|
||||
Find(&cards)
|
||||
|
||||
type CardResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CardKey string `json:"card_key"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
AppName string `json:"app_name"`
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
CardTypeName string `json:"card_type_name"`
|
||||
Price float64 `json:"price"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
var result []CardResponse
|
||||
for _, card := range cards {
|
||||
appName := ""
|
||||
if card.Application != nil {
|
||||
appName = card.Application.Name
|
||||
}
|
||||
cardTypeName := ""
|
||||
if card.CardType.ID != 0 {
|
||||
cardTypeName = card.CardType.Name
|
||||
}
|
||||
result = append(result, CardResponse{
|
||||
ID: card.ID,
|
||||
CardKey: card.CardKey,
|
||||
ApplicationID: card.ApplicationID,
|
||||
AppName: appName,
|
||||
CardTypeID: card.CardTypeID,
|
||||
CardTypeName: cardTypeName,
|
||||
Price: card.CardType.Price,
|
||||
Status: card.Status,
|
||||
CreatedAt: card.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"cards": result,
|
||||
"total": len(result),
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateAgentCards(c *gin.Context) {
|
||||
agentID := c.Param("id")
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Where("id = ? AND role = ?", agentID, "developer").First(&user).Error; err != nil {
|
||||
response.Error(c, 404, "代理不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Cards []struct {
|
||||
CardID uint `json:"card_id"`
|
||||
Status string `json:"status"`
|
||||
} `json:"cards"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
for _, cardReq := range req.Cards {
|
||||
database.DB.Model(&model.Card{}).
|
||||
Where("id = ? AND creator_id = ?", cardReq.CardID, user.ID).
|
||||
Update("status", cardReq.Status)
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "更新成功",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupAnnouncementRoutes(r *gin.RouterGroup) {
|
||||
announcements := r.Group("/announcements")
|
||||
{
|
||||
announcements.GET("", handleGetAllAnnouncements)
|
||||
announcements.GET("/:id", handleGetAnnouncementByID)
|
||||
announcements.DELETE("/batch", handleBatchDeleteAnnouncements)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetAllAnnouncements(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
log.Printf("[DEBUG] handleGetAllAnnouncements called, userID: %d\n", userID)
|
||||
|
||||
page := c.DefaultQuery("page", "1")
|
||||
pageSize := c.DefaultQuery("page_size", "20")
|
||||
|
||||
var total int64
|
||||
|
||||
var userApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&userApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Found %d user apps\n", len(userApps))
|
||||
for i, app := range userApps {
|
||||
log.Printf("[DEBUG] App %d: ID=%d, Name=%s\n", i, app.ID, app.Name)
|
||||
}
|
||||
|
||||
if len(userApps) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"announcements": []interface{}{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
appIDs := make([]uint, len(userApps))
|
||||
appNameMap := make(map[uint]string)
|
||||
for i, app := range userApps {
|
||||
appIDs[i] = app.ID
|
||||
appNameMap[app.ID] = app.Name
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] appNameMap: %v\n", appNameMap)
|
||||
|
||||
database.DB.Model(&model.Announcement{}).Where("application_id IN ?", appIDs).Count(&total)
|
||||
|
||||
var announcements []model.Announcement
|
||||
offset := 0
|
||||
if pageInt, err := strconv.Atoi(page); err == nil && pageInt > 1 {
|
||||
offset = (pageInt - 1) * 20
|
||||
}
|
||||
|
||||
limit := 20
|
||||
if pageSizeInt, err := strconv.Atoi(pageSize); err == nil && pageSizeInt > 0 {
|
||||
limit = pageSizeInt
|
||||
}
|
||||
|
||||
if err := database.DB.Where("application_id IN ?", appIDs).Order("is_top DESC, created_at DESC").Limit(limit).Offset(offset).Find(&announcements).Error; err != nil {
|
||||
response.Error(c, 500, "获取公告列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Found %d announcements\n", len(announcements))
|
||||
for i, a := range announcements {
|
||||
log.Printf("[DEBUG] Announcement %d: ID=%d, ApplicationID=%d, Title=%s\n", i, a.ID, a.ApplicationID, a.Title)
|
||||
}
|
||||
|
||||
type AnnouncementWithAppName struct {
|
||||
model.Announcement
|
||||
ApplicationName string `json:"application_name"`
|
||||
}
|
||||
|
||||
result := make([]AnnouncementWithAppName, len(announcements))
|
||||
for i, a := range announcements {
|
||||
appName := appNameMap[a.ApplicationID]
|
||||
result[i] = AnnouncementWithAppName{
|
||||
Announcement: a,
|
||||
ApplicationName: appName,
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"announcements": result,
|
||||
"total": total,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetAnnouncementByID(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
announcementID := c.Param("id")
|
||||
|
||||
var userApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&userApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
appIDs := make([]uint, len(userApps))
|
||||
appNameMap := make(map[uint]string)
|
||||
for i, app := range userApps {
|
||||
appIDs[i] = app.ID
|
||||
appNameMap[app.ID] = app.Name
|
||||
}
|
||||
|
||||
var announcement model.Announcement
|
||||
if err := database.DB.Where("id = ? AND application_id IN ?", announcementID, appIDs).First(&announcement).Error; err != nil {
|
||||
response.Error(c, 404, "公告不存在")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": announcement.ID,
|
||||
"application_id": announcement.ApplicationID,
|
||||
"application_name": appNameMap[announcement.ApplicationID],
|
||||
"title": announcement.Title,
|
||||
"content": announcement.Content,
|
||||
"type": announcement.Type,
|
||||
"status": announcement.Status,
|
||||
"is_top": announcement.IsTop,
|
||||
"created_at": announcement.CreatedAt,
|
||||
"updated_at": announcement.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
func handleBatchDeleteAnnouncements(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要删除的公告")
|
||||
return
|
||||
}
|
||||
|
||||
var userApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&userApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
appIDs := make([]uint, len(userApps))
|
||||
for i, app := range userApps {
|
||||
appIDs[i] = app.ID
|
||||
}
|
||||
|
||||
var announcements []model.Announcement
|
||||
if err := database.DB.Where("id IN ? AND application_id IN ?", req.IDs, appIDs).Find(&announcements).Error; err != nil {
|
||||
response.Error(c, 500, "获取公告失败")
|
||||
return
|
||||
}
|
||||
|
||||
for _, announcement := range announcements {
|
||||
for _, app := range userApps {
|
||||
if app.ID == announcement.ApplicationID && service.GetApplicationDisabledStatus(app.ID) {
|
||||
response.Error(c, 403, fmt.Sprintf("应用 %s 已被禁用,无法删除其公告", app.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id IN ? AND application_id IN ?", req.IDs, appIDs).Delete(&model.Announcement{}).Error; err != nil {
|
||||
response.Error(c, 500, "批量删除公告失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,969 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
"verification-platform-backend/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func SetupCardRoutes(r *gin.RouterGroup) {
|
||||
cardTypes := r.Group("/card-types")
|
||||
{
|
||||
cardTypes.GET("", handleGetCardTypes)
|
||||
cardTypes.GET("/:id", handleGetCardType)
|
||||
cardTypes.POST("", handleCreateCardType)
|
||||
cardTypes.PUT("/:id", handleUpdateCardType)
|
||||
cardTypes.DELETE("/:id", handleDeleteCardType)
|
||||
}
|
||||
|
||||
cards := r.Group("/cards")
|
||||
{
|
||||
cards.GET("", handleGetCards)
|
||||
cards.POST("", handleCreateCards)
|
||||
cards.GET("/:id", handleGetCard)
|
||||
cards.PUT("/:id", handleUpdateCard)
|
||||
cards.DELETE("/:id", handleDeleteCard)
|
||||
cards.PUT("/:id/status", handleUpdateCardStatus)
|
||||
cards.PUT("/batch/status", handleBatchUpdateCardStatus)
|
||||
cards.DELETE("/batch", handleBatchDeleteCards)
|
||||
cards.POST("/use", handleUseCard)
|
||||
cards.GET("/export", handleExportCards)
|
||||
}
|
||||
}
|
||||
|
||||
func SetupCardRoutesWithoutPackage(r *gin.RouterGroup) {
|
||||
cards := r.Group("/cards")
|
||||
{
|
||||
cards.POST("/batch", handleBatchGenerateCards)
|
||||
}
|
||||
}
|
||||
|
||||
func checkDeveloperPackageValid(developerID uint) bool {
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, developerID).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if user.CurrentPackageID == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var userPackage model.UserPackage
|
||||
if err := database.DB.Where("user_id = ? AND package_id = ? AND status = ?",
|
||||
developerID, user.CurrentPackageID, "active").First(&userPackage).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if userPackage.ExpiredAt != nil && userPackage.ExpiredAt.Before(time.Now()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func handleGetCardTypes(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
applicationID := c.Query("application_id")
|
||||
|
||||
log.Printf("[DEBUG] handleGetCardTypes called: userID=%d, applicationID=%s\n", userID, applicationID)
|
||||
|
||||
var cardTypes []model.CardType
|
||||
var query *gorm.DB
|
||||
|
||||
if applicationID != "" {
|
||||
appID, err := strconv.ParseUint(applicationID, 10, 32)
|
||||
if err == nil {
|
||||
var authorizedApps []model.AgentApplication
|
||||
database.DB.Where("agent_id = ? AND application_id = ? AND status = ?", userID, uint(appID), "active").
|
||||
Preload("CardTypes").
|
||||
Find(&authorizedApps)
|
||||
|
||||
log.Printf("[DEBUG] Found %d authorized apps for user %d and application %d\n", len(authorizedApps), userID, uint(appID))
|
||||
|
||||
var authorizedCardTypeIDs []uint
|
||||
for _, aa := range authorizedApps {
|
||||
for _, ct := range aa.CardTypes {
|
||||
if ct.CanGenerate {
|
||||
authorizedCardTypeIDs = append(authorizedCardTypeIDs, ct.CardTypeID)
|
||||
log.Printf("[DEBUG] Authorized card type - ID: %d\n", ct.CardTypeID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(authorizedCardTypeIDs) > 0 {
|
||||
query = database.DB.Where("(user_id = ? AND application_id = ?) OR (id IN (?))", userID, uint(appID), authorizedCardTypeIDs)
|
||||
log.Printf("[DEBUG] Querying card types with authorizedCardTypeIDs: %v\n", authorizedCardTypeIDs)
|
||||
} else {
|
||||
query = database.DB.Where("user_id = ? AND application_id = ?", userID, uint(appID))
|
||||
}
|
||||
} else {
|
||||
query = database.DB.Where("user_id = ?", userID)
|
||||
}
|
||||
} else {
|
||||
query = database.DB.Where("user_id = ?", userID)
|
||||
}
|
||||
|
||||
if err := query.Preload("Application").Find(&cardTypes).Error; err != nil {
|
||||
log.Printf("[ERROR] Failed to query card types: %v\n", err)
|
||||
response.Error(c, 500, "获取卡密类型失败")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Found %d card types\n", len(cardTypes))
|
||||
for i, ct := range cardTypes {
|
||||
log.Printf("[DEBUG] CardType %d: ID=%d, Name=%s, ApplicationID=%v\n", i, ct.ID, ct.Name, ct.ApplicationID)
|
||||
}
|
||||
|
||||
var cardTypesWithCount []map[string]interface{}
|
||||
for _, ct := range cardTypes {
|
||||
var count int64
|
||||
database.DB.Model(&model.Card{}).Where("card_type_id = ?", ct.ID).Count(&count)
|
||||
log.Printf("[DEBUG] CardType ID=%d, Name=%s, GeneratedCount=%d\n", ct.ID, ct.Name, count)
|
||||
|
||||
cardTypeMap := map[string]interface{}{
|
||||
"id": ct.ID,
|
||||
"user_id": ct.UserID,
|
||||
"application_id": ct.ApplicationID,
|
||||
"name": ct.Name,
|
||||
"value": ct.Value,
|
||||
"price": ct.Price,
|
||||
"description": ct.Description,
|
||||
"status": ct.Status,
|
||||
"created_at": ct.CreatedAt,
|
||||
"updated_at": ct.UpdatedAt,
|
||||
"generatedCount": count,
|
||||
}
|
||||
if ct.Application != nil {
|
||||
cardTypeMap["application"] = ct.Application
|
||||
}
|
||||
cardTypesWithCount = append(cardTypesWithCount, cardTypeMap)
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"card_types": cardTypesWithCount,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetCardType(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var cardType model.CardType
|
||||
if err := database.DB.Preload("Application").First(&cardType, id).Error; err != nil {
|
||||
response.Error(c, 404, "卡类不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if cardType.UserID != userID {
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, cardType.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 403, "无权限查看该卡类")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", cardType.ApplicationID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限查看该卡类")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"card_type": cardType,
|
||||
})
|
||||
}
|
||||
|
||||
func handleCreateCardType(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
RechargeType string `json:"recharge_type"`
|
||||
Value float64 `json:"value"`
|
||||
ValueUnit string `json:"value_unit"`
|
||||
Price float64 `json:"price"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
fmt.Printf("创建卡密类型参数错误: %v\n", err)
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("创建卡密类型请求: Name=%s, RechargeType=%s, Value=%f, ValueUnit=%s, Price=%f, ApplicationID=%d\n",
|
||||
req.Name, req.RechargeType, req.Value, req.ValueUnit, req.Price, req.ApplicationID)
|
||||
|
||||
if req.Name == "" {
|
||||
response.Error(c, 400, "卡密类型名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if req.ApplicationID == 0 {
|
||||
response.Error(c, 400, "所属应用不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 400, "所属应用不存在或无权限")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Price < 0 {
|
||||
response.Error(c, 400, "价格不能为负数")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Value == 0 {
|
||||
response.Error(c, 400, "面值不能为0")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Value < -1 {
|
||||
response.Error(c, 400, "面值无效,必须大于0或为-1(表示永久有效)")
|
||||
return
|
||||
}
|
||||
|
||||
rechargeType := req.RechargeType
|
||||
if rechargeType == "" {
|
||||
rechargeType = "balance"
|
||||
}
|
||||
|
||||
valueUnit := req.ValueUnit
|
||||
if rechargeType == "subscription" && valueUnit == "" {
|
||||
valueUnit = "day"
|
||||
}
|
||||
|
||||
cardType := model.CardType{
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
RechargeType: rechargeType,
|
||||
Value: req.Value,
|
||||
ValueUnit: valueUnit,
|
||||
Price: req.Price,
|
||||
ApplicationID: req.ApplicationID,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&cardType).Error; err != nil {
|
||||
response.Error(c, 500, "创建卡密类型失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "create", "card_type", &cardType.ID, fmt.Sprintf("创建卡密类型: %s", cardType.Name), nil)
|
||||
|
||||
response.Success(c, cardType)
|
||||
}
|
||||
|
||||
func handleUpdateCardType(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
RechargeType string `json:"recharge_type"`
|
||||
Value float64 `json:"value"`
|
||||
ValueUnit string `json:"value_unit"`
|
||||
Price float64 `json:"price"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
var cardType model.CardType
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&cardType).Error; err != nil {
|
||||
response.Error(c, 404, "卡密类型不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
cardType.Name = req.Name
|
||||
}
|
||||
if req.Description != "" {
|
||||
cardType.Description = req.Description
|
||||
}
|
||||
if req.RechargeType != "" {
|
||||
cardType.RechargeType = req.RechargeType
|
||||
}
|
||||
if req.ValueUnit != "" {
|
||||
cardType.ValueUnit = req.ValueUnit
|
||||
}
|
||||
if req.Price >= 0 {
|
||||
cardType.Price = req.Price
|
||||
}
|
||||
if req.Value > 0 || req.Value == -1 {
|
||||
cardType.Value = req.Value
|
||||
}
|
||||
if req.Price > 0 {
|
||||
cardType.Price = req.Price
|
||||
}
|
||||
if req.ApplicationID > 0 {
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 400, "所属应用不存在或无权限")
|
||||
return
|
||||
}
|
||||
cardType.ApplicationID = req.ApplicationID
|
||||
}
|
||||
if req.Status != "" {
|
||||
cardType.Status = req.Status
|
||||
}
|
||||
|
||||
if err := database.DB.Save(&cardType).Error; err != nil {
|
||||
response.Error(c, 500, "更新卡密类型失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "update", "card_type", &cardType.ID, fmt.Sprintf("更新卡密类型: %s", cardType.Name), nil)
|
||||
|
||||
response.Success(c, cardType)
|
||||
}
|
||||
|
||||
func handleDeleteCardType(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var cardType model.CardType
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&cardType).Error; err != nil {
|
||||
response.Error(c, 404, "卡密类型不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&model.CardType{}).Error; err != nil {
|
||||
response.Error(c, 500, "删除卡密类型失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "delete", "card_type", &cardType.ID, fmt.Sprintf("删除卡密类型: %s", cardType.Name), nil)
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleGetCards(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
fmt.Printf("[DEBUG] handleGetCards called, userID: %d\n", userID)
|
||||
|
||||
applicationID := c.Query("application_id")
|
||||
cardTypeID := c.Query("card_type_id")
|
||||
status := c.Query("status")
|
||||
search := c.Query("search")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
var cards []model.Card
|
||||
|
||||
var authorizedAppIDs []uint
|
||||
database.DB.Model(&model.AgentApplication{}).
|
||||
Where("agent_id = ? AND status = ?", userID, "active").
|
||||
Pluck("application_id", &authorizedAppIDs)
|
||||
|
||||
fmt.Printf("[DEBUG] Authorized app IDs: %v\n", authorizedAppIDs)
|
||||
|
||||
query := database.DB.Model(&model.Card{}).
|
||||
Joins("JOIN card_types ON cards.card_type_id = card_types.id").
|
||||
Joins("JOIN applications ON card_types.application_id = applications.id").
|
||||
Preload("Application").
|
||||
Preload("CardType").
|
||||
Preload("Creator").
|
||||
Preload("AppUser")
|
||||
|
||||
if len(authorizedAppIDs) > 0 {
|
||||
query = query.Where("applications.user_id = ? OR (cards.application_id IN ? AND cards.creator_id = ?)", userID, authorizedAppIDs, userID)
|
||||
} else {
|
||||
query = query.Where("applications.user_id = ?", userID)
|
||||
}
|
||||
|
||||
if applicationID != "" {
|
||||
appID, err := strconv.ParseUint(applicationID, 10, 32)
|
||||
if err == nil {
|
||||
query = query.Where("cards.application_id = ?", uint(appID))
|
||||
}
|
||||
}
|
||||
|
||||
if cardTypeID != "" {
|
||||
ctID, err := strconv.ParseUint(cardTypeID, 10, 32)
|
||||
if err == nil {
|
||||
query = query.Where("cards.card_type_id = ?", uint(ctID))
|
||||
}
|
||||
}
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("cards.status = ?", status)
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
searchPattern := "%" + search + "%"
|
||||
query = query.Where("cards.card_key LIKE ? OR card_types.name LIKE ? OR applications.name LIKE ?", searchPattern, searchPattern, searchPattern)
|
||||
}
|
||||
|
||||
if startDate != "" {
|
||||
query = query.Where("cards.created_at >= ?", startDate+" 00:00:00")
|
||||
}
|
||||
|
||||
if endDate != "" {
|
||||
query = query.Where("cards.created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
|
||||
if err := query.Order("cards.created_at DESC").Find(&cards).Error; err != nil {
|
||||
fmt.Printf("[DEBUG] Error fetching cards: %v\n", err)
|
||||
response.Error(c, 500, "获取卡密列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] Found %d cards for user %d\n", len(cards), userID)
|
||||
|
||||
response.Success(c, cards)
|
||||
}
|
||||
|
||||
func handleCreateCards(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
Count int `json:"count"`
|
||||
Prefix string `json:"prefix"`
|
||||
Length int `json:"length"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var cardType model.CardType
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.CardTypeID, userID).First(&cardType).Error; err != nil {
|
||||
response.Error(c, 404, "卡密类型不存在")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "卡密创建成功",
|
||||
"count": req.Count,
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateCard(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
var card model.Card
|
||||
if err := database.DB.Joins("JOIN card_types ON cards.card_type_id = card_types.id").
|
||||
Joins("JOIN applications ON card_types.application_id = applications.id").
|
||||
Where("cards.id = ? AND applications.user_id = ?", id, userID).First(&card).Error; err != nil {
|
||||
response.Error(c, 404, "卡密不存在")
|
||||
return
|
||||
}
|
||||
|
||||
card.Status = req.Status
|
||||
|
||||
if err := database.DB.Save(&card).Error; err != nil {
|
||||
response.Error(c, 500, "更新卡密失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, card)
|
||||
}
|
||||
|
||||
func handleDeleteCard(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
fmt.Printf("删除卡密请求: ID=%s, UserID=%d\n", id, userID)
|
||||
|
||||
var card model.Card
|
||||
if err := database.DB.Preload("CardType").Preload("CardType.Application").First(&card, id).Error; err != nil {
|
||||
fmt.Printf("卡密不存在: %v\n", err)
|
||||
response.Error(c, 404, "卡密不存在")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("查询到的卡密: ID=%d, CardTypeID=%d, CreatorID=%d\n", card.ID, card.CardTypeID, card.CreatorID)
|
||||
|
||||
// 如果创建者是当前用户,直接允许删除
|
||||
if card.CreatorID == userID {
|
||||
fmt.Printf("创建者匹配,允许删除\n")
|
||||
if err := database.DB.Delete(&card).Error; err != nil {
|
||||
fmt.Printf("删除卡密失败: %v\n", err)
|
||||
response.Error(c, 500, "删除卡密失败")
|
||||
return
|
||||
}
|
||||
fmt.Printf("删除卡密成功: ID=%s\n", id)
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果创建者不匹配,检查应用的所有者
|
||||
if card.CardType.ID > 0 && card.CardType.Application != nil {
|
||||
fmt.Printf("卡密类型和应用存在,检查应用所有者\n")
|
||||
if card.CardType.Application.UserID == userID {
|
||||
fmt.Printf("应用所有者匹配,允许删除\n")
|
||||
if err := database.DB.Delete(&card).Error; err != nil {
|
||||
fmt.Printf("删除卡密失败: %v\n", err)
|
||||
response.Error(c, 500, "删除卡密失败")
|
||||
return
|
||||
}
|
||||
fmt.Printf("删除卡密成功: ID=%s\n", id)
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果卡密类型或应用不存在,尝试通过ApplicationID查询应用(包括软删除的)
|
||||
if card.CardType.ID == 0 || card.CardType.Application == nil {
|
||||
fmt.Printf("卡密类型或应用不存在,尝试查询应用\n")
|
||||
if card.CardType.ID == 0 {
|
||||
// 卡密类型不存在,直接通过CardTypeID查询卡密类型(包括软删除的)
|
||||
var cardType model.CardType
|
||||
if err := database.DB.Unscoped().First(&cardType, card.CardTypeID).Error; err != nil {
|
||||
fmt.Printf("卡密类型不存在: %v\n", err)
|
||||
response.Error(c, 404, "卡密类型不存在")
|
||||
return
|
||||
}
|
||||
card.CardType = cardType
|
||||
}
|
||||
|
||||
if card.CardType.Application == nil {
|
||||
// 应用不存在,直接通过ApplicationID查询应用(包括软删除的)
|
||||
var app model.Application
|
||||
if err := database.DB.Unscoped().First(&app, card.CardType.ApplicationID).Error; err != nil {
|
||||
fmt.Printf("应用不存在: %v\n", err)
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
card.CardType.Application = &app
|
||||
}
|
||||
|
||||
fmt.Printf("查询到的应用: ID=%d, UserID=%d\n", card.CardType.Application.ID, card.CardType.Application.UserID)
|
||||
if card.CardType.Application.UserID == userID {
|
||||
fmt.Printf("应用所有者匹配,允许删除\n")
|
||||
if err := database.DB.Delete(&card).Error; err != nil {
|
||||
fmt.Printf("删除卡密失败: %v\n", err)
|
||||
response.Error(c, 500, "删除卡密失败")
|
||||
return
|
||||
}
|
||||
fmt.Printf("删除卡密成功: ID=%s\n", id)
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("无权限删除此卡密\n")
|
||||
response.Error(c, 403, "无权限删除此卡密")
|
||||
}
|
||||
|
||||
func handleGetCard(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
var card model.Card
|
||||
if err := database.DB.Joins("JOIN card_types ON cards.card_type_id = card_types.id").
|
||||
Joins("JOIN applications ON card_types.application_id = applications.id").
|
||||
Where("cards.id = ? AND applications.user_id = ?", id, userID).First(&card).Error; err != nil {
|
||||
response.Error(c, 404, "卡密不存在")
|
||||
return
|
||||
}
|
||||
response.Success(c, card)
|
||||
}
|
||||
|
||||
func handleUseCard(c *gin.Context) {
|
||||
var req struct {
|
||||
CardKey string `json:"card_key"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var card model.Card
|
||||
if err := database.DB.Where("card_key = ?", req.CardKey).First(&card).Error; err != nil {
|
||||
response.Error(c, 404, "卡密不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if card.Status != "unused" {
|
||||
response.Error(c, 400, "卡密已被使用或已禁用")
|
||||
return
|
||||
}
|
||||
|
||||
card.Status = "used"
|
||||
database.DB.Save(&card)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "卡密使用成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleBatchGenerateCards(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
fmt.Printf("[DEBUG] handleBatchGenerateCards called, userID: %d\n", userID)
|
||||
|
||||
var req struct {
|
||||
ApplicationID uint `json:"application_id"`
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
Count int `json:"count"`
|
||||
Prefix string `json:"prefix"`
|
||||
Length int `json:"length"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
fmt.Printf("[DEBUG] JSON bind error: %v\n", err)
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] Received parameters - ApplicationID: %v, CardTypeID: %d, Count: %d, Prefix: %s, Length: %d\n",
|
||||
req.ApplicationID, req.CardTypeID, req.Count, req.Prefix, req.Length)
|
||||
|
||||
if req.Count <= 0 || req.Count > 1000 {
|
||||
response.Error(c, 400, "生成数量需在1-1000之间")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Length <= 0 {
|
||||
req.Length = 16
|
||||
}
|
||||
if req.Prefix == "" {
|
||||
req.Prefix = "CK"
|
||||
}
|
||||
|
||||
var cardType model.CardType
|
||||
if err := database.DB.Where("id = ?", req.CardTypeID).First(&cardType).Error; err != nil {
|
||||
fmt.Printf("[DEBUG] CardType not found error: %v\n", err)
|
||||
response.Error(c, 404, "卡密类型不存在")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] CardType found - ID: %d, Name: %s, UserID: %d\n", cardType.ID, cardType.Name, cardType.UserID)
|
||||
|
||||
if req.ApplicationID == 0 {
|
||||
req.ApplicationID = cardType.ApplicationID
|
||||
}
|
||||
|
||||
var agentApp *model.AgentApplication
|
||||
var effectiveUserID uint = userID
|
||||
|
||||
if cardType.UserID != userID {
|
||||
fmt.Printf("[DEBUG] CardType belongs to another user, checking authorization...\n")
|
||||
|
||||
if req.ApplicationID == 0 {
|
||||
fmt.Printf("[DEBUG] ApplicationID is required for authorized card types\n")
|
||||
response.Error(c, 400, "生成授权应用的卡密需要指定应用ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Where("agent_id = ? AND application_id = ? AND status = ?", userID, req.ApplicationID, "active").
|
||||
Preload("CardTypes", "card_type_id = ?", req.CardTypeID).
|
||||
First(&agentApp).Error; err != nil {
|
||||
fmt.Printf("[DEBUG] Authorization not found error: %v\n", err)
|
||||
response.Error(c, 403, "您没有权限生成此卡密类型")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] AgentApp found - ID: %d, DeveloperID: %d\n", agentApp.ID, agentApp.DeveloperID)
|
||||
|
||||
var cardTypePerm *model.AgentApplicationCardType
|
||||
for _, ct := range agentApp.CardTypes {
|
||||
if ct.CardTypeID == req.CardTypeID {
|
||||
cardTypePerm = &ct
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if cardTypePerm == nil || !cardTypePerm.CanGenerate {
|
||||
fmt.Printf("[DEBUG] No permission to generate this card type\n")
|
||||
response.Error(c, 403, "您没有权限生成此卡密类型")
|
||||
return
|
||||
}
|
||||
|
||||
var agentUser model.User
|
||||
if err := database.DB.First(&agentUser, userID).Error; err != nil {
|
||||
response.Error(c, 500, "获取代理信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
totalCost := float64(req.Count) * cardType.Price
|
||||
fmt.Printf("[DEBUG] Total cost: %f, Balance: %f\n", totalCost, agentUser.Balance)
|
||||
|
||||
if agentUser.Balance < totalCost {
|
||||
fmt.Printf("[DEBUG] Insufficient balance\n")
|
||||
response.Error(c, 400, "余额不足")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&agentUser).Update("balance", agentUser.Balance-totalCost).Error; err != nil {
|
||||
fmt.Printf("[DEBUG] Update balance error: %v\n", err)
|
||||
response.Error(c, 500, "扣款失败")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] Balance updated successfully\n")
|
||||
}
|
||||
|
||||
cards := make([]model.Card, 0, req.Count)
|
||||
for i := 0; i < req.Count; i++ {
|
||||
cardKey := req.Prefix + utils.GenerateRandomString(req.Length)
|
||||
card := model.Card{
|
||||
ApplicationID: req.ApplicationID,
|
||||
CardTypeID: req.CardTypeID,
|
||||
CardKey: cardKey,
|
||||
CreatorID: effectiveUserID,
|
||||
Status: "unused",
|
||||
}
|
||||
cards = append(cards, card)
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] Generated %d cards, saving to database...\n", len(cards))
|
||||
|
||||
if err := database.DB.Create(&cards).Error; err != nil {
|
||||
fmt.Printf("[DEBUG] Database create error: %v\n", err)
|
||||
response.Error(c, 500, "生成卡密失败")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] Successfully created %d cards\n", len(cards))
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "批量生成成功",
|
||||
"count": req.Count,
|
||||
})
|
||||
}
|
||||
|
||||
func handleExportCards(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
applicationID := c.Query("application_id")
|
||||
cardTypeID := c.Query("card_type_id")
|
||||
status := c.Query("status")
|
||||
search := c.Query("search")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
var cards []model.Card
|
||||
query := database.DB.Model(&model.Card{}).
|
||||
Joins("JOIN card_types ON cards.card_type_id = card_types.id").
|
||||
Joins("JOIN applications ON card_types.application_id = applications.id").
|
||||
Preload("Application").
|
||||
Preload("CardType").
|
||||
Where("applications.user_id = ?", userID)
|
||||
|
||||
if applicationID != "" {
|
||||
appID, err := strconv.ParseUint(applicationID, 10, 32)
|
||||
if err == nil {
|
||||
query = query.Where("cards.application_id = ?", uint(appID))
|
||||
}
|
||||
}
|
||||
|
||||
if cardTypeID != "" {
|
||||
ctID, err := strconv.ParseUint(cardTypeID, 10, 32)
|
||||
if err == nil {
|
||||
query = query.Where("cards.card_type_id = ?", uint(ctID))
|
||||
}
|
||||
}
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("cards.status = ?", status)
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
searchPattern := "%" + search + "%"
|
||||
query = query.Where("cards.card_key LIKE ? OR card_types.name LIKE ? OR applications.name LIKE ?", searchPattern, searchPattern, searchPattern)
|
||||
}
|
||||
|
||||
if startDate != "" {
|
||||
query = query.Where("cards.created_at >= ?", startDate+" 00:00:00")
|
||||
}
|
||||
|
||||
if endDate != "" {
|
||||
query = query.Where("cards.created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
|
||||
if err := query.Order("cards.created_at DESC").Find(&cards).Error; err != nil {
|
||||
response.Error(c, 500, "导出卡密失败")
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "text/csv; charset=utf-8")
|
||||
c.Header("Content-Disposition", "attachment; filename=cards_export.csv")
|
||||
|
||||
csv := "卡密,应用,卡类,状态,创建时间,使用时间\n"
|
||||
for _, card := range cards {
|
||||
statusMap := map[string]string{
|
||||
"unused": "未使用",
|
||||
"used": "已使用",
|
||||
"banned": "已禁用",
|
||||
}
|
||||
statusText := statusMap[card.Status]
|
||||
if statusText == "" {
|
||||
statusText = card.Status
|
||||
}
|
||||
|
||||
appName := ""
|
||||
if card.Application != nil {
|
||||
appName = card.Application.Name
|
||||
}
|
||||
|
||||
cardTypeName := card.CardType.Name
|
||||
|
||||
usedAt := ""
|
||||
if card.UsedAt != nil {
|
||||
usedAt = card.UsedAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
csv += fmt.Sprintf("%s,%s,%s,%s,%s,%s\n",
|
||||
card.CardKey,
|
||||
appName,
|
||||
cardTypeName,
|
||||
statusText,
|
||||
card.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
usedAt,
|
||||
)
|
||||
}
|
||||
|
||||
c.String(200, csv)
|
||||
}
|
||||
|
||||
func handleUpdateCardStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Status != "unused" && req.Status != "used" && req.Status != "banned" {
|
||||
response.Error(c, 400, "无效的状态")
|
||||
return
|
||||
}
|
||||
|
||||
var card model.Card
|
||||
if err := database.DB.Preload("CardType").Preload("CardType.Application").First(&card, id).Error; err != nil {
|
||||
response.Error(c, 404, "卡密不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if card.CreatorID != userID && (card.CardType.Application == nil || card.CardType.Application.UserID != userID) {
|
||||
response.Error(c, 403, "无权限修改此卡密")
|
||||
return
|
||||
}
|
||||
|
||||
card.Status = req.Status
|
||||
if err := database.DB.Save(&card).Error; err != nil {
|
||||
response.Error(c, 500, "更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, card)
|
||||
}
|
||||
|
||||
func handleBatchUpdateCardStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Status != "unused" && req.Status != "used" && req.Status != "banned" {
|
||||
response.Error(c, 400, "无效的状态")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要更新的卡密")
|
||||
return
|
||||
}
|
||||
|
||||
var cards []model.Card
|
||||
if err := database.DB.Preload("CardType").Preload("CardType.Application").Where("id IN ?", req.IDs).Find(&cards).Error; err != nil {
|
||||
response.Error(c, 500, "查询卡密失败")
|
||||
return
|
||||
}
|
||||
|
||||
var validIDs []uint
|
||||
for _, card := range cards {
|
||||
if card.CreatorID == userID || (card.CardType.Application != nil && card.CardType.Application.UserID == userID) {
|
||||
validIDs = append(validIDs, card.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(validIDs) == 0 {
|
||||
response.Error(c, 403, "无权限修改选中的卡密")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&model.Card{}).Where("id IN ?", validIDs).Update("status", req.Status).Error; err != nil {
|
||||
response.Error(c, 500, "批量更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"updated_count": len(validIDs)})
|
||||
}
|
||||
|
||||
func handleBatchDeleteCards(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要删除的卡密")
|
||||
return
|
||||
}
|
||||
|
||||
var cards []model.Card
|
||||
if err := database.DB.Preload("CardType").Preload("CardType.Application").Where("id IN ?", req.IDs).Find(&cards).Error; err != nil {
|
||||
response.Error(c, 500, "查询卡密失败")
|
||||
return
|
||||
}
|
||||
|
||||
var validIDs []uint
|
||||
for _, card := range cards {
|
||||
if card.CreatorID == userID || (card.CardType.Application != nil && card.CardType.Application.UserID == userID) {
|
||||
validIDs = append(validIDs, card.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(validIDs) == 0 {
|
||||
response.Error(c, 403, "无权限删除选中的卡密")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id IN ?", validIDs).Delete(&model.Card{}).Error; err != nil {
|
||||
response.Error(c, 500, "批量删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"deleted_count": len(validIDs)})
|
||||
}
|
||||
@@ -0,0 +1,827 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/middleware"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupCloudRoutes(r *gin.RouterGroup) {
|
||||
cloudConstants := r.Group("/cloud-constants")
|
||||
{
|
||||
cloudConstants.GET("", handleGetCloudConstants)
|
||||
cloudConstants.GET("/:id", handleGetCloudConstant)
|
||||
cloudConstants.GET("/:id/download", handleDownloadCloudConstant)
|
||||
cloudConstants.POST("", handleCreateCloudConstant)
|
||||
cloudConstants.POST("/upload", handleUploadCloudConstant)
|
||||
cloudConstants.PUT("/:id", handleUpdateCloudConstant)
|
||||
cloudConstants.DELETE("/:id", handleDeleteCloudConstant)
|
||||
}
|
||||
|
||||
cloudVariables := r.Group("/cloud-variables")
|
||||
{
|
||||
cloudVariables.GET("", handleGetCloudVariables)
|
||||
cloudVariables.GET("/:id", handleGetCloudVariable)
|
||||
cloudVariables.GET("/:id/download", handleDownloadCloudVariable)
|
||||
cloudVariables.POST("", handleCreateCloudVariable)
|
||||
cloudVariables.POST("/upload", handleUploadCloudVariable)
|
||||
cloudVariables.PUT("/:id", handleUpdateCloudVariable)
|
||||
cloudVariables.DELETE("/:id", handleDeleteCloudVariable)
|
||||
cloudVariables.GET("/:id/records", handleGetCloudVariableRecords)
|
||||
cloudVariables.DELETE("/:id/records", handleDeleteCloudVariableRecords)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetCloudConstants(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
appID := c.Query("app_id")
|
||||
|
||||
var constants []model.CloudConstant
|
||||
query := database.DB.Where("user_id = ?", userID)
|
||||
if appID != "" {
|
||||
query = query.Where("app_id = ?", appID)
|
||||
}
|
||||
if err := query.Find(&constants).Error; err != nil {
|
||||
response.Error(c, 500, "获取云端常量失败")
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{
|
||||
"constants": constants,
|
||||
"total": len(constants),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetCloudConstant(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var constant model.CloudConstant
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&constant).Error; err != nil {
|
||||
response.Error(c, 404, "云端常量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, constant)
|
||||
}
|
||||
|
||||
func handleCreateCloudConstant(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
AppID uint `json:"app_id"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
VarType string `json:"var_type"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Key == "" {
|
||||
response.Error(c, 400, "变量名不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Status != "active" && req.Status != "inactive" {
|
||||
req.Status = "active"
|
||||
}
|
||||
|
||||
if req.VarType == "" {
|
||||
req.VarType = "string"
|
||||
}
|
||||
|
||||
constant := model.CloudConstant{
|
||||
UserID: userID,
|
||||
AppID: &req.AppID,
|
||||
Key: req.Key,
|
||||
Value: req.Value,
|
||||
VarType: req.VarType,
|
||||
Description: req.Description,
|
||||
Status: req.Status,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&constant).Error; err != nil {
|
||||
response.Error(c, 500, "创建云端常量失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "create", "cloud_constant", &constant.ID, fmt.Sprintf("创建云端常量: %s", constant.Key), nil)
|
||||
|
||||
response.Success(c, constant)
|
||||
}
|
||||
|
||||
func handleUpdateCloudConstant(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
VarType string `json:"var_type"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
var constant model.CloudConstant
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&constant).Error; err != nil {
|
||||
response.Error(c, 404, "云端常量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
constant.Key = req.Key
|
||||
constant.Value = req.Value
|
||||
constant.VarType = req.VarType
|
||||
constant.Description = req.Description
|
||||
if req.Status == "active" || req.Status == "inactive" {
|
||||
constant.Status = req.Status
|
||||
}
|
||||
|
||||
if err := database.DB.Save(&constant).Error; err != nil {
|
||||
response.Error(c, 500, "更新云端常量失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "update", "cloud_constant", &constant.ID, fmt.Sprintf("更新云端常量: %s", constant.Key), nil)
|
||||
|
||||
response.Success(c, constant)
|
||||
}
|
||||
|
||||
func handleDeleteCloudConstant(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var constant model.CloudConstant
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&constant).Error; err != nil {
|
||||
response.Error(c, 404, "云端常量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if constant.VarType == "binary" && constant.FilePath != "" {
|
||||
if err := middleware.UpdateStorageUsed(userID, constant.FileSize, "delete"); err != nil {
|
||||
fmt.Printf("更新存储使用量失败: %v\n", err)
|
||||
}
|
||||
filePath := strings.TrimPrefix(constant.FilePath, "/")
|
||||
if _, err := os.Stat(filePath); err == nil {
|
||||
os.Remove(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&model.CloudConstant{}).Error; err != nil {
|
||||
response.Error(c, 500, "删除云端常量失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "delete", "cloud_constant", &constant.ID, fmt.Sprintf("删除云端常量: %s", constant.Key), nil)
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleUploadCloudConstant(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
file, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
response.Error(c, 400, "请选择要上传的文件")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
appIDStr := c.PostForm("app_id")
|
||||
key := c.PostForm("key")
|
||||
description := c.PostForm("description")
|
||||
status := c.PostForm("status")
|
||||
|
||||
if appIDStr == "" {
|
||||
response.Error(c, 400, "请选择应用")
|
||||
return
|
||||
}
|
||||
|
||||
if key == "" {
|
||||
response.Error(c, 400, "请输入变量名")
|
||||
return
|
||||
}
|
||||
|
||||
var appID uint
|
||||
fmt.Sscanf(appIDStr, "%d", &appID)
|
||||
|
||||
if status != "active" && status != "inactive" {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
if user.CurrentPackageID != nil {
|
||||
var permission model.PackagePermission
|
||||
if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&permission).Error; err == nil {
|
||||
maxStorageBytes := int64(permission.MaxStorage) * 1024 * 1024
|
||||
if user.StorageUsed+header.Size > maxStorageBytes {
|
||||
usedMB := float64(user.StorageUsed) / 1024 / 1024
|
||||
maxMB := float64(permission.MaxStorage)
|
||||
response.Error(c, 403, fmt.Sprintf("存储空间不足,已使用 %.2f MB / %.2f MB", usedMB, maxMB))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uploadDir := "uploads/cloud-files"
|
||||
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||||
response.Error(c, 500, "创建上传目录失败")
|
||||
return
|
||||
}
|
||||
|
||||
ext := filepath.Ext(header.Filename)
|
||||
filename := fmt.Sprintf("%d_%d%s", userID, time.Now().UnixNano(), ext)
|
||||
filePath := filepath.Join(uploadDir, filename)
|
||||
|
||||
dst, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "创建文件失败")
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
hash := md5.New()
|
||||
multiWriter := io.MultiWriter(dst, hash)
|
||||
if _, err := io.Copy(multiWriter, file); err != nil {
|
||||
response.Error(c, 500, "保存文件失败")
|
||||
return
|
||||
}
|
||||
fileMD5 := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
fileURL := "/uploads/cloud-files/" + filename
|
||||
|
||||
mimeType := header.Header.Get("Content-Type")
|
||||
if mimeType == "" {
|
||||
mimeType = "application/octet-stream"
|
||||
}
|
||||
|
||||
constant := model.CloudConstant{
|
||||
UserID: userID,
|
||||
AppID: &appID,
|
||||
Key: key,
|
||||
Value: fileURL,
|
||||
VarType: "binary",
|
||||
FilePath: fileURL,
|
||||
FileSize: header.Size,
|
||||
MimeType: mimeType,
|
||||
OriginalName: header.Filename,
|
||||
FileMD5: fileMD5,
|
||||
Description: description,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&constant).Error; err != nil {
|
||||
os.Remove(filePath)
|
||||
response.Error(c, 500, "创建云端常量失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err := middleware.UpdateStorageUsed(userID, header.Size, "upload"); err != nil {
|
||||
fmt.Printf("更新存储使用量失败: %v\n", err)
|
||||
}
|
||||
|
||||
usage := model.StorageUsage{
|
||||
UserID: userID,
|
||||
ApplicationID: &appID,
|
||||
ResourceType: "cloud_constant",
|
||||
ResourceID: constant.ID,
|
||||
FileName: header.Filename,
|
||||
FileSize: header.Size,
|
||||
Action: "upload",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
database.DB.Create(&usage)
|
||||
|
||||
service.LogOperation(c, "create", "cloud_constant", &constant.ID, fmt.Sprintf("上传文件常量: %s", constant.Key), nil)
|
||||
|
||||
response.Success(c, constant)
|
||||
}
|
||||
|
||||
func handleDownloadCloudConstant(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var constant model.CloudConstant
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&constant).Error; err != nil {
|
||||
response.Error(c, 404, "云端常量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if constant.VarType != "binary" || constant.FilePath == "" {
|
||||
response.Error(c, 400, "该常量不是文件类型")
|
||||
return
|
||||
}
|
||||
|
||||
filePath := constant.FilePath
|
||||
if strings.HasPrefix(filePath, "/") {
|
||||
filePath = filePath[1:]
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
response.Error(c, 404, "文件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Description", "File Transfer")
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", constant.OriginalName))
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
c.Header("Expires", "0")
|
||||
c.Header("Cache-Control", "must-revalidate")
|
||||
c.Header("Pragma", "public")
|
||||
c.FileAttachment(filePath, constant.OriginalName)
|
||||
}
|
||||
|
||||
func handleGetCloudVariables(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
appID := c.Query("app_id")
|
||||
|
||||
var variables []model.CloudVariable
|
||||
query := database.DB.Where("user_id = ?", userID)
|
||||
if appID != "" {
|
||||
query = query.Where("app_id = ?", appID)
|
||||
}
|
||||
if err := query.Find(&variables).Error; err != nil {
|
||||
response.Error(c, 500, "获取云端变量失败")
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{
|
||||
"variables": variables,
|
||||
"total": len(variables),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetCloudVariable(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var variable model.CloudVariable
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
|
||||
response.Error(c, 404, "云端变量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, variable)
|
||||
}
|
||||
|
||||
func handleCreateCloudVariable(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
AppID uint `json:"app_id"`
|
||||
Key string `json:"key"`
|
||||
DefaultValue string `json:"default_value"`
|
||||
VarType string `json:"var_type"`
|
||||
DataType string `json:"data_type"`
|
||||
MaxRecords int `json:"max_records"`
|
||||
Scope string `json:"scope"`
|
||||
WritePermission string `json:"write_permission"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Key == "" {
|
||||
response.Error(c, 400, "变量名不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Scope != "app" && req.Scope != "user" {
|
||||
req.Scope = "app"
|
||||
}
|
||||
|
||||
if req.WritePermission != "developer" && req.WritePermission != "user" && req.WritePermission != "app_user" {
|
||||
req.WritePermission = "developer"
|
||||
}
|
||||
|
||||
if req.Status != "active" && req.Status != "inactive" {
|
||||
req.Status = "active"
|
||||
}
|
||||
|
||||
if req.VarType == "" {
|
||||
req.VarType = "string"
|
||||
}
|
||||
|
||||
if req.DataType != "single" && req.DataType != "stream" {
|
||||
req.DataType = "single"
|
||||
}
|
||||
|
||||
variable := model.CloudVariable{
|
||||
UserID: userID,
|
||||
AppID: &req.AppID,
|
||||
Key: req.Key,
|
||||
DefaultValue: req.DefaultValue,
|
||||
VarType: req.VarType,
|
||||
DataType: req.DataType,
|
||||
MaxRecords: req.MaxRecords,
|
||||
Scope: req.Scope,
|
||||
WritePermission: req.WritePermission,
|
||||
Description: req.Description,
|
||||
Status: req.Status,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&variable).Error; err != nil {
|
||||
response.Error(c, 500, "创建云端变量失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, variable)
|
||||
}
|
||||
|
||||
func handleUpdateCloudVariable(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
Key string `json:"key"`
|
||||
DefaultValue string `json:"default_value"`
|
||||
VarType string `json:"var_type"`
|
||||
DataType string `json:"data_type"`
|
||||
MaxRecords int `json:"max_records"`
|
||||
WritePermission string `json:"write_permission"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
var variable model.CloudVariable
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
|
||||
response.Error(c, 404, "云端变量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
variable.Key = req.Key
|
||||
variable.DefaultValue = req.DefaultValue
|
||||
variable.VarType = req.VarType
|
||||
if req.DataType == "single" || req.DataType == "stream" {
|
||||
variable.DataType = req.DataType
|
||||
}
|
||||
variable.MaxRecords = req.MaxRecords
|
||||
if req.WritePermission == "developer" || req.WritePermission == "user" || req.WritePermission == "app_user" {
|
||||
variable.WritePermission = req.WritePermission
|
||||
}
|
||||
variable.Description = req.Description
|
||||
if req.Status == "active" || req.Status == "inactive" {
|
||||
variable.Status = req.Status
|
||||
}
|
||||
|
||||
if err := database.DB.Save(&variable).Error; err != nil {
|
||||
response.Error(c, 500, "更新云端变量失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, variable)
|
||||
}
|
||||
|
||||
func handleDeleteCloudVariable(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var variable model.CloudVariable
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
|
||||
response.Error(c, 404, "云端变量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if variable.VarType == "binary" && variable.FilePath != "" {
|
||||
if err := middleware.UpdateStorageUsed(userID, variable.FileSize, "delete"); err != nil {
|
||||
fmt.Printf("更新存储使用量失败: %v\n", err)
|
||||
}
|
||||
filePath := strings.TrimPrefix(variable.FilePath, "/")
|
||||
if _, err := os.Stat(filePath); err == nil {
|
||||
os.Remove(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&variable).Error; err != nil {
|
||||
response.Error(c, 500, "删除云端变量失败")
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleUploadCloudVariable(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
file, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
response.Error(c, 400, "请选择要上传的文件")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
appIDStr := c.PostForm("app_id")
|
||||
key := c.PostForm("key")
|
||||
description := c.PostForm("description")
|
||||
status := c.PostForm("status")
|
||||
scope := c.PostForm("scope")
|
||||
writePermission := c.PostForm("write_permission")
|
||||
|
||||
if appIDStr == "" {
|
||||
response.Error(c, 400, "请选择应用")
|
||||
return
|
||||
}
|
||||
|
||||
if key == "" {
|
||||
response.Error(c, 400, "请输入变量名")
|
||||
return
|
||||
}
|
||||
|
||||
var appID uint
|
||||
fmt.Sscanf(appIDStr, "%d", &appID)
|
||||
|
||||
if status != "active" && status != "inactive" {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
if scope != "app" && scope != "user" {
|
||||
scope = "app"
|
||||
}
|
||||
|
||||
if writePermission != "developer" && writePermission != "user" {
|
||||
writePermission = "developer"
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
if user.CurrentPackageID != nil {
|
||||
var permission model.PackagePermission
|
||||
if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&permission).Error; err == nil {
|
||||
maxStorageBytes := int64(permission.MaxStorage) * 1024 * 1024
|
||||
if user.StorageUsed+header.Size > maxStorageBytes {
|
||||
usedMB := float64(user.StorageUsed) / 1024 / 1024
|
||||
maxMB := float64(permission.MaxStorage)
|
||||
response.Error(c, 403, fmt.Sprintf("存储空间不足,已使用 %.2f MB / %.2f MB", usedMB, maxMB))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uploadDir := "uploads/cloud-files"
|
||||
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||||
response.Error(c, 500, "创建上传目录失败")
|
||||
return
|
||||
}
|
||||
|
||||
ext := filepath.Ext(header.Filename)
|
||||
filename := fmt.Sprintf("%d_%d%s", userID, time.Now().UnixNano(), ext)
|
||||
filePath := filepath.Join(uploadDir, filename)
|
||||
|
||||
dst, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "创建文件失败")
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
hash := md5.New()
|
||||
multiWriter := io.MultiWriter(dst, hash)
|
||||
if _, err := io.Copy(multiWriter, file); err != nil {
|
||||
response.Error(c, 500, "保存文件失败")
|
||||
return
|
||||
}
|
||||
fileMD5 := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
fileURL := "/uploads/cloud-files/" + filename
|
||||
|
||||
mimeType := header.Header.Get("Content-Type")
|
||||
if mimeType == "" {
|
||||
mimeType = "application/octet-stream"
|
||||
}
|
||||
|
||||
variable := model.CloudVariable{
|
||||
UserID: userID,
|
||||
AppID: &appID,
|
||||
Key: key,
|
||||
DefaultValue: fileURL,
|
||||
VarType: "binary",
|
||||
FilePath: fileURL,
|
||||
FileSize: header.Size,
|
||||
MimeType: mimeType,
|
||||
OriginalName: header.Filename,
|
||||
FileMD5: fileMD5,
|
||||
Scope: scope,
|
||||
WritePermission: writePermission,
|
||||
Description: description,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&variable).Error; err != nil {
|
||||
os.Remove(filePath)
|
||||
response.Error(c, 500, "创建云端变量失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err := middleware.UpdateStorageUsed(userID, header.Size, "upload"); err != nil {
|
||||
fmt.Printf("更新存储使用量失败: %v\n", err)
|
||||
}
|
||||
|
||||
usage := model.StorageUsage{
|
||||
UserID: userID,
|
||||
ApplicationID: &appID,
|
||||
ResourceType: "cloud_variable",
|
||||
ResourceID: variable.ID,
|
||||
FileName: header.Filename,
|
||||
FileSize: header.Size,
|
||||
Action: "upload",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
database.DB.Create(&usage)
|
||||
|
||||
service.LogOperation(c, "create", "cloud_variable", &variable.ID, fmt.Sprintf("上传文件变量: %s", variable.Key), nil)
|
||||
|
||||
response.Success(c, variable)
|
||||
}
|
||||
|
||||
func handleDownloadCloudVariable(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var variable model.CloudVariable
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
|
||||
response.Error(c, 404, "云端变量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if variable.VarType != "binary" || variable.FilePath == "" {
|
||||
response.Error(c, 400, "该变量不是文件类型")
|
||||
return
|
||||
}
|
||||
|
||||
filePath := variable.FilePath
|
||||
if strings.HasPrefix(filePath, "/") {
|
||||
filePath = filePath[1:]
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
response.Error(c, 404, "文件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Description", "File Transfer")
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", variable.OriginalName))
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
c.Header("Expires", "0")
|
||||
c.Header("Cache-Control", "must-revalidate")
|
||||
c.Header("Pragma", "public")
|
||||
c.FileAttachment(filePath, variable.OriginalName)
|
||||
}
|
||||
|
||||
func handleGetCloudVariableRecords(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var variable model.CloudVariable
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
|
||||
response.Error(c, 404, "云端变量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if variable.DataType != "stream" {
|
||||
response.Error(c, 400, "该变量不是流水类型")
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
userIDFilter := c.Query("user_id")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
var total int64
|
||||
query := database.DB.Model(&model.CloudVariableRecord{}).Where("cloud_variable_id = ?", variable.ID)
|
||||
if userIDFilter != "" {
|
||||
query = query.Where("app_user_id = ?", userIDFilter)
|
||||
}
|
||||
if startDate != "" {
|
||||
query = query.Where("created_at >= ?", startDate+" 00:00:00")
|
||||
}
|
||||
if endDate != "" {
|
||||
query = query.Where("created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
query.Count(&total)
|
||||
|
||||
var records []model.CloudVariableRecord
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("created_at DESC").Limit(pageSize).Offset(offset).Find(&records).Error; err != nil {
|
||||
response.Error(c, 500, "获取记录失败")
|
||||
return
|
||||
}
|
||||
|
||||
result := make([]gin.H, len(records))
|
||||
for i, r := range records {
|
||||
var data map[string]interface{}
|
||||
json.Unmarshal([]byte(r.Data), &data)
|
||||
record := gin.H{
|
||||
"id": r.ID,
|
||||
"data": data,
|
||||
"created_at": r.CreatedAt,
|
||||
}
|
||||
if r.AppUserID != nil {
|
||||
record["user_id"] = r.AppUserID
|
||||
var appUser model.AppUser
|
||||
if err := database.DB.Select("id, username").First(&appUser, *r.AppUserID).Error; err == nil {
|
||||
record["user"] = gin.H{
|
||||
"id": appUser.ID,
|
||||
"username": appUser.Username,
|
||||
}
|
||||
}
|
||||
}
|
||||
result[i] = record
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"records": result,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
})
|
||||
}
|
||||
|
||||
func handleDeleteCloudVariableRecords(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var variable model.CloudVariable
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&variable).Error; err != nil {
|
||||
response.Error(c, 404, "云端变量不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if variable.DataType != "stream" {
|
||||
response.Error(c, 400, "该变量不是流水类型")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
Before string `json:"before"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) > 0 {
|
||||
if err := database.DB.Where("cloud_variable_id = ? AND id IN ?", variable.ID, req.IDs).Delete(&model.CloudVariableRecord{}).Error; err != nil {
|
||||
response.Error(c, 500, "删除记录失败")
|
||||
return
|
||||
}
|
||||
} else if req.Before != "" {
|
||||
if err := database.DB.Where("cloud_variable_id = ? AND created_at < ?", variable.ID, req.Before).Delete(&model.CloudVariableRecord{}).Error; err != nil {
|
||||
response.Error(c, 500, "删除记录失败")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
response.Error(c, 400, "请指定要删除的记录")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"time"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupDashboardRoutes(r *gin.RouterGroup) {
|
||||
r.GET("/dashboard", handleGetDashboard)
|
||||
}
|
||||
|
||||
func handleGetDashboard(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var stats struct {
|
||||
TotalApplications int64 `json:"totalApplications"`
|
||||
TotalUsers int64 `json:"totalUsers"`
|
||||
TotalCards int64 `json:"totalCards"`
|
||||
MonthlyRevenue float64 `json:"monthlyRevenue"`
|
||||
}
|
||||
|
||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Count(&stats.TotalApplications)
|
||||
|
||||
var appIDs []uint
|
||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
||||
if len(appIDs) > 0 {
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Count(&stats.TotalUsers)
|
||||
}
|
||||
|
||||
database.DB.Model(&model.Card{}).Where("creator_id = ?", userID).Count(&stats.TotalCards)
|
||||
|
||||
var monthlyRevenue float64
|
||||
if len(appIDs) > 0 {
|
||||
var appUserIDs []uint
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Pluck("id", &appUserIDs)
|
||||
if len(appUserIDs) > 0 {
|
||||
database.DB.Model(&model.RechargeRecord{}).
|
||||
Where("user_id IN ? AND status = ? AND created_at >= ?", appUserIDs, "success", time.Now().AddDate(0, -1, 0)).
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Scan(&monthlyRevenue)
|
||||
}
|
||||
}
|
||||
stats.MonthlyRevenue = monthlyRevenue
|
||||
|
||||
var subscription struct {
|
||||
Plan string `json:"plan"`
|
||||
Status string `json:"status"`
|
||||
ExpireDate string `json:"expireDate"`
|
||||
APIQuota int `json:"apiQuota"`
|
||||
APIUsed int `json:"apiUsed"`
|
||||
AppCount int `json:"appCount"`
|
||||
CanCreateAgent bool `json:"canCreateAgent"`
|
||||
StorageQuota int64 `json:"storageQuota"`
|
||||
StorageUsed int64 `json:"storageUsed"`
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err == nil {
|
||||
subscription.Plan = "基础版"
|
||||
if user.Role == "admin" {
|
||||
subscription.Plan = "管理员"
|
||||
}
|
||||
subscription.Status = "正常"
|
||||
if user.Status == "banned" {
|
||||
subscription.Status = "已禁用"
|
||||
}
|
||||
subscription.ExpireDate = "永久"
|
||||
}
|
||||
subscription.AppCount = int(stats.TotalApplications)
|
||||
subscription.APIQuota = 10000
|
||||
subscription.StorageQuota = 100 * 1024 * 1024
|
||||
|
||||
var apiUsed int64
|
||||
database.DB.Model(&model.ApiUsage{}).Where("user_id = ?", userID).Count(&apiUsed)
|
||||
subscription.APIUsed = int(apiUsed)
|
||||
|
||||
var storageUsed int64
|
||||
database.DB.Model(&model.StorageUsage{}).Where("user_id = ?", userID).Select("COALESCE(SUM(size), 0)").Scan(&storageUsed)
|
||||
subscription.StorageUsed = storageUsed
|
||||
|
||||
userDistribution := gin.H{
|
||||
"provinces": []gin.H{},
|
||||
"overseas": []gin.H{},
|
||||
}
|
||||
|
||||
if len(appIDs) > 0 {
|
||||
type DeviceCount struct {
|
||||
DeviceType string
|
||||
Count int
|
||||
}
|
||||
var deviceCounts []DeviceCount
|
||||
database.DB.Model(&model.UserDevice{}).
|
||||
Select("device_type, COUNT(*) as count").
|
||||
Where("application_id IN ?", appIDs).
|
||||
Group("device_type").
|
||||
Order("count DESC").
|
||||
Find(&deviceCounts)
|
||||
|
||||
provinces := make([]gin.H, 0, len(deviceCounts))
|
||||
for _, dc := range deviceCounts {
|
||||
provinces = append(provinces, gin.H{
|
||||
"name": dc.DeviceType,
|
||||
"count": dc.Count,
|
||||
})
|
||||
}
|
||||
userDistribution["provinces"] = provinces
|
||||
}
|
||||
|
||||
onlineTrend := make([]gin.H, 0, 30)
|
||||
for i := 29; i >= 0; i-- {
|
||||
date := time.Now().AddDate(0, 0, -i)
|
||||
dateStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
dateEnd := dateStart.Add(24 * time.Hour)
|
||||
|
||||
var count int64
|
||||
if len(appIDs) > 0 {
|
||||
database.DB.Model(&model.DeviceSession{}).
|
||||
Joins("JOIN user_devices ON device_sessions.device_id = user_devices.id").
|
||||
Where("user_devices.application_id IN ? AND device_sessions.last_heartbeat >= ? AND device_sessions.last_heartbeat < ?", appIDs, dateStart, dateEnd).
|
||||
Count(&count)
|
||||
}
|
||||
|
||||
onlineTrend = append(onlineTrend, gin.H{
|
||||
"date": date.Format("01-02"),
|
||||
"value": count,
|
||||
})
|
||||
}
|
||||
|
||||
recentActivities := make([]gin.H, 0, 10)
|
||||
var logs []model.Log
|
||||
database.DB.Where("user_id = ?", userID).Order("created_at DESC").Limit(10).Find(&logs)
|
||||
for _, log := range logs {
|
||||
recentActivities = append(recentActivities, gin.H{
|
||||
"id": log.ID,
|
||||
"action": log.Action,
|
||||
"details": log.Details,
|
||||
"resource": log.Resource,
|
||||
"log_type": log.LogType,
|
||||
"status": log.Status,
|
||||
"created_at": log.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
recentTickets := make([]gin.H, 0, 5)
|
||||
var tickets []model.Ticket
|
||||
database.DB.Where("user_id = ?", userID).Order("created_at DESC").Limit(5).Find(&tickets)
|
||||
for _, ticket := range tickets {
|
||||
recentTickets = append(recentTickets, gin.H{
|
||||
"id": ticket.ID,
|
||||
"title": ticket.Title,
|
||||
"status": ticket.Status,
|
||||
"priority": ticket.Priority,
|
||||
"created_at": ticket.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"stats": stats,
|
||||
"subscription": subscription,
|
||||
"userDistribution": userDistribution,
|
||||
"onlineTrend": onlineTrend,
|
||||
"recentActivities": recentActivities,
|
||||
"recentTickets": recentTickets,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupRoutes(r *gin.RouterGroup) {
|
||||
SetupDashboardRoutes(r)
|
||||
SetupApplicationRoutes(r)
|
||||
SetupCardRoutes(r)
|
||||
SetupUserRoutes(r)
|
||||
SetupDeviceRoutes(r)
|
||||
SetupFinanceRoutes(r)
|
||||
SetupLogRoutes(r)
|
||||
SetupTicketRoutes(r)
|
||||
SetupAgentAppRoutes(r)
|
||||
SetupAgentsRoutes(r)
|
||||
SetupCloudRoutes(r)
|
||||
SetupDynamicRoutes(r)
|
||||
SetupExtensionRoutes(r)
|
||||
SetupOrderRoutes(r)
|
||||
SetupUsageRoutes(r)
|
||||
SetupAnnouncementRoutes(r)
|
||||
SetupVersionRoutes(r)
|
||||
SetupProfileRoutes(r)
|
||||
SetupEmailRoutes(r)
|
||||
}
|
||||
|
||||
func SetupRoutesWithoutPackage(r *gin.RouterGroup) {
|
||||
SetupAgentAppRoutesWithoutPackage(r)
|
||||
SetupCardRoutesWithoutPackage(r)
|
||||
}
|
||||
|
||||
func SetupExtensionRoutes(r *gin.RouterGroup) {
|
||||
extension := r.Group("/extension")
|
||||
{
|
||||
// Webhook配置
|
||||
extension.GET("/webhooks", handleGetWebhooks)
|
||||
extension.POST("/webhooks", handleCreateWebhook)
|
||||
extension.PUT("/webhooks/:id", handleUpdateWebhook)
|
||||
extension.PUT("/webhooks/:id/status", handleUpdateWebhookStatus)
|
||||
extension.DELETE("/webhooks/:id", handleDeleteWebhook)
|
||||
extension.PUT("/webhooks/batch/status", handleBatchUpdateWebhookStatus)
|
||||
extension.DELETE("/webhooks/batch", handleBatchDeleteWebhooks)
|
||||
extension.GET("/webhooks/logs", handleGetWebhookLogs)
|
||||
extension.POST("/webhooks/:id/test", handleTestWebhook)
|
||||
|
||||
// API密钥
|
||||
extension.GET("/api-keys", handleGetAPIKeys)
|
||||
extension.POST("/api-keys", handleCreateAPIKey)
|
||||
extension.PUT("/api-keys/:id", handleUpdateAPIKey)
|
||||
extension.PUT("/api-keys/:id/status", handleUpdateAPIKeyStatus)
|
||||
extension.DELETE("/api-keys/:id", handleDeleteAPIKey)
|
||||
extension.PUT("/api-keys/batch/status", handleBatchUpdateAPIKeyStatus)
|
||||
extension.DELETE("/api-keys/batch", handleBatchDeleteAPIKeys)
|
||||
extension.POST("/api-keys/:id/regenerate", handleRegenerateAPIKey)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type DeviceWithDetails struct {
|
||||
model.UserDevice
|
||||
OnlineSessions int `json:"online_sessions"`
|
||||
}
|
||||
|
||||
func SetupDeviceRoutes(r *gin.RouterGroup) {
|
||||
devices := r.Group("/devices")
|
||||
{
|
||||
devices.GET("", handleGetDevices)
|
||||
devices.PUT("/:id/status", handleUpdateDeviceStatus)
|
||||
devices.DELETE("/:id", handleUnbindDevice)
|
||||
devices.DELETE("/batch", handleBatchUnbindDevices)
|
||||
devices.POST("/batch/status", handleBatchUpdateDeviceStatus)
|
||||
devices.POST("/:id/force-offline", handleForceOfflineDevice)
|
||||
}
|
||||
|
||||
sessions := r.Group("/sessions")
|
||||
{
|
||||
sessions.GET("", handleGetSessions)
|
||||
sessions.DELETE("/:id", handleDeleteSession)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetDevices(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
log.Printf("[DEBUG] handleGetDevices called, userID: %d", userID)
|
||||
|
||||
userIDFilter := c.Query("user_id")
|
||||
deviceIDFilter := c.Query("device_id")
|
||||
|
||||
var devices []model.UserDevice
|
||||
var appHeartbeatTimeoutMap map[uint]int
|
||||
|
||||
var ownApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&ownApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var agentApps []model.AgentApplication
|
||||
if err := database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps).Error; err != nil {
|
||||
log.Printf("[DEBUG] Failed to get agent apps: %v", err)
|
||||
}
|
||||
|
||||
appHeartbeatTimeoutMap = make(map[uint]int)
|
||||
var appIDs []uint
|
||||
|
||||
for _, app := range ownApps {
|
||||
timeout := app.HeartbeatTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 300
|
||||
}
|
||||
appHeartbeatTimeoutMap[app.ID] = timeout
|
||||
appIDs = append(appIDs, app.ID)
|
||||
}
|
||||
|
||||
for _, agentApp := range agentApps {
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, agentApp.ApplicationID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
timeout := app.HeartbeatTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 300
|
||||
}
|
||||
appHeartbeatTimeoutMap[app.ID] = timeout
|
||||
appIDs = append(appIDs, app.ID)
|
||||
}
|
||||
|
||||
if len(appIDs) == 0 {
|
||||
response.Success(c, gin.H{"devices": []DeviceWithDetails{}})
|
||||
return
|
||||
}
|
||||
|
||||
query := database.DB.Preload("User").Preload("Application").Where("application_id IN ?", appIDs)
|
||||
|
||||
if userIDFilter != "" {
|
||||
query = query.Where("user_id = ?", userIDFilter)
|
||||
}
|
||||
|
||||
if deviceIDFilter != "" {
|
||||
query = query.Where("device_id = ?", deviceIDFilter)
|
||||
}
|
||||
|
||||
if err := query.Find(&devices).Error; err != nil {
|
||||
response.Error(c, 500, "获取设备列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
devicesWithDetails := make([]DeviceWithDetails, 0, len(devices))
|
||||
for _, device := range devices {
|
||||
heartbeatTimeout := appHeartbeatTimeoutMap[device.ApplicationID]
|
||||
timeoutThreshold := time.Now().Add(-time.Duration(heartbeatTimeout) * time.Second)
|
||||
|
||||
var onlineSessionCount int64
|
||||
database.DB.Model(&model.DeviceSession{}).
|
||||
Where("device_id = ? AND last_heartbeat > ?", device.ID, timeoutThreshold).
|
||||
Count(&onlineSessionCount)
|
||||
|
||||
devicesWithDetails = append(devicesWithDetails, DeviceWithDetails{
|
||||
UserDevice: device,
|
||||
OnlineSessions: int(onlineSessionCount),
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"devices": devicesWithDetails})
|
||||
}
|
||||
|
||||
func handleUpdateDeviceStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
deviceID := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var device model.UserDevice
|
||||
if err := database.DB.First(&device, deviceID).Error; err != nil {
|
||||
response.Error(c, 404, "设备不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, device.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限修改该设备")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
device.Status = req.Status
|
||||
if err := database.DB.Save(&device).Error; err != nil {
|
||||
response.Error(c, 500, "更新设备状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, device)
|
||||
}
|
||||
|
||||
func handleUnbindDevice(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
deviceID := c.Param("id")
|
||||
|
||||
var device model.UserDevice
|
||||
if err := database.DB.First(&device, deviceID).Error; err != nil {
|
||||
response.Error(c, 404, "设备不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, device.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限解绑该设备")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
database.DB.Where("device_id = ?", device.ID).Delete(&model.DeviceSession{})
|
||||
|
||||
if err := database.DB.Delete(&device).Error; err != nil {
|
||||
response.Error(c, 500, "解绑设备失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "unbind", "device", &device.ID, fmt.Sprintf("解绑设备: %s (应用: %s)", device.DeviceID, app.Name), nil)
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleBatchUnbindDevices(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
DeviceIDs []uint `json:"device_ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var devices []model.UserDevice
|
||||
if err := database.DB.Where("id IN ?", req.DeviceIDs).Find(&devices).Error; err != nil {
|
||||
response.Error(c, 500, "获取设备列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var validDeviceIDs []uint
|
||||
for _, device := range devices {
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, device.ApplicationID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if app.UserID == userID {
|
||||
validDeviceIDs = append(validDeviceIDs, device.ID)
|
||||
} else {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
|
||||
validDeviceIDs = append(validDeviceIDs, device.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(validDeviceIDs) > 0 {
|
||||
database.DB.Where("device_id IN ?", validDeviceIDs).Delete(&model.DeviceSession{})
|
||||
if err := database.DB.Where("id IN ?", validDeviceIDs).Delete(&model.UserDevice{}).Error; err != nil {
|
||||
response.Error(c, 500, "批量解绑失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
service.LogOperation(c, "batch_unbind", "device", nil, fmt.Sprintf("批量解绑设备: %d个", len(validDeviceIDs)), nil)
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleBatchUpdateDeviceStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
DeviceIDs []uint `json:"device_ids"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var devices []model.UserDevice
|
||||
if err := database.DB.Where("id IN ?", req.DeviceIDs).Find(&devices).Error; err != nil {
|
||||
response.Error(c, 500, "获取设备列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var validDeviceIDs []uint
|
||||
for _, device := range devices {
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, device.ApplicationID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if app.UserID == userID {
|
||||
validDeviceIDs = append(validDeviceIDs, device.ID)
|
||||
} else {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
|
||||
validDeviceIDs = append(validDeviceIDs, device.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(validDeviceIDs) > 0 {
|
||||
if err := database.DB.Model(&model.UserDevice{}).Where("id IN ?", validDeviceIDs).Update("status", req.Status).Error; err != nil {
|
||||
response.Error(c, 500, "批量更新状态失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
type SessionWithDetails struct {
|
||||
model.DeviceSession
|
||||
DeviceID string `json:"device_identifier"`
|
||||
DeviceName string `json:"device_name"`
|
||||
Username string `json:"username"`
|
||||
AppName string `json:"app_name"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
}
|
||||
|
||||
func handleGetSessions(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
deviceIDFilter := c.Query("device_id")
|
||||
appIDFilter := c.Query("app_id")
|
||||
|
||||
var ownApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&ownApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var agentApps []model.AgentApplication
|
||||
if err := database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps).Error; err != nil {
|
||||
log.Printf("[DEBUG] Failed to get agent apps: %v", err)
|
||||
}
|
||||
|
||||
appHeartbeatTimeoutMap := make(map[uint]int)
|
||||
var appIDs []uint
|
||||
|
||||
for _, app := range ownApps {
|
||||
timeout := app.HeartbeatTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 300
|
||||
}
|
||||
appHeartbeatTimeoutMap[app.ID] = timeout
|
||||
appIDs = append(appIDs, app.ID)
|
||||
}
|
||||
|
||||
for _, agentApp := range agentApps {
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, agentApp.ApplicationID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
timeout := app.HeartbeatTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 300
|
||||
}
|
||||
appHeartbeatTimeoutMap[app.ID] = timeout
|
||||
appIDs = append(appIDs, app.ID)
|
||||
}
|
||||
|
||||
if len(appIDs) == 0 {
|
||||
response.Success(c, gin.H{"sessions": []SessionWithDetails{}})
|
||||
return
|
||||
}
|
||||
|
||||
query := database.DB.Model(&model.DeviceSession{}).Where("application_id IN ?", appIDs)
|
||||
|
||||
if deviceIDFilter != "" {
|
||||
query = query.Where("device_id = ?", deviceIDFilter)
|
||||
}
|
||||
|
||||
if appIDFilter != "" {
|
||||
query = query.Where("application_id = ?", appIDFilter)
|
||||
}
|
||||
|
||||
var sessions []model.DeviceSession
|
||||
if err := query.Find(&sessions).Error; err != nil {
|
||||
response.Error(c, 500, "获取会话列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
sessionsWithDetails := make([]SessionWithDetails, 0, len(sessions))
|
||||
for _, session := range sessions {
|
||||
var device model.UserDevice
|
||||
if err := database.DB.First(&device, session.DeviceID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var user model.AppUser
|
||||
if err := database.DB.First(&user, session.UserID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, session.ApplicationID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
heartbeatTimeout := appHeartbeatTimeoutMap[session.ApplicationID]
|
||||
timeoutThreshold := time.Now().Add(-time.Duration(heartbeatTimeout) * time.Second)
|
||||
isOnline := session.LastHeartbeat != nil && session.LastHeartbeat.After(timeoutThreshold)
|
||||
|
||||
sessionsWithDetails = append(sessionsWithDetails, SessionWithDetails{
|
||||
DeviceSession: session,
|
||||
DeviceID: device.DeviceID,
|
||||
DeviceName: device.DeviceName,
|
||||
Username: user.Username,
|
||||
AppName: app.Name,
|
||||
IsOnline: isOnline,
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"sessions": sessionsWithDetails})
|
||||
}
|
||||
|
||||
func handleDeleteSession(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
sessionID := c.Param("id")
|
||||
|
||||
if sessionID == "" {
|
||||
response.Error(c, 400, "会话ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var session model.DeviceSession
|
||||
if err := database.DB.First(&session, sessionID).Error; err != nil {
|
||||
response.Error(c, 404, "会话不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var ownApps []model.Application
|
||||
database.DB.Where("user_id = ?", userID).Find(&ownApps)
|
||||
ownAppIDs := make([]uint, len(ownApps))
|
||||
for i, app := range ownApps {
|
||||
ownAppIDs[i] = app.ID
|
||||
}
|
||||
|
||||
var agentApps []model.AgentApplication
|
||||
database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps)
|
||||
agentAppIDs := make([]uint, 0)
|
||||
for _, agentApp := range agentApps {
|
||||
agentAppIDs = append(agentAppIDs, agentApp.ApplicationID)
|
||||
}
|
||||
|
||||
validAppIDs := append(ownAppIDs, agentAppIDs...)
|
||||
isValid := false
|
||||
for _, appID := range validAppIDs {
|
||||
if session.ApplicationID == appID {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isValid {
|
||||
response.Error(c, 403, "无权操作此会话")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&session).Error; err != nil {
|
||||
response.Error(c, 500, "删除会话失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleForceOfflineDevice(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
deviceID := c.Param("id")
|
||||
|
||||
if deviceID == "" {
|
||||
response.Error(c, 400, "设备ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var device model.UserDevice
|
||||
if err := database.DB.First(&device, deviceID).Error; err != nil {
|
||||
response.Error(c, 404, "设备不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var ownApps []model.Application
|
||||
database.DB.Where("user_id = ?", userID).Find(&ownApps)
|
||||
ownAppIDs := make([]uint, len(ownApps))
|
||||
for i, app := range ownApps {
|
||||
ownAppIDs[i] = app.ID
|
||||
}
|
||||
|
||||
var agentApps []model.AgentApplication
|
||||
database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps)
|
||||
agentAppIDs := make([]uint, 0)
|
||||
for _, agentApp := range agentApps {
|
||||
agentAppIDs = append(agentAppIDs, agentApp.ApplicationID)
|
||||
}
|
||||
|
||||
validAppIDs := append(ownAppIDs, agentAppIDs...)
|
||||
isValid := false
|
||||
for _, appID := range validAppIDs {
|
||||
if device.ApplicationID == appID {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isValid {
|
||||
response.Error(c, 403, "无权操作此设备")
|
||||
return
|
||||
}
|
||||
|
||||
var sessions []model.DeviceSession
|
||||
if err := database.DB.Where("device_id = ?", device.ID).Find(&sessions).Error; err != nil {
|
||||
response.Error(c, 500, "获取会话列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
if len(sessions) == 0 {
|
||||
response.Success(c, gin.H{"count": 0})
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Where("device_id = ?", device.ID).Delete(&model.DeviceSession{}).Error; err != nil {
|
||||
response.Error(c, 500, "强制离线失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"count": len(sessions)})
|
||||
}
|
||||
@@ -0,0 +1,805 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func normalizeKey(name string) string {
|
||||
key := strings.ToLower(strings.TrimSpace(name))
|
||||
key = strings.Map(func(r rune) rune {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-' {
|
||||
return r
|
||||
}
|
||||
return '_'
|
||||
}, key)
|
||||
return key
|
||||
}
|
||||
|
||||
func validateDynamicCode(code string) error {
|
||||
vm := goja.New()
|
||||
vm.Set("params", vm.NewObject())
|
||||
vm.Set("user", vm.NewObject())
|
||||
vm.Set("app", vm.NewObject())
|
||||
wrappedCode := "(function() { " + code + " })()"
|
||||
_, err := vm.RunString(wrappedCode)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
if strings.Contains(errStr, "ReferenceError") || strings.Contains(errStr, "is not defined") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetupDynamicRoutes(r *gin.RouterGroup) {
|
||||
dynamicCodes := r.Group("/dynamic-codes")
|
||||
{
|
||||
dynamicCodes.GET("", handleListDynamicCodes)
|
||||
dynamicCodes.GET("/:id", handleGetDynamicCode)
|
||||
dynamicCodes.POST("", handleCreateDynamicCode)
|
||||
dynamicCodes.PUT("/:id", handleUpdateDynamicCode)
|
||||
dynamicCodes.DELETE("/:id", handleDeleteDynamicCode)
|
||||
dynamicCodes.PUT("/:id/status", handleUpdateDynamicCodeStatus)
|
||||
dynamicCodes.DELETE("/batch", handleBatchDeleteDynamicCodes)
|
||||
dynamicCodes.PUT("/batch/status", handleBatchUpdateDynamicCodeStatus)
|
||||
}
|
||||
|
||||
riskControl := r.Group("/risk-control")
|
||||
{
|
||||
riskControl.GET("/rules", handleGetRiskControlRules)
|
||||
riskControl.POST("/rules", handleCreateRiskControlRule)
|
||||
riskControl.PUT("/rules/:id", handleUpdateRiskControlRule)
|
||||
riskControl.DELETE("/rules/:id", handleDeleteRiskControlRule)
|
||||
riskControl.PUT("/rules/:id/status", handleUpdateRiskControlRuleStatus)
|
||||
riskControl.DELETE("/rules/batch", handleBatchDeleteRiskControlRules)
|
||||
riskControl.PUT("/rules/batch/status", handleBatchUpdateRiskControlRuleStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func handleListDynamicCodes(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var applicationIDs []uint
|
||||
for _, app := range applications {
|
||||
applicationIDs = append(applicationIDs, app.ID)
|
||||
}
|
||||
|
||||
var dynamicCodes []model.DynamicCode
|
||||
if err := database.DB.Preload("Application").Preload("Creator").Where("application_id IN ?", applicationIDs).Find(&dynamicCodes).Error; err != nil {
|
||||
response.Error(c, 500, "获取动态代码列表失败")
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
type DynamicCodeResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
ApplicationName string `json:"application_name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Creator *struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
} `json:"creator,omitempty"`
|
||||
}
|
||||
|
||||
var result []DynamicCodeResponse
|
||||
for _, dc := range dynamicCodes {
|
||||
var creator *struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
if dc.UserID != nil && dc.Creator.ID != 0 {
|
||||
creator = &struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
}{
|
||||
ID: dc.Creator.ID,
|
||||
Username: dc.Creator.Username,
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, DynamicCodeResponse{
|
||||
ID: dc.ID,
|
||||
Name: dc.Name,
|
||||
Code: dc.Code,
|
||||
Description: dc.Description,
|
||||
ApplicationID: dc.ApplicationID,
|
||||
ApplicationName: dc.Application.Name,
|
||||
Enabled: dc.Status == "active",
|
||||
CreatedAt: dc.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: dc.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
Creator: creator,
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
func handleGetDynamicCode(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var dynamicCode model.DynamicCode
|
||||
if err := database.DB.Preload("Application").Preload("Creator").Where("id = ?", id).First(&dynamicCode).Error; err != nil {
|
||||
response.Error(c, 404, "动态代码不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", dynamicCode.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 403, "无权限访问此动态代码")
|
||||
return
|
||||
}
|
||||
|
||||
type DynamicCodeResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
ApplicationName string `json:"application_name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Creator *struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
} `json:"creator,omitempty"`
|
||||
}
|
||||
|
||||
var creator *struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
if dynamicCode.UserID != nil && dynamicCode.Creator.ID != 0 {
|
||||
creator = &struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
}{
|
||||
ID: dynamicCode.Creator.ID,
|
||||
Username: dynamicCode.Creator.Username,
|
||||
}
|
||||
}
|
||||
|
||||
result := DynamicCodeResponse{
|
||||
ID: dynamicCode.ID,
|
||||
Name: dynamicCode.Name,
|
||||
Code: dynamicCode.Code,
|
||||
Description: dynamicCode.Description,
|
||||
ApplicationID: dynamicCode.ApplicationID,
|
||||
ApplicationName: dynamicCode.Application.Name,
|
||||
Enabled: dynamicCode.Status == "active",
|
||||
CreatedAt: dynamicCode.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: dynamicCode.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
Creator: creator,
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
func handleCreateDynamicCode(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ApplicationID uint `json:"application_id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateDynamicCode(req.Code); err != nil {
|
||||
response.Error(c, 400, "代码语法错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
status := "inactive"
|
||||
if req.Enabled {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
key := normalizeKey(req.Name)
|
||||
|
||||
var existingCode model.DynamicCode
|
||||
err := database.DB.Unscoped().Where("key = ?", key).First(&existingCode).Error
|
||||
if err == nil {
|
||||
if existingCode.DeletedAt.Valid {
|
||||
database.DB.Unscoped().Delete(&existingCode)
|
||||
} else {
|
||||
response.Error(c, 400, "该名称的动态代码已存在")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
dynamicCode := model.DynamicCode{
|
||||
UserID: &userID,
|
||||
ApplicationID: req.ApplicationID,
|
||||
Name: req.Name,
|
||||
Key: key,
|
||||
Code: req.Code,
|
||||
Description: req.Description,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&dynamicCode).Error; err != nil {
|
||||
if strings.Contains(err.Error(), "UNIQUE constraint failed") || strings.Contains(err.Error(), "constraint failed") {
|
||||
response.Error(c, 400, "该名称的动态代码已存在")
|
||||
return
|
||||
}
|
||||
response.Error(c, 500, "创建失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dynamicCode)
|
||||
}
|
||||
|
||||
func handleUpdateDynamicCode(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var dynamicCode model.DynamicCode
|
||||
if err := database.DB.Where("id = ?", id).First(&dynamicCode).Error; err != nil {
|
||||
response.Error(c, 404, "动态代码不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", dynamicCode.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 403, "无权限操作此动态代码")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ApplicationID uint `json:"application_id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateDynamicCode(req.Code); err != nil {
|
||||
response.Error(c, 400, "代码语法错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if req.ApplicationID != dynamicCode.ApplicationID {
|
||||
var newApp model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&newApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限操作此应用")
|
||||
return
|
||||
}
|
||||
dynamicCode.ApplicationID = req.ApplicationID
|
||||
}
|
||||
|
||||
status := "inactive"
|
||||
if req.Enabled {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
dynamicCode.Name = req.Name
|
||||
dynamicCode.Key = normalizeKey(req.Name)
|
||||
dynamicCode.Code = req.Code
|
||||
dynamicCode.Description = req.Description
|
||||
dynamicCode.Status = status
|
||||
|
||||
if err := database.DB.Save(&dynamicCode).Error; err != nil {
|
||||
response.Error(c, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dynamicCode)
|
||||
}
|
||||
|
||||
func handleDeleteDynamicCode(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var dynamicCode model.DynamicCode
|
||||
if err := database.DB.Where("id = ?", id).First(&dynamicCode).Error; err != nil {
|
||||
response.Error(c, 404, "动态代码不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", dynamicCode.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 403, "无权限操作此动态代码")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&dynamicCode).Error; err != nil {
|
||||
response.Error(c, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"message": "删除成功"})
|
||||
}
|
||||
|
||||
func handleUpdateDynamicCodeStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var dynamicCode model.DynamicCode
|
||||
if err := database.DB.Where("id = ?", id).First(&dynamicCode).Error; err != nil {
|
||||
response.Error(c, 404, "动态代码不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", dynamicCode.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 403, "无权限操作此动态代码")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
status := "inactive"
|
||||
if req.Enabled {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
dynamicCode.Status = status
|
||||
|
||||
if err := database.DB.Save(&dynamicCode).Error; err != nil {
|
||||
response.Error(c, 500, "更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dynamicCode)
|
||||
}
|
||||
|
||||
func handleBatchDeleteDynamicCodes(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var applicationIDs []uint
|
||||
for _, app := range applications {
|
||||
applicationIDs = append(applicationIDs, app.ID)
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id IN ? AND application_id IN ?", req.IDs, applicationIDs).Delete(&model.DynamicCode{}).Error; err != nil {
|
||||
response.Error(c, 500, "批量删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"message": "批量删除成功"})
|
||||
}
|
||||
|
||||
func handleBatchUpdateDynamicCodeStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var applicationIDs []uint
|
||||
for _, app := range applications {
|
||||
applicationIDs = append(applicationIDs, app.ID)
|
||||
}
|
||||
|
||||
status := "inactive"
|
||||
if req.Enabled {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&model.DynamicCode{}).Where("id IN ? AND application_id IN ?", req.IDs, applicationIDs).Update("status", status).Error; err != nil {
|
||||
response.Error(c, 500, "批量更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"message": "批量更新状态成功"})
|
||||
}
|
||||
|
||||
type RiskControlRule struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
Reason string `json:"reason"`
|
||||
Status string `json:"status"`
|
||||
ExpiresAt *string `json:"expires_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
func handleGetRiskControlRules(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var applicationIDs []uint
|
||||
for _, app := range applications {
|
||||
applicationIDs = append(applicationIDs, app.ID)
|
||||
}
|
||||
|
||||
var rules []model.RiskControlRule
|
||||
if err := database.DB.Where("user_id = ? OR application_id IN ?", userID, applicationIDs).Order("created_at DESC").Find(&rules).Error; err != nil {
|
||||
response.Error(c, 500, "获取风控规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
var result []gin.H
|
||||
for _, rule := range rules {
|
||||
var expiresAt *string
|
||||
if rule.ExpiresAt != nil {
|
||||
t := rule.ExpiresAt.Format("2006-01-02 15:04:05")
|
||||
expiresAt = &t
|
||||
}
|
||||
|
||||
var appID *uint
|
||||
if rule.ApplicationID != nil {
|
||||
appID = rule.ApplicationID
|
||||
}
|
||||
|
||||
result = append(result, gin.H{
|
||||
"id": rule.ID,
|
||||
"type": rule.Type,
|
||||
"value": rule.Value,
|
||||
"reason": rule.Reason,
|
||||
"status": rule.Status,
|
||||
"expires_at": expiresAt,
|
||||
"application_id": appID,
|
||||
"is_global": rule.ApplicationID == nil,
|
||||
"created_at": rule.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
func handleCreateRiskControlRule(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
Type string `json:"type" binding:"required"`
|
||||
Value string `json:"value" binding:"required"`
|
||||
Reason string `json:"reason"`
|
||||
ExpiresAt *string `json:"expires_at"`
|
||||
ApplicationID *uint `json:"application_id"`
|
||||
IsGlobal bool `json:"is_global"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[ERROR] Failed to bind JSON: %v", err)
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Create risk control rule: type=%s, value=%s, is_global=%v, application_id=%v", req.Type, req.Value, req.IsGlobal, req.ApplicationID)
|
||||
|
||||
var appID *uint
|
||||
switch req.Type {
|
||||
case "user":
|
||||
if req.ApplicationID == nil {
|
||||
response.Error(c, 400, "用户封禁规则必须指定应用")
|
||||
return
|
||||
}
|
||||
var application model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", *req.ApplicationID, userID).First(&application).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在或无权限")
|
||||
return
|
||||
}
|
||||
appID = req.ApplicationID
|
||||
case "ip", "device", "region":
|
||||
if !req.IsGlobal && req.ApplicationID != nil {
|
||||
var application model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", *req.ApplicationID, userID).First(&application).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在或无权限")
|
||||
return
|
||||
}
|
||||
appID = req.ApplicationID
|
||||
} else {
|
||||
appID = nil
|
||||
}
|
||||
default:
|
||||
response.Error(c, 400, "不支持的规则类型")
|
||||
return
|
||||
}
|
||||
|
||||
rule := model.RiskControlRule{
|
||||
UserID: userID,
|
||||
ApplicationID: appID,
|
||||
Type: req.Type,
|
||||
Value: req.Value,
|
||||
Reason: req.Reason,
|
||||
Status: "active",
|
||||
}
|
||||
|
||||
if req.ExpiresAt != nil && *req.ExpiresAt != "" {
|
||||
t, err := time.Parse("2006-01-02T15:04", *req.ExpiresAt)
|
||||
if err == nil {
|
||||
rule.ExpiresAt = &t
|
||||
} else {
|
||||
log.Printf("[WARN] Failed to parse expires_at: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Creating rule: %+v", rule)
|
||||
|
||||
if err := database.DB.Create(&rule).Error; err != nil {
|
||||
log.Printf("[ERROR] Failed to create risk control rule: %v", err)
|
||||
response.Error(c, 500, "创建风控规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
var expiresAt *string
|
||||
if rule.ExpiresAt != nil {
|
||||
t := rule.ExpiresAt.Format("2006-01-02 15:04:05")
|
||||
expiresAt = &t
|
||||
}
|
||||
|
||||
var appIDResp *uint
|
||||
if rule.ApplicationID != nil {
|
||||
appIDResp = rule.ApplicationID
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": rule.ID,
|
||||
"type": rule.Type,
|
||||
"value": rule.Value,
|
||||
"reason": rule.Reason,
|
||||
"status": rule.Status,
|
||||
"expires_at": expiresAt,
|
||||
"application_id": appIDResp,
|
||||
"is_global": rule.ApplicationID == nil,
|
||||
"created_at": rule.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateRiskControlRule(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
ruleID := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Type string `json:"type" binding:"required"`
|
||||
Value string `json:"value" binding:"required"`
|
||||
Reason string `json:"reason"`
|
||||
ExpiresAt *string `json:"expires_at"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var applicationIDs []uint
|
||||
for _, app := range applications {
|
||||
applicationIDs = append(applicationIDs, app.ID)
|
||||
}
|
||||
|
||||
var rule model.RiskControlRule
|
||||
if err := database.DB.Where("id = ? AND application_id IN ?", ruleID, applicationIDs).First(&rule).Error; err != nil {
|
||||
response.Error(c, 404, "风控规则不存在")
|
||||
return
|
||||
}
|
||||
|
||||
rule.Type = req.Type
|
||||
rule.Value = req.Value
|
||||
rule.Reason = req.Reason
|
||||
|
||||
if req.ExpiresAt != nil && *req.ExpiresAt != "" {
|
||||
t, err := time.Parse("2006-01-02T15:04", *req.ExpiresAt)
|
||||
if err == nil {
|
||||
rule.ExpiresAt = &t
|
||||
}
|
||||
} else {
|
||||
rule.ExpiresAt = nil
|
||||
}
|
||||
|
||||
if err := database.DB.Save(&rule).Error; err != nil {
|
||||
response.Error(c, 500, "更新风控规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
var expiresAt *string
|
||||
if rule.ExpiresAt != nil {
|
||||
t := rule.ExpiresAt.Format("2006-01-02 15:04:05")
|
||||
expiresAt = &t
|
||||
}
|
||||
|
||||
response.Success(c, RiskControlRule{
|
||||
ID: rule.ID,
|
||||
Type: rule.Type,
|
||||
Value: rule.Value,
|
||||
Reason: rule.Reason,
|
||||
Status: rule.Status,
|
||||
ExpiresAt: expiresAt,
|
||||
CreatedAt: rule.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
func handleDeleteRiskControlRule(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
ruleID := c.Param("id")
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var applicationIDs []uint
|
||||
for _, app := range applications {
|
||||
applicationIDs = append(applicationIDs, app.ID)
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id = ? AND application_id IN ?", ruleID, applicationIDs).Delete(&model.RiskControlRule{}).Error; err != nil {
|
||||
response.Error(c, 500, "删除风控规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"message": "删除成功"})
|
||||
}
|
||||
|
||||
func handleUpdateRiskControlRuleStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
ruleID := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var applicationIDs []uint
|
||||
for _, app := range applications {
|
||||
applicationIDs = append(applicationIDs, app.ID)
|
||||
}
|
||||
|
||||
var rule model.RiskControlRule
|
||||
if err := database.DB.Where("id = ? AND application_id IN ?", ruleID, applicationIDs).First(&rule).Error; err != nil {
|
||||
response.Error(c, 404, "风控规则不存在")
|
||||
return
|
||||
}
|
||||
|
||||
rule.Status = req.Status
|
||||
if err := database.DB.Save(&rule).Error; err != nil {
|
||||
response.Error(c, 500, "更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"message": "状态更新成功"})
|
||||
}
|
||||
|
||||
func handleBatchDeleteRiskControlRules(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var applicationIDs []uint
|
||||
for _, app := range applications {
|
||||
applicationIDs = append(applicationIDs, app.ID)
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id IN ? AND application_id IN ?", req.IDs, applicationIDs).Delete(&model.RiskControlRule{}).Error; err != nil {
|
||||
response.Error(c, 500, "批量删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"message": "批量删除成功"})
|
||||
}
|
||||
|
||||
func handleBatchUpdateRiskControlRuleStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var applications []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var applicationIDs []uint
|
||||
for _, app := range applications {
|
||||
applicationIDs = append(applicationIDs, app.ID)
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&model.RiskControlRule{}).Where("id IN ? AND application_id IN ?", req.IDs, applicationIDs).Update("status", req.Status).Error; err != nil {
|
||||
response.Error(c, 500, "批量更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"message": "批量更新状态成功"})
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupEmailRoutes(r *gin.RouterGroup) {
|
||||
r.GET("/applications/:id/email-config", handleGetEmailConfig)
|
||||
r.PUT("/applications/:id/email-config", handleUpdateEmailConfig)
|
||||
r.POST("/applications/:id/email-config/test", handleTestEmailConfig)
|
||||
r.GET("/applications/:id/email-templates", handleGetEmailTemplates)
|
||||
r.GET("/applications/:id/email-templates/:template_id", handleGetEmailTemplate)
|
||||
r.POST("/applications/:id/email-templates", handleCreateEmailTemplate)
|
||||
r.PUT("/applications/:id/email-templates/:template_id", handleUpdateEmailTemplate)
|
||||
r.DELETE("/applications/:id/email-templates/:template_id", handleDeleteEmailTemplate)
|
||||
r.POST("/applications/:id/send-verify-code", handleSendVerifyCode)
|
||||
}
|
||||
|
||||
func handleGetEmailConfig(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
appID := c.Param("id")
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var permission model.PackagePermission
|
||||
hasPermission := checkEmailPermission(userID.(uint), &permission)
|
||||
if !hasPermission {
|
||||
response.Error(c, 403, "您的套餐不支持邮件功能")
|
||||
return
|
||||
}
|
||||
|
||||
var smtpConfig model.AppSMTPConfig
|
||||
database.DB.Where("application_id = ?", app.ID).First(&smtpConfig)
|
||||
|
||||
result := gin.H{
|
||||
"enable_email_verify": app.EnableEmailVerify,
|
||||
"require_email_verify": app.RequireEmailVerify,
|
||||
"enable_password_reset": app.EnablePasswordReset,
|
||||
"permission": gin.H{
|
||||
"allow_email": permission.AllowEmail,
|
||||
},
|
||||
}
|
||||
|
||||
if smtpConfig.ID > 0 {
|
||||
result["smtp_config"] = gin.H{
|
||||
"id": smtpConfig.ID,
|
||||
"host": smtpConfig.Host,
|
||||
"port": smtpConfig.Port,
|
||||
"user": smtpConfig.User,
|
||||
"from_name": smtpConfig.FromName,
|
||||
"from_email": smtpConfig.FromEmail,
|
||||
"use_ssl": smtpConfig.UseSSL,
|
||||
"status": smtpConfig.Status,
|
||||
}
|
||||
} else {
|
||||
result["smtp_config"] = nil
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
func handleUpdateEmailConfig(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
appID := c.Param("id")
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var permission model.PackagePermission
|
||||
hasPermission := checkEmailPermission(userID.(uint), &permission)
|
||||
if !hasPermission {
|
||||
response.Error(c, 403, "您的套餐不支持邮件功能")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
EnableEmailVerify bool `json:"enable_email_verify"`
|
||||
RequireEmailVerify bool `json:"require_email_verify"`
|
||||
EnablePasswordReset bool `json:"enable_password_reset"`
|
||||
SMTPConfig *struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
FromName string `json:"from_name"`
|
||||
FromEmail string `json:"from_email"`
|
||||
UseSSL bool `json:"use_ssl"`
|
||||
} `json:"smtp_config"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
"enable_email_verify": req.EnableEmailVerify,
|
||||
"require_email_verify": req.RequireEmailVerify,
|
||||
"enable_password_reset": req.EnablePasswordReset,
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&app).Updates(updates).Error; err != nil {
|
||||
response.Error(c, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
|
||||
if req.SMTPConfig != nil {
|
||||
var smtpConfig model.AppSMTPConfig
|
||||
database.DB.Where("application_id = ?", app.ID).First(&smtpConfig)
|
||||
|
||||
smtpConfig.ApplicationID = app.ID
|
||||
smtpConfig.Host = req.SMTPConfig.Host
|
||||
smtpConfig.Port = req.SMTPConfig.Port
|
||||
smtpConfig.User = req.SMTPConfig.User
|
||||
if req.SMTPConfig.Password != "" {
|
||||
smtpConfig.Password = req.SMTPConfig.Password
|
||||
}
|
||||
smtpConfig.FromName = req.SMTPConfig.FromName
|
||||
smtpConfig.FromEmail = req.SMTPConfig.FromEmail
|
||||
smtpConfig.UseSSL = req.SMTPConfig.UseSSL
|
||||
|
||||
if smtpConfig.ID > 0 {
|
||||
database.DB.Save(&smtpConfig)
|
||||
} else {
|
||||
database.DB.Create(&smtpConfig)
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "更新成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleTestEmailConfig(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
appID := c.Param("id")
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "请输入有效的邮箱地址")
|
||||
return
|
||||
}
|
||||
|
||||
var smtpConfig model.AppSMTPConfig
|
||||
if err := database.DB.Where("application_id = ?", app.ID).First(&smtpConfig).Error; err != nil {
|
||||
response.Error(c, 400, "请先配置SMTP")
|
||||
return
|
||||
}
|
||||
|
||||
emailService := service.NewEmailService()
|
||||
config := service.EmailConfig{
|
||||
Host: smtpConfig.Host,
|
||||
Port: smtpConfig.Port,
|
||||
User: smtpConfig.User,
|
||||
Password: smtpConfig.Password,
|
||||
FromName: smtpConfig.FromName,
|
||||
FromEmail: smtpConfig.FromEmail,
|
||||
UseSSL: smtpConfig.UseSSL,
|
||||
}
|
||||
|
||||
if err := emailService.SendTestEmail(config, req.Email, app.Name); err != nil {
|
||||
response.Error(c, 500, fmt.Sprintf("发送失败: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "测试邮件已发送",
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetEmailTemplates(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
appID := c.Param("id")
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var permission model.PackagePermission
|
||||
hasPermission := checkEmailPermission(userID.(uint), &permission)
|
||||
if !hasPermission {
|
||||
response.Error(c, 403, "您的套餐不支持邮箱验证功能")
|
||||
return
|
||||
}
|
||||
|
||||
var templates []model.EmailTemplate
|
||||
database.DB.Where("application_id = ?", app.ID).Order("created_at DESC").Find(&templates)
|
||||
|
||||
response.Success(c, templates)
|
||||
}
|
||||
|
||||
func handleGetEmailTemplate(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
appID := c.Param("id")
|
||||
templateID := c.Param("template_id")
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var template model.EmailTemplate
|
||||
if err := database.DB.Where("id = ? AND application_id = ?", templateID, app.ID).First(&template).Error; err != nil {
|
||||
response.Error(c, 404, "模板不存在")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, template)
|
||||
}
|
||||
|
||||
func handleCreateEmailTemplate(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
appID := c.Param("id")
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var permission model.PackagePermission
|
||||
hasPermission := checkEmailPermission(userID.(uint), &permission)
|
||||
if !hasPermission {
|
||||
response.Error(c, 403, "您的套餐不支持邮件功能")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Type string `json:"type" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Subject string `json:"subject" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
template := model.EmailTemplate{
|
||||
ApplicationID: app.ID,
|
||||
Type: req.Type,
|
||||
Name: req.Name,
|
||||
Subject: req.Subject,
|
||||
Content: req.Content,
|
||||
Status: "active",
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&template).Error; err != nil {
|
||||
response.Error(c, 500, "创建失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, template)
|
||||
}
|
||||
|
||||
func handleUpdateEmailTemplate(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
appID := c.Param("id")
|
||||
templateID := c.Param("template_id")
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var permission model.PackagePermission
|
||||
hasPermission := checkEmailPermission(userID.(uint), &permission)
|
||||
if !hasPermission {
|
||||
response.Error(c, 403, "您的套餐不支持邮件功能")
|
||||
return
|
||||
}
|
||||
|
||||
var template model.EmailTemplate
|
||||
if err := database.DB.Where("id = ? AND application_id = ?", templateID, app.ID).First(&template).Error; err != nil {
|
||||
response.Error(c, 404, "模板不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Subject string `json:"subject"`
|
||||
Content string `json:"content"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
if req.Type != "" {
|
||||
updates["type"] = req.Type
|
||||
}
|
||||
if req.Name != "" {
|
||||
updates["name"] = req.Name
|
||||
}
|
||||
if req.Subject != "" {
|
||||
updates["subject"] = req.Subject
|
||||
}
|
||||
if req.Content != "" {
|
||||
updates["content"] = req.Content
|
||||
}
|
||||
if req.Status != "" {
|
||||
updates["status"] = req.Status
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&template).Updates(updates).Error; err != nil {
|
||||
response.Error(c, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, template)
|
||||
}
|
||||
|
||||
func handleDeleteEmailTemplate(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
appID := c.Param("id")
|
||||
templateID := c.Param("template_id")
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var permission model.PackagePermission
|
||||
hasPermission := checkEmailPermission(userID.(uint), &permission)
|
||||
if !hasPermission {
|
||||
response.Error(c, 403, "您的套餐不支持邮件功能")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id = ? AND application_id = ?", templateID, app.ID).Delete(&model.EmailTemplate{}).Error; err != nil {
|
||||
response.Error(c, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleSendVerifyCode(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
appID := c.Param("id")
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", appID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Purpose string `json:"purpose"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "请输入有效的邮箱地址")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Purpose == "" {
|
||||
req.Purpose = "register"
|
||||
}
|
||||
|
||||
var smtpConfig model.AppSMTPConfig
|
||||
if err := database.DB.Where("application_id = ?", app.ID).First(&smtpConfig).Error; err != nil {
|
||||
response.Error(c, 400, "请先配置SMTP")
|
||||
return
|
||||
}
|
||||
|
||||
var template model.EmailTemplate
|
||||
database.DB.Where("application_id = ? AND type = ? AND status = ?", app.ID, req.Purpose, "active").
|
||||
Order("is_default DESC").First(&template)
|
||||
|
||||
code := generateVerifyCode()
|
||||
expireAt := time.Now().Add(15 * time.Minute)
|
||||
|
||||
verifyCode := model.EmailVerifyCode{
|
||||
ApplicationID: app.ID,
|
||||
Email: req.Email,
|
||||
Code: code,
|
||||
Purpose: req.Purpose,
|
||||
ExpiresAt: expireAt,
|
||||
}
|
||||
database.DB.Create(&verifyCode)
|
||||
|
||||
emailService := service.NewEmailService()
|
||||
config := service.EmailConfig{
|
||||
Host: smtpConfig.Host,
|
||||
Port: smtpConfig.Port,
|
||||
User: smtpConfig.User,
|
||||
Password: smtpConfig.Password,
|
||||
FromName: smtpConfig.FromName,
|
||||
FromEmail: smtpConfig.FromEmail,
|
||||
UseSSL: smtpConfig.UseSSL,
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("验证码 - %s", app.Name)
|
||||
content := fmt.Sprintf(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body style="font-family: Arial, sans-serif; padding: 20px; background-color: #f5f5f5;">
|
||||
<div style="max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
<h2 style="color: #333; margin-bottom: 20px;">邮箱验证</h2>
|
||||
<p style="color: #666; line-height: 1.6;">您的验证码是:<strong style="font-size: 24px; color: #1890ff;">%s</strong></p>
|
||||
<p style="color: #999; font-size: 12px;">验证码有效期为15分钟,请尽快使用。</p>
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||||
<p style="color: #999; font-size: 12px;">此邮件由 %s 系统自动发送,请勿回复。</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`, code, app.Name)
|
||||
|
||||
if template.ID > 0 {
|
||||
subject = template.Subject
|
||||
content = template.Content
|
||||
}
|
||||
|
||||
if err := emailService.SendEmail(config, req.Email, subject, content); err != nil {
|
||||
response.Error(c, 500, fmt.Sprintf("发送失败: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "验证码已发送",
|
||||
})
|
||||
}
|
||||
|
||||
func checkEmailPermission(userID uint, permission *model.PackagePermission) bool {
|
||||
var userPackage model.UserPackage
|
||||
if err := database.DB.Where("user_id = ? AND status = ?", userID, "active").
|
||||
Preload("Package").First(&userPackage).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if userPackage.ExpiredAt != nil && userPackage.ExpiredAt.Before(time.Now()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := database.DB.Where("package_id = ?", userPackage.PackageID).First(permission).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return permission.AllowEmail
|
||||
}
|
||||
|
||||
func generateVerifyCode() string {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
return fmt.Sprintf("%06d", r.Intn(1000000))
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func handleGetWebhooks(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
appID := c.Query("application_id")
|
||||
|
||||
var webhooks []struct {
|
||||
model.WebhookConfig
|
||||
ApplicationName string `json:"application_name"`
|
||||
}
|
||||
|
||||
query := database.DB.Model(&model.WebhookConfig{}).
|
||||
Select("webhook_configs.*, applications.name as application_name").
|
||||
Joins("JOIN applications ON webhook_configs.application_id = applications.id").
|
||||
Where("applications.user_id = ?", userID)
|
||||
|
||||
if appID != "" && appID != "all" {
|
||||
query = query.Where("webhook_configs.application_id = ?", appID)
|
||||
}
|
||||
|
||||
if err := query.Find(&webhooks).Error; err != nil {
|
||||
response.Error(c, 500, "获取Webhook配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, webhooks)
|
||||
}
|
||||
|
||||
func handleCreateWebhook(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
ApplicationID uint `json:"application_id" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
URL string `json:"url" binding:"required,url"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
Events []string `json:"events" binding:"required"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
Timeout int `json:"timeout"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
eventsJSON, _ := json.Marshal(req.Events)
|
||||
if req.RetryCount == 0 {
|
||||
req.RetryCount = 3
|
||||
}
|
||||
if req.Timeout == 0 {
|
||||
req.Timeout = 10
|
||||
}
|
||||
|
||||
webhook := model.WebhookConfig{
|
||||
ApplicationID: req.ApplicationID,
|
||||
Name: req.Name,
|
||||
URL: req.URL,
|
||||
SecretKey: req.SecretKey,
|
||||
Events: string(eventsJSON),
|
||||
Status: "active",
|
||||
RetryCount: req.RetryCount,
|
||||
Timeout: req.Timeout,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&webhook).Error; err != nil {
|
||||
response.Error(c, 500, "创建Webhook配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "create", "webhook", &webhook.ID, fmt.Sprintf("创建Webhook: %s", webhook.Name), nil)
|
||||
|
||||
response.Success(c, webhook)
|
||||
}
|
||||
|
||||
func handleUpdateWebhook(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url" binding:"omitempty,url"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
Events []string `json:"events"`
|
||||
Status string `json:"status"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
Timeout int `json:"timeout"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var webhook model.WebhookConfig
|
||||
if err := database.DB.Joins("JOIN applications ON webhook_configs.application_id = applications.id").
|
||||
Where("webhook_configs.id = ? AND applications.user_id = ?", id, userID).
|
||||
First(&webhook).Error; err != nil {
|
||||
response.Error(c, 404, "Webhook配置不存在")
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.Name != "" {
|
||||
updates["name"] = req.Name
|
||||
}
|
||||
if req.URL != "" {
|
||||
updates["url"] = req.URL
|
||||
}
|
||||
if req.SecretKey != "" {
|
||||
updates["secret_key"] = req.SecretKey
|
||||
}
|
||||
if len(req.Events) > 0 {
|
||||
eventsJSON, _ := json.Marshal(req.Events)
|
||||
updates["events"] = string(eventsJSON)
|
||||
}
|
||||
if req.Status != "" {
|
||||
updates["status"] = req.Status
|
||||
}
|
||||
if req.RetryCount > 0 {
|
||||
updates["retry_count"] = req.RetryCount
|
||||
}
|
||||
if req.Timeout > 0 {
|
||||
updates["timeout"] = req.Timeout
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&webhook).Updates(updates).Error; err != nil {
|
||||
response.Error(c, 500, "更新Webhook配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, webhook)
|
||||
}
|
||||
|
||||
func handleDeleteWebhook(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var webhook model.WebhookConfig
|
||||
if err := database.DB.Joins("JOIN applications ON webhook_configs.application_id = applications.id").
|
||||
Where("webhook_configs.id = ? AND applications.user_id = ?", id, userID).
|
||||
First(&webhook).Error; err != nil {
|
||||
response.Error(c, 404, "Webhook配置不存在")
|
||||
return
|
||||
}
|
||||
|
||||
webhookName := webhook.Name
|
||||
webhookID := webhook.ID
|
||||
|
||||
if err := database.DB.Delete(&webhook).Error; err != nil {
|
||||
response.Error(c, 500, "删除Webhook配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "delete", "webhook", &webhookID, fmt.Sprintf("删除Webhook: %s", webhookName), nil)
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleGetWebhookLogs(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
webhookID := c.Query("webhook_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
var logs []model.WebhookLog
|
||||
var total int64
|
||||
|
||||
query := database.DB.Model(&model.WebhookLog{}).
|
||||
Joins("JOIN webhook_configs ON webhook_logs.webhook_id = webhook_configs.id").
|
||||
Joins("JOIN applications ON webhook_configs.application_id = applications.id").
|
||||
Where("applications.user_id = ?", userID)
|
||||
|
||||
if webhookID != "" {
|
||||
query = query.Where("webhook_logs.webhook_id = ?", webhookID)
|
||||
}
|
||||
|
||||
query.Count(&total)
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("webhook_logs.created_at DESC").
|
||||
Offset(offset).Limit(pageSize).
|
||||
Find(&logs).Error; err != nil {
|
||||
response.Error(c, 500, "获取Webhook日志失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"logs": logs,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_page": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
})
|
||||
}
|
||||
|
||||
func handleTestWebhook(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var webhook model.WebhookConfig
|
||||
if err := database.DB.Joins("JOIN applications ON webhook_configs.application_id = applications.id").
|
||||
Where("webhook_configs.id = ? AND applications.user_id = ?", id, userID).
|
||||
First(&webhook).Error; err != nil {
|
||||
response.Error(c, 404, "Webhook配置不存在")
|
||||
return
|
||||
}
|
||||
|
||||
testData := map[string]interface{}{
|
||||
"event": "test",
|
||||
"timestamp": time.Now().Unix(),
|
||||
"data": map[string]interface{}{
|
||||
"message": "This is a test webhook",
|
||||
},
|
||||
}
|
||||
|
||||
go sendWebhook(&webhook, testData)
|
||||
|
||||
response.Success(c, gin.H{"message": "测试请求已发送"})
|
||||
}
|
||||
|
||||
func handleGetAPIKeys(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
appID := c.Query("application_id")
|
||||
|
||||
var apiKeys []struct {
|
||||
model.ExtensionAPIKey
|
||||
ApplicationName string `json:"application_name"`
|
||||
}
|
||||
|
||||
query := database.DB.Model(&model.ExtensionAPIKey{}).
|
||||
Select("extension_api_keys.*, applications.name as application_name").
|
||||
Joins("JOIN applications ON extension_api_keys.application_id = applications.id").
|
||||
Where("applications.user_id = ?", userID)
|
||||
|
||||
if appID != "" && appID != "all" {
|
||||
query = query.Where("extension_api_keys.application_id = ?", appID)
|
||||
}
|
||||
|
||||
if err := query.Find(&apiKeys).Error; err != nil {
|
||||
response.Error(c, 500, "获取API密钥失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, apiKeys)
|
||||
}
|
||||
|
||||
func handleCreateAPIKey(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
ApplicationID uint `json:"application_id" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Permissions []string `json:"permissions"`
|
||||
ExpiresAt *string `json:"expires_at"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
accessKey := generateRandomKey(32)
|
||||
secretKey := generateRandomKey(32)
|
||||
|
||||
permissionsJSON, _ := json.Marshal(req.Permissions)
|
||||
|
||||
apiKey := model.ExtensionAPIKey{
|
||||
ApplicationID: req.ApplicationID,
|
||||
Name: req.Name,
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
Permissions: string(permissionsJSON),
|
||||
Status: "active",
|
||||
}
|
||||
|
||||
if req.ExpiresAt != nil {
|
||||
expiresAt, err := time.Parse(time.RFC3339, *req.ExpiresAt)
|
||||
if err == nil {
|
||||
apiKey.ExpiresAt = &expiresAt
|
||||
}
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&apiKey).Error; err != nil {
|
||||
response.Error(c, 500, "创建API密钥失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, apiKey)
|
||||
}
|
||||
|
||||
func handleUpdateAPIKey(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Permissions []string `json:"permissions"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var apiKey model.ExtensionAPIKey
|
||||
if err := database.DB.Joins("JOIN applications ON extension_api_keys.application_id = applications.id").
|
||||
Where("extension_api_keys.id = ? AND applications.user_id = ?", id, userID).
|
||||
First(&apiKey).Error; err != nil {
|
||||
response.Error(c, 404, "API密钥不存在")
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.Name != "" {
|
||||
updates["name"] = req.Name
|
||||
}
|
||||
if len(req.Permissions) > 0 {
|
||||
permissionsJSON, _ := json.Marshal(req.Permissions)
|
||||
updates["permissions"] = string(permissionsJSON)
|
||||
}
|
||||
if req.Status != "" {
|
||||
updates["status"] = req.Status
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&apiKey).Updates(updates).Error; err != nil {
|
||||
response.Error(c, 500, "更新API密钥失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, apiKey)
|
||||
}
|
||||
|
||||
func handleDeleteAPIKey(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var apiKey model.ExtensionAPIKey
|
||||
if err := database.DB.Joins("JOIN applications ON extension_api_keys.application_id = applications.id").
|
||||
Where("extension_api_keys.id = ? AND applications.user_id = ?", id, userID).
|
||||
First(&apiKey).Error; err != nil {
|
||||
response.Error(c, 404, "API密钥不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&apiKey).Error; err != nil {
|
||||
response.Error(c, 500, "删除API密钥失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleRegenerateAPIKey(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var apiKey model.ExtensionAPIKey
|
||||
if err := database.DB.Joins("JOIN applications ON extension_api_keys.application_id = applications.id").
|
||||
Where("extension_api_keys.id = ? AND applications.user_id = ?", id, userID).
|
||||
First(&apiKey).Error; err != nil {
|
||||
response.Error(c, 404, "API密钥不存在")
|
||||
return
|
||||
}
|
||||
|
||||
newSecretKey := generateRandomKey(32)
|
||||
if err := database.DB.Model(&apiKey).Update("secret_key", newSecretKey).Error; err != nil {
|
||||
response.Error(c, 500, "重新生成密钥失败")
|
||||
return
|
||||
}
|
||||
|
||||
apiKey.SecretKey = newSecretKey
|
||||
response.Success(c, apiKey)
|
||||
}
|
||||
|
||||
func generateRandomKey(length int) string {
|
||||
bytes := make([]byte, length)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return ""
|
||||
}
|
||||
return hex.EncodeToString(bytes)[:length*2]
|
||||
}
|
||||
|
||||
func sendWebhook(webhook *model.WebhookConfig, data map[string]interface{}) {
|
||||
jsonData, _ := json.Marshal(data)
|
||||
|
||||
startTime := time.Now()
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(webhook.Timeout) * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", webhook.URL, bytes.NewReader(jsonData))
|
||||
if err != nil {
|
||||
logWebhookError(webhook.ID, data, err, time.Since(startTime).Milliseconds())
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if webhook.SecretKey != "" {
|
||||
req.Header.Set("X-Webhook-Secret", webhook.SecretKey)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
duration := time.Since(startTime).Milliseconds()
|
||||
|
||||
if err != nil {
|
||||
logWebhookError(webhook.ID, data, err, duration)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
logWebhookSuccess(webhook.ID, data, resp.StatusCode, duration)
|
||||
}
|
||||
|
||||
func logWebhookSuccess(webhookID uint, requestData map[string]interface{}, statusCode int, duration int64) {
|
||||
requestJSON, _ := json.Marshal(requestData)
|
||||
log := model.WebhookLog{
|
||||
WebhookID: webhookID,
|
||||
Event: requestData["event"].(string),
|
||||
RequestData: string(requestJSON),
|
||||
ResponseCode: statusCode,
|
||||
Status: "success",
|
||||
Duration: int(duration),
|
||||
}
|
||||
database.DB.Create(&log)
|
||||
}
|
||||
|
||||
func logWebhookError(webhookID uint, requestData map[string]interface{}, err error, duration int64) {
|
||||
requestJSON, _ := json.Marshal(requestData)
|
||||
log := model.WebhookLog{
|
||||
WebhookID: webhookID,
|
||||
Event: requestData["event"].(string),
|
||||
RequestData: string(requestJSON),
|
||||
Status: "failed",
|
||||
ErrorMessage: err.Error(),
|
||||
Duration: int(duration),
|
||||
}
|
||||
database.DB.Create(&log)
|
||||
}
|
||||
|
||||
func handleUpdateWebhookStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required,oneof=active inactive"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var webhook model.WebhookConfig
|
||||
if err := database.DB.Joins("JOIN applications ON webhook_configs.application_id = applications.id").
|
||||
Where("webhook_configs.id = ? AND applications.user_id = ?", id, userID).
|
||||
First(&webhook).Error; err != nil {
|
||||
response.Error(c, 404, "Webhook配置不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&webhook).Update("status", req.Status).Error; err != nil {
|
||||
response.Error(c, 500, "更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, webhook)
|
||||
}
|
||||
|
||||
func handleBatchUpdateWebhookStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
Status string `json:"status" binding:"required,oneof=active inactive"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要更新的Webhook")
|
||||
return
|
||||
}
|
||||
|
||||
result := database.DB.Model(&model.WebhookConfig{}).
|
||||
Joins("JOIN applications ON webhook_configs.application_id = applications.id").
|
||||
Where("applications.user_id = ? AND webhook_configs.id IN ?", userID, req.IDs).
|
||||
Update("status", req.Status)
|
||||
|
||||
if result.Error != nil {
|
||||
response.Error(c, 500, "批量更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"updated_count": result.RowsAffected})
|
||||
}
|
||||
|
||||
func handleBatchDeleteWebhooks(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要删除的Webhook")
|
||||
return
|
||||
}
|
||||
|
||||
result := database.DB.Where("id IN (?) AND application_id IN (SELECT id FROM applications WHERE user_id = ?)", req.IDs, userID).
|
||||
Delete(&model.WebhookConfig{})
|
||||
|
||||
if result.Error != nil {
|
||||
response.Error(c, 500, "批量删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"deleted_count": result.RowsAffected})
|
||||
}
|
||||
|
||||
func handleUpdateAPIKeyStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required,oneof=active inactive"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var apiKey model.ExtensionAPIKey
|
||||
if err := database.DB.Joins("JOIN applications ON extension_api_keys.application_id = applications.id").
|
||||
Where("extension_api_keys.id = ? AND applications.user_id = ?", id, userID).
|
||||
First(&apiKey).Error; err != nil {
|
||||
response.Error(c, 404, "API密钥不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&apiKey).Update("status", req.Status).Error; err != nil {
|
||||
response.Error(c, 500, "更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, apiKey)
|
||||
}
|
||||
|
||||
func handleBatchUpdateAPIKeyStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
Status string `json:"status" binding:"required,oneof=active inactive"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要更新的API密钥")
|
||||
return
|
||||
}
|
||||
|
||||
result := database.DB.Model(&model.ExtensionAPIKey{}).
|
||||
Joins("JOIN applications ON extension_api_keys.application_id = applications.id").
|
||||
Where("applications.user_id = ? AND extension_api_keys.id IN ?", userID, req.IDs).
|
||||
Update("status", req.Status)
|
||||
|
||||
if result.Error != nil {
|
||||
response.Error(c, 500, "批量更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"updated_count": result.RowsAffected})
|
||||
}
|
||||
|
||||
func handleBatchDeleteAPIKeys(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要删除的API密钥")
|
||||
return
|
||||
}
|
||||
|
||||
result := database.DB.Where("id IN (?) AND application_id IN (SELECT id FROM applications WHERE user_id = ?)", req.IDs, userID).
|
||||
Delete(&model.ExtensionAPIKey{})
|
||||
|
||||
if result.Error != nil {
|
||||
response.Error(c, 500, "批量删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"deleted_count": result.RowsAffected})
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type FinanceRecordResponse struct {
|
||||
ID uint `json:"id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
Type string `json:"type"`
|
||||
UserID uint `json:"user_id"`
|
||||
AppID uint `json:"app_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
Detail string `json:"detail"`
|
||||
Status string `json:"status"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
User *struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
} `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
func SetupFinanceRoutes(r *gin.RouterGroup) {
|
||||
finance := r.Group("/finance")
|
||||
{
|
||||
finance.GET("/stats", handleGetFinanceStatistics)
|
||||
finance.GET("/statistics", handleGetFinanceStatistics)
|
||||
finance.GET("/recharge-records", handleGetRechargeRecords)
|
||||
finance.GET("/consumption-records", handleGetConsumptionRecords)
|
||||
finance.GET("/records", handleGetFinanceRecords)
|
||||
finance.DELETE("/records/:id", handleDeleteFinanceRecord)
|
||||
finance.POST("/records/batch-delete", handleBatchDeleteFinanceRecords)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetFinanceStatistics(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
var appIDs []uint
|
||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
||||
|
||||
if len(appIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"total_income": 0,
|
||||
"total_expense": 0,
|
||||
"net_profit": 0,
|
||||
"monthly_transactions": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var appUserIDs []uint
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Pluck("id", &appUserIDs)
|
||||
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"total_income": 0,
|
||||
"total_expense": 0,
|
||||
"net_profit": 0,
|
||||
"monthly_transactions": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var totalIncome float64
|
||||
var totalExpense float64
|
||||
|
||||
database.DB.Model(&model.RechargeRecord{}).
|
||||
Where("user_id IN ? AND status = ?", appUserIDs, "success").
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Scan(&totalIncome)
|
||||
|
||||
database.DB.Model(&model.ConsumptionRecord{}).
|
||||
Where("user_id IN ? AND status = ?", appUserIDs, "success").
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Scan(&totalExpense)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"total_income": totalIncome,
|
||||
"total_expense": totalExpense,
|
||||
"net_profit": totalIncome - totalExpense,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetRechargeRecords(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
var appIDs []uint
|
||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
||||
|
||||
if len(appIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"records": []model.RechargeRecord{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var appUserIDs []uint
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Pluck("id", &appUserIDs)
|
||||
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"records": []model.RechargeRecord{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var records []model.RechargeRecord
|
||||
query := database.DB.Model(&model.RechargeRecord{}).Preload("AppUser").Where("user_id IN ?", appUserIDs)
|
||||
|
||||
search := c.Query("search")
|
||||
status := c.Query("status")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
if search != "" {
|
||||
query = query.Where("order_no LIKE ? OR card_code LIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
if status != "" && status != "all" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
if startDate != "" {
|
||||
query = query.Where("created_at >= ?", startDate)
|
||||
}
|
||||
if endDate != "" {
|
||||
query = query.Where("created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
|
||||
if err := query.Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
response.Error(c, 500, "获取充值记录失败")
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{
|
||||
"records": records,
|
||||
"total": len(records),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetConsumptionRecords(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
var appIDs []uint
|
||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
||||
|
||||
if len(appIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"records": []model.ConsumptionRecord{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var appUserIDs []uint
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Pluck("id", &appUserIDs)
|
||||
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"records": []model.ConsumptionRecord{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var records []model.ConsumptionRecord
|
||||
query := database.DB.Model(&model.ConsumptionRecord{}).Preload("AppUser").Where("user_id IN ?", appUserIDs)
|
||||
|
||||
search := c.Query("search")
|
||||
recordType := c.Query("type")
|
||||
status := c.Query("status")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
if search != "" {
|
||||
query = query.Where("order_no LIKE ? OR content LIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
if recordType != "" && recordType != "all" {
|
||||
query = query.Where("type = ?", recordType)
|
||||
}
|
||||
if status != "" && status != "all" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
if startDate != "" {
|
||||
query = query.Where("created_at >= ?", startDate)
|
||||
}
|
||||
if endDate != "" {
|
||||
query = query.Where("created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
|
||||
if err := query.Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
response.Error(c, 500, "获取消费记录失败")
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{
|
||||
"records": records,
|
||||
"total": len(records),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetFinanceRecords(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
var appIDs []uint
|
||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
||||
|
||||
if len(appIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"recharge_records": []FinanceRecordResponse{},
|
||||
"consumption_records": []FinanceRecordResponse{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var appUserIDs []uint
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Pluck("id", &appUserIDs)
|
||||
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"recharge_records": []FinanceRecordResponse{},
|
||||
"consumption_records": []FinanceRecordResponse{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
page := c.DefaultQuery("page", "1")
|
||||
pageSize := c.DefaultQuery("page_size", "10")
|
||||
search := c.Query("search")
|
||||
recordType := c.Query("type")
|
||||
status := c.Query("status")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
var rechargeRecords []model.RechargeRecord
|
||||
rechargeQuery := database.DB.Model(&model.RechargeRecord{}).Preload("AppUser").Where("user_id IN ?", appUserIDs)
|
||||
|
||||
if search != "" {
|
||||
rechargeQuery = rechargeQuery.Where("order_no LIKE ? OR card_code LIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
if recordType == "recharge" || recordType == "" || recordType == "all" {
|
||||
if status != "" && status != "all" {
|
||||
rechargeQuery = rechargeQuery.Where("status = ?", status)
|
||||
}
|
||||
if startDate != "" {
|
||||
rechargeQuery = rechargeQuery.Where("created_at >= ?", startDate)
|
||||
}
|
||||
if endDate != "" {
|
||||
rechargeQuery = rechargeQuery.Where("created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
if err := rechargeQuery.Order("created_at DESC").Find(&rechargeRecords).Error; err != nil {
|
||||
response.Error(c, 500, "获取充值记录失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var consumptionRecords []model.ConsumptionRecord
|
||||
consumptionQuery := database.DB.Model(&model.ConsumptionRecord{}).Preload("AppUser").Where("user_id IN ?", appUserIDs)
|
||||
|
||||
if search != "" {
|
||||
consumptionQuery = consumptionQuery.Where("order_no LIKE ? OR content LIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
if recordType == "consumption" || recordType == "" || recordType == "all" {
|
||||
if status != "" && status != "all" {
|
||||
consumptionQuery = consumptionQuery.Where("status = ?", status)
|
||||
}
|
||||
if startDate != "" {
|
||||
consumptionQuery = consumptionQuery.Where("created_at >= ?", startDate)
|
||||
}
|
||||
if endDate != "" {
|
||||
consumptionQuery = consumptionQuery.Where("created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
if err := consumptionQuery.Order("created_at DESC").Find(&consumptionRecords).Error; err != nil {
|
||||
response.Error(c, 500, "获取消费记录失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var allRecords []FinanceRecordResponse
|
||||
for _, r := range rechargeRecords {
|
||||
var user *struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
if r.AppUser != nil && r.AppUser.ID != 0 {
|
||||
user = &struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}{
|
||||
ID: r.AppUser.ID,
|
||||
Username: r.AppUser.Username,
|
||||
Email: r.AppUser.Email,
|
||||
}
|
||||
}
|
||||
allRecords = append(allRecords, FinanceRecordResponse{
|
||||
ID: r.ID,
|
||||
OrderNo: r.OrderNo,
|
||||
Type: "recharge",
|
||||
UserID: r.UserID,
|
||||
AppID: r.AppUser.ApplicationID,
|
||||
Amount: r.Amount,
|
||||
Detail: r.CardCode,
|
||||
Status: r.Status,
|
||||
PaymentType: r.PaymentType,
|
||||
Remark: r.Remark,
|
||||
CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
User: user,
|
||||
})
|
||||
}
|
||||
|
||||
for _, r := range consumptionRecords {
|
||||
var user *struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
if r.AppUser != nil && r.AppUser.ID != 0 {
|
||||
user = &struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}{
|
||||
ID: r.AppUser.ID,
|
||||
Username: r.AppUser.Username,
|
||||
Email: r.AppUser.Email,
|
||||
}
|
||||
}
|
||||
allRecords = append(allRecords, FinanceRecordResponse{
|
||||
ID: r.ID,
|
||||
OrderNo: r.OrderNo,
|
||||
Type: "consumption",
|
||||
UserID: r.UserID,
|
||||
AppID: r.AppUser.ApplicationID,
|
||||
Amount: r.Amount,
|
||||
Detail: r.Content,
|
||||
Status: r.Status,
|
||||
PaymentType: r.PaymentType,
|
||||
Remark: r.Remark,
|
||||
CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
User: user,
|
||||
})
|
||||
}
|
||||
|
||||
total := len(allRecords)
|
||||
start := 0
|
||||
end := total
|
||||
if p, err := parseInt(page); err == nil && p > 0 {
|
||||
if ps, err := parseInt(pageSize); err == nil && ps > 0 {
|
||||
start = (p - 1) * ps
|
||||
end = start + ps
|
||||
if start > total {
|
||||
start = total
|
||||
}
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if start > end {
|
||||
start = end
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"records": allRecords[start:end],
|
||||
"total": total,
|
||||
})
|
||||
}
|
||||
|
||||
func parseInt(s string) (int, error) {
|
||||
var result int
|
||||
_, err := fmt.Sscanf(s, "%d", &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func handleDeleteFinanceRecord(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
recordID := c.Param("id")
|
||||
recordType := c.Query("type")
|
||||
|
||||
var appIDs []uint
|
||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
||||
if len(appIDs) == 0 {
|
||||
response.Error(c, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var appUserIDs []uint
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Pluck("id", &appUserIDs)
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Error(c, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var deletedType string
|
||||
var deletedAmount float64
|
||||
|
||||
if recordType == "recharge" {
|
||||
var record model.RechargeRecord
|
||||
if err := database.DB.Where("id = ? AND user_id IN ?", recordID, appUserIDs).First(&record).Error; err != nil {
|
||||
response.Error(c, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
if err := database.DB.Delete(&record).Error; err != nil {
|
||||
response.Error(c, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
deletedType = "充值记录"
|
||||
deletedAmount = record.Amount
|
||||
} else if recordType == "consumption" {
|
||||
var record model.ConsumptionRecord
|
||||
if err := database.DB.Where("id = ? AND user_id IN ?", recordID, appUserIDs).First(&record).Error; err != nil {
|
||||
response.Error(c, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
if err := database.DB.Delete(&record).Error; err != nil {
|
||||
response.Error(c, 500, "删除失败")
|
||||
return
|
||||
}
|
||||
deletedType = "消费记录"
|
||||
deletedAmount = record.Amount
|
||||
} else {
|
||||
var rechargeRecord model.RechargeRecord
|
||||
var consumptionRecord model.ConsumptionRecord
|
||||
|
||||
if err := database.DB.Where("id = ? AND user_id IN ?", recordID, appUserIDs).First(&rechargeRecord).Error; err == nil {
|
||||
database.DB.Delete(&rechargeRecord)
|
||||
deletedType = "充值记录"
|
||||
deletedAmount = rechargeRecord.Amount
|
||||
service.LogOperation(c, "delete", "finance_record", nil, fmt.Sprintf("删除%s: %.2f", deletedType, deletedAmount), nil)
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id = ? AND user_id IN ?", recordID, appUserIDs).First(&consumptionRecord).Error; err == nil {
|
||||
database.DB.Delete(&consumptionRecord)
|
||||
deletedType = "消费记录"
|
||||
deletedAmount = consumptionRecord.Amount
|
||||
service.LogOperation(c, "delete", "finance_record", nil, fmt.Sprintf("删除%s: %.2f", deletedType, deletedAmount), nil)
|
||||
response.Success(c, nil)
|
||||
return
|
||||
}
|
||||
|
||||
response.Error(c, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "delete", "finance_record", nil, fmt.Sprintf("删除%s: %.2f", deletedType, deletedAmount), nil)
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleBatchDeleteFinanceRecords(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要删除的记录")
|
||||
return
|
||||
}
|
||||
|
||||
var appIDs []uint
|
||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
||||
if len(appIDs) == 0 {
|
||||
response.Error(c, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var appUserIDs []uint
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Pluck("id", &appUserIDs)
|
||||
if len(appUserIDs) == 0 {
|
||||
response.Error(c, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
database.DB.Where("id IN ? AND user_id IN ?", req.IDs, appUserIDs).Delete(&model.RechargeRecord{})
|
||||
database.DB.Where("id IN ? AND user_id IN ?", req.IDs, appUserIDs).Delete(&model.ConsumptionRecord{})
|
||||
|
||||
service.LogOperation(c, "batch_delete", "finance_record", nil, fmt.Sprintf("批量删除财务记录: %d条", len(req.IDs)), nil)
|
||||
|
||||
response.Success(c, gin.H{"deleted": len(req.IDs)})
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupLogRoutes(r *gin.RouterGroup) {
|
||||
logs := r.Group("/logs")
|
||||
{
|
||||
logs.GET("", handleGetLogs)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetLogs(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var appIDs []uint
|
||||
database.DB.Model(&model.Application{}).Where("user_id = ?", userID).Pluck("id", &appIDs)
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
logType := c.Query("type")
|
||||
status := c.Query("status")
|
||||
applicationID := c.Query("application_id")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
search := c.Query("search")
|
||||
|
||||
var logs []model.Log
|
||||
var total int64
|
||||
|
||||
query := database.DB.Model(&model.Log{})
|
||||
|
||||
if len(appIDs) > 0 {
|
||||
query = query.Where("application_id IN ? OR user_id = ?", appIDs, userID)
|
||||
} else {
|
||||
query = query.Where("user_id = ?", userID)
|
||||
}
|
||||
|
||||
if logType != "" && logType != "all" {
|
||||
query = query.Where("log_type = ?", logType)
|
||||
}
|
||||
|
||||
if status != "" && status != "all" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
if applicationID != "" && applicationID != "all" {
|
||||
query = query.Where("application_id = ?", applicationID)
|
||||
}
|
||||
|
||||
if startDate != "" {
|
||||
query = query.Where("created_at >= ?", startDate+" 00:00:00")
|
||||
}
|
||||
|
||||
if endDate != "" {
|
||||
query = query.Where("created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
query = query.Where("action LIKE ? OR details LIKE ? OR resource LIKE ?", "%"+search+"%", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
|
||||
query.Count(&total)
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Preload("User").Preload("Application").Preload("AppUser").Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil {
|
||||
response.Error(c, 500, "获取日志失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"logs": logs,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_page": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupOrderRoutes(r *gin.RouterGroup) {
|
||||
r.GET("/orders", handleGetOrders)
|
||||
r.GET("/orders/:id", handleGetOrder)
|
||||
r.POST("/orders/:id/refund", handleRefundOrder)
|
||||
r.GET("/orders/stats", handleGetOrderStats)
|
||||
}
|
||||
|
||||
func handleGetOrders(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
orderType := c.Query("order_type")
|
||||
status := c.Query("status")
|
||||
applicationID := c.Query("application_id")
|
||||
search := c.Query("search")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
var orders []model.Order
|
||||
var total int64
|
||||
|
||||
query := database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("applications.user_id = ? OR orders.application_id IS NULL", userID)
|
||||
|
||||
if orderType != "" {
|
||||
query = query.Where("orders.order_type = ?", orderType)
|
||||
}
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("orders.status = ?", status)
|
||||
}
|
||||
|
||||
if applicationID != "" && applicationID != "all" {
|
||||
query = query.Where("orders.application_id = ?", applicationID)
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
query = query.Where("orders.order_no LIKE ? OR orders.title LIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
|
||||
if startDate != "" {
|
||||
query = query.Where("orders.created_at >= ?", startDate+" 00:00:00")
|
||||
}
|
||||
|
||||
if endDate != "" {
|
||||
query = query.Where("orders.created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
|
||||
query.Count(&total)
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Preload("User").Preload("Application").
|
||||
Order("orders.created_at DESC").
|
||||
Offset(offset).Limit(pageSize).
|
||||
Find(&orders).Error; err != nil {
|
||||
response.Error(c, 500, "获取订单列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"orders": orders,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_page": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetOrder(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
orderID := c.Param("id")
|
||||
|
||||
var order model.Order
|
||||
if err := database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("orders.id = ? AND (applications.user_id = ? OR orders.application_id IS NULL)", orderID, userID).
|
||||
Preload("User").Preload("Application").
|
||||
First(&order).Error; err != nil {
|
||||
response.Error(c, 404, "订单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, order)
|
||||
}
|
||||
|
||||
func handleRefundOrder(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
orderID := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var order model.Order
|
||||
if err := database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("orders.id = ? AND (applications.user_id = ? OR orders.application_id IS NULL)", orderID, userID).
|
||||
First(&order).Error; err != nil {
|
||||
response.Error(c, 404, "订单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if order.Status != "paid" {
|
||||
response.Error(c, 400, "只能退款已支付的订单")
|
||||
return
|
||||
}
|
||||
|
||||
tx := database.DB.Begin()
|
||||
|
||||
now := time.Now()
|
||||
order.Status = "refunded"
|
||||
order.RefundAt = &now
|
||||
order.RefundReason = req.Reason
|
||||
|
||||
if err := tx.Save(&order).Error; err != nil {
|
||||
tx.Rollback()
|
||||
response.Error(c, 500, "退款失败")
|
||||
return
|
||||
}
|
||||
|
||||
switch order.OrderType {
|
||||
case "card_recharge":
|
||||
var rechargeRecord model.RechargeRecord
|
||||
if err := tx.Where("order_no = ?", order.OrderNo).First(&rechargeRecord).Error; err == nil {
|
||||
rechargeRecord.Status = "refunded"
|
||||
tx.Save(&rechargeRecord)
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "退款成功",
|
||||
"order": order,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetOrderStats(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
applicationID := c.Query("application_id")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
query := database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("applications.user_id = ? OR orders.application_id IS NULL", userID)
|
||||
|
||||
if applicationID != "" && applicationID != "all" {
|
||||
query = query.Where("orders.application_id = ?", applicationID)
|
||||
}
|
||||
|
||||
if startDate != "" {
|
||||
query = query.Where("orders.created_at >= ?", startDate+" 00:00:00")
|
||||
}
|
||||
|
||||
if endDate != "" {
|
||||
query = query.Where("orders.created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
|
||||
var totalOrders, pendingOrders, paidOrders, refundedOrders int64
|
||||
var totalAmount, paidAmount, refundedAmount float64
|
||||
|
||||
query.Count(&totalOrders)
|
||||
|
||||
database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("applications.user_id = ? OR orders.application_id IS NULL", userID).
|
||||
Where("orders.status = ?", "pending").
|
||||
Count(&pendingOrders)
|
||||
|
||||
database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("applications.user_id = ? OR orders.application_id IS NULL", userID).
|
||||
Where("orders.status = ?", "paid").
|
||||
Count(&paidOrders)
|
||||
|
||||
database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("applications.user_id = ? OR orders.application_id IS NULL", userID).
|
||||
Where("orders.status = ?", "refunded").
|
||||
Count(&refundedOrders)
|
||||
|
||||
database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("applications.user_id = ? OR orders.application_id IS NULL", userID).
|
||||
Where("orders.status IN ?", []string{"paid", "refunded"}).
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Scan(&totalAmount)
|
||||
|
||||
database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("applications.user_id = ? OR orders.application_id IS NULL", userID).
|
||||
Where("orders.status = ?", "paid").
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Scan(&paidAmount)
|
||||
|
||||
database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("applications.user_id = ? OR orders.application_id IS NULL", userID).
|
||||
Where("orders.status = ?", "refunded").
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Scan(&refundedAmount)
|
||||
|
||||
var typeStats []struct {
|
||||
OrderType string
|
||||
Count int64
|
||||
TotalAmount float64
|
||||
}
|
||||
|
||||
database.DB.Model(&model.Order{}).
|
||||
Joins("LEFT JOIN applications ON orders.application_id = applications.id").
|
||||
Where("applications.user_id = ? OR orders.application_id IS NULL", userID).
|
||||
Select("order_type, COUNT(*) as count, COALESCE(SUM(amount), 0) as total_amount").
|
||||
Group("order_type").
|
||||
Scan(&typeStats)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"total_orders": totalOrders,
|
||||
"pending_orders": pendingOrders,
|
||||
"paid_orders": paidOrders,
|
||||
"refunded_orders": refundedOrders,
|
||||
"total_amount": totalAmount,
|
||||
"paid_amount": paidAmount,
|
||||
"refunded_amount": refundedAmount,
|
||||
"type_stats": typeStats,
|
||||
})
|
||||
}
|
||||
|
||||
func CreateOrder(orderType string, userID uint, applicationID *uint, title string, amount float64, paymentType string, description string) (*model.Order, error) {
|
||||
orderNo := fmt.Sprintf("ORD%d%d", time.Now().Unix(), userID)
|
||||
|
||||
order := model.Order{
|
||||
OrderNo: orderNo,
|
||||
UserID: userID,
|
||||
ApplicationID: applicationID,
|
||||
OrderType: orderType,
|
||||
Title: title,
|
||||
Amount: amount,
|
||||
PaymentType: paymentType,
|
||||
Status: "pending",
|
||||
Description: description,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&order).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &order, nil
|
||||
}
|
||||
|
||||
func PayOrder(orderNo string) error {
|
||||
var order model.Order
|
||||
if err := database.DB.Where("order_no = ?", orderNo).First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
order.Status = "paid"
|
||||
order.PaymentAt = &now
|
||||
|
||||
return database.DB.Save(&order).Error
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func SetupProfileRoutes(r *gin.RouterGroup) {
|
||||
profile := r.Group("/profile")
|
||||
{
|
||||
profile.GET("", handleGetProfile)
|
||||
profile.PUT("", handleUpdateProfile)
|
||||
profile.PUT("/password", handleChangePassword)
|
||||
profile.POST("/avatar", handleUploadAvatar)
|
||||
profile.POST("/api-token", handleGenerateApiToken)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetProfile(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
profile := struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Avatar string `json:"avatar"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
ApiToken string `json:"api_token"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
}{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: "",
|
||||
Phone: "",
|
||||
Avatar: user.Avatar,
|
||||
Role: user.Role,
|
||||
Status: user.Status,
|
||||
ApiToken: user.ApiToken,
|
||||
CreatedAt: user.CreatedAt,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
}
|
||||
|
||||
if user.Email != nil {
|
||||
profile.Email = *user.Email
|
||||
}
|
||||
|
||||
subscription := getSubscriptionInfo(&user)
|
||||
|
||||
transactions := getRecentTransactions(userID)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"user": profile,
|
||||
"subscription": subscription,
|
||||
"transactions": transactions,
|
||||
})
|
||||
}
|
||||
|
||||
func getSubscriptionInfo(user *model.User) gin.H {
|
||||
var pkg model.Package
|
||||
var packagePermission model.PackagePermission
|
||||
|
||||
defaultQuota := 10000
|
||||
defaultStorage := int64(100 * 1024 * 1024)
|
||||
|
||||
if user.CurrentPackageID != nil {
|
||||
if err := database.DB.First(&pkg, *user.CurrentPackageID).Error; err == nil {
|
||||
database.DB.Where("package_id = ?", pkg.ID).First(&packagePermission)
|
||||
}
|
||||
}
|
||||
|
||||
planName := "基础版"
|
||||
if pkg.ID > 0 {
|
||||
planName = pkg.Name
|
||||
}
|
||||
|
||||
apiQuota := defaultQuota
|
||||
if packagePermission.MaxApiCalls > 0 {
|
||||
apiQuota = packagePermission.MaxApiCalls
|
||||
}
|
||||
|
||||
storageQuota := defaultStorage
|
||||
if packagePermission.MaxStorage > 0 {
|
||||
storageQuota = int64(packagePermission.MaxStorage) * 1024 * 1024
|
||||
}
|
||||
|
||||
var expireDate string
|
||||
if pkg.Period == "monthly" {
|
||||
expireDate = time.Now().AddDate(0, 1, 0).Format("2006-01-02")
|
||||
} else if pkg.Period == "yearly" {
|
||||
expireDate = time.Now().AddDate(1, 0, 0).Format("2006-01-02")
|
||||
} else {
|
||||
expireDate = "永久有效"
|
||||
}
|
||||
|
||||
return gin.H{
|
||||
"plan": planName,
|
||||
"status": "active",
|
||||
"expire_date": expireDate,
|
||||
"api_quota": apiQuota,
|
||||
"api_used": user.ApiCallsUsed,
|
||||
"storage_quota": storageQuota,
|
||||
"storage_used": user.StorageUsed,
|
||||
}
|
||||
}
|
||||
|
||||
func getRecentTransactions(userID uint) []gin.H {
|
||||
var orders []model.Order
|
||||
database.DB.Where("user_id = ? AND status = ?", userID, "paid").
|
||||
Order("created_at DESC").
|
||||
Limit(5).
|
||||
Find(&orders)
|
||||
|
||||
transactions := make([]gin.H, 0, len(orders))
|
||||
for _, order := range orders {
|
||||
txType := "consume"
|
||||
if order.OrderType == "user_recharge" || order.OrderType == "card_recharge" {
|
||||
txType = "recharge"
|
||||
} else if order.OrderType == "refund" {
|
||||
txType = "refund"
|
||||
}
|
||||
|
||||
transactions = append(transactions, gin.H{
|
||||
"id": order.ID,
|
||||
"type": txType,
|
||||
"amount": order.Amount,
|
||||
"description": order.Title,
|
||||
"created_at": order.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return transactions
|
||||
}
|
||||
|
||||
type UpdateProfileRequest struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
func handleUpdateProfile(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req UpdateProfileRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Username == "" {
|
||||
response.Error(c, 400, "用户名不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" {
|
||||
response.Error(c, 400, "邮箱不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var existingUser model.User
|
||||
if err := database.DB.Where("username = ? AND id != ?", req.Username, userID).First(&existingUser).Error; err == nil {
|
||||
response.Error(c, 400, "用户名已被使用")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Where("email = ? AND id != ?", req.Email, userID).First(&existingUser).Error; err == nil {
|
||||
response.Error(c, 400, "邮箱已被使用")
|
||||
return
|
||||
}
|
||||
|
||||
user.Username = req.Username
|
||||
email := req.Email
|
||||
user.Email = &email
|
||||
|
||||
if err := database.DB.Save(&user).Error; err != nil {
|
||||
log.Printf("更新用户信息失败: %v", err)
|
||||
response.Error(c, 500, "更新用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"user": gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"role": user.Role,
|
||||
"status": user.Status,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type ChangePasswordRequest struct {
|
||||
CurrentPassword string `json:"current_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
func handleChangePassword(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req ChangePasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.CurrentPassword == "" {
|
||||
response.Error(c, 400, "请输入当前密码")
|
||||
return
|
||||
}
|
||||
|
||||
if req.NewPassword == "" {
|
||||
response.Error(c, 400, "请输入新密码")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.NewPassword) < 6 {
|
||||
response.Error(c, 400, "密码长度至少6位")
|
||||
return
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.CurrentPassword)); err != nil {
|
||||
response.Error(c, 400, "当前密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Printf("密码加密失败: %v", err)
|
||||
response.Error(c, 500, "密码加密失败")
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = string(hashedPassword)
|
||||
if err := database.DB.Save(&user).Error; err != nil {
|
||||
log.Printf("更新密码失败: %v", err)
|
||||
response.Error(c, 500, "更新密码失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"message": "密码修改成功"})
|
||||
}
|
||||
|
||||
func handleUploadAvatar(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
file, header, err := c.Request.FormFile("avatar")
|
||||
if err != nil {
|
||||
response.Error(c, 400, "请选择要上传的文件")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(header.Filename))
|
||||
allowedExts := map[string]bool{
|
||||
".jpg": true,
|
||||
".jpeg": true,
|
||||
".png": true,
|
||||
".gif": true,
|
||||
".webp": true,
|
||||
}
|
||||
|
||||
if !allowedExts[ext] {
|
||||
response.Error(c, 400, "不支持的文件格式,仅支持 JPG、PNG、GIF、WEBP")
|
||||
return
|
||||
}
|
||||
|
||||
const maxSize = 2 * 1024 * 1024
|
||||
if header.Size > maxSize {
|
||||
response.Error(c, 400, "文件大小不能超过2MB")
|
||||
return
|
||||
}
|
||||
|
||||
uploadDir := "uploads/avatars"
|
||||
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||||
log.Printf("创建上传目录失败: %v", err)
|
||||
response.Error(c, 500, "创建上传目录失败")
|
||||
return
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%d_%d%s", userID, time.Now().UnixNano(), ext)
|
||||
filePath := filepath.Join(uploadDir, filename)
|
||||
|
||||
dst, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
log.Printf("创建文件失败: %v", err)
|
||||
response.Error(c, 500, "创建文件失败")
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err := io.Copy(dst, file); err != nil {
|
||||
log.Printf("保存文件失败: %v", err)
|
||||
response.Error(c, 500, "保存文件失败")
|
||||
return
|
||||
}
|
||||
|
||||
avatarURL := "/uploads/avatars/" + filename
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if user.Avatar != "" && strings.HasPrefix(user.Avatar, "/uploads/avatars/") {
|
||||
oldPath := "." + user.Avatar
|
||||
if _, err := os.Stat(oldPath); err == nil {
|
||||
os.Remove(oldPath)
|
||||
}
|
||||
}
|
||||
|
||||
user.Avatar = avatarURL
|
||||
if err := database.DB.Save(&user).Error; err != nil {
|
||||
log.Printf("更新头像失败: %v", err)
|
||||
response.Error(c, 500, "更新头像失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"avatar": avatarURL})
|
||||
}
|
||||
|
||||
func handleGenerateApiToken(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
bytes := make([]byte, 32)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
log.Printf("生成API Token失败: %v", err)
|
||||
response.Error(c, 500, "生成API Token失败")
|
||||
return
|
||||
}
|
||||
apiToken := hex.EncodeToString(bytes)
|
||||
|
||||
user.ApiToken = apiToken
|
||||
if err := database.DB.Save(&user).Error; err != nil {
|
||||
log.Printf("保存API Token失败: %v", err)
|
||||
response.Error(c, 500, "保存API Token失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"api_token": apiToken,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupTicketRoutes(r *gin.RouterGroup) {
|
||||
tickets := r.Group("/tickets")
|
||||
{
|
||||
tickets.GET("/stats", handleGetTicketStats)
|
||||
tickets.GET("", handleGetTickets)
|
||||
tickets.GET("/:id", handleGetTicket)
|
||||
tickets.POST("", handleCreateTicket)
|
||||
tickets.PUT("/:id", handleUpdateTicket)
|
||||
tickets.PUT("/:id/status", handleUpdateTicketStatus)
|
||||
tickets.DELETE("/:id", handleDeleteTicket)
|
||||
tickets.DELETE("/batch", handleBatchDeleteTickets)
|
||||
tickets.GET("/:id/replies", handleGetTicketReplies)
|
||||
tickets.POST("/:id/replies", handleCreateTicketReply)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetTicketStats(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var total, open, processing, resolved, closed int64
|
||||
|
||||
database.DB.Model(&model.Ticket{}).Where("user_id = ? OR assigned_to = ?", userID, userID).Count(&total)
|
||||
database.DB.Model(&model.Ticket{}).Where("(user_id = ? OR assigned_to = ?) AND status = ?", userID, userID, "open").Count(&open)
|
||||
database.DB.Model(&model.Ticket{}).Where("(user_id = ? OR assigned_to = ?) AND status = ?", userID, userID, "processing").Count(&processing)
|
||||
database.DB.Model(&model.Ticket{}).Where("(user_id = ? OR assigned_to = ?) AND status = ?", userID, userID, "resolved").Count(&resolved)
|
||||
database.DB.Model(&model.Ticket{}).Where("(user_id = ? OR assigned_to = ?) AND status = ?", userID, userID, "closed").Count(&closed)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"total_count": total,
|
||||
"open_count": open,
|
||||
"processing_count": processing,
|
||||
"resolved_count": resolved,
|
||||
"closed_count": closed,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetTickets(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var tickets []model.Ticket
|
||||
if err := database.DB.Where("user_id = ? OR assigned_to = ?", userID, userID).
|
||||
Preload("Replies").
|
||||
Preload("Application").
|
||||
Preload("User").
|
||||
Preload("AssignedUser").
|
||||
Find(&tickets).Error; err != nil {
|
||||
response.Error(c, 500, "获取工单列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
type TicketWithNames struct {
|
||||
model.Ticket
|
||||
AppName string `json:"app_name"`
|
||||
UserName string `json:"user_name"`
|
||||
}
|
||||
|
||||
var result []TicketWithNames
|
||||
for _, ticket := range tickets {
|
||||
appName := ""
|
||||
if ticket.Application != nil {
|
||||
appName = ticket.Application.Name
|
||||
}
|
||||
userName := ""
|
||||
if ticket.User.ID != 0 {
|
||||
userName = ticket.User.Username
|
||||
}
|
||||
result = append(result, TicketWithNames{
|
||||
Ticket: ticket,
|
||||
AppName: appName,
|
||||
UserName: userName,
|
||||
})
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"tickets": result,
|
||||
"total": len(result),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetTicket(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
var ticket model.Ticket
|
||||
if err := database.DB.Where("id = ? AND (user_id = ? OR assigned_to = ?)", id, userID, userID).
|
||||
Preload("Application").
|
||||
Preload("User").
|
||||
Preload("AssignedUser").
|
||||
First(&ticket).Error; err != nil {
|
||||
response.Error(c, 404, "工单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
type TicketWithNames struct {
|
||||
model.Ticket
|
||||
AppName string `json:"app_name"`
|
||||
UserName string `json:"user_name"`
|
||||
}
|
||||
|
||||
appName := ""
|
||||
if ticket.Application != nil {
|
||||
appName = ticket.Application.Name
|
||||
}
|
||||
userName := ""
|
||||
if ticket.User.ID != 0 {
|
||||
userName = ticket.User.Username
|
||||
}
|
||||
|
||||
result := TicketWithNames{
|
||||
Ticket: ticket,
|
||||
AppName: appName,
|
||||
UserName: userName,
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"ticket": result,
|
||||
})
|
||||
}
|
||||
|
||||
func handleCreateTicket(c *gin.Context) {
|
||||
log.Printf("handleCreateTicket called\n")
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
log.Printf("Starting ParseMultipartForm...\n")
|
||||
if err := c.Request.ParseMultipartForm(32 << 20); err != nil {
|
||||
log.Printf("ParseMultipartForm error: %v\n", err)
|
||||
response.Error(c, 400, "解析表单数据失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
log.Printf("ParseMultipartForm succeeded\n")
|
||||
|
||||
ticketType := c.PostForm("type")
|
||||
title := c.PostForm("title")
|
||||
content := c.PostForm("content")
|
||||
priority := c.PostForm("priority")
|
||||
applicationIDStr := c.PostForm("application_id")
|
||||
|
||||
log.Printf("Received ticket data - type: '%s', title: '%s', content: '%s', priority: '%s', application_id: '%s'\n",
|
||||
ticketType, title, content, priority, applicationIDStr)
|
||||
|
||||
if ticketType == "" {
|
||||
ticketType = "system"
|
||||
}
|
||||
|
||||
if title == "" {
|
||||
log.Printf("Title is empty\n")
|
||||
response.Error(c, 400, "标题不能为空")
|
||||
return
|
||||
}
|
||||
if content == "" {
|
||||
log.Printf("Content is empty\n")
|
||||
response.Error(c, 400, "描述不能为空")
|
||||
return
|
||||
}
|
||||
if priority == "" {
|
||||
priority = "normal"
|
||||
}
|
||||
|
||||
var applicationID *uint
|
||||
if ticketType == "application" && applicationIDStr != "" {
|
||||
var appID uint
|
||||
if _, err := fmt.Sscanf(applicationIDStr, "%d", &appID); err == nil {
|
||||
applicationID = &appID
|
||||
}
|
||||
}
|
||||
|
||||
ticket := model.Ticket{
|
||||
UserID: userID,
|
||||
ApplicationID: applicationID,
|
||||
Title: title,
|
||||
Content: content,
|
||||
Category: "other",
|
||||
Priority: priority,
|
||||
Status: "open",
|
||||
Type: ticketType,
|
||||
}
|
||||
|
||||
if applicationID != nil {
|
||||
var application model.Application
|
||||
if err := database.DB.First(&application, *applicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
ticket.AssignedTo = &application.UserID
|
||||
}
|
||||
|
||||
log.Printf("Creating ticket in database...\n")
|
||||
if err := database.DB.Create(&ticket).Error; err != nil {
|
||||
log.Printf("Create ticket error: %v\n", err)
|
||||
response.Error(c, 500, "创建工单失败")
|
||||
return
|
||||
}
|
||||
log.Printf("Ticket created with ID: %d\n", ticket.ID)
|
||||
|
||||
service.LogOperation(c, "create", "ticket", &ticket.ID, fmt.Sprintf("创建工单: %s", ticket.Title), nil)
|
||||
|
||||
log.Printf("Loading ticket details...\n")
|
||||
if err := database.DB.Preload("User").Preload("Application").First(&ticket, ticket.ID).Error; err != nil {
|
||||
response.Error(c, 500, "获取工单信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
type TicketWithNames struct {
|
||||
model.Ticket
|
||||
AppName string `json:"app_name"`
|
||||
UserName string `json:"user_name"`
|
||||
}
|
||||
|
||||
appName := ""
|
||||
if ticket.Application != nil {
|
||||
appName = ticket.Application.Name
|
||||
}
|
||||
userName := ""
|
||||
if ticket.User.ID != 0 {
|
||||
userName = ticket.User.Username
|
||||
}
|
||||
|
||||
result := TicketWithNames{
|
||||
Ticket: ticket,
|
||||
AppName: appName,
|
||||
UserName: userName,
|
||||
}
|
||||
|
||||
log.Printf("Returning success response\n")
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
func handleUpdateTicket(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
var ticket model.Ticket
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&ticket).Error; err != nil {
|
||||
response.Error(c, 404, "工单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
ticket.Status = req.Status
|
||||
|
||||
if err := database.DB.Save(&ticket).Error; err != nil {
|
||||
response.Error(c, 500, "更新工单失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, ticket)
|
||||
}
|
||||
|
||||
func handleUpdateTicketStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
var ticket model.Ticket
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&ticket).Error; err != nil {
|
||||
response.Error(c, 404, "工单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
ticket.Status = req.Status
|
||||
|
||||
if err := database.DB.Save(&ticket).Error; err != nil {
|
||||
response.Error(c, 500, "更新工单状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, ticket)
|
||||
}
|
||||
|
||||
func handleDeleteTicket(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var ticket model.Ticket
|
||||
if err := database.DB.Where("id = ? AND (user_id = ? OR assigned_to = ?)", id, userID, userID).First(&ticket).Error; err != nil {
|
||||
response.Error(c, 404, "工单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&ticket).Error; err != nil {
|
||||
response.Error(c, 500, "删除工单失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "delete", "ticket", &ticket.ID, fmt.Sprintf("删除工单: %s", ticket.Title), nil)
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleBatchDeleteTickets(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要删除的工单")
|
||||
return
|
||||
}
|
||||
|
||||
var tickets []model.Ticket
|
||||
if err := database.DB.Where("id IN ? AND (user_id = ? OR assigned_to = ?)", req.IDs, userID, userID).Find(&tickets).Error; err != nil {
|
||||
response.Error(c, 500, "查询工单失败")
|
||||
return
|
||||
}
|
||||
|
||||
if len(tickets) == 0 {
|
||||
response.Error(c, 404, "没有找到可删除的工单")
|
||||
return
|
||||
}
|
||||
|
||||
var validIDs []uint
|
||||
for _, ticket := range tickets {
|
||||
validIDs = append(validIDs, ticket.ID)
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id IN ?", validIDs).Delete(&model.Ticket{}).Error; err != nil {
|
||||
response.Error(c, 500, "批量删除工单失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"deleted": len(validIDs),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetTicketReplies(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
ticketID := c.Param("id")
|
||||
|
||||
var ticket model.Ticket
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", ticketID, userID).First(&ticket).Error; err != nil {
|
||||
response.Error(c, 404, "工单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var replies []model.TicketReply
|
||||
if err := database.DB.Where("ticket_id = ?", ticketID).Preload("User").Order("created_at ASC").Find(&replies).Error; err != nil {
|
||||
response.Error(c, 500, "获取回复列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"replies": replies,
|
||||
"total": len(replies),
|
||||
})
|
||||
}
|
||||
|
||||
func handleCreateTicketReply(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
ticketID := c.Param("id")
|
||||
|
||||
var ticket model.Ticket
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", ticketID, userID).First(&ticket).Error; err != nil {
|
||||
response.Error(c, 404, "工单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
reply := model.TicketReply{
|
||||
TicketID: parseUint(ticketID),
|
||||
UserID: userID,
|
||||
Content: req.Content,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&reply).Error; err != nil {
|
||||
response.Error(c, 500, "创建回复失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, reply)
|
||||
}
|
||||
|
||||
func parseUint(s string) uint {
|
||||
var val uint
|
||||
fmt.Sscanf(s, "%d", &val)
|
||||
return val
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupUsageRoutes(r *gin.RouterGroup) {
|
||||
usage := r.Group("/usage")
|
||||
{
|
||||
usage.GET("/stats", handleGetUsageStats)
|
||||
usage.GET("/api-history", handleGetApiUsageHistory)
|
||||
usage.GET("/storage-history", handleGetStorageUsageHistory)
|
||||
usage.GET("/alerts", handleGetUsageAlerts)
|
||||
usage.GET("/notifications", handleGetNotifications)
|
||||
usage.PUT("/notifications/:id/read", handleMarkNotificationAsRead)
|
||||
usage.GET("/notifications/unread-count", handleGetUnreadNotificationCount)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetUsageStats(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Preload("CurrentPackage").First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
var permission *model.PackagePermission
|
||||
if user.CurrentPackageID != nil {
|
||||
var perm model.PackagePermission
|
||||
if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&perm).Error; err == nil {
|
||||
permission = &perm
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
|
||||
var todayApiCalls int64
|
||||
database.DB.Model(&model.ApiUsage{}).
|
||||
Where("user_id = ? AND created_at >= ?", userID, today).
|
||||
Count(&todayApiCalls)
|
||||
|
||||
var last30DaysApiCalls int64
|
||||
database.DB.Model(&model.ApiUsage{}).
|
||||
Where("user_id = ? AND created_at >= ?", userID, now.AddDate(0, 0, -30)).
|
||||
Count(&last30DaysApiCalls)
|
||||
|
||||
var totalApiCalls int64
|
||||
database.DB.Model(&model.ApiUsage{}).
|
||||
Where("user_id = ?", userID).
|
||||
Count(&totalApiCalls)
|
||||
|
||||
storageUsedMB := float64(user.StorageUsed) / 1024 / 1024
|
||||
maxStorageMB := 0.0
|
||||
if permission != nil {
|
||||
maxStorageMB = float64(permission.MaxStorage)
|
||||
}
|
||||
|
||||
usageData := gin.H{
|
||||
"api_calls": gin.H{
|
||||
"today": todayApiCalls,
|
||||
"last_30_days": last30DaysApiCalls,
|
||||
"total": totalApiCalls,
|
||||
"limit": 0,
|
||||
"used_today": user.ApiCallsUsed,
|
||||
"reset_at": user.ApiCallsResetAt,
|
||||
},
|
||||
"storage": gin.H{
|
||||
"used_mb": storageUsedMB,
|
||||
"max_mb": maxStorageMB,
|
||||
"used_bytes": user.StorageUsed,
|
||||
"max_bytes": int64(maxStorageMB * 1024 * 1024),
|
||||
"usage_percent": 0.0,
|
||||
},
|
||||
"package": gin.H{
|
||||
"id": nil,
|
||||
"name": nil,
|
||||
"expired_at": nil,
|
||||
},
|
||||
}
|
||||
|
||||
if permission != nil {
|
||||
usageData["api_calls"].(gin.H)["limit"] = permission.MaxApiCalls
|
||||
if maxStorageMB > 0 {
|
||||
usageData["storage"].(gin.H)["usage_percent"] = (storageUsedMB / maxStorageMB) * 100
|
||||
}
|
||||
}
|
||||
|
||||
if user.CurrentPackage != nil {
|
||||
usageData["package"].(gin.H)["id"] = user.CurrentPackage.ID
|
||||
usageData["package"].(gin.H)["name"] = user.CurrentPackage.Name
|
||||
|
||||
var userPackage model.UserPackage
|
||||
if err := database.DB.Where("user_id = ? AND package_id = ? AND status = ?",
|
||||
userID, user.CurrentPackageID, "active").
|
||||
First(&userPackage).Error; err == nil {
|
||||
usageData["package"].(gin.H)["expired_at"] = userPackage.ExpiredAt
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, usageData)
|
||||
}
|
||||
|
||||
func handleGetApiUsageHistory(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
page := 1
|
||||
pageSize := 20
|
||||
if p, ok := c.GetQuery("page"); ok {
|
||||
fmt.Sscanf(p, "%d", &page)
|
||||
}
|
||||
if ps, ok := c.GetQuery("page_size"); ok {
|
||||
fmt.Sscanf(ps, "%d", &pageSize)
|
||||
}
|
||||
|
||||
var total int64
|
||||
database.DB.Model(&model.ApiUsage{}).Where("user_id = ?", userID).Count(&total)
|
||||
|
||||
var usages []model.ApiUsage
|
||||
offset := (page - 1) * pageSize
|
||||
if err := database.DB.Where("user_id = ?", userID).
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&usages).Error; err != nil {
|
||||
response.Error(c, 500, "获取API调用历史失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"list": usages,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetUsageAlerts(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
alertService := service.NewUsageAlertService()
|
||||
alerts, err := alertService.CheckAndCreateAlerts(userID.(uint))
|
||||
if err != nil {
|
||||
response.Error(c, 500, "检查用量告警失败")
|
||||
return
|
||||
}
|
||||
|
||||
history, err := alertService.GetUserAlerts(userID.(uint), 20)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "获取告警历史失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"current_alerts": alerts,
|
||||
"history": history,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetNotifications(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
page := 1
|
||||
pageSize := 20
|
||||
if p, ok := c.GetQuery("page"); ok {
|
||||
fmt.Sscanf(p, "%d", &page)
|
||||
}
|
||||
if ps, ok := c.GetQuery("page_size"); ok {
|
||||
fmt.Sscanf(ps, "%d", &pageSize)
|
||||
}
|
||||
|
||||
var total int64
|
||||
database.DB.Model(&model.Notification{}).Where("user_id = ?", userID).Count(&total)
|
||||
|
||||
var notifications []model.Notification
|
||||
offset := (page - 1) * pageSize
|
||||
if err := database.DB.Where("user_id = ?", userID).
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(¬ifications).Error; err != nil {
|
||||
response.Error(c, 500, "获取通知失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"list": notifications,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
func handleMarkNotificationAsRead(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
notificationID := c.Param("id")
|
||||
|
||||
var notification model.Notification
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", notificationID, userID).First(¬ification).Error; err != nil {
|
||||
response.Error(c, 404, "通知不存在")
|
||||
return
|
||||
}
|
||||
|
||||
notification.IsRead = true
|
||||
if err := database.DB.Save(¬ification).Error; err != nil {
|
||||
response.Error(c, 500, "标记通知失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleGetUnreadNotificationCount(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
var count int64
|
||||
database.DB.Model(&model.Notification{}).
|
||||
Where("user_id = ? AND is_read = ?", userID, false).
|
||||
Count(&count)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"count": count,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetStorageUsageHistory(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
page := 1
|
||||
pageSize := 20
|
||||
if p, ok := c.GetQuery("page"); ok {
|
||||
fmt.Sscanf(p, "%d", &page)
|
||||
}
|
||||
if ps, ok := c.GetQuery("page_size"); ok {
|
||||
fmt.Sscanf(ps, "%d", &pageSize)
|
||||
}
|
||||
|
||||
var total int64
|
||||
database.DB.Model(&model.StorageUsage{}).Where("user_id = ?", userID).Count(&total)
|
||||
|
||||
var usages []model.StorageUsage
|
||||
offset := (page - 1) * pageSize
|
||||
if err := database.DB.Where("user_id = ?", userID).
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&usages).Error; err != nil {
|
||||
response.Error(c, 500, "获取存储使用历史失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"list": usages,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,673 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type UserWithStatus struct {
|
||||
model.AppUser
|
||||
OnlineStatus string `json:"online_status"`
|
||||
DeviceCount int `json:"device_count"`
|
||||
}
|
||||
|
||||
func getUserOnlineStatus(user model.AppUser, heartbeatTimeout int) string {
|
||||
if user.Status == "banned" {
|
||||
return "banned"
|
||||
}
|
||||
|
||||
if user.LastHeartbeatAt == nil {
|
||||
return "offline"
|
||||
}
|
||||
|
||||
offlineThreshold := time.Duration(heartbeatTimeout) * time.Second
|
||||
if time.Since(*user.LastHeartbeatAt) > offlineThreshold {
|
||||
return "offline"
|
||||
}
|
||||
|
||||
return "online"
|
||||
}
|
||||
|
||||
func SetupUserRoutes(r *gin.RouterGroup) {
|
||||
appUsers := r.Group("/app-users")
|
||||
{
|
||||
appUsers.GET("", handleGetUsers)
|
||||
appUsers.POST("", handleCreateUser)
|
||||
appUsers.GET("/:id", handleGetUser)
|
||||
appUsers.PUT("/:id", handleUpdateUser)
|
||||
appUsers.DELETE("/:id", handleDeleteUser)
|
||||
appUsers.GET("/:id/devices", handleGetUserDevices)
|
||||
appUsers.DELETE("/:id/devices/:deviceId", handleUnbindDevice)
|
||||
appUsers.PUT("/:id/expiry", handleUpdateExpiry)
|
||||
appUsers.POST("/batch/status", handleBatchUpdateStatus)
|
||||
appUsers.DELETE("/batch", handleBatchDelete)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetUsers(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
log.Printf("[DEBUG] handleGetUsers called, userID: %d", userID)
|
||||
|
||||
var users []model.AppUser
|
||||
var appHeartbeatTimeoutMap map[uint]int
|
||||
|
||||
applicationID := c.Query("application_id")
|
||||
if applicationID != "" {
|
||||
var appID uint
|
||||
if _, err := fmt.Sscanf(applicationID, "%d", &appID); err != nil {
|
||||
response.Error(c, 400, "应用ID格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, appID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
appHeartbeatTimeoutMap = make(map[uint]int)
|
||||
timeout := app.HeartbeatTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 300
|
||||
}
|
||||
appHeartbeatTimeoutMap[app.ID] = timeout
|
||||
|
||||
if app.UserID == userID {
|
||||
if err := database.DB.Preload("Application").Where("application_id = ?", appID).Find(&users).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户列表失败")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", appID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限查看该应用的用户")
|
||||
return
|
||||
}
|
||||
|
||||
var cardUserIDs []uint
|
||||
if err := database.DB.Model(&model.Card{}).
|
||||
Where("creator_id = ? AND application_id = ? AND app_user_id IS NOT NULL", userID, appID).
|
||||
Pluck("app_user_id", &cardUserIDs).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
if len(cardUserIDs) > 0 {
|
||||
if err := database.DB.Preload("Application").Where("id IN ?", cardUserIDs).Find(&users).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户列表失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var ownApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&ownApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var agentApps []model.AgentApplication
|
||||
if err := database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps).Error; err != nil {
|
||||
log.Printf("[DEBUG] Failed to get agent apps: %v", err)
|
||||
response.Error(c, 500, "获取授权列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Found %d agent apps for user %d", len(agentApps), userID)
|
||||
|
||||
appHeartbeatTimeoutMap = make(map[uint]int)
|
||||
|
||||
for _, app := range ownApps {
|
||||
timeout := app.HeartbeatTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 300
|
||||
}
|
||||
appHeartbeatTimeoutMap[app.ID] = timeout
|
||||
|
||||
var appUsers []model.AppUser
|
||||
if err := database.DB.Preload("Application").Where("application_id = ?", app.ID).Find(&appUsers).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户列表失败")
|
||||
return
|
||||
}
|
||||
users = append(users, appUsers...)
|
||||
}
|
||||
|
||||
for _, agentApp := range agentApps {
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, agentApp.ApplicationID).Error; err != nil {
|
||||
log.Printf("[DEBUG] Failed to get application %d: %v", agentApp.ApplicationID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Processing agent app: ApplicationID=%d, AppName=%s", app.ID, app.Name)
|
||||
|
||||
timeout := app.HeartbeatTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 300
|
||||
}
|
||||
appHeartbeatTimeoutMap[app.ID] = timeout
|
||||
|
||||
var cardUserIDs []uint
|
||||
if err := database.DB.Model(&model.Card{}).
|
||||
Where("creator_id = ? AND application_id = ? AND app_user_id IS NOT NULL", userID, app.ID).
|
||||
Pluck("app_user_id", &cardUserIDs).Error; err != nil {
|
||||
log.Printf("[DEBUG] Failed to get card user IDs for app %d: %v", app.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Found %d card user IDs for app %d: %v", len(cardUserIDs), app.ID, cardUserIDs)
|
||||
|
||||
if len(cardUserIDs) > 0 {
|
||||
var appUsers []model.AppUser
|
||||
if err := database.DB.Preload("Application").Where("id IN ?", cardUserIDs).Find(&appUsers).Error; err != nil {
|
||||
log.Printf("[DEBUG] Failed to get users for app %d: %v", app.ID, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("[DEBUG] Found %d users for app %d", len(appUsers), app.ID)
|
||||
users = append(users, appUsers...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalCount := len(users)
|
||||
onlineCount := 0
|
||||
offlineCount := 0
|
||||
bannedCount := 0
|
||||
|
||||
usersWithStatus := make([]UserWithStatus, 0, len(users))
|
||||
for _, user := range users {
|
||||
log.Printf("[DEBUG] User ID=%d, Username=%s, LastLoginAt=%v, LastHeartbeatAt=%v", user.ID, user.Username, user.LastLoginAt, user.LastHeartbeatAt)
|
||||
|
||||
heartbeatTimeout := 300
|
||||
if applicationID != "" {
|
||||
heartbeatTimeout = appHeartbeatTimeoutMap[user.ApplicationID]
|
||||
} else if appHeartbeatTimeoutMap != nil {
|
||||
heartbeatTimeout = appHeartbeatTimeoutMap[user.ApplicationID]
|
||||
}
|
||||
|
||||
onlineStatus := getUserOnlineStatus(user, heartbeatTimeout)
|
||||
|
||||
switch onlineStatus {
|
||||
case "online":
|
||||
onlineCount++
|
||||
case "offline":
|
||||
offlineCount++
|
||||
case "banned":
|
||||
bannedCount++
|
||||
}
|
||||
|
||||
var deviceCount int64
|
||||
database.DB.Model(&model.UserDevice{}).Where("user_id = ?", user.ID).Count(&deviceCount)
|
||||
|
||||
log.Printf("[DEBUG] 用户 %s (ID=%d) 余额: %f", user.Username, user.ID, user.Balance)
|
||||
|
||||
usersWithStatus = append(usersWithStatus, UserWithStatus{
|
||||
AppUser: user,
|
||||
OnlineStatus: onlineStatus,
|
||||
DeviceCount: int(deviceCount),
|
||||
})
|
||||
}
|
||||
|
||||
responseData := gin.H{
|
||||
"users": usersWithStatus,
|
||||
"total": totalCount,
|
||||
"online_count": onlineCount,
|
||||
"offline_count": offlineCount,
|
||||
"banned_count": bannedCount,
|
||||
}
|
||||
log.Printf("[DEBUG] Response data: %+v", responseData)
|
||||
response.Success(c, responseData)
|
||||
}
|
||||
|
||||
func handleCreateUser(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Username == "" {
|
||||
response.Error(c, 400, "用户名不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Password == "" {
|
||||
response.Error(c, 400, "密码不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if req.ApplicationID == 0 {
|
||||
response.Error(c, 400, "所属应用不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, req.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", req.ApplicationID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限在该应用下创建用户")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var existingUser model.AppUser
|
||||
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, app.ID).First(&existingUser).Error; err == nil {
|
||||
response.Error(c, 400, "用户已存在")
|
||||
return
|
||||
}
|
||||
|
||||
user := model.AppUser{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
Avatar: "",
|
||||
Status: "active",
|
||||
ApplicationID: app.ID,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&user).Error; err != nil {
|
||||
response.Error(c, 500, "创建用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "create", "app_user", &user.ID, fmt.Sprintf("创建用户: %s (应用: %s)", user.Username, app.Name), nil)
|
||||
|
||||
response.Success(c, user)
|
||||
}
|
||||
|
||||
func handleGetUser(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
var user model.AppUser
|
||||
if err := database.DB.Preload("Application").First(&user, id).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限查看该用户")
|
||||
return
|
||||
}
|
||||
|
||||
var card model.Card
|
||||
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err != nil {
|
||||
response.Error(c, 403, "无权限查看该用户")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"user": user,
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateUser(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Status string `json:"status"`
|
||||
ApplicationID uint `json:"application_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var user model.AppUser
|
||||
if err := database.DB.First(&user, id).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限修改该用户")
|
||||
return
|
||||
}
|
||||
|
||||
var card model.Card
|
||||
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err != nil {
|
||||
response.Error(c, 403, "无权限修改该用户")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if req.Username != "" {
|
||||
user.Username = req.Username
|
||||
}
|
||||
if req.Email != "" {
|
||||
user.Email = req.Email
|
||||
}
|
||||
if req.Password != "" {
|
||||
user.Password = req.Password
|
||||
}
|
||||
if req.Status != "" {
|
||||
user.Status = req.Status
|
||||
}
|
||||
if req.ApplicationID != 0 {
|
||||
var newApp model.Application
|
||||
if err := database.DB.First(&newApp, req.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "目标应用不存在")
|
||||
return
|
||||
}
|
||||
if newApp.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", req.ApplicationID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限将用户转移到该应用")
|
||||
return
|
||||
}
|
||||
}
|
||||
user.ApplicationID = req.ApplicationID
|
||||
}
|
||||
|
||||
if err := database.DB.Save(&user).Error; err != nil {
|
||||
response.Error(c, 500, "更新用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "update", "app_user", &user.ID, fmt.Sprintf("更新用户: %s", user.Username), nil)
|
||||
|
||||
response.Success(c, user)
|
||||
}
|
||||
|
||||
func handleDeleteUser(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var user model.AppUser
|
||||
if err := database.DB.First(&user, id).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限删除该用户")
|
||||
return
|
||||
}
|
||||
|
||||
var card model.Card
|
||||
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err != nil {
|
||||
response.Error(c, 403, "无权限删除该用户")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&user).Error; err != nil {
|
||||
response.Error(c, 500, "删除用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "delete", "app_user", &user.ID, fmt.Sprintf("删除用户: %s (应用: %s)", user.Username, app.Name), nil)
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleGetUserDevices(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
|
||||
var user model.AppUser
|
||||
if err := database.DB.First(&user, id).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限查看该用户设备")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var devices []model.UserDevice
|
||||
if err := database.DB.Where("user_id = ? AND application_id = ?", user.ID, app.ID).Find(&devices).Error; err != nil {
|
||||
response.Error(c, 500, "获取设备列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, devices)
|
||||
}
|
||||
|
||||
func handleUpdateExpiry(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id := c.Param("id")
|
||||
var req struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Type string `json:"type"`
|
||||
Field string `json:"field"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var appUser model.AppUser
|
||||
if err := database.DB.First(&appUser, id).Error; err != nil {
|
||||
response.Error(c, 404, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, appUser.ApplicationID).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if app.UserID != userID {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
||||
response.Error(c, 403, "无权限修改该用户")
|
||||
return
|
||||
}
|
||||
|
||||
var card model.Card
|
||||
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, appUser.ID).First(&card).Error; err != nil {
|
||||
response.Error(c, 403, "无权限修改该用户")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if req.Amount == 0 {
|
||||
response.Error(c, 400, "数值不能为0")
|
||||
return
|
||||
}
|
||||
|
||||
if appUser.Balance == -1 {
|
||||
response.Error(c, 400, "该用户为永久会员,无需操作")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if app.BillingType == "subscription" || req.Field == "days" {
|
||||
if req.Type == "recharge" {
|
||||
var baseTime time.Time
|
||||
if appUser.ExpiryAt != nil && appUser.ExpiryAt.After(now) {
|
||||
baseTime = *appUser.ExpiryAt
|
||||
} else {
|
||||
baseTime = now
|
||||
}
|
||||
duration := time.Duration(req.Amount) * 24 * time.Hour
|
||||
newExpiry := baseTime.Add(duration)
|
||||
appUser.ExpiryAt = &newExpiry
|
||||
} else if req.Type == "deduct" {
|
||||
if appUser.ExpiryAt == nil || appUser.ExpiryAt.Before(now) {
|
||||
response.Error(c, 400, "用户订阅已过期")
|
||||
return
|
||||
}
|
||||
duration := time.Duration(req.Amount) * 24 * time.Hour
|
||||
newExpiry := appUser.ExpiryAt.Add(-duration)
|
||||
if newExpiry.Before(now) {
|
||||
newExpiry = now
|
||||
}
|
||||
appUser.ExpiryAt = &newExpiry
|
||||
} else {
|
||||
response.Error(c, 400, "操作类型错误")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if req.Type == "recharge" {
|
||||
appUser.Balance += req.Amount
|
||||
} else if req.Type == "deduct" {
|
||||
if appUser.Balance < req.Amount {
|
||||
response.Error(c, 400, "余额不足")
|
||||
return
|
||||
}
|
||||
appUser.Balance -= req.Amount
|
||||
} else {
|
||||
response.Error(c, 400, "操作类型错误")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := database.DB.Save(&appUser).Error; err != nil {
|
||||
response.Error(c, 500, "更新失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, appUser)
|
||||
}
|
||||
|
||||
func handleBatchUpdateStatus(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
UserIDs []uint `json:"user_ids"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var users []model.AppUser
|
||||
if err := database.DB.Where("id IN ?", req.UserIDs).Find(&users).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var validUserIDs []uint
|
||||
for _, user := range users {
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if app.UserID == userID {
|
||||
validUserIDs = append(validUserIDs, user.ID)
|
||||
} else {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
|
||||
var card model.Card
|
||||
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err == nil {
|
||||
validUserIDs = append(validUserIDs, user.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(validUserIDs) > 0 {
|
||||
if err := database.DB.Model(&model.AppUser{}).Where("id IN ?", validUserIDs).Update("status", req.Status).Error; err != nil {
|
||||
response.Error(c, 500, "批量更新状态失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleBatchDelete(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req struct {
|
||||
UserIDs []uint `json:"user_ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var users []model.AppUser
|
||||
if err := database.DB.Where("id IN ?", req.UserIDs).Find(&users).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
var validUserIDs []uint
|
||||
for _, user := range users {
|
||||
var app model.Application
|
||||
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if app.UserID == userID {
|
||||
validUserIDs = append(validUserIDs, user.ID)
|
||||
} else {
|
||||
var agentApp model.AgentApplication
|
||||
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
|
||||
var card model.Card
|
||||
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err == nil {
|
||||
validUserIDs = append(validUserIDs, user.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(validUserIDs) > 0 {
|
||||
if err := database.DB.Where("id IN ?", validUserIDs).Delete(&model.AppUser{}).Error; err != nil {
|
||||
response.Error(c, 500, "批量删除失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
@@ -0,0 +1,565 @@
|
||||
package developer
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/middleware"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupVersionRoutes(r *gin.RouterGroup) {
|
||||
versions := r.Group("/versions")
|
||||
{
|
||||
versions.GET("", handleGetAllVersions)
|
||||
versions.GET("/:id", handleGetVersionByID)
|
||||
versions.POST("", handleCreateVersionGlobal)
|
||||
versions.PUT("/:id", handleUpdateVersionGlobal)
|
||||
versions.DELETE("/batch", handleBatchDeleteVersions)
|
||||
versions.POST("/upload-zip", handleUploadVersionZip)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetAllVersions(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
log.Printf("[DEBUG] handleGetAllVersions called, userID: %d\n", userID)
|
||||
|
||||
page := c.DefaultQuery("page", "1")
|
||||
pageSize := c.DefaultQuery("page_size", "20")
|
||||
|
||||
var total int64
|
||||
|
||||
var userApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&userApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Found %d user apps\n", len(userApps))
|
||||
for i, app := range userApps {
|
||||
log.Printf("[DEBUG] App %d: ID=%d, Name=%s\n", i, app.ID, app.Name)
|
||||
}
|
||||
|
||||
if len(userApps) == 0 {
|
||||
response.Success(c, gin.H{
|
||||
"versions": []interface{}{},
|
||||
"total": 0,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
appIDs := make([]uint, len(userApps))
|
||||
appNameMap := make(map[uint]string)
|
||||
for i, app := range userApps {
|
||||
appIDs[i] = app.ID
|
||||
appNameMap[app.ID] = app.Name
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] appNameMap: %v\n", appNameMap)
|
||||
|
||||
database.DB.Model(&model.Version{}).Where("application_id IN ?", appIDs).Count(&total)
|
||||
|
||||
var versions []model.Version
|
||||
offset := 0
|
||||
if pageInt, err := strconv.Atoi(page); err == nil && pageInt > 1 {
|
||||
offset = (pageInt - 1) * 20
|
||||
}
|
||||
|
||||
limit := 20
|
||||
if pageSizeInt, err := strconv.Atoi(pageSize); err == nil && pageSizeInt > 0 {
|
||||
limit = pageSizeInt
|
||||
}
|
||||
|
||||
if err := database.DB.Where("application_id IN ?", appIDs).Order("created_at DESC").Limit(limit).Offset(offset).Find(&versions).Error; err != nil {
|
||||
response.Error(c, 500, "获取版本列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Found %d versions\n", len(versions))
|
||||
for i, v := range versions {
|
||||
log.Printf("[DEBUG] Version %d: ID=%d, ApplicationID=%d, Version=%s\n", i, v.ID, v.ApplicationID, v.Version)
|
||||
}
|
||||
|
||||
type VersionWithAppName struct {
|
||||
model.Version
|
||||
ApplicationName string `json:"application_name"`
|
||||
}
|
||||
|
||||
result := make([]VersionWithAppName, len(versions))
|
||||
for i, v := range versions {
|
||||
appName := appNameMap[v.ApplicationID]
|
||||
log.Printf("[DEBUG] Mapping version %d: ApplicationID=%d -> AppName=%s\n", i, v.ApplicationID, appName)
|
||||
result[i] = VersionWithAppName{
|
||||
Version: v,
|
||||
ApplicationName: appName,
|
||||
}
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"versions": result,
|
||||
"total": total,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetVersionByID(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
versionID := c.Param("id")
|
||||
|
||||
var userApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&userApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
appIDs := make([]uint, len(userApps))
|
||||
appNameMap := make(map[uint]string)
|
||||
for i, app := range userApps {
|
||||
appIDs[i] = app.ID
|
||||
appNameMap[app.ID] = app.Name
|
||||
}
|
||||
|
||||
var version model.Version
|
||||
if err := database.DB.Where("id = ? AND application_id IN ?", versionID, appIDs).Preload("Files").First(&version).Error; err != nil {
|
||||
response.Error(c, 404, "版本不存在")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": version.ID,
|
||||
"application_id": version.ApplicationID,
|
||||
"application_name": appNameMap[version.ApplicationID],
|
||||
"version": version.Version,
|
||||
"description": version.Description,
|
||||
"file_path": version.FilePath,
|
||||
"file_size": version.FileSize,
|
||||
"file_hash": version.FileHash,
|
||||
"force_update": version.ForceUpdate,
|
||||
"update_strategy": version.UpdateStrategy,
|
||||
"update_method": version.UpdateMethod,
|
||||
"min_version": version.MinVersion,
|
||||
"changelog": version.Changelog,
|
||||
"status": version.Status,
|
||||
"files": version.Files,
|
||||
"created_at": version.CreatedAt,
|
||||
"updated_at": version.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
type CreateVersionRequest struct {
|
||||
ApplicationID uint `json:"application_id"`
|
||||
Version string `json:"version"`
|
||||
FilePath string `json:"file_path"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
FileHash string `json:"file_hash"`
|
||||
EntryFile string `json:"entry_file"`
|
||||
ForceUpdate bool `json:"force_update"`
|
||||
UpdateStrategy string `json:"update_strategy"`
|
||||
UpdateMethod string `json:"update_method"`
|
||||
MinVersion string `json:"min_version"`
|
||||
Description string `json:"description"`
|
||||
Changelog string `json:"changelog"`
|
||||
}
|
||||
|
||||
func handleCreateVersionGlobal(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req CreateVersionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if service.GetApplicationDisabledStatus(app.ID) {
|
||||
response.Error(c, 403, "应用已被禁用,无法创建版本")
|
||||
return
|
||||
}
|
||||
|
||||
var existingVersion model.Version
|
||||
if err := database.DB.Where("application_id = ? AND version = ?", req.ApplicationID, req.Version).First(&existingVersion).Error; err == nil {
|
||||
response.Error(c, 400, "该版本号已存在")
|
||||
return
|
||||
}
|
||||
|
||||
version := model.Version{
|
||||
ApplicationID: req.ApplicationID,
|
||||
Version: req.Version,
|
||||
FilePath: req.FilePath,
|
||||
FileSize: req.FileSize,
|
||||
FileHash: req.FileHash,
|
||||
EntryFile: req.EntryFile,
|
||||
ForceUpdate: req.ForceUpdate,
|
||||
UpdateStrategy: req.UpdateStrategy,
|
||||
UpdateMethod: req.UpdateMethod,
|
||||
MinVersion: req.MinVersion,
|
||||
Description: req.Description,
|
||||
Changelog: req.Changelog,
|
||||
Status: "active",
|
||||
}
|
||||
|
||||
if version.UpdateStrategy == "" {
|
||||
version.UpdateStrategy = "optional"
|
||||
}
|
||||
if version.UpdateMethod == "" {
|
||||
version.UpdateMethod = "manual"
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&version).Error; err != nil {
|
||||
response.Error(c, 500, "创建版本失败")
|
||||
return
|
||||
}
|
||||
|
||||
userIDPtr := &userID
|
||||
versionIDPtr := &version.ID
|
||||
service.CreateLog(service.LogParams{
|
||||
UserID: userIDPtr,
|
||||
LogType: "version",
|
||||
Action: "create",
|
||||
Resource: "version",
|
||||
ResourceID: versionIDPtr,
|
||||
Details: fmt.Sprintf("创建版本: %s (应用ID: %d)", version.Version, version.ApplicationID),
|
||||
})
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": version.ID,
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateVersionRequest struct {
|
||||
Version string `json:"version"`
|
||||
ForceUpdate bool `json:"force_update"`
|
||||
UpdateStrategy string `json:"update_strategy"`
|
||||
UpdateMethod string `json:"update_method"`
|
||||
MinVersion string `json:"min_version"`
|
||||
Description string `json:"description"`
|
||||
Changelog string `json:"changelog"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func handleUpdateVersionGlobal(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
versionID := c.Param("id")
|
||||
|
||||
var req UpdateVersionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
var userApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&userApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
appIDs := make([]uint, len(userApps))
|
||||
for i, app := range userApps {
|
||||
appIDs[i] = app.ID
|
||||
}
|
||||
|
||||
var version model.Version
|
||||
if err := database.DB.Where("id = ? AND application_id IN ?", versionID, appIDs).First(&version).Error; err != nil {
|
||||
response.Error(c, 404, "版本不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Version != "" && req.Version != version.Version {
|
||||
var existingVersion model.Version
|
||||
if err := database.DB.Where("application_id = ? AND version = ? AND id != ?", version.ApplicationID, req.Version, version.ID).First(&existingVersion).Error; err == nil {
|
||||
response.Error(c, 400, "该版本号已存在")
|
||||
return
|
||||
}
|
||||
version.Version = req.Version
|
||||
}
|
||||
|
||||
if req.Version != "" {
|
||||
version.Version = req.Version
|
||||
}
|
||||
version.ForceUpdate = req.ForceUpdate
|
||||
if req.UpdateStrategy != "" {
|
||||
version.UpdateStrategy = req.UpdateStrategy
|
||||
}
|
||||
if req.UpdateMethod != "" {
|
||||
version.UpdateMethod = req.UpdateMethod
|
||||
}
|
||||
version.MinVersion = req.MinVersion
|
||||
version.Description = req.Description
|
||||
version.Changelog = req.Changelog
|
||||
if req.Status != "" {
|
||||
version.Status = req.Status
|
||||
}
|
||||
|
||||
if err := database.DB.Save(&version).Error; err != nil {
|
||||
response.Error(c, 500, "更新版本失败")
|
||||
return
|
||||
}
|
||||
|
||||
userIDPtr := &userID
|
||||
versionIDPtr := &version.ID
|
||||
service.CreateLog(service.LogParams{
|
||||
UserID: userIDPtr,
|
||||
LogType: "version",
|
||||
Action: "update",
|
||||
Resource: "version",
|
||||
ResourceID: versionIDPtr,
|
||||
Details: fmt.Sprintf("更新版本: %s (应用ID: %d)", version.Version, version.ApplicationID),
|
||||
})
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
func handleBatchDeleteVersions(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
response.Error(c, 400, "请选择要删除的版本")
|
||||
return
|
||||
}
|
||||
|
||||
var userApps []model.Application
|
||||
if err := database.DB.Where("user_id = ?", userID).Find(&userApps).Error; err != nil {
|
||||
response.Error(c, 500, "获取应用列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
appIDs := make([]uint, len(userApps))
|
||||
for i, app := range userApps {
|
||||
appIDs[i] = app.ID
|
||||
}
|
||||
|
||||
var versions []model.Version
|
||||
if err := database.DB.Where("id IN ? AND application_id IN ?", req.IDs, appIDs).Find(&versions).Error; err != nil {
|
||||
response.Error(c, 500, "获取版本失败")
|
||||
return
|
||||
}
|
||||
|
||||
for _, version := range versions {
|
||||
for _, app := range userApps {
|
||||
if app.ID == version.ApplicationID && service.GetApplicationDisabledStatus(app.ID) {
|
||||
response.Error(c, 403, fmt.Sprintf("应用 %s 已被禁用,无法删除其版本", app.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := database.DB.Where("id IN ? AND application_id IN ?", req.IDs, appIDs).Delete(&model.Version{}).Error; err != nil {
|
||||
response.Error(c, 500, "批量删除版本失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
type UploadVersionZipResponse struct {
|
||||
FilePath string `json:"file_path"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
FileHash string `json:"file_hash"`
|
||||
Files []VersionFileInfo `json:"files"`
|
||||
}
|
||||
|
||||
type VersionFileInfo struct {
|
||||
FilePath string `json:"file_path"`
|
||||
FileName string `json:"file_name"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
FileHash string `json:"file_hash"`
|
||||
FileType string `json:"file_type"`
|
||||
}
|
||||
|
||||
func handleUploadVersionZip(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
file, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
response.Error(c, 400, "请上传ZIP文件")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if !strings.HasSuffix(strings.ToLower(header.Filename), ".zip") {
|
||||
response.Error(c, 400, "只支持ZIP格式文件")
|
||||
return
|
||||
}
|
||||
|
||||
fileSize := header.Size
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
if user.CurrentPackageID != nil {
|
||||
var permission model.PackagePermission
|
||||
if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&permission).Error; err == nil {
|
||||
maxStorageBytes := int64(permission.MaxStorage) * 1024 * 1024
|
||||
if user.StorageUsed+fileSize > maxStorageBytes {
|
||||
usedMB := float64(user.StorageUsed) / 1024 / 1024
|
||||
maxMB := float64(permission.MaxStorage)
|
||||
response.Error(c, 403, fmt.Sprintf("存储空间不足,已使用 %.2f MB / %.2f MB", usedMB, maxMB))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timestamp := time.Now().Unix()
|
||||
filename := fmt.Sprintf("version_%d_%d.zip", userID, timestamp)
|
||||
dst := filepath.Join("uploads", "versions", filename)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
response.Error(c, 500, "创建目录失败")
|
||||
return
|
||||
}
|
||||
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
response.Error(c, 500, "创建文件失败")
|
||||
return
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
if _, err := io.Copy(dstFile, file); err != nil {
|
||||
response.Error(c, 500, "保存文件失败")
|
||||
return
|
||||
}
|
||||
|
||||
zipHash, err := calculateFileHash(dst)
|
||||
if err != nil {
|
||||
os.Remove(dst)
|
||||
response.Error(c, 500, "计算文件哈希失败")
|
||||
return
|
||||
}
|
||||
|
||||
files, err := parseZipFile(dst)
|
||||
if err != nil {
|
||||
os.Remove(dst)
|
||||
response.Error(c, 500, fmt.Sprintf("解析ZIP文件失败: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := middleware.UpdateStorageUsed(userID, fileSize, "upload"); err != nil {
|
||||
log.Printf("更新存储使用量失败: %v\n", err)
|
||||
}
|
||||
|
||||
usage := model.StorageUsage{
|
||||
UserID: userID,
|
||||
ResourceType: "version",
|
||||
ResourceID: 0,
|
||||
FileName: header.Filename,
|
||||
FileSize: fileSize,
|
||||
Action: "upload",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
database.DB.Create(&usage)
|
||||
|
||||
response.Success(c, UploadVersionZipResponse{
|
||||
FilePath: fmt.Sprintf("/uploads/versions/%s", filename),
|
||||
FileSize: fileSize,
|
||||
FileHash: zipHash,
|
||||
Files: files,
|
||||
})
|
||||
}
|
||||
|
||||
func calculateFileHash(filePath string) (string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func parseZipFile(zipPath string) ([]VersionFileInfo, error) {
|
||||
reader, err := zip.OpenReader(zipPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
var files []VersionFileInfo
|
||||
|
||||
for _, f := range reader.File {
|
||||
if f.FileInfo().IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, rc); err != nil {
|
||||
rc.Close()
|
||||
continue
|
||||
}
|
||||
rc.Close()
|
||||
|
||||
fileHash := hex.EncodeToString(hash.Sum(nil))
|
||||
fileType := getFileType(f.Name)
|
||||
|
||||
files = append(files, VersionFileInfo{
|
||||
FilePath: f.Name,
|
||||
FileName: filepath.Base(f.Name),
|
||||
FileSize: int64(f.UncompressedSize64),
|
||||
FileHash: fileHash,
|
||||
FileType: fileType,
|
||||
})
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func getFileType(filename string) string {
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
switch ext {
|
||||
case ".exe", ".dll", ".so", ".dylib", ".app":
|
||||
return "executable"
|
||||
case ".json", ".xml", ".yaml", ".yml", ".ini", ".conf", ".cfg":
|
||||
return "config"
|
||||
case ".txt", ".md", ".log":
|
||||
return "text"
|
||||
case ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".svg":
|
||||
return "image"
|
||||
case ".mp3", ".wav", ".ogg", ".flac":
|
||||
return "audio"
|
||||
case ".mp4", ".avi", ".mkv", ".mov", ".wmv":
|
||||
return "video"
|
||||
case ".db", ".sqlite", ".sqlite3":
|
||||
return "database"
|
||||
default:
|
||||
return "resource"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user