refactor(api): extract repository layer and thin HTTP handlers

Introduce internal/repository for data access and cache-backed system
config reads. Move business logic into logics.go across admin push,
user, template, cache, system_config, and upload/handler packages.

Remove Gin from internal/util by relocating request-scoped helpers to
oauth/gin_context.go. Propagate request context for config lookups in
user flows. Slim model entities and delete model-level DB/cache helpers.

Wire handlers to logics/repository so targeted packages no longer call
db.DB directly. Update admin router tests to use ErrorHandlerMiddleware.
This commit is contained in:
ryan
2026-06-18 12:12:49 +08:00
parent e5b3a60f73
commit 1b2e083aec
77 changed files with 2370 additions and 1783 deletions
+23 -124
View File
@@ -3,19 +3,20 @@
package push
import ("encoding/json"
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"github.com/Rain-kl/Wavelet/internal/db"
"github.com/Rain-kl/Wavelet/internal/model"
pkgpush "github.com/Rain-kl/Wavelet/pkg/push"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"github.com/Rain-kl/Wavelet/internal/common/response")
"github.com/Rain-kl/Wavelet/internal/common/response"
"github.com/Rain-kl/Wavelet/internal/model"
)
// ListChannelDefinitions 获取各种消息通道的表单配置定义列表
// @Summary 获取所有消息通道配置字段定义
@@ -38,9 +39,8 @@ func ListChannelDefinitions(c *gin.Context) {
// @Success 200 {object} response.Any{data=[]model.PushChannel} "消息通道列表"
// @Router /api/v1/admin/push/channels [get]
func ListChannels(c *gin.Context) {
ctx := c.Request.Context()
var channels []model.PushChannel
if err := db.DB(ctx).Order("created_at DESC").Find(&channels).Error; err != nil {
channels, err := listPushChannels(c.Request.Context())
if err != nil {
response.AbortInternal(c, err.Error())
return
}
@@ -75,41 +75,11 @@ func CreateChannel(c *gin.Context) {
return
}
ctx := c.Request.Context()
var count int64
if err := db.DB(ctx).Model(&model.PushChannel{}).Where("name = ?", req.Name).Count(&count).Error; err != nil {
response.AbortInternal(c, err.Error())
return
}
if count > 0 {
response.AbortBadRequest(c, "channel name already exists")
return
}
channel := model.PushChannel{
Name: req.Name,
Description: req.Description,
Type: req.Type,
Token: req.Token,
URL: req.URL,
Other: req.Other,
Enabled: req.Enabled,
}
if err := channel.Validate(); err != nil {
channel, err := createPushChannel(c.Request.Context(), req)
if err != nil {
response.AbortBadRequest(c, err.Error())
return
}
if err := db.DB(ctx).Create(&channel).Error; err != nil {
response.AbortInternal(c, err.Error())
return
}
// 缓存一致性:清除渠道缓存
model.DeleteActivePushChannelCache(ctx, channel.Name)
c.JSON(http.StatusOK, response.OK(channel))
}
@@ -135,8 +105,7 @@ type UpdateChannelRequest struct {
// @Success 200 {object} response.Any{data=model.PushChannel} "更新成功"
// @Router /api/v1/admin/push/channels/{id} [put]
func UpdateChannel(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.AbortBadRequest(c, "invalid channel id")
return
@@ -148,10 +117,8 @@ func UpdateChannel(c *gin.Context) {
return
}
ctx := c.Request.Context()
var channel model.PushChannel
if err := db.DB(ctx).Where("id = ?", id).First(&channel).Error; err != nil {
channel, err := updatePushChannel(c.Request.Context(), id, req)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
response.AbortNotFound(c, "channel not found")
return
@@ -159,27 +126,6 @@ func UpdateChannel(c *gin.Context) {
response.AbortInternal(c, err.Error())
return
}
channel.Description = req.Description
channel.Type = req.Type
channel.Token = req.Token
channel.URL = req.URL
channel.Other = req.Other
channel.Enabled = req.Enabled
if err := channel.Validate(); err != nil {
response.AbortBadRequest(c, err.Error())
return
}
if err := db.DB(ctx).Save(&channel).Error; err != nil {
response.AbortInternal(c, err.Error())
return
}
// 缓存一致性:清除渠道缓存
model.DeleteActivePushChannelCache(ctx, channel.Name)
c.JSON(http.StatusOK, response.OK(channel))
}
@@ -193,16 +139,13 @@ func UpdateChannel(c *gin.Context) {
// @Success 200 {object} response.Any "删除成功"
// @Router /api/v1/admin/push/channels/{id} [delete]
func DeleteChannel(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
response.AbortBadRequest(c, "invalid channel id")
return
}
ctx := c.Request.Context()
var channel model.PushChannel
if err := db.DB(ctx).Where("id = ?", id).First(&channel).Error; err != nil {
if err := deletePushChannel(c.Request.Context(), id); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
response.AbortNotFound(c, "channel not found")
return
@@ -210,15 +153,6 @@ func DeleteChannel(c *gin.Context) {
response.AbortInternal(c, err.Error())
return
}
if err := db.DB(ctx).Delete(&channel).Error; err != nil {
response.AbortInternal(c, err.Error())
return
}
// 缓存一致性:清除渠道缓存
model.DeleteActivePushChannelCache(ctx, channel.Name)
c.JSON(http.StatusOK, response.OKNil())
}
@@ -250,26 +184,12 @@ func TestChannel(c *gin.Context) {
}
ctx := c.Request.Context()
var url, token, other, channelType string
if req.Name != "" {
var channel model.PushChannel
if err := db.DB(ctx).Where("name = ?", req.Name).First(&channel).Error; err != nil {
response.AbortBadRequest(c, "channel not found")
return
}
url = channel.URL
token = channel.Token
other = channel.Other
channelType = channel.Type
} else {
url = req.URL
token = req.Token
other = req.Other
channelType = req.Type
url, token, other, channelType, err := loadChannelForTest(ctx, req)
if err != nil {
response.AbortBadRequest(c, err.Error())
return
}
// 对邮件类型应用全局配置作为回退
if channelType == channelEmail {
url, token, other = resolveSMTPConfig(ctx, url, token, other)
}
@@ -282,7 +202,6 @@ func TestChannel(c *gin.Context) {
Type: channelType,
Enabled: true,
}
if err := tempChannel.Validate(); err != nil {
response.AbortBadRequest(c, err.Error())
return
@@ -291,34 +210,16 @@ func TestChannel(c *gin.Context) {
var config pkgpush.Config
var renderedJSON string
switch channelType {
case channelLark:
config = pkgpush.Config{
Channel: channelLark,
URL: url,
Secret: token,
}
config = pkgpush.Config{Channel: channelLark, URL: url, Secret: token}
renderedJSON = other
case channelEmail:
config = pkgpush.Config{
Channel: channelEmail,
URL: url,
Key: token,
Secret: other,
}
config = pkgpush.Config{Channel: channelEmail, URL: url, Key: token, Secret: other}
case channelTelegram:
config = pkgpush.Config{
Channel: channelTelegram,
URL: url,
Secret: token,
Key: other,
}
config = pkgpush.Config{Channel: channelTelegram, URL: url, Secret: token, Key: other}
default:
config = pkgpush.Config{
Channel: channelCustom,
URL: url,
}
config = pkgpush.Config{Channel: channelCustom, URL: url}
customPushReq := CustomPushRequest{
Title: "通道测试通知",
Content: "这是一条来自系统的消息通道连通性测试消息。",
@@ -340,12 +241,10 @@ func TestChannel(c *gin.Context) {
},
Template: renderedJSON,
}
if err := enqueuePushTask(ctx, payload); err != nil {
response.AbortInternal(c, err.Error())
return
}
c.JSON(http.StatusOK, response.OKNil())
}
@@ -376,4 +275,4 @@ func renderCustomPayload(template string, req CustomPushRequest) string {
result = strings.ReplaceAll(result, "$url", escapeJSONString(req.URL))
result = strings.ReplaceAll(result, "$to", escapeJSONString(req.To))
return result
}
}