feat: 导航页文案可后台配置 + webhook修复 + 企业微信通知可用

- 导航页所有文案(标题/副标题/标签/咨询区/页脚/工作室名称)均可在后台「导航页设置」页配置
- 新增公开API GET /api/v1/nav-settings,导航页加载时自动拉取配置覆盖默认值
- CustomerHandler改为从数据库读取webhook URL,不再依赖config.yaml
- 新增默认企业微信webhook地址,开箱即用
- 后台新增webhook测试专用接口 POST /api/v1/admin/webhook/test
- 后台新增批量配置保存接口 PUT /api/v1/admin/config-batch
- 后台新增「导航页设置」管理页面,支持分组编辑保存
- 页脚添加工作室名称展示位
This commit is contained in:
2026-09-09 02:49:39 +08:00
parent 5e47757c73
commit 3580066a13
7 changed files with 328 additions and 58 deletions

View File

@@ -8,19 +8,20 @@ import (
"net/http"
"time"
"gotest/internal/config"
"gotest/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// CustomerHandler 客户信息收集处理器
type CustomerHandler struct {
cfg *config.Config
db *gorm.DB
}
// NewCustomerHandler 创建客户信息收集处理器
func NewCustomerHandler(cfg *config.Config) *CustomerHandler {
return &CustomerHandler{cfg: cfg}
func NewCustomerHandler(db *gorm.DB) *CustomerHandler {
return &CustomerHandler{db: db}
}
// CustomerRequest 客户信息请求
@@ -54,7 +55,6 @@ func (h *CustomerHandler) Submit(c *gin.Context) {
// 发送到企业微信webhook
if err := h.sendToWechatWebhook(content); err != nil {
// 即使webhook失败,也记录下来并返回成功(避免暴露内部错误)
fmt.Printf("发送企业微信webhook失败: %v\n", err)
}
@@ -63,13 +63,19 @@ func (h *CustomerHandler) Submit(c *gin.Context) {
})
}
// getWebhookURL 从数据库获取webhook URL
func (h *CustomerHandler) getWebhookURL() string {
var cfg model.Config
if err := h.db.Where("`key` = ?", "wechat_webhook_url").First(&cfg).Error; err == nil && cfg.Value != "" {
return cfg.Value
}
return "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=71110fd5-9f51-4695-88dc-85ca34385d66"
}
// sendToWechatWebhook 发送消息到企业微信群机器人
func (h *CustomerHandler) sendToWechatWebhook(content string) error {
if h.cfg.WechatWebhook.URL == "" {
return fmt.Errorf("企业微信webhook URL未配置")
}
webhookURL := h.getWebhookURL()
// 构建消息体
message := map[string]interface{}{
"msgtype": "text",
"text": map[string]string{
@@ -82,31 +88,26 @@ func (h *CustomerHandler) sendToWechatWebhook(content string) error {
return fmt.Errorf("序列化消息失败: %w", err)
}
// 发送POST请求
resp, err := http.Post(h.cfg.WechatWebhook.URL, "application/json", bytes.NewBuffer(jsonData))
resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("发送请求失败: %w", err)
}
defer resp.Body.Close()
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("读取响应失败: %w", err)
}
// 检查响应状态
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("请求失败,状态码: %d,响应: %s", resp.StatusCode, string(body))
}
// 解析响应JSON
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("解析响应失败: %w", err)
}
// 检查企业微信返回的错误码
if errcode, ok := result["errcode"].(float64); ok && errcode != 0 {
errmsg, _ := result["errmsg"].(string)
return fmt.Errorf("企业微信返回错误: %v - %s", errcode, errmsg)
@@ -114,3 +115,43 @@ func (h *CustomerHandler) sendToWechatWebhook(content string) error {
return nil
}
// TestWebhook 测试webhook POST /api/v1/admin/webhook/test
func (h *CustomerHandler) TestWebhook(c *gin.Context) {
webhookURL := h.getWebhookURL()
content := fmt.Sprintf("✅ Webhook 测试成功\n⏰ 时间:%s", time.Now().Format("2006-01-02 15:04:05"))
message := map[string]interface{}{
"msgtype": "text",
"text": map[string]string{
"content": content,
},
}
jsonData, err := json.Marshal(message)
if err != nil {
FailInternal(c, "序列化失败: "+err.Error())
return
}
resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
FailInternal(c, "发送失败: "+err.Error())
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result map[string]interface{}
json.Unmarshal(body, &result)
if errcode, ok := result["errcode"].(float64); ok && errcode != 0 {
errmsg, _ := result["errmsg"].(string)
FailInternal(c, fmt.Sprintf("企业微信返回错误: %v - %s", errcode, errmsg))
return
}
SuccessMsg(c, "测试消息发送成功!请检查企业微信群")
}