Files
2026-01-16 15:49:34 +08:00

326 lines
8.8 KiB
Go

package admin
import (
"crypto/tls"
"fmt"
"net/smtp"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"github.com/spf13/cast"
apperrors "goravel/app/errors"
"goravel/app/http/response"
"goravel/app/models"
)
type ConfigController struct {
}
func NewConfigController() *ConfigController {
return &ConfigController{}
}
// GetByGroup 根据分组获取配置
func (r *ConfigController) GetByGroup(ctx http.Context) http.Response {
group := ctx.Request().Route("group")
if group == "" {
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrConfigGroupRequired.Code)
}
var configs []models.Config
// 查询配置,即使没有数据也返回空数组,不返回错误
_ = facades.Orm().Query().Where("group", group).Order("sort asc, id asc").Get(&configs)
// 如果是邮箱配置分组,将密码字段的值设为空,不让前端看到
if group == "email" {
for i := range configs {
if configs[i].Key == "email_password" {
configs[i].Value = ""
}
}
}
return response.Success(ctx, http.Json{
"configs": configs,
})
}
// Save 保存配置(按分组批量保存)
func (r *ConfigController) Save(ctx http.Context) http.Response {
group := ctx.Request().Input("group")
if group == "" {
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrConfigGroupRequired.Code)
}
configsMap := ctx.Request().InputMap("configs")
if len(configsMap) == 0 {
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrConfigsRequired.Code)
}
// 获取该分组下的所有配置(即使查询失败也继续,使用空数组)
var existingConfigs []models.Config
_ = facades.Orm().Query().Where("group", group).Get(&existingConfigs)
// 创建key到config的映射
configMap := make(map[string]*models.Config)
for i := range existingConfigs {
configMap[existingConfigs[i].Key] = &existingConfigs[i]
}
now := carbon.Now()
// 对于 storage 分组,只允许保存驱动选择字段(白名单)
if group == "storage" {
allowedKeys := map[string]bool{
"file_disk": true,
"storage_disk": true, // 向后兼容,保留但不推荐使用
"export_disk": true, // 向后兼容
}
filteredConfigs := make(map[string]any)
for key, value := range configsMap {
if allowedKeys[key] {
filteredConfigs[key] = value
}
}
configsMap = filteredConfigs
}
// 批量处理配置更新和创建
for key, value := range configsMap {
// 转换值为字符串,处理布尔值
var valueStr string
switch v := value.(type) {
case bool:
if v {
valueStr = "1"
} else {
valueStr = "0"
}
case nil:
valueStr = ""
default:
valueStr = cast.ToString(value)
}
// 如果是邮箱配置的密码字段,且值为空,且配置已存在,则跳过更新(保持原有值)
if group == "email" && key == "email_password" && valueStr == "" {
if _, exists := configMap[key]; exists {
continue
}
// 如果配置不存在,则创建空值配置(允许首次创建时为空)
}
if config, exists := configMap[key]; exists {
// 更新现有配置
config.Value = valueStr
if err := facades.Orm().Query().Save(config); err != nil {
return response.ErrorWithLog(ctx, "config", err, map[string]any{
"group": group,
"key": key,
})
}
} else {
// 创建新配置
configData := map[string]any{
"group": group,
"key": key,
"value": valueStr,
"type": "input",
"sort": 0,
"created_at": now,
"updated_at": now,
}
if err := facades.Orm().Query().Table("configs").Create(configData); err != nil {
return response.ErrorWithLog(ctx, "config", err, map[string]any{
"group": group,
"key": key,
})
}
}
}
return response.Success(ctx)
}
// TestEmail 测试邮件发送
func (r *ConfigController) TestEmail(ctx http.Context) http.Response {
emailHost := ctx.Request().Input("email_host")
emailPort := cast.ToInt(ctx.Request().Input("email_port", "587"))
emailUsername := ctx.Request().Input("email_username")
emailPassword := ctx.Request().Input("email_password")
emailFrom := ctx.Request().Input("email_from")
emailFromName := ctx.Request().Input("email_from_name")
emailEncryption := ctx.Request().Input("email_encryption", "tls")
// 验证必填字段
if emailHost == "" || emailPort == 0 || emailUsername == "" || emailFrom == "" {
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrEmailConfigRequired.Code)
}
// 获取当前登录的管理员邮箱作为测试收件人
adminValue := ctx.Value("admin")
if adminValue == nil {
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
}
admin, ok := adminValue.(models.Admin)
if !ok {
return response.Error(ctx, http.StatusUnauthorized, apperrors.ErrNotLoggedIn.Code)
}
// 如果没有邮箱,使用发件人邮箱作为测试收件人
testEmail := emailFrom
if admin.Email != "" {
testEmail = admin.Email
}
// 构建邮件内容
fromName := emailFromName
if fromName == "" {
fromName = emailFrom
}
subject := "测试邮件"
body := fmt.Sprintf(`<h2>这是一封测试邮件</h2>
<p>如果您收到这封邮件,说明邮件配置正确。</p>
<p>发送时间:%s</p>
<p>SMTP服务器:%s:%d</p>
<p>加密方式:%s</p>`, carbon.Now().ToDateTimeString(), emailHost, emailPort, emailEncryption)
// 构建邮件消息
message := fmt.Sprintf("From: %s <%s>\r\n", fromName, emailFrom)
message += fmt.Sprintf("To: %s\r\n", testEmail)
message += fmt.Sprintf("Subject: %s\r\n", subject)
message += "MIME-Version: 1.0\r\n"
message += "Content-Type: text/html; charset=UTF-8\r\n"
message += "\r\n" + body
// 构建SMTP地址
addr := fmt.Sprintf("%s:%d", emailHost, emailPort)
// 创建SMTP认证
auth := smtp.PlainAuth("", emailUsername, emailPassword, emailHost)
// 发送邮件
var err error
if emailEncryption == "ssl" {
// SSL连接
tlsConfig := &tls.Config{
InsecureSkipVerify: false,
ServerName: emailHost,
}
conn, connErr := tls.Dial("tcp", addr, tlsConfig)
if connErr != nil {
return response.ErrorWithLog(ctx, "config", connErr, map[string]any{
"host": emailHost,
"port": emailPort,
})
}
defer conn.Close()
client, clientErr := smtp.NewClient(conn, emailHost)
if clientErr != nil {
return response.ErrorWithLog(ctx, "config", clientErr)
}
defer client.Close()
if err = client.Auth(auth); err != nil {
return response.ErrorWithLog(ctx, "config", err)
}
if err = client.Mail(emailFrom); err != nil {
return response.ErrorWithLog(ctx, "config", err)
}
if err = client.Rcpt(testEmail); err != nil {
return response.ErrorWithLog(ctx, "config", err)
}
writer, writerErr := client.Data()
if writerErr != nil {
return response.ErrorWithLog(ctx, "config", writerErr)
}
_, err = writer.Write([]byte(message))
if err != nil {
writer.Close()
return response.ErrorWithLog(ctx, "config", err)
}
err = writer.Close()
if err != nil {
return response.ErrorWithLog(ctx, "config", err)
}
} else {
// TLS或普通连接
if emailEncryption == "tls" {
// TLS连接
tlsConfig := &tls.Config{
InsecureSkipVerify: false,
ServerName: emailHost,
}
err = smtp.SendMail(addr, auth, emailFrom, []string{testEmail}, []byte(message))
if err != nil {
// 如果直接SendMail失败,尝试手动TLS
conn, connErr := smtp.Dial(addr)
if connErr != nil {
return response.ErrorWithLog(ctx, "config", connErr, map[string]any{
"host": emailHost,
"port": emailPort,
})
}
defer conn.Close()
if err = conn.StartTLS(tlsConfig); err != nil {
return response.ErrorWithLog(ctx, "config", err)
}
if err = conn.Auth(auth); err != nil {
return response.ErrorWithLog(ctx, "config", err)
}
if err = conn.Mail(emailFrom); err != nil {
return response.ErrorWithLog(ctx, "config", err)
}
if err = conn.Rcpt(testEmail); err != nil {
return response.ErrorWithLog(ctx, "config", err)
}
writer, writerErr := conn.Data()
if writerErr != nil {
return response.ErrorWithLog(ctx, "config", writerErr)
}
_, err = writer.Write([]byte(message))
if err != nil {
writer.Close()
return response.ErrorWithLog(ctx, "config", err)
}
err = writer.Close()
if err != nil {
return response.ErrorWithLog(ctx, "config", err)
}
}
} else {
// 普通连接(无加密)
err = smtp.SendMail(addr, auth, emailFrom, []string{testEmail}, []byte(message))
}
}
if err != nil {
return response.ErrorWithLog(ctx, "config", err, map[string]any{
"host": emailHost,
"port": emailPort,
"from": emailFrom,
"to": testEmail,
})
}
return response.Success(ctx, "test_email_success", http.Json{
"message": "测试邮件已发送到 " + testEmail,
})
}