Initial commit: 网络验证平台

This commit is contained in:
Admin
2026-04-27 17:22:56 +08:00
commit afe67d704e
780 changed files with 88960 additions and 0 deletions
@@ -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,
})
}
+585
View File
@@ -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
+969
View File
@@ -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)})
}
+827
View File
@@ -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": "批量更新状态成功"})
}
+481
View File
@@ -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)})
}
+84
View File
@@ -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),
})
}
+282
View File
@@ -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
}
+296
View File
@@ -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(&notifications).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(&notification).Error; err != nil {
response.Error(c, 404, "通知不存在")
return
}
notification.IsRead = true
if err := database.DB.Save(&notification).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,
})
}
+673
View File
@@ -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"
}
}