init
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user