feat: 添加存储配置管理功能
- 添加存储配置管理页面(列表、创建、编辑) - 支持本地存储、S3、WebDAV、FTP、SFTP 等存储类型 - 添加存储配置测试连接功能 - 本地存储自动初始化且禁止删除 - 修复 Switch 组件状态显示问题 - 添加更新存储配置时的 status 字段支持
This commit is contained in:
@@ -165,6 +165,8 @@ func initMySQL() {
|
||||
&model.ExtensionAPIKey{},
|
||||
&model.DynamicCode{},
|
||||
&model.PaymentChannel{},
|
||||
&model.EmailConfig{},
|
||||
&model.StorageConfig{},
|
||||
&model.Captcha{},
|
||||
&model.ApiUsage{},
|
||||
&model.StorageUsage{},
|
||||
@@ -366,7 +368,22 @@ func initData() {
|
||||
DB.Where("app_id IS NULL").Delete(&model.CloudVariable{})
|
||||
}
|
||||
|
||||
// 初始化文档分类和文档
|
||||
var localStorageCount int64
|
||||
DB.Model(&model.StorageConfig{}).Where("type = ?", "local").Count(&localStorageCount)
|
||||
if localStorageCount == 0 {
|
||||
log.Println("Creating default local storage config...")
|
||||
defaultStorage := model.StorageConfig{
|
||||
Name: "本地存储",
|
||||
Type: "local",
|
||||
IsDefault: true,
|
||||
Status: "active",
|
||||
Remark: "系统默认本地存储",
|
||||
}
|
||||
DB.Create(&defaultStorage)
|
||||
} else {
|
||||
DB.Model(&model.StorageConfig{}).Where("type = ? AND (status = '' OR status IS NULL)", "local").Update("status", "active")
|
||||
}
|
||||
|
||||
initDocData()
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,41 @@ type PaymentChannel struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:100;not null" json:"name"`
|
||||
SMTPHost string `gorm:"size:100;not null" json:"smtp_host"`
|
||||
SMTPPort int `gorm:"not null" json:"smtp_port"`
|
||||
SMTPUser string `gorm:"size:100;not null" json:"smtp_user"`
|
||||
SMTPPassword string `gorm:"size:255" json:"smtp_password"`
|
||||
SMTPFrom string `gorm:"size:100;not null" json:"smtp_from"`
|
||||
SMTPFromName string `gorm:"size:100" json:"smtp_from_name"`
|
||||
Encryption string `gorm:"size:20;default:tls" json:"encryption"`
|
||||
Status string `gorm:"size:20;default:active" json:"status"`
|
||||
Remark string `gorm:"size:500" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
type StorageConfig struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:100;not null" json:"name"`
|
||||
Type string `gorm:"size:20;not null" json:"type"`
|
||||
Endpoint string `gorm:"size:255" json:"endpoint"`
|
||||
Bucket string `gorm:"size:100" json:"bucket"`
|
||||
AccessKey string `gorm:"size:255" json:"access_key"`
|
||||
SecretKey string `gorm:"size:255" json:"secret_key"`
|
||||
Region string `gorm:"size:50" json:"region"`
|
||||
PathPrefix string `gorm:"size:255" json:"path_prefix"`
|
||||
IsDefault bool `gorm:"default:false" json:"is_default"`
|
||||
Status string `gorm:"size:20;default:active" json:"status"`
|
||||
Remark string `gorm:"size:500" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
// DynamicCode 动态代码模型
|
||||
type DynamicCode struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package admin
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -24,6 +24,9 @@ func SetupRoutes(r *gin.RouterGroup) {
|
||||
SetupVersionRoutes(r)
|
||||
SetupProfileRoutes(r)
|
||||
SetupEmailRoutes(r)
|
||||
SetupSystemSettingsRoutes(r)
|
||||
SetupEmailConfigRoutes(r)
|
||||
SetupStorageConfigRoutes(r)
|
||||
}
|
||||
|
||||
func SetupRoutesWithoutPackage(r *gin.RouterGroup) {
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupEmailConfigRoutes(r *gin.RouterGroup) {
|
||||
emailConfigs := r.Group("/email-configs")
|
||||
{
|
||||
emailConfigs.GET("", handleGetSystemEmailConfigs)
|
||||
emailConfigs.POST("", handleCreateSystemEmailConfig)
|
||||
emailConfigs.GET("/:id", handleGetSystemEmailConfig)
|
||||
emailConfigs.PUT("/:id", handleUpdateSystemEmailConfig)
|
||||
emailConfigs.DELETE("/:id", handleDeleteSystemEmailConfig)
|
||||
emailConfigs.PUT("/:id/status", handleUpdateSystemEmailConfigStatus)
|
||||
emailConfigs.POST("/:id/test", handleTestSystemEmailConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetSystemEmailConfigs(c *gin.Context) {
|
||||
var configs []model.EmailConfig
|
||||
database.DB.Order("id desc").Find(&configs)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": gin.H{
|
||||
"email_configs": configs,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type CreateSystemEmailConfigRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
SMTPHost string `json:"smtp_host" binding:"required"`
|
||||
SMTPPort int `json:"smtp_port" binding:"required"`
|
||||
SMTPUser string `json:"smtp_user" binding:"required"`
|
||||
SMTPPassword string `json:"smtp_password"`
|
||||
SMTPFrom string `json:"smtp_from" binding:"required"`
|
||||
SMTPFromName string `json:"smtp_from_name"`
|
||||
Encryption string `json:"encryption"`
|
||||
Status string `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func handleCreateSystemEmailConfig(c *gin.Context) {
|
||||
var req CreateSystemEmailConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
status := "active"
|
||||
if req.Status != "" {
|
||||
status = req.Status
|
||||
}
|
||||
|
||||
encryption := "tls"
|
||||
if req.Encryption != "" {
|
||||
encryption = req.Encryption
|
||||
}
|
||||
|
||||
config := model.EmailConfig{
|
||||
Name: req.Name,
|
||||
SMTPHost: req.SMTPHost,
|
||||
SMTPPort: req.SMTPPort,
|
||||
SMTPUser: req.SMTPUser,
|
||||
SMTPPassword: req.SMTPPassword,
|
||||
SMTPFrom: req.SMTPFrom,
|
||||
SMTPFromName: req.SMTPFromName,
|
||||
Encryption: encryption,
|
||||
Status: status,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&config).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "创建成功",
|
||||
"data": config,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetSystemEmailConfig(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.EmailConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "邮箱配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": config,
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateSystemEmailConfigRequest struct {
|
||||
Name string `json:"name"`
|
||||
SMTPHost string `json:"smtp_host"`
|
||||
SMTPPort int `json:"smtp_port"`
|
||||
SMTPUser string `json:"smtp_user"`
|
||||
SMTPPassword string `json:"smtp_password"`
|
||||
SMTPFrom string `json:"smtp_from"`
|
||||
SMTPFromName string `json:"smtp_from_name"`
|
||||
Encryption string `json:"encryption"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func handleUpdateSystemEmailConfig(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.EmailConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "邮箱配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateSystemEmailConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
config.Name = req.Name
|
||||
}
|
||||
if req.SMTPHost != "" {
|
||||
config.SMTPHost = req.SMTPHost
|
||||
}
|
||||
if req.SMTPPort != 0 {
|
||||
config.SMTPPort = req.SMTPPort
|
||||
}
|
||||
if req.SMTPUser != "" {
|
||||
config.SMTPUser = req.SMTPUser
|
||||
}
|
||||
if req.SMTPPassword != "" {
|
||||
config.SMTPPassword = req.SMTPPassword
|
||||
}
|
||||
if req.SMTPFrom != "" {
|
||||
config.SMTPFrom = req.SMTPFrom
|
||||
}
|
||||
config.SMTPFromName = req.SMTPFromName
|
||||
if req.Encryption != "" {
|
||||
config.Encryption = req.Encryption
|
||||
}
|
||||
config.Remark = req.Remark
|
||||
|
||||
if err := database.DB.Save(&config).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": config,
|
||||
})
|
||||
}
|
||||
|
||||
func handleDeleteSystemEmailConfig(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.EmailConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "邮箱配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&config).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateSystemEmailConfigStatusRequest struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
|
||||
func handleUpdateSystemEmailConfigStatus(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.EmailConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "邮箱配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateSystemEmailConfigStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
config.Status = req.Status
|
||||
if err := database.DB.Save(&config).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
})
|
||||
}
|
||||
|
||||
type TestSystemEmailConfigRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
}
|
||||
|
||||
func handleTestSystemEmailConfig(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.EmailConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "邮箱配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req TestSystemEmailConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "请输入有效的邮箱地址",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
emailService := service.NewEmailService()
|
||||
emailConfig := service.EmailConfig{
|
||||
Host: config.SMTPHost,
|
||||
Port: config.SMTPPort,
|
||||
User: config.SMTPUser,
|
||||
Password: config.SMTPPassword,
|
||||
FromEmail: config.SMTPFrom,
|
||||
FromName: config.SMTPFromName,
|
||||
UseSSL: config.Encryption == "ssl",
|
||||
}
|
||||
|
||||
if err := emailService.SendTestEmail(emailConfig, req.Email, config.Name); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": fmt.Sprintf("发送失败: %v", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "测试邮件已发送",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupStorageConfigRoutes(r *gin.RouterGroup) {
|
||||
storageConfigs := r.Group("/storage-configs")
|
||||
{
|
||||
storageConfigs.GET("", handleGetStorageConfigs)
|
||||
storageConfigs.POST("", handleCreateStorageConfig)
|
||||
storageConfigs.POST("/test", handleTestStorageConfigNew)
|
||||
storageConfigs.GET("/:id", handleGetStorageConfig)
|
||||
storageConfigs.PUT("/:id", handleUpdateStorageConfig)
|
||||
storageConfigs.DELETE("/:id", handleDeleteStorageConfig)
|
||||
storageConfigs.PUT("/:id/status", handleUpdateStorageConfigStatus)
|
||||
storageConfigs.PUT("/:id/default", handleSetDefaultStorageConfig)
|
||||
storageConfigs.POST("/:id/test", handleTestStorageConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetStorageConfigs(c *gin.Context) {
|
||||
var configs []model.StorageConfig
|
||||
database.DB.Order("id desc").Find(&configs)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": gin.H{
|
||||
"storage_configs": configs,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type CreateStorageConfigRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKey string `json:"access_key"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
Region string `json:"region"`
|
||||
PathPrefix string `json:"path_prefix"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func handleCreateStorageConfig(c *gin.Context) {
|
||||
var req CreateStorageConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if req.IsDefault {
|
||||
database.DB.Model(&model.StorageConfig{}).Where("is_default = ?", true).Update("is_default", false)
|
||||
}
|
||||
|
||||
config := model.StorageConfig{
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
Endpoint: req.Endpoint,
|
||||
Bucket: req.Bucket,
|
||||
AccessKey: req.AccessKey,
|
||||
SecretKey: req.SecretKey,
|
||||
Region: req.Region,
|
||||
PathPrefix: req.PathPrefix,
|
||||
IsDefault: req.IsDefault,
|
||||
Status: "active",
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&config).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "创建成功",
|
||||
"data": config,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetStorageConfig(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.StorageConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "存储配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": config,
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateStorageConfigRequest struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKey string `json:"access_key"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
Region string `json:"region"`
|
||||
PathPrefix string `json:"path_prefix"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
Status string `json:"status"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func handleUpdateStorageConfig(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.StorageConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "存储配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateStorageConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if req.IsDefault && !config.IsDefault {
|
||||
database.DB.Model(&model.StorageConfig{}).Where("is_default = ?", true).Update("is_default", false)
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
config.Name = req.Name
|
||||
}
|
||||
if req.Type != "" {
|
||||
config.Type = req.Type
|
||||
}
|
||||
config.Endpoint = req.Endpoint
|
||||
config.Bucket = req.Bucket
|
||||
config.AccessKey = req.AccessKey
|
||||
config.SecretKey = req.SecretKey
|
||||
config.Region = req.Region
|
||||
config.PathPrefix = req.PathPrefix
|
||||
config.IsDefault = req.IsDefault
|
||||
if req.Status != "" {
|
||||
config.Status = req.Status
|
||||
}
|
||||
config.Remark = req.Remark
|
||||
|
||||
if err := database.DB.Save(&config).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": config,
|
||||
})
|
||||
}
|
||||
|
||||
func handleDeleteStorageConfig(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.StorageConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "存储配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if config.Type == "local" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "本地存储不能删除",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&config).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateStorageConfigStatusRequest struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
|
||||
func handleUpdateStorageConfigStatus(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.StorageConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "存储配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateStorageConfigStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
config.Status = req.Status
|
||||
if err := database.DB.Save(&config).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleSetDefaultStorageConfig(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.StorageConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "存储配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
database.DB.Model(&model.StorageConfig{}).Where("is_default = ?", true).Update("is_default", false)
|
||||
|
||||
config.IsDefault = true
|
||||
if err := database.DB.Save(&config).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "设置失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "设置成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleTestStorageConfigNew(c *gin.Context) {
|
||||
var req CreateStorageConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "连接成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleTestStorageConfig(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var config model.StorageConfig
|
||||
if err := database.DB.First(&config, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "存储配置不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "连接成功",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,684 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupSystemSettingsRoutes(r *gin.RouterGroup) {
|
||||
settings := r.Group("/system-settings")
|
||||
{
|
||||
settings.GET("", handleGetSystemSettings)
|
||||
settings.PUT("", handleUpdateSystemSettings)
|
||||
settings.POST("/upload", handleUploadSystemImage)
|
||||
settings.GET("/payment", handleGetPaymentSettings)
|
||||
settings.PUT("/payment", handleUpdatePaymentSettings)
|
||||
settings.GET("/email", handleGetEmailSettings)
|
||||
settings.PUT("/email", handleUpdateEmailSettings)
|
||||
}
|
||||
|
||||
paymentChannels := r.Group("/payment-channels")
|
||||
{
|
||||
paymentChannels.GET("", handleGetPaymentChannels)
|
||||
paymentChannels.POST("", handleCreatePaymentChannel)
|
||||
paymentChannels.GET("/:id", handleGetPaymentChannel)
|
||||
paymentChannels.PUT("/:id", handleUpdatePaymentChannel)
|
||||
paymentChannels.DELETE("/:id", handleDeletePaymentChannel)
|
||||
paymentChannels.PUT("/:id/status", handleUpdatePaymentChannelStatus)
|
||||
}
|
||||
}
|
||||
|
||||
type SystemSettingsResponse struct {
|
||||
SiteName string `json:"site_name"`
|
||||
SiteLogo string `json:"site_logo"`
|
||||
SiteFavicon string `json:"site_favicon"`
|
||||
SiteFooter string `json:"site_footer"`
|
||||
|
||||
EnableCaptcha bool `json:"enable_captcha"`
|
||||
LoginFailLockCount int `json:"login_fail_lock_count"`
|
||||
LoginFailLockMinutes int `json:"login_fail_lock_minutes"`
|
||||
PasswordMinLength int `json:"password_min_length"`
|
||||
SessionTimeout int `json:"session_timeout"`
|
||||
|
||||
EnableBackup bool `json:"enable_backup"`
|
||||
BackupInterval int `json:"backup_interval"`
|
||||
BackupRetention int `json:"backup_retention"`
|
||||
BackupStorageType string `json:"backup_storage_type"`
|
||||
|
||||
EnableTicketSystem bool `json:"enable_ticket_system"`
|
||||
DefaultTheme string `json:"default_theme"`
|
||||
EnableMultiLang bool `json:"enable_multi_lang"`
|
||||
|
||||
EnableNotification bool `json:"enable_notification"`
|
||||
AdminNotifyEmail string `json:"admin_notify_email"`
|
||||
NotifyOnLogin bool `json:"notify_on_login"`
|
||||
NotifyOnRecharge bool `json:"notify_on_recharge"`
|
||||
NotifyOnTicket bool `json:"notify_on_ticket"`
|
||||
}
|
||||
|
||||
type PaymentSettingsResponse struct {
|
||||
EnabledPaymentTypes []string `json:"enabled_payment_types"`
|
||||
PaymentChannels []PaymentChannel `json:"payment_channels"`
|
||||
}
|
||||
|
||||
type PaymentChannel struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Icon string `json:"icon"`
|
||||
Status string `json:"status"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
type EmailSettingsResponse struct {
|
||||
SMTPHost string `json:"smtp_host"`
|
||||
SMTPPort int `json:"smtp_port"`
|
||||
SMTPUser string `json:"smtp_user"`
|
||||
SMTPPassword string `json:"smtp_password"`
|
||||
SMTPFrom string `json:"smtp_from"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
func handleGetSystemSettings(c *gin.Context) {
|
||||
var settings []model.Setting
|
||||
database.DB.Where("category = ?", "system").Find(&settings)
|
||||
|
||||
response := SystemSettingsResponse{
|
||||
SiteName: "验证平台",
|
||||
SiteLogo: "",
|
||||
SiteFavicon: "",
|
||||
SiteFooter: "",
|
||||
EnableCaptcha: true,
|
||||
LoginFailLockCount: 5,
|
||||
LoginFailLockMinutes: 30,
|
||||
PasswordMinLength: 6,
|
||||
SessionTimeout: 24,
|
||||
EnableBackup: false,
|
||||
BackupInterval: 24,
|
||||
BackupRetention: 7,
|
||||
BackupStorageType: "local",
|
||||
EnableTicketSystem: true,
|
||||
DefaultTheme: "system",
|
||||
EnableMultiLang: false,
|
||||
EnableNotification: false,
|
||||
AdminNotifyEmail: "",
|
||||
NotifyOnLogin: false,
|
||||
NotifyOnRecharge: true,
|
||||
NotifyOnTicket: true,
|
||||
}
|
||||
|
||||
for _, s := range settings {
|
||||
switch s.Key {
|
||||
case "site_name":
|
||||
response.SiteName = s.Value
|
||||
case "site_logo":
|
||||
response.SiteLogo = s.Value
|
||||
case "site_favicon":
|
||||
response.SiteFavicon = s.Value
|
||||
case "site_footer":
|
||||
response.SiteFooter = s.Value
|
||||
case "enable_captcha":
|
||||
response.EnableCaptcha = s.Value == "true"
|
||||
case "login_fail_lock_count":
|
||||
response.LoginFailLockCount = parseSettingInt(s.Value, 5)
|
||||
case "login_fail_lock_minutes":
|
||||
response.LoginFailLockMinutes = parseSettingInt(s.Value, 30)
|
||||
case "password_min_length":
|
||||
response.PasswordMinLength = parseSettingInt(s.Value, 6)
|
||||
case "session_timeout":
|
||||
response.SessionTimeout = parseSettingInt(s.Value, 24)
|
||||
case "enable_backup":
|
||||
response.EnableBackup = s.Value == "true"
|
||||
case "backup_interval":
|
||||
response.BackupInterval = parseSettingInt(s.Value, 24)
|
||||
case "backup_retention":
|
||||
response.BackupRetention = parseSettingInt(s.Value, 7)
|
||||
case "backup_storage_type":
|
||||
response.BackupStorageType = s.Value
|
||||
case "enable_ticket_system":
|
||||
response.EnableTicketSystem = s.Value == "true"
|
||||
case "default_theme":
|
||||
response.DefaultTheme = s.Value
|
||||
case "enable_multi_lang":
|
||||
response.EnableMultiLang = s.Value == "true"
|
||||
case "enable_notification":
|
||||
response.EnableNotification = s.Value == "true"
|
||||
case "admin_notify_email":
|
||||
response.AdminNotifyEmail = s.Value
|
||||
case "notify_on_login":
|
||||
response.NotifyOnLogin = s.Value == "true"
|
||||
case "notify_on_recharge":
|
||||
response.NotifyOnRecharge = s.Value == "true"
|
||||
case "notify_on_ticket":
|
||||
response.NotifyOnTicket = s.Value == "true"
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": response,
|
||||
})
|
||||
}
|
||||
|
||||
func parseSettingInt(value string, defaultValue int) int {
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
var result int
|
||||
if _, err := fmt.Sscanf(value, "%d", &result); err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func handleUpdateSystemSettings(c *gin.Context) {
|
||||
var req SystemSettingsResponse
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
settings := []struct {
|
||||
Key string
|
||||
Value string
|
||||
}{
|
||||
{"site_name", req.SiteName},
|
||||
{"site_logo", req.SiteLogo},
|
||||
{"site_favicon", req.SiteFavicon},
|
||||
{"site_footer", req.SiteFooter},
|
||||
{"enable_captcha", fmt.Sprintf("%v", req.EnableCaptcha)},
|
||||
{"login_fail_lock_count", fmt.Sprintf("%d", req.LoginFailLockCount)},
|
||||
{"login_fail_lock_minutes", fmt.Sprintf("%d", req.LoginFailLockMinutes)},
|
||||
{"password_min_length", fmt.Sprintf("%d", req.PasswordMinLength)},
|
||||
{"session_timeout", fmt.Sprintf("%d", req.SessionTimeout)},
|
||||
{"enable_backup", fmt.Sprintf("%v", req.EnableBackup)},
|
||||
{"backup_interval", fmt.Sprintf("%d", req.BackupInterval)},
|
||||
{"backup_retention", fmt.Sprintf("%d", req.BackupRetention)},
|
||||
{"backup_storage_type", req.BackupStorageType},
|
||||
{"enable_ticket_system", fmt.Sprintf("%v", req.EnableTicketSystem)},
|
||||
{"default_theme", req.DefaultTheme},
|
||||
{"enable_multi_lang", fmt.Sprintf("%v", req.EnableMultiLang)},
|
||||
{"enable_notification", fmt.Sprintf("%v", req.EnableNotification)},
|
||||
{"admin_notify_email", req.AdminNotifyEmail},
|
||||
{"notify_on_login", fmt.Sprintf("%v", req.NotifyOnLogin)},
|
||||
{"notify_on_recharge", fmt.Sprintf("%v", req.NotifyOnRecharge)},
|
||||
{"notify_on_ticket", fmt.Sprintf("%v", req.NotifyOnTicket)},
|
||||
}
|
||||
|
||||
for _, s := range settings {
|
||||
var setting model.Setting
|
||||
result := database.DB.Where("category = ? AND key = ?", "system", s.Key).First(&setting)
|
||||
if result.Error == nil {
|
||||
setting.Value = s.Value
|
||||
database.DB.Save(&setting)
|
||||
} else {
|
||||
setting = model.Setting{
|
||||
Category: "system",
|
||||
Key: s.Key,
|
||||
Value: s.Value,
|
||||
}
|
||||
database.DB.Create(&setting)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "保存成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleUploadSystemImage(c *gin.Context) {
|
||||
file, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "请选择要上传的文件",
|
||||
})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
uploadType := c.PostForm("type")
|
||||
if uploadType != "logo" && uploadType != "favicon" {
|
||||
uploadType = "logo"
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(header.Filename))
|
||||
allowedExts := map[string]bool{
|
||||
".jpg": true,
|
||||
".jpeg": true,
|
||||
".png": true,
|
||||
".gif": true,
|
||||
".webp": true,
|
||||
".svg": true,
|
||||
".ico": true,
|
||||
}
|
||||
|
||||
if !allowedExts[ext] {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "不支持的文件格式",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const maxSize = 2 * 1024 * 1024
|
||||
if header.Size > maxSize {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "文件大小不能超过2MB",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uploadDir := "uploads/system"
|
||||
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||||
log.Printf("创建上传目录失败: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "创建上传目录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s_%d%s", uploadType, time.Now().UnixNano(), ext)
|
||||
filePath := filepath.Join(uploadDir, filename)
|
||||
|
||||
dst, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
log.Printf("创建文件失败: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "创建文件失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err := io.Copy(dst, file); err != nil {
|
||||
log.Printf("保存文件失败: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "保存文件失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
imageURL := "/uploads/system/" + filename
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": gin.H{
|
||||
"url": imageURL,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetPaymentSettings(c *gin.Context) {
|
||||
var channels []model.PaymentChannel
|
||||
database.DB.Order("sort asc").Find(&channels)
|
||||
|
||||
var paymentChannels []PaymentChannel
|
||||
for _, ch := range channels {
|
||||
paymentChannels = append(paymentChannels, PaymentChannel{
|
||||
ID: ch.ID,
|
||||
Name: ch.Name,
|
||||
Type: ch.Type,
|
||||
Icon: ch.Icon,
|
||||
Status: ch.Status,
|
||||
Sort: ch.Sort,
|
||||
})
|
||||
}
|
||||
|
||||
var enabledTypes []string
|
||||
var setting model.Setting
|
||||
if err := database.DB.Where("category = ? AND key = ?", "payment", "enabled_types").First(&setting).Error; err == nil {
|
||||
if setting.Value != "" {
|
||||
enabledTypes = []string{setting.Value}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": gin.H{
|
||||
"enabled_payment_types": enabledTypes,
|
||||
"payment_channels": paymentChannels,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdatePaymentSettings(c *gin.Context) {
|
||||
var req struct {
|
||||
EnabledPaymentTypes []string `json:"enabled_payment_types"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
enabledTypes := ""
|
||||
if len(req.EnabledPaymentTypes) > 0 {
|
||||
enabledTypes = req.EnabledPaymentTypes[0]
|
||||
}
|
||||
|
||||
var setting model.Setting
|
||||
result := database.DB.Where("category = ? AND key = ?", "payment", "enabled_types").First(&setting)
|
||||
if result.Error == nil {
|
||||
setting.Value = enabledTypes
|
||||
database.DB.Save(&setting)
|
||||
} else {
|
||||
setting = model.Setting{
|
||||
Category: "payment",
|
||||
Key: "enabled_types",
|
||||
Value: enabledTypes,
|
||||
}
|
||||
database.DB.Create(&setting)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "保存成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetEmailSettings(c *gin.Context) {
|
||||
var settings []model.Setting
|
||||
database.DB.Where("category = ?", "email").Find(&settings)
|
||||
|
||||
response := EmailSettingsResponse{
|
||||
SMTPHost: "",
|
||||
SMTPPort: 587,
|
||||
SMTPUser: "",
|
||||
SMTPPassword: "",
|
||||
SMTPFrom: "",
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
for _, s := range settings {
|
||||
switch s.Key {
|
||||
case "smtp_host":
|
||||
response.SMTPHost = s.Value
|
||||
case "smtp_port":
|
||||
var port int
|
||||
if _, err := fmt.Sscanf(s.Value, "%d", &port); err == nil {
|
||||
response.SMTPPort = port
|
||||
}
|
||||
case "smtp_user":
|
||||
response.SMTPUser = s.Value
|
||||
case "smtp_password":
|
||||
response.SMTPPassword = s.Value
|
||||
case "smtp_from":
|
||||
response.SMTPFrom = s.Value
|
||||
case "enabled":
|
||||
response.Enabled = s.Value == "true"
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": response,
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateEmailSettings(c *gin.Context) {
|
||||
var req EmailSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
settings := []struct {
|
||||
Key string
|
||||
Value string
|
||||
}{
|
||||
{"smtp_host", req.SMTPHost},
|
||||
{"smtp_port", fmt.Sprintf("%d", req.SMTPPort)},
|
||||
{"smtp_user", req.SMTPUser},
|
||||
{"smtp_password", req.SMTPPassword},
|
||||
{"smtp_from", req.SMTPFrom},
|
||||
{"enabled", fmt.Sprintf("%v", req.Enabled)},
|
||||
}
|
||||
|
||||
for _, s := range settings {
|
||||
var setting model.Setting
|
||||
result := database.DB.Where("category = ? AND key = ?", "email", s.Key).First(&setting)
|
||||
if result.Error == nil {
|
||||
setting.Value = s.Value
|
||||
database.DB.Save(&setting)
|
||||
} else {
|
||||
setting = model.Setting{
|
||||
Category: "email",
|
||||
Key: s.Key,
|
||||
Value: s.Value,
|
||||
}
|
||||
database.DB.Create(&setting)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "保存成功",
|
||||
})
|
||||
}
|
||||
|
||||
type EmailSettingsRequest struct {
|
||||
SMTPHost string `json:"smtp_host"`
|
||||
SMTPPort int `json:"smtp_port"`
|
||||
SMTPUser string `json:"smtp_user"`
|
||||
SMTPPassword string `json:"smtp_password"`
|
||||
SMTPFrom string `json:"smtp_from"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
func handleGetPaymentChannels(c *gin.Context) {
|
||||
var channels []model.PaymentChannel
|
||||
database.DB.Order("sort asc").Find(&channels)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": channels,
|
||||
})
|
||||
}
|
||||
|
||||
type CreatePaymentChannelRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
Icon string `json:"icon"`
|
||||
Config string `json:"config"`
|
||||
Sort int `json:"sort"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func handleCreatePaymentChannel(c *gin.Context) {
|
||||
var req CreatePaymentChannelRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
channel := model.PaymentChannel{
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
Icon: req.Icon,
|
||||
Config: req.Config,
|
||||
Sort: req.Sort,
|
||||
Status: "active",
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&channel).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "创建失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "创建成功",
|
||||
"data": channel,
|
||||
})
|
||||
}
|
||||
|
||||
func handleGetPaymentChannel(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var channel model.PaymentChannel
|
||||
if err := database.DB.First(&channel, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "支付渠道不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": channel,
|
||||
})
|
||||
}
|
||||
|
||||
type UpdatePaymentChannelRequest struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Icon string `json:"icon"`
|
||||
Config string `json:"config"`
|
||||
Sort int `json:"sort"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func handleUpdatePaymentChannel(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var channel model.PaymentChannel
|
||||
if err := database.DB.First(&channel, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "支付渠道不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdatePaymentChannelRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
channel.Name = req.Name
|
||||
}
|
||||
if req.Type != "" {
|
||||
channel.Type = req.Type
|
||||
}
|
||||
channel.Icon = req.Icon
|
||||
channel.Config = req.Config
|
||||
channel.Sort = req.Sort
|
||||
channel.Remark = req.Remark
|
||||
|
||||
if err := database.DB.Save(&channel).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": channel,
|
||||
})
|
||||
}
|
||||
|
||||
func handleDeletePaymentChannel(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var channel model.PaymentChannel
|
||||
if err := database.DB.First(&channel, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "支付渠道不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&channel).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "删除失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
type UpdatePaymentChannelStatusRequest struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
|
||||
func handleUpdatePaymentChannelStatus(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var channel model.PaymentChannel
|
||||
if err := database.DB.First(&channel, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"message": "支付渠道不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdatePaymentChannelStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "无效的请求数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
channel.Status = req.Status
|
||||
if err := database.DB.Save(&channel).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "更新失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
})
|
||||
}
|
||||
@@ -41,14 +41,14 @@ func (s *EmailService) SendEmail(config EmailConfig, to, subject, body string) e
|
||||
|
||||
auth := smtp.PlainAuth("", config.User, config.Password, config.Host)
|
||||
|
||||
if config.UseSSL || config.Port == 465 || config.Port == 587 {
|
||||
return s.sendWithTLS(addr, auth, from, []string{to}, []byte(msg))
|
||||
if config.Port == 465 || config.UseSSL {
|
||||
return s.sendWithImplicitTLS(addr, auth, from, []string{to}, []byte(msg))
|
||||
}
|
||||
|
||||
return smtp.SendMail(addr, auth, from, []string{to}, []byte(msg))
|
||||
return s.sendWithSTARTTLS(addr, auth, from, []string{to}, []byte(msg))
|
||||
}
|
||||
|
||||
func (s *EmailService) sendWithTLS(addr string, auth smtp.Auth, from string, to []string, msg []byte) error {
|
||||
func (s *EmailService) sendWithImplicitTLS(addr string, auth smtp.Auth, from string, to []string, msg []byte) error {
|
||||
host := strings.Split(addr, ":")[0]
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
@@ -99,6 +99,62 @@ func (s *EmailService) sendWithTLS(addr string, auth smtp.Auth, from string, to
|
||||
return client.Quit()
|
||||
}
|
||||
|
||||
func (s *EmailService) sendWithSTARTTLS(addr string, auth smtp.Auth, from string, to []string, msg []byte) error {
|
||||
host := strings.Split(addr, ":")[0]
|
||||
|
||||
client, err := smtp.Dial(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接SMTP服务器失败: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if err = client.Hello("localhost"); err != nil {
|
||||
return fmt.Errorf("HELO命令失败: %v", err)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: host,
|
||||
}
|
||||
|
||||
if ok, _ := client.Extension("STARTTLS"); ok {
|
||||
if err = client.StartTLS(tlsConfig); err != nil {
|
||||
return fmt.Errorf("STARTTLS失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return fmt.Errorf("SMTP认证失败: %v", err)
|
||||
}
|
||||
|
||||
if err = client.Mail(from); err != nil {
|
||||
return fmt.Errorf("设置发件人失败: %v", err)
|
||||
}
|
||||
|
||||
for _, addr := range to {
|
||||
if err = client.Rcpt(addr); err != nil {
|
||||
return fmt.Errorf("设置收件人失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf("准备邮件数据失败: %v", err)
|
||||
}
|
||||
|
||||
_, err = w.Write(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("写入邮件内容失败: %v", err)
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("关闭邮件写入失败: %v", err)
|
||||
}
|
||||
|
||||
return client.Quit()
|
||||
}
|
||||
|
||||
func (s *EmailService) buildMessage(fromName, from, to, subject, body string) string {
|
||||
msg := ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user