f
This commit is contained in:
168
internal/infrastructure/http/handlers/admin_security_handler.go
Normal file
168
internal/infrastructure/http/handlers/admin_security_handler.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
securityEntities "hyapi-server/internal/domains/security/entities"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
"hyapi-server/internal/shared/ipgeo"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AdminSecurityHandler 管理员安全数据处理器
|
||||
type AdminSecurityHandler struct {
|
||||
db *gorm.DB
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
ipLocator *ipgeo.Locator
|
||||
}
|
||||
|
||||
func NewAdminSecurityHandler(
|
||||
db *gorm.DB,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
ipLocator *ipgeo.Locator,
|
||||
) *AdminSecurityHandler {
|
||||
return &AdminSecurityHandler{
|
||||
db: db,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
ipLocator: ipLocator,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AdminSecurityHandler) getIntQuery(c *gin.Context, key string, defaultValue int) int {
|
||||
if value := c.Query(key); value != "" {
|
||||
if intValue, err := strconv.Atoi(value); err == nil && intValue > 0 {
|
||||
return intValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func (h *AdminSecurityHandler) parseRange(c *gin.Context) (time.Time, time.Time, bool) {
|
||||
startTime := time.Now().Add(-24 * time.Hour)
|
||||
endTime := time.Now()
|
||||
|
||||
if start := strings.TrimSpace(c.Query("start_time")); start != "" {
|
||||
t, err := time.Parse("2006-01-02 15:04:05", start)
|
||||
if err != nil {
|
||||
h.responseBuilder.BadRequest(c, "start_time格式错误,示例:2026-03-19 10:00:00")
|
||||
return time.Time{}, time.Time{}, false
|
||||
}
|
||||
startTime = t
|
||||
}
|
||||
if end := strings.TrimSpace(c.Query("end_time")); end != "" {
|
||||
t, err := time.Parse("2006-01-02 15:04:05", end)
|
||||
if err != nil {
|
||||
h.responseBuilder.BadRequest(c, "end_time格式错误,示例:2026-03-19 12:00:00")
|
||||
return time.Time{}, time.Time{}, false
|
||||
}
|
||||
endTime = t
|
||||
}
|
||||
return startTime, endTime, true
|
||||
}
|
||||
|
||||
// ListSuspiciousIPs 获取可疑IP列表
|
||||
func (h *AdminSecurityHandler) ListSuspiciousIPs(c *gin.Context) {
|
||||
page := h.getIntQuery(c, "page", 1)
|
||||
pageSize := h.getIntQuery(c, "page_size", 20)
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
startTime, endTime, ok := h.parseRange(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ip := strings.TrimSpace(c.Query("ip"))
|
||||
path := strings.TrimSpace(c.Query("path"))
|
||||
|
||||
query := h.db.Model(&securityEntities.SuspiciousIPRecord{}).
|
||||
Where("created_at >= ? AND created_at <= ?", startTime, endTime)
|
||||
if ip != "" {
|
||||
query = query.Where("ip = ?", ip)
|
||||
}
|
||||
if path != "" {
|
||||
query = query.Where("path LIKE ?", "%"+path+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
h.logger.Error("查询可疑IP总数失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
var items []securityEntities.SuspiciousIPRecord
|
||||
if err := query.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&items).Error; err != nil {
|
||||
h.logger.Error("查询可疑IP列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, gin.H{
|
||||
"items": items,
|
||||
"total": total,
|
||||
}, "获取成功")
|
||||
}
|
||||
|
||||
type geoStreamRow struct {
|
||||
IP string `json:"ip"`
|
||||
Path string `json:"path"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// GetSuspiciousIPGeoStream 获取地球请求流数据
|
||||
func (h *AdminSecurityHandler) GetSuspiciousIPGeoStream(c *gin.Context) {
|
||||
startTime, endTime, ok := h.parseRange(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
topN := h.getIntQuery(c, "top_n", 200)
|
||||
if topN > 1000 {
|
||||
topN = 1000
|
||||
}
|
||||
|
||||
var rows []geoStreamRow
|
||||
err := h.db.Model(&securityEntities.SuspiciousIPRecord{}).
|
||||
Select("ip, path, COUNT(1) as count").
|
||||
Where("created_at >= ? AND created_at <= ?", startTime, endTime).
|
||||
Group("ip, path").
|
||||
Order("count DESC").
|
||||
Limit(topN).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
h.logger.Error("查询地球请求流失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 目标固定服务器点位(上海)
|
||||
const serverName = "HYAPI-Server"
|
||||
const serverLng = 121.4737
|
||||
const serverLat = 31.2304
|
||||
|
||||
result := make([]gin.H, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
record := securityEntities.SuspiciousIPRecord{IP: row.IP}
|
||||
fromName, fromLng, fromLat := h.ipLocator.ToGeoPoint(record)
|
||||
result = append(result, gin.H{
|
||||
"from_name": fromName,
|
||||
"from_lng": fromLng,
|
||||
"from_lat": fromLat,
|
||||
"to_name": serverName,
|
||||
"to_lng": serverLng,
|
||||
"to_lat": serverLat,
|
||||
"value": row.Count,
|
||||
"path": row.Path,
|
||||
"ip": row.IP,
|
||||
})
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取成功")
|
||||
}
|
||||
411
internal/infrastructure/http/handlers/announcement_handler.go
Normal file
411
internal/infrastructure/http/handlers/announcement_handler.go
Normal file
@@ -0,0 +1,411 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/application/article"
|
||||
"hyapi-server/internal/application/article/dto/commands"
|
||||
appQueries "hyapi-server/internal/application/article/dto/queries"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AnnouncementHandler 公告HTTP处理器
|
||||
type AnnouncementHandler struct {
|
||||
appService article.AnnouncementApplicationService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
validator interfaces.RequestValidator
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAnnouncementHandler 创建公告HTTP处理器
|
||||
func NewAnnouncementHandler(
|
||||
appService article.AnnouncementApplicationService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
validator interfaces.RequestValidator,
|
||||
logger *zap.Logger,
|
||||
) *AnnouncementHandler {
|
||||
return &AnnouncementHandler{
|
||||
appService: appService,
|
||||
responseBuilder: responseBuilder,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAnnouncement 创建公告
|
||||
// @Summary 创建公告
|
||||
// @Description 创建新的公告
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.CreateAnnouncementCommand true "创建公告请求"
|
||||
// @Success 201 {object} map[string]interface{} "公告创建成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements [post]
|
||||
func (h *AnnouncementHandler) CreateAnnouncement(c *gin.Context) {
|
||||
var cmd commands.CreateAnnouncementCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 验证用户是否已登录
|
||||
if _, exists := c.Get("user_id"); !exists {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.CreateAnnouncement(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("创建公告失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, nil, "公告创建成功")
|
||||
}
|
||||
|
||||
// GetAnnouncementByID 获取公告详情
|
||||
// @Summary 获取公告详情
|
||||
// @Description 根据ID获取公告详情
|
||||
// @Tags 公告管理-用户端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "公告ID"
|
||||
// @Success 200 {object} responses.AnnouncementInfoResponse "获取公告详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 404 {object} map[string]interface{} "公告不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/announcements/{id} [get]
|
||||
func (h *AnnouncementHandler) GetAnnouncementByID(c *gin.Context) {
|
||||
var query appQueries.GetAnnouncementQuery
|
||||
|
||||
// 绑定URI参数(公告ID)
|
||||
if err := h.validator.ValidateParam(c, &query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.appService.GetAnnouncementByID(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取公告详情失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, "公告不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取公告详情成功")
|
||||
}
|
||||
|
||||
// ListAnnouncements 获取公告列表
|
||||
// @Summary 获取公告列表
|
||||
// @Description 分页获取公告列表,支持多种筛选条件
|
||||
// @Tags 公告管理-用户端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param status query string false "公告状态"
|
||||
// @Param title query string false "标题关键词"
|
||||
// @Param order_by query string false "排序字段"
|
||||
// @Param order_dir query string false "排序方向"
|
||||
// @Success 200 {object} responses.AnnouncementListResponse "获取公告列表成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/announcements [get]
|
||||
func (h *AnnouncementHandler) ListAnnouncements(c *gin.Context) {
|
||||
var query appQueries.ListAnnouncementQuery
|
||||
if err := h.validator.ValidateQuery(c, &query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if query.Page <= 0 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize <= 0 {
|
||||
query.PageSize = 10
|
||||
}
|
||||
if query.PageSize > 100 {
|
||||
query.PageSize = 100
|
||||
}
|
||||
|
||||
response, err := h.appService.ListAnnouncements(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取公告列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取公告列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取公告列表成功")
|
||||
}
|
||||
|
||||
// PublishAnnouncement 发布公告
|
||||
// @Summary 发布公告
|
||||
// @Description 发布指定的公告
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "公告ID"
|
||||
// @Success 200 {object} map[string]interface{} "发布成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements/{id}/publish [post]
|
||||
func (h *AnnouncementHandler) PublishAnnouncement(c *gin.Context) {
|
||||
var cmd commands.PublishAnnouncementCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.PublishAnnouncement(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("发布公告失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "发布成功")
|
||||
}
|
||||
|
||||
// WithdrawAnnouncement 撤回公告
|
||||
// @Summary 撤回公告
|
||||
// @Description 撤回已发布的公告
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "公告ID"
|
||||
// @Success 200 {object} map[string]interface{} "撤回成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements/{id}/withdraw [post]
|
||||
func (h *AnnouncementHandler) WithdrawAnnouncement(c *gin.Context) {
|
||||
var cmd commands.WithdrawAnnouncementCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.WithdrawAnnouncement(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("撤回公告失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "撤回成功")
|
||||
}
|
||||
|
||||
// ArchiveAnnouncement 归档公告
|
||||
// @Summary 归档公告
|
||||
// @Description 归档指定的公告
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "公告ID"
|
||||
// @Success 200 {object} map[string]interface{} "归档成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements/{id}/archive [post]
|
||||
func (h *AnnouncementHandler) ArchiveAnnouncement(c *gin.Context) {
|
||||
var cmd commands.ArchiveAnnouncementCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.ArchiveAnnouncement(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("归档公告失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "归档成功")
|
||||
}
|
||||
|
||||
// UpdateAnnouncement 更新公告
|
||||
// @Summary 更新公告
|
||||
// @Description 更新指定的公告
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "公告ID"
|
||||
// @Param request body commands.UpdateAnnouncementCommand true "更新公告请求"
|
||||
// @Success 200 {object} map[string]interface{} "更新成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements/{id} [put]
|
||||
func (h *AnnouncementHandler) UpdateAnnouncement(c *gin.Context) {
|
||||
var cmd commands.UpdateAnnouncementCommand
|
||||
|
||||
// 先绑定URI参数(公告ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 再绑定JSON请求体(公告信息)
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.UpdateAnnouncement(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("更新公告失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "更新成功")
|
||||
}
|
||||
|
||||
// DeleteAnnouncement 删除公告
|
||||
// @Summary 删除公告
|
||||
// @Description 删除指定的公告
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "公告ID"
|
||||
// @Success 200 {object} map[string]interface{} "删除成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements/{id} [delete]
|
||||
func (h *AnnouncementHandler) DeleteAnnouncement(c *gin.Context) {
|
||||
var cmd commands.DeleteAnnouncementCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.DeleteAnnouncement(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("删除公告失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "删除成功")
|
||||
}
|
||||
|
||||
// SchedulePublishAnnouncement 定时发布公告
|
||||
// @Summary 定时发布公告
|
||||
// @Description 设置公告的定时发布时间
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "公告ID"
|
||||
// @Param request body commands.SchedulePublishAnnouncementCommand true "定时发布请求"
|
||||
// @Success 200 {object} map[string]interface{} "设置成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements/{id}/schedule-publish [post]
|
||||
func (h *AnnouncementHandler) SchedulePublishAnnouncement(c *gin.Context) {
|
||||
var cmd commands.SchedulePublishAnnouncementCommand
|
||||
|
||||
// 先绑定URI参数(公告ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 再绑定JSON请求体(定时发布时间)
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.SchedulePublishAnnouncement(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("设置定时发布失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "设置成功")
|
||||
}
|
||||
|
||||
// UpdateSchedulePublishAnnouncement 更新定时发布公告
|
||||
// @Summary 更新定时发布公告
|
||||
// @Description 修改公告的定时发布时间
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "公告ID"
|
||||
// @Param request body commands.UpdateSchedulePublishAnnouncementCommand true "更新定时发布请求"
|
||||
// @Success 200 {object} map[string]interface{} "更新成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements/{id}/update-schedule-publish [post]
|
||||
func (h *AnnouncementHandler) UpdateSchedulePublishAnnouncement(c *gin.Context) {
|
||||
var cmd commands.UpdateSchedulePublishAnnouncementCommand
|
||||
|
||||
// 先绑定URI参数(公告ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 再绑定JSON请求体(定时发布时间)
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.UpdateSchedulePublishAnnouncement(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("更新定时发布时间失败", zap.String("id", cmd.ID), zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "更新成功")
|
||||
}
|
||||
|
||||
// CancelSchedulePublishAnnouncement 取消定时发布公告
|
||||
// @Summary 取消定时发布公告
|
||||
// @Description 取消公告的定时发布
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "公告ID"
|
||||
// @Success 200 {object} map[string]interface{} "取消成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements/{id}/cancel-schedule [post]
|
||||
func (h *AnnouncementHandler) CancelSchedulePublishAnnouncement(c *gin.Context) {
|
||||
var cmd commands.CancelSchedulePublishAnnouncementCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.CancelSchedulePublishAnnouncement(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("取消定时发布失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "取消成功")
|
||||
}
|
||||
|
||||
// GetAnnouncementStats 获取公告统计信息
|
||||
// @Summary 获取公告统计信息
|
||||
// @Description 获取公告的统计数据
|
||||
// @Tags 公告管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.AnnouncementStatsResponse "获取统计信息成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/announcements/stats [get]
|
||||
func (h *AnnouncementHandler) GetAnnouncementStats(c *gin.Context) {
|
||||
response, err := h.appService.GetAnnouncementStats(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取公告统计信息失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取统计信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取统计信息成功")
|
||||
}
|
||||
666
internal/infrastructure/http/handlers/api_handler.go
Normal file
666
internal/infrastructure/http/handlers/api_handler.go
Normal file
@@ -0,0 +1,666 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
"hyapi-server/internal/application/api"
|
||||
"hyapi-server/internal/application/api/commands"
|
||||
"hyapi-server/internal/application/api/dto"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ApiHandler API调用HTTP处理器
|
||||
type ApiHandler struct {
|
||||
appService api.ApiApplicationService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
validator interfaces.RequestValidator
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewApiHandler 创建API调用HTTP处理器
|
||||
func NewApiHandler(
|
||||
appService api.ApiApplicationService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
validator interfaces.RequestValidator,
|
||||
logger *zap.Logger,
|
||||
) *ApiHandler {
|
||||
return &ApiHandler{
|
||||
appService: appService,
|
||||
responseBuilder: responseBuilder,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// HandleApiCall 统一API调用入口
|
||||
// @Summary API调用
|
||||
// @Description 统一API调用入口,参数加密传输
|
||||
// @Tags API调用
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.ApiCallCommand true "API调用请求"
|
||||
// @Success 200 {object} dto.ApiCallResponse "调用成功"
|
||||
// @Failure 400 {object} dto.ApiCallResponse "请求参数错误"
|
||||
// @Failure 401 {object} dto.ApiCallResponse "未授权"
|
||||
// @Failure 429 {object} dto.ApiCallResponse "请求过于频繁"
|
||||
// @Failure 500 {object} dto.ApiCallResponse "服务器内部错误"
|
||||
// @Router /api/v1/:api_name [post]
|
||||
func (h *ApiHandler) HandleApiCall(c *gin.Context) {
|
||||
// 1. 基础参数校验
|
||||
accessId := c.GetHeader("Access-Id")
|
||||
if accessId == "" {
|
||||
response := dto.NewErrorResponse(1005, "缺少Access-Id", "")
|
||||
c.JSON(200, response)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 绑定和校验请求参数
|
||||
var cmd commands.ApiCallCommand
|
||||
cmd.ClientIP = c.ClientIP()
|
||||
cmd.AccessId = accessId
|
||||
cmd.ApiName = c.Param("api_name")
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
response := dto.NewErrorResponse(1003, "请求参数结构不正确", "")
|
||||
c.JSON(200, response)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 调用应用服务
|
||||
transactionId, encryptedResp, err := h.appService.CallApi(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
// 根据错误类型返回对应的错误码和预定义错误消息
|
||||
errorCode := api.GetErrorCode(err)
|
||||
errorMessage := api.GetErrorMessage(err)
|
||||
response := dto.NewErrorResponse(errorCode, errorMessage, transactionId)
|
||||
c.JSON(200, response) // API调用接口统一返回200状态码
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 返回成功响应
|
||||
response := dto.NewSuccessResponse(transactionId, encryptedResp)
|
||||
c.JSON(200, response)
|
||||
}
|
||||
|
||||
// GetUserApiKeys 获取用户API密钥
|
||||
func (h *ApiHandler) GetUserApiKeys(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.appService.GetUserApiKeys(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户API密钥失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取API密钥成功")
|
||||
}
|
||||
|
||||
// GetUserWhiteList 获取用户白名单列表
|
||||
func (h *ApiHandler) GetUserWhiteList(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取查询参数
|
||||
remarkKeyword := c.Query("remark") // 备注模糊查询关键词
|
||||
|
||||
result, err := h.appService.GetUserWhiteList(c.Request.Context(), userID, remarkKeyword)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户白名单失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取白名单成功")
|
||||
}
|
||||
|
||||
// AddWhiteListIP 添加白名单IP
|
||||
func (h *ApiHandler) AddWhiteListIP(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.WhiteListRequest
|
||||
if err := h.validator.BindAndValidate(c, &req); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.appService.AddWhiteListIP(c.Request.Context(), userID, req.IPAddress, req.Remark)
|
||||
if err != nil {
|
||||
h.logger.Error("添加白名单IP失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "添加白名单IP成功")
|
||||
}
|
||||
|
||||
// DeleteWhiteListIP 删除白名单IP
|
||||
// @Summary 删除白名单IP
|
||||
// @Description 从当前用户的白名单中删除指定IP地址
|
||||
// @Tags API管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param ip path string true "IP地址"
|
||||
// @Success 200 {object} map[string]interface{} "删除白名单IP成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/my/whitelist/{ip} [delete]
|
||||
func (h *ApiHandler) DeleteWhiteListIP(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
ipAddress := c.Param("ip")
|
||||
if ipAddress == "" {
|
||||
h.responseBuilder.BadRequest(c, "IP地址不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.appService.DeleteWhiteListIP(c.Request.Context(), userID, ipAddress)
|
||||
if err != nil {
|
||||
h.logger.Error("删除白名单IP失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "删除白名单IP成功")
|
||||
}
|
||||
|
||||
// EncryptParams 加密参数接口(用于前端调试)
|
||||
// @Summary 加密参数
|
||||
// @Description 用于前端调试时加密API调用参数
|
||||
// @Tags API调试
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.EncryptCommand true "加密请求"
|
||||
// @Success 200 {object} dto.EncryptResponse "加密成功"
|
||||
// @Failure 400 {object} dto.EncryptResponse "请求参数错误"
|
||||
// @Failure 401 {object} dto.EncryptResponse "未授权"
|
||||
// @Router /api/v1/encrypt [post]
|
||||
func (h *ApiHandler) EncryptParams(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.EncryptCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 调用应用服务层进行加密
|
||||
encryptedData, err := h.appService.EncryptParams(c.Request.Context(), userID, &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("加密参数失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "加密参数失败")
|
||||
return
|
||||
}
|
||||
|
||||
response := dto.EncryptResponse{
|
||||
EncryptedData: encryptedData,
|
||||
}
|
||||
h.responseBuilder.Success(c, response, "加密成功")
|
||||
}
|
||||
|
||||
// DecryptParams 解密参数
|
||||
// @Summary 解密参数
|
||||
// @Description 使用密钥解密加密的数据
|
||||
// @Tags API调试
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.DecryptCommand true "解密请求"
|
||||
// @Success 200 {object} map[string]interface{} "解密成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未授权"
|
||||
// @Failure 500 {object} map[string]interface{} "解密失败"
|
||||
// @Router /api/v1/decrypt [post]
|
||||
func (h *ApiHandler) DecryptParams(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.DecryptCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 调用应用服务层进行解密
|
||||
decryptedData, err := h.appService.DecryptParams(c.Request.Context(), userID, &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("解密参数失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "解密参数失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, decryptedData, "解密成功")
|
||||
}
|
||||
|
||||
// GetFormConfig 获取指定API的表单配置
|
||||
// @Summary 获取表单配置
|
||||
// @Description 获取指定API的表单配置,用于前端动态生成表单
|
||||
// @Tags API调试
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param api_code path string true "API代码"
|
||||
// @Success 200 {object} map[string]interface{} "获取成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未授权"
|
||||
// @Failure 404 {object} map[string]interface{} "API接口不存在"
|
||||
// @Router /api/v1/form-config/{api_code} [get]
|
||||
func (h *ApiHandler) GetFormConfig(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
apiCode := c.Param("api_code")
|
||||
if apiCode == "" {
|
||||
h.responseBuilder.BadRequest(c, "API代码不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("获取表单配置", zap.String("api_code", apiCode), zap.String("user_id", userID))
|
||||
|
||||
// 获取表单配置
|
||||
config, err := h.appService.GetFormConfig(c.Request.Context(), apiCode)
|
||||
if err != nil {
|
||||
h.logger.Error("获取表单配置失败", zap.String("api_code", apiCode), zap.String("user_id", userID), zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "获取表单配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
h.responseBuilder.BadRequest(c, "API接口不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("获取表单配置成功", zap.String("api_code", apiCode), zap.String("user_id", userID), zap.Int("field_count", len(config.Fields)))
|
||||
h.responseBuilder.Success(c, config, "获取表单配置成功")
|
||||
}
|
||||
|
||||
// getCurrentUserID 获取当前用户ID
|
||||
func (h *ApiHandler) getCurrentUserID(c *gin.Context) string {
|
||||
if userID, exists := c.Get("user_id"); exists {
|
||||
if id, ok := userID.(string); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetUserApiCalls 获取用户API调用记录
|
||||
// @Summary 获取用户API调用记录
|
||||
// @Description 获取当前用户的API调用记录列表,支持分页和筛选
|
||||
// @Tags API管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param start_time query string false "开始时间 (格式: 2006-01-02 15:04:05)"
|
||||
// @Param end_time query string false "结束时间 (格式: 2006-01-02 15:04:05)"
|
||||
// @Param transaction_id query string false "交易ID"
|
||||
// @Param product_name query string false "产品名称"
|
||||
// @Param status query string false "状态 (pending/success/failed)"
|
||||
// @Success 200 {object} dto.ApiCallListResponse "获取成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/my/api-calls [get]
|
||||
func (h *ApiHandler) GetUserApiCalls(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析查询参数
|
||||
page := h.getIntQuery(c, "page", 1)
|
||||
pageSize := h.getIntQuery(c, "page_size", 10)
|
||||
|
||||
// 构建筛选条件
|
||||
filters := make(map[string]interface{})
|
||||
|
||||
// 时间范围筛选
|
||||
if startTime := c.Query("start_time"); startTime != "" {
|
||||
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
|
||||
filters["start_time"] = t
|
||||
}
|
||||
}
|
||||
if endTime := c.Query("end_time"); endTime != "" {
|
||||
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
|
||||
filters["end_time"] = t
|
||||
}
|
||||
}
|
||||
|
||||
// 交易ID筛选
|
||||
if transactionId := c.Query("transaction_id"); transactionId != "" {
|
||||
filters["transaction_id"] = transactionId
|
||||
}
|
||||
|
||||
// 产品名称筛选
|
||||
if productName := c.Query("product_name"); productName != "" {
|
||||
filters["product_name"] = productName
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if status := c.Query("status"); status != "" {
|
||||
filters["status"] = status
|
||||
}
|
||||
|
||||
// 构建分页选项
|
||||
options := interfaces.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Sort: "created_at",
|
||||
Order: "desc",
|
||||
}
|
||||
|
||||
result, err := h.appService.GetUserApiCalls(c.Request.Context(), userID, filters, options)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户API调用记录失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "获取API调用记录失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取API调用记录成功")
|
||||
}
|
||||
|
||||
// GetAdminApiCalls 获取管理端API调用记录
|
||||
// @Summary 获取管理端API调用记录
|
||||
// @Description 管理员获取API调用记录,支持筛选和分页
|
||||
// @Tags API管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param user_id query string false "用户ID"
|
||||
// @Param transaction_id query string false "交易ID"
|
||||
// @Param product_name query string false "产品名称"
|
||||
// @Param status query string false "状态"
|
||||
// @Param start_time query string false "开始时间" format(date-time)
|
||||
// @Param end_time query string false "结束时间" format(date-time)
|
||||
// @Param sort_by query string false "排序字段"
|
||||
// @Param sort_order query string false "排序方向" Enums(asc, desc)
|
||||
// @Success 200 {object} dto.ApiCallListResponse "获取API调用记录成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/api-calls [get]
|
||||
func (h *ApiHandler) GetAdminApiCalls(c *gin.Context) {
|
||||
// 解析查询参数
|
||||
page := h.getIntQuery(c, "page", 1)
|
||||
pageSize := h.getIntQuery(c, "page_size", 10)
|
||||
|
||||
// 构建筛选条件
|
||||
filters := make(map[string]interface{})
|
||||
|
||||
// 用户ID筛选
|
||||
if userId := c.Query("user_id"); userId != "" {
|
||||
filters["user_id"] = userId
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if startTime := c.Query("start_time"); startTime != "" {
|
||||
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
|
||||
filters["start_time"] = t
|
||||
}
|
||||
}
|
||||
if endTime := c.Query("end_time"); endTime != "" {
|
||||
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
|
||||
filters["end_time"] = t
|
||||
}
|
||||
}
|
||||
|
||||
// 交易ID筛选
|
||||
if transactionId := c.Query("transaction_id"); transactionId != "" {
|
||||
filters["transaction_id"] = transactionId
|
||||
}
|
||||
|
||||
// 产品名称筛选
|
||||
if productName := c.Query("product_name"); productName != "" {
|
||||
filters["product_name"] = productName
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if status := c.Query("status"); status != "" {
|
||||
filters["status"] = status
|
||||
}
|
||||
|
||||
// 构建分页选项
|
||||
options := interfaces.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Sort: "created_at",
|
||||
Order: "desc",
|
||||
}
|
||||
|
||||
result, err := h.appService.GetAdminApiCalls(c.Request.Context(), filters, options)
|
||||
if err != nil {
|
||||
h.logger.Error("获取管理端API调用记录失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "获取API调用记录失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取API调用记录成功")
|
||||
}
|
||||
|
||||
// ExportAdminApiCalls 导出管理端API调用记录
|
||||
// @Summary 导出管理端API调用记录
|
||||
// @Description 管理员导出API调用记录,支持Excel和CSV格式
|
||||
// @Tags API调用管理
|
||||
// @Accept json
|
||||
// @Produce application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv
|
||||
// @Security Bearer
|
||||
// @Param user_ids query string false "用户ID列表,逗号分隔"
|
||||
// @Param product_ids query string false "产品ID列表,逗号分隔"
|
||||
// @Param start_time query string false "开始时间" format(date-time)
|
||||
// @Param end_time query string false "结束时间" format(date-time)
|
||||
// @Param format query string false "导出格式" Enums(excel, csv) default(excel)
|
||||
// @Success 200 {file} file "导出文件"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/api-calls/export [get]
|
||||
func (h *ApiHandler) ExportAdminApiCalls(c *gin.Context) {
|
||||
// 解析查询参数
|
||||
filters := make(map[string]interface{})
|
||||
|
||||
// 用户ID筛选
|
||||
if userIds := c.Query("user_ids"); userIds != "" {
|
||||
filters["user_ids"] = userIds
|
||||
}
|
||||
|
||||
// 产品ID筛选
|
||||
if productIds := c.Query("product_ids"); productIds != "" {
|
||||
filters["product_ids"] = productIds
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if startTime := c.Query("start_time"); startTime != "" {
|
||||
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
|
||||
filters["start_time"] = t
|
||||
}
|
||||
}
|
||||
if endTime := c.Query("end_time"); endTime != "" {
|
||||
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
|
||||
filters["end_time"] = t
|
||||
}
|
||||
}
|
||||
|
||||
// 获取导出格式,默认为excel
|
||||
format := c.DefaultQuery("format", "excel")
|
||||
if format != "excel" && format != "csv" {
|
||||
h.responseBuilder.BadRequest(c, "不支持的导出格式")
|
||||
return
|
||||
}
|
||||
|
||||
// 调用应用服务导出数据
|
||||
fileData, err := h.appService.ExportAdminApiCalls(c.Request.Context(), filters, format)
|
||||
if err != nil {
|
||||
h.logger.Error("导出API调用记录失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "导出API调用记录失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
contentType := "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
filename := "API调用记录.xlsx"
|
||||
if format == "csv" {
|
||||
contentType = "text/csv;charset=utf-8"
|
||||
filename = "API调用记录.csv"
|
||||
}
|
||||
|
||||
c.Header("Content-Type", contentType)
|
||||
c.Header("Content-Disposition", "attachment; filename="+filename)
|
||||
c.Data(200, contentType, fileData)
|
||||
}
|
||||
|
||||
// getIntQuery 获取整数查询参数
|
||||
func (h *ApiHandler) getIntQuery(c *gin.Context, key string, defaultValue int) int {
|
||||
if value := c.Query(key); value != "" {
|
||||
if intValue, err := strconv.Atoi(value); err == nil && intValue > 0 {
|
||||
return intValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetUserBalanceAlertSettings 获取用户余额预警设置
|
||||
// @Summary 获取用户余额预警设置
|
||||
// @Description 获取当前用户的余额预警配置
|
||||
// @Tags 用户设置
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{} "获取成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未授权"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/user/balance-alert/settings [get]
|
||||
func (h *ApiHandler) GetUserBalanceAlertSettings(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := h.appService.GetUserBalanceAlertSettings(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户余额预警设置失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取预警设置失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, settings, "获取成功")
|
||||
}
|
||||
|
||||
// UpdateUserBalanceAlertSettings 更新用户余额预警设置
|
||||
// @Summary 更新用户余额预警设置
|
||||
// @Description 更新当前用户的余额预警配置
|
||||
// @Tags 用户设置
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body map[string]interface{} true "预警设置"
|
||||
// @Success 200 {object} map[string]interface{} "更新成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未授权"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/user/balance-alert/settings [put]
|
||||
func (h *ApiHandler) UpdateUserBalanceAlertSettings(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var request struct {
|
||||
Enabled bool `json:"enabled" binding:"required"`
|
||||
Threshold float64 `json:"threshold" binding:"required,min=0"`
|
||||
AlertPhone string `json:"alert_phone" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err := h.appService.UpdateUserBalanceAlertSettings(c.Request.Context(), userID, request.Enabled, request.Threshold, request.AlertPhone)
|
||||
if err != nil {
|
||||
h.logger.Error("更新用户余额预警设置失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "更新预警设置失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, gin.H{}, "更新成功")
|
||||
}
|
||||
|
||||
// TestBalanceAlertSms 测试余额预警短信
|
||||
// @Summary 测试余额预警短信
|
||||
// @Description 发送测试预警短信到指定手机号
|
||||
// @Tags 用户设置
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body map[string]interface{} true "测试参数"
|
||||
// @Success 200 {object} map[string]interface{} "发送成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未授权"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/user/balance-alert/test-sms [post]
|
||||
func (h *ApiHandler) TestBalanceAlertSms(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var request struct {
|
||||
Phone string `json:"phone" binding:"required,len=11"`
|
||||
Balance float64 `json:"balance" binding:"required"`
|
||||
AlertType string `json:"alert_type" binding:"required,oneof=low_balance arrears"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err := h.appService.TestBalanceAlertSms(c.Request.Context(), userID, request.Phone, request.Balance, request.AlertType)
|
||||
if err != nil {
|
||||
h.logger.Error("发送测试预警短信失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "发送测试短信失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, gin.H{}, "测试短信发送成功")
|
||||
}
|
||||
776
internal/infrastructure/http/handlers/article_handler.go
Normal file
776
internal/infrastructure/http/handlers/article_handler.go
Normal file
@@ -0,0 +1,776 @@
|
||||
//nolint:unused
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/application/article"
|
||||
"hyapi-server/internal/application/article/dto/commands"
|
||||
appQueries "hyapi-server/internal/application/article/dto/queries"
|
||||
_ "hyapi-server/internal/application/article/dto/responses"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ArticleHandler 文章HTTP处理器
|
||||
type ArticleHandler struct {
|
||||
appService article.ArticleApplicationService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
validator interfaces.RequestValidator
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewArticleHandler 创建文章HTTP处理器
|
||||
func NewArticleHandler(
|
||||
appService article.ArticleApplicationService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
validator interfaces.RequestValidator,
|
||||
logger *zap.Logger,
|
||||
) *ArticleHandler {
|
||||
return &ArticleHandler{
|
||||
appService: appService,
|
||||
responseBuilder: responseBuilder,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateArticle 创建文章
|
||||
// @Summary 创建文章
|
||||
// @Description 创建新的文章
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.CreateArticleCommand true "创建文章请求"
|
||||
// @Success 201 {object} map[string]interface{} "文章创建成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles [post]
|
||||
func (h *ArticleHandler) CreateArticle(c *gin.Context) {
|
||||
var cmd commands.CreateArticleCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 验证用户是否已登录
|
||||
if _, exists := c.Get("user_id"); !exists {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.CreateArticle(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("创建文章失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, nil, "文章创建成功")
|
||||
}
|
||||
|
||||
// GetArticleByID 获取文章详情
|
||||
// @Summary 获取文章详情
|
||||
// @Description 根据ID获取文章详情
|
||||
// @Tags 文章管理-用户端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "文章ID"
|
||||
// @Success 200 {object} responses.ArticleInfoResponse "获取文章详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 404 {object} map[string]interface{} "文章不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/articles/{id} [get]
|
||||
func (h *ArticleHandler) GetArticleByID(c *gin.Context) {
|
||||
var query appQueries.GetArticleQuery
|
||||
|
||||
// 绑定URI参数(文章ID)
|
||||
if err := h.validator.ValidateParam(c, &query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.appService.GetArticleByID(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取文章详情失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, "文章不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取文章详情成功")
|
||||
}
|
||||
|
||||
// ListArticles 获取文章列表
|
||||
// @Summary 获取文章列表
|
||||
// @Description 分页获取文章列表,支持多种筛选条件
|
||||
// @Tags 文章管理-用户端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param status query string false "文章状态"
|
||||
// @Param category_id query string false "分类ID"
|
||||
// @Param tag_id query string false "标签ID"
|
||||
// @Param title query string false "标题关键词"
|
||||
// @Param summary query string false "摘要关键词"
|
||||
// @Param is_featured query bool false "是否推荐"
|
||||
// @Param order_by query string false "排序字段"
|
||||
// @Param order_dir query string false "排序方向"
|
||||
// @Success 200 {object} responses.ArticleListResponse "获取文章列表成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/articles [get]
|
||||
func (h *ArticleHandler) ListArticles(c *gin.Context) {
|
||||
var query appQueries.ListArticleQuery
|
||||
if err := h.validator.ValidateQuery(c, &query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if query.Page <= 0 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize <= 0 {
|
||||
query.PageSize = 10
|
||||
}
|
||||
if query.PageSize > 100 {
|
||||
query.PageSize = 100
|
||||
}
|
||||
|
||||
response, err := h.appService.ListArticles(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取文章列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取文章列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取文章列表成功")
|
||||
}
|
||||
|
||||
// ListArticlesForAdmin 获取文章列表(管理员端)
|
||||
// @Summary 获取文章列表(管理员端)
|
||||
// @Description 分页获取文章列表,支持多种筛选条件,包含所有状态的文章
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param status query string false "文章状态"
|
||||
// @Param category_id query string false "分类ID"
|
||||
// @Param tag_id query string false "标签ID"
|
||||
// @Param title query string false "标题关键词"
|
||||
// @Param summary query string false "摘要关键词"
|
||||
// @Param is_featured query bool false "是否推荐"
|
||||
// @Param order_by query string false "排序字段"
|
||||
// @Param order_dir query string false "排序方向"
|
||||
// @Success 200 {object} responses.ArticleListResponse "获取文章列表成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles [get]
|
||||
func (h *ArticleHandler) ListArticlesForAdmin(c *gin.Context) {
|
||||
var query appQueries.ListArticleQuery
|
||||
if err := h.validator.ValidateQuery(c, &query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if query.Page <= 0 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize <= 0 {
|
||||
query.PageSize = 10
|
||||
}
|
||||
if query.PageSize > 100 {
|
||||
query.PageSize = 100
|
||||
}
|
||||
|
||||
response, err := h.appService.ListArticlesForAdmin(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取文章列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取文章列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取文章列表成功")
|
||||
}
|
||||
|
||||
// UpdateArticle 更新文章
|
||||
// @Summary 更新文章
|
||||
// @Description 更新文章信息
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "文章ID"
|
||||
// @Param request body commands.UpdateArticleCommand true "更新文章请求"
|
||||
// @Success 200 {object} map[string]interface{} "文章更新成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "文章不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles/{id} [put]
|
||||
func (h *ArticleHandler) UpdateArticle(c *gin.Context) {
|
||||
var cmd commands.UpdateArticleCommand
|
||||
|
||||
// 先绑定URI参数(文章ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 再绑定JSON请求体(文章信息)
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.UpdateArticle(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("更新文章失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "文章更新成功")
|
||||
}
|
||||
|
||||
// DeleteArticle 删除文章
|
||||
// @Summary 删除文章
|
||||
// @Description 删除指定文章
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "文章ID"
|
||||
// @Success 200 {object} map[string]interface{} "文章删除成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "文章不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles/{id} [delete]
|
||||
func (h *ArticleHandler) DeleteArticle(c *gin.Context) {
|
||||
var cmd commands.DeleteArticleCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.DeleteArticle(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("删除文章失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "文章删除成功")
|
||||
}
|
||||
|
||||
// PublishArticle 发布文章
|
||||
// @Summary 发布文章
|
||||
// @Description 将草稿文章发布
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "文章ID"
|
||||
// @Success 200 {object} map[string]interface{} "文章发布成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "文章不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles/{id}/publish [post]
|
||||
func (h *ArticleHandler) PublishArticle(c *gin.Context) {
|
||||
var cmd commands.PublishArticleCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.PublishArticle(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("发布文章失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "文章发布成功")
|
||||
}
|
||||
|
||||
// SchedulePublishArticle 定时发布文章
|
||||
// @Summary 定时发布文章
|
||||
// @Description 设置文章的定时发布时间,支持格式:YYYY-MM-DD HH:mm:ss
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "文章ID"
|
||||
// @Param request body commands.SchedulePublishCommand true "定时发布请求"
|
||||
// @Success 200 {object} map[string]interface{} "定时发布设置成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "文章不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles/{id}/schedule-publish [post]
|
||||
func (h *ArticleHandler) SchedulePublishArticle(c *gin.Context) {
|
||||
var cmd commands.SchedulePublishCommand
|
||||
|
||||
// 先绑定URI参数(文章ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 再绑定JSON请求体(定时发布时间)
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.SchedulePublishArticle(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("设置定时发布失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "定时发布设置成功")
|
||||
}
|
||||
|
||||
// CancelSchedulePublishArticle 取消定时发布文章
|
||||
// @Summary 取消定时发布文章
|
||||
// @Description 取消文章的定时发布设置
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "文章ID"
|
||||
// @Success 200 {object} map[string]interface{} "取消定时发布成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "文章不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles/{id}/cancel-schedule [post]
|
||||
func (h *ArticleHandler) CancelSchedulePublishArticle(c *gin.Context) {
|
||||
var cmd commands.CancelScheduleCommand
|
||||
|
||||
// 绑定URI参数(文章ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.CancelSchedulePublishArticle(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("取消定时发布失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "取消定时发布成功")
|
||||
}
|
||||
|
||||
// ArchiveArticle 归档文章
|
||||
// @Summary 归档文章
|
||||
// @Description 将已发布文章归档
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "文章ID"
|
||||
// @Success 200 {object} map[string]interface{} "文章归档成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "文章不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles/{id}/archive [post]
|
||||
func (h *ArticleHandler) ArchiveArticle(c *gin.Context) {
|
||||
var cmd commands.ArchiveArticleCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.ArchiveArticle(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("归档文章失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "文章归档成功")
|
||||
}
|
||||
|
||||
// SetFeatured 设置推荐状态
|
||||
// @Summary 设置推荐状态
|
||||
// @Description 设置文章的推荐状态
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "文章ID"
|
||||
// @Param request body commands.SetFeaturedCommand true "设置推荐状态请求"
|
||||
// @Success 200 {object} map[string]interface{} "设置推荐状态成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "文章不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles/{id}/featured [put]
|
||||
func (h *ArticleHandler) SetFeatured(c *gin.Context) {
|
||||
var cmd commands.SetFeaturedCommand
|
||||
|
||||
// 先绑定URI参数(文章ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 再绑定JSON请求体(推荐状态)
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.SetFeatured(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("设置推荐状态失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "设置推荐状态成功")
|
||||
}
|
||||
|
||||
// GetArticleStats 获取文章统计
|
||||
// @Summary 获取文章统计
|
||||
// @Description 获取文章相关统计数据
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.ArticleStatsResponse "获取统计成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles/stats [get]
|
||||
func (h *ArticleHandler) GetArticleStats(c *gin.Context) {
|
||||
response, err := h.appService.GetArticleStats(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取文章统计失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取文章统计失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取统计成功")
|
||||
}
|
||||
|
||||
// UpdateSchedulePublishArticle 修改定时发布时间
|
||||
// @Summary 修改定时发布时间
|
||||
// @Description 修改文章的定时发布时间
|
||||
// @Tags 文章管理-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "文章ID"
|
||||
// @Param request body commands.SchedulePublishCommand true "修改定时发布请求"
|
||||
// @Success 200 {object} map[string]interface{} "修改定时发布时间成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "文章不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/articles/{id}/update-schedule-publish [post]
|
||||
func (h *ArticleHandler) UpdateSchedulePublishArticle(c *gin.Context) {
|
||||
var cmd commands.SchedulePublishCommand
|
||||
|
||||
// 先绑定URI参数(文章ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 再绑定JSON请求体(定时发布时间)
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.UpdateSchedulePublishArticle(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("修改定时发布时间失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "修改定时发布时间成功")
|
||||
}
|
||||
|
||||
// ==================== 分类相关方法 ====================
|
||||
|
||||
// ListCategories 获取分类列表
|
||||
// @Summary 获取分类列表
|
||||
// @Description 获取所有文章分类
|
||||
// @Tags 文章分类-用户端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} responses.CategoryListResponse "获取分类列表成功"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/article-categories [get]
|
||||
func (h *ArticleHandler) ListCategories(c *gin.Context) {
|
||||
response, err := h.appService.ListCategories(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取分类列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取分类列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取分类列表成功")
|
||||
}
|
||||
|
||||
// GetCategoryByID 获取分类详情
|
||||
// @Summary 获取分类详情
|
||||
// @Description 根据ID获取分类详情
|
||||
// @Tags 文章分类-用户端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "分类ID"
|
||||
// @Success 200 {object} responses.CategoryInfoResponse "获取分类详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 404 {object} map[string]interface{} "分类不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/article-categories/{id} [get]
|
||||
func (h *ArticleHandler) GetCategoryByID(c *gin.Context) {
|
||||
var query appQueries.GetCategoryQuery
|
||||
|
||||
// 绑定URI参数(分类ID)
|
||||
if err := h.validator.ValidateParam(c, &query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.appService.GetCategoryByID(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取分类详情失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, "分类不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取分类详情成功")
|
||||
}
|
||||
|
||||
// CreateCategory 创建分类
|
||||
// @Summary 创建分类
|
||||
// @Description 创建新的文章分类
|
||||
// @Tags 文章分类-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.CreateCategoryCommand true "创建分类请求"
|
||||
// @Success 201 {object} map[string]interface{} "分类创建成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/article-categories [post]
|
||||
func (h *ArticleHandler) CreateCategory(c *gin.Context) {
|
||||
var cmd commands.CreateCategoryCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.CreateCategory(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("创建分类失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, nil, "分类创建成功")
|
||||
}
|
||||
|
||||
// UpdateCategory 更新分类
|
||||
// @Summary 更新分类
|
||||
// @Description 更新分类信息
|
||||
// @Tags 文章分类-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "分类ID"
|
||||
// @Param request body commands.UpdateCategoryCommand true "更新分类请求"
|
||||
// @Success 200 {object} map[string]interface{} "分类更新成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "分类不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/article-categories/{id} [put]
|
||||
func (h *ArticleHandler) UpdateCategory(c *gin.Context) {
|
||||
var cmd commands.UpdateCategoryCommand
|
||||
|
||||
// 先绑定URI参数(分类ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 再绑定JSON请求体(分类信息)
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.UpdateCategory(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("更新分类失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "分类更新成功")
|
||||
}
|
||||
|
||||
// DeleteCategory 删除分类
|
||||
// @Summary 删除分类
|
||||
// @Description 删除指定分类
|
||||
// @Tags 文章分类-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "分类ID"
|
||||
// @Success 200 {object} map[string]interface{} "分类删除成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "分类不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/article-categories/{id} [delete]
|
||||
func (h *ArticleHandler) DeleteCategory(c *gin.Context) {
|
||||
var cmd commands.DeleteCategoryCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.DeleteCategory(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("删除分类失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "分类删除成功")
|
||||
}
|
||||
|
||||
// ==================== 标签相关方法 ====================
|
||||
|
||||
// ListTags 获取标签列表
|
||||
// @Summary 获取标签列表
|
||||
// @Description 获取所有文章标签
|
||||
// @Tags 文章标签-用户端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} responses.TagListResponse "获取标签列表成功"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/article-tags [get]
|
||||
func (h *ArticleHandler) ListTags(c *gin.Context) {
|
||||
response, err := h.appService.ListTags(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取标签列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取标签列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取标签列表成功")
|
||||
}
|
||||
|
||||
// GetTagByID 获取标签详情
|
||||
// @Summary 获取标签详情
|
||||
// @Description 根据ID获取标签详情
|
||||
// @Tags 文章标签-用户端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "标签ID"
|
||||
// @Success 200 {object} responses.TagInfoResponse "获取标签详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 404 {object} map[string]interface{} "标签不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/article-tags/{id} [get]
|
||||
func (h *ArticleHandler) GetTagByID(c *gin.Context) {
|
||||
var query appQueries.GetTagQuery
|
||||
|
||||
// 绑定URI参数(标签ID)
|
||||
if err := h.validator.ValidateParam(c, &query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.appService.GetTagByID(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取标签详情失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, "标签不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取标签详情成功")
|
||||
}
|
||||
|
||||
// CreateTag 创建标签
|
||||
// @Summary 创建标签
|
||||
// @Description 创建新的文章标签
|
||||
// @Tags 文章标签-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.CreateTagCommand true "创建标签请求"
|
||||
// @Success 201 {object} map[string]interface{} "标签创建成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/article-tags [post]
|
||||
func (h *ArticleHandler) CreateTag(c *gin.Context) {
|
||||
var cmd commands.CreateTagCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.CreateTag(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("创建标签失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, nil, "标签创建成功")
|
||||
}
|
||||
|
||||
// UpdateTag 更新标签
|
||||
// @Summary 更新标签
|
||||
// @Description 更新标签信息
|
||||
// @Tags 文章标签-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "标签ID"
|
||||
// @Param request body commands.UpdateTagCommand true "更新标签请求"
|
||||
// @Success 200 {object} map[string]interface{} "标签更新成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "标签不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/article-tags/{id} [put]
|
||||
func (h *ArticleHandler) UpdateTag(c *gin.Context) {
|
||||
var cmd commands.UpdateTagCommand
|
||||
|
||||
// 先绑定URI参数(标签ID)
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 再绑定JSON请求体(标签信息)
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.UpdateTag(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("更新标签失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "标签更新成功")
|
||||
}
|
||||
|
||||
// DeleteTag 删除标签
|
||||
// @Summary 删除标签
|
||||
// @Description 删除指定标签
|
||||
// @Tags 文章标签-管理端
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "标签ID"
|
||||
// @Success 200 {object} map[string]interface{} "标签删除成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "标签不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/article-tags/{id} [delete]
|
||||
func (h *ArticleHandler) DeleteTag(c *gin.Context) {
|
||||
var cmd commands.DeleteTagCommand
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.DeleteTag(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("删除标签失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "标签删除成功")
|
||||
}
|
||||
92
internal/infrastructure/http/handlers/captcha_handler.go
Normal file
92
internal/infrastructure/http/handlers/captcha_handler.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/config"
|
||||
"hyapi-server/internal/infrastructure/external/captcha"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// CaptchaHandler 验证码(滑块)HTTP 处理器
|
||||
type CaptchaHandler struct {
|
||||
captchaService *captcha.CaptchaService
|
||||
response interfaces.ResponseBuilder
|
||||
config *config.Config
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCaptchaHandler 创建验证码处理器
|
||||
func NewCaptchaHandler(
|
||||
captchaService *captcha.CaptchaService,
|
||||
response interfaces.ResponseBuilder,
|
||||
cfg *config.Config,
|
||||
logger *zap.Logger,
|
||||
) *CaptchaHandler {
|
||||
return &CaptchaHandler{
|
||||
captchaService: captchaService,
|
||||
response: response,
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// EncryptedSceneIdReq 获取加密场景 ID 的请求(可选参数)
|
||||
type EncryptedSceneIdReq struct {
|
||||
ExpireSeconds *int `form:"expire_seconds" json:"expire_seconds"` // 有效期秒数,1~86400,默认 3600
|
||||
}
|
||||
|
||||
// GetEncryptedSceneId 获取加密场景 ID,供前端加密模式初始化阿里云验证码
|
||||
// @Summary 获取验证码加密场景ID
|
||||
// @Description 用于加密模式下发 EncryptedSceneId,前端用此初始化滑块验证码
|
||||
// @Tags 验证码
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body EncryptedSceneIdReq false "可选:expire_seconds 有效期(1-86400),默认3600"
|
||||
// @Success 200 {object} map[string]interface{} "encryptedSceneId"
|
||||
// @Failure 400 {object} map[string]interface{} "配置未启用或参数错误"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/captcha/encryptedSceneId [post]
|
||||
func (h *CaptchaHandler) GetEncryptedSceneId(c *gin.Context) {
|
||||
expireSec := 3600
|
||||
if c.Request.ContentLength > 0 {
|
||||
var req EncryptedSceneIdReq
|
||||
if err := c.ShouldBindJSON(&req); err == nil && req.ExpireSeconds != nil {
|
||||
expireSec = *req.ExpireSeconds
|
||||
}
|
||||
}
|
||||
if expireSec <= 0 || expireSec > 86400 {
|
||||
h.response.BadRequest(c, "expire_seconds 必须在 1~86400 之间")
|
||||
return
|
||||
}
|
||||
|
||||
encrypted, err := h.captchaService.GetEncryptedSceneId(expireSec)
|
||||
if err != nil {
|
||||
if err == captcha.ErrCaptchaEncryptMissing || err == captcha.ErrCaptchaConfig {
|
||||
h.logger.Warn("验证码加密场景ID生成失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "验证码加密模式未配置或配置错误")
|
||||
return
|
||||
}
|
||||
h.logger.Error("验证码加密场景ID生成失败", zap.Error(err))
|
||||
h.response.InternalError(c, "生成失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, map[string]string{"encryptedSceneId": encrypted}, "ok")
|
||||
}
|
||||
|
||||
// GetConfig 获取验证码前端配置(是否启用、场景ID等),便于前端决定是否展示滑块
|
||||
// @Summary 获取验证码配置
|
||||
// @Description 返回是否启用滑块、场景ID(非加密模式用)
|
||||
// @Tags 验证码
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{} "captchaEnabled, sceneId"
|
||||
// @Router /api/v1/captcha/config [get]
|
||||
func (h *CaptchaHandler) GetConfig(c *gin.Context) {
|
||||
data := map[string]interface{}{
|
||||
"captchaEnabled": h.config.SMS.CaptchaEnabled,
|
||||
"sceneId": h.config.SMS.SceneID,
|
||||
}
|
||||
h.response.Success(c, data, "ok")
|
||||
}
|
||||
729
internal/infrastructure/http/handlers/certification_handler.go
Normal file
729
internal/infrastructure/http/handlers/certification_handler.go
Normal file
@@ -0,0 +1,729 @@
|
||||
//nolint:unused
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/application/certification"
|
||||
"hyapi-server/internal/application/certification/dto/commands"
|
||||
"hyapi-server/internal/application/certification/dto/queries"
|
||||
_ "hyapi-server/internal/application/certification/dto/responses"
|
||||
"hyapi-server/internal/infrastructure/external/storage"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
)
|
||||
|
||||
// CertificationHandler 认证HTTP处理器
|
||||
type CertificationHandler struct {
|
||||
appService certification.CertificationApplicationService
|
||||
response interfaces.ResponseBuilder
|
||||
validator interfaces.RequestValidator
|
||||
logger *zap.Logger
|
||||
jwtAuth *middleware.JWTAuthMiddleware
|
||||
storageService *storage.QiNiuStorageService
|
||||
}
|
||||
|
||||
// NewCertificationHandler 创建认证处理器
|
||||
func NewCertificationHandler(
|
||||
appService certification.CertificationApplicationService,
|
||||
response interfaces.ResponseBuilder,
|
||||
validator interfaces.RequestValidator,
|
||||
logger *zap.Logger,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
storageService *storage.QiNiuStorageService,
|
||||
) *CertificationHandler {
|
||||
return &CertificationHandler{
|
||||
appService: appService,
|
||||
response: response,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
jwtAuth: jwtAuth,
|
||||
storageService: storageService,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 认证申请管理 ================
|
||||
// GetCertification 获取认证详情
|
||||
// @Summary 获取认证详情
|
||||
// @Description 根据认证ID获取认证详情
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.CertificationResponse "获取认证详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certifications/details [get]
|
||||
func (h *CertificationHandler) GetCertification(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
query := &queries.GetCertificationQuery{
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
result, err := h.appService.GetCertification(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取认证详情失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "获取认证详情成功")
|
||||
}
|
||||
|
||||
// ================ 企业信息管理 ================
|
||||
|
||||
// SubmitEnterpriseInfo 提交企业信息
|
||||
// @Summary 提交企业信息
|
||||
// @Description 提交企业认证所需的企业信息
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.SubmitEnterpriseInfoCommand true "提交企业信息请求"
|
||||
// @Success 200 {object} responses.CertificationResponse "企业信息提交成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certifications/enterprise-info [post]
|
||||
func (h *CertificationHandler) SubmitEnterpriseInfo(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.SubmitEnterpriseInfoCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
cmd.UserID = userID
|
||||
|
||||
result, err := h.appService.SubmitEnterpriseInfo(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("提交企业信息失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "企业信息提交成功")
|
||||
}
|
||||
|
||||
// ConfirmAuth 前端确认是否完成认证
|
||||
// @Summary 前端确认认证状态
|
||||
// @Description 前端轮询确认企业认证是否完成
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body queries.ConfirmAuthCommand true "确认状态请求"
|
||||
// @Success 200 {object} responses.ConfirmAuthResponse "状态确认成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certifications/confirm-auth [post]
|
||||
func (h *CertificationHandler) ConfirmAuth(c *gin.Context) {
|
||||
var cmd queries.ConfirmAuthCommand
|
||||
cmd.UserID = h.getCurrentUserID(c)
|
||||
if cmd.UserID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.appService.ConfirmAuth(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("确认认证/签署状态失败", zap.Error(err), zap.String("user_id", cmd.UserID))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "状态确认成功")
|
||||
}
|
||||
|
||||
// ConfirmSign 前端确认是否完成签署
|
||||
// @Summary 前端确认签署状态
|
||||
// @Description 前端轮询确认合同签署是否完成
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body queries.ConfirmSignCommand true "确认状态请求"
|
||||
// @Success 200 {object} responses.ConfirmSignResponse "状态确认成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certifications/confirm-sign [post]
|
||||
func (h *CertificationHandler) ConfirmSign(c *gin.Context) {
|
||||
var cmd queries.ConfirmSignCommand
|
||||
cmd.UserID = h.getCurrentUserID(c)
|
||||
if cmd.UserID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
result, err := h.appService.ConfirmSign(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("确认认证/签署状态失败", zap.Error(err), zap.String("user_id", cmd.UserID))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "状态确认成功")
|
||||
}
|
||||
|
||||
// ================ 合同管理 ================
|
||||
|
||||
// ApplyContract 申请合同签署
|
||||
// @Summary 申请合同签署
|
||||
// @Description 申请企业认证合同签署
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.ApplyContractCommand true "申请合同请求"
|
||||
// @Success 200 {object} responses.ContractSignUrlResponse "合同申请成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certifications/apply-contract [post]
|
||||
func (h *CertificationHandler) ApplyContract(c *gin.Context) {
|
||||
var cmd commands.ApplyContractCommand
|
||||
cmd.UserID = h.getCurrentUserID(c)
|
||||
if cmd.UserID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.appService.ApplyContract(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("申请合同失败", zap.Error(err), zap.String("user_id", cmd.UserID))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "合同申请成功")
|
||||
}
|
||||
|
||||
// RecognizeBusinessLicense OCR识别营业执照
|
||||
// @Summary OCR识别营业执照
|
||||
// @Description 上传营业执照图片进行OCR识别,自动填充企业信息
|
||||
// @Tags 认证管理
|
||||
// @Accept multipart/form-data
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param image formData file true "营业执照图片文件"
|
||||
// @Success 200 {object} responses.BusinessLicenseResult "营业执照识别成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certifications/ocr/business-license [post]
|
||||
func (h *CertificationHandler) RecognizeBusinessLicense(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取上传的文件
|
||||
file, err := c.FormFile("image")
|
||||
if err != nil {
|
||||
h.logger.Error("获取上传文件失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.response.BadRequest(c, "请选择要上传的营业执照图片")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件类型
|
||||
allowedTypes := map[string]bool{
|
||||
"image/jpeg": true,
|
||||
"image/jpg": true,
|
||||
"image/png": true,
|
||||
"image/webp": true,
|
||||
}
|
||||
if !allowedTypes[file.Header.Get("Content-Type")] {
|
||||
h.response.BadRequest(c, "只支持JPG、PNG、WEBP格式的图片")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件大小(限制为5MB)
|
||||
if file.Size > 5*1024*1024 {
|
||||
h.response.BadRequest(c, "图片大小不能超过5MB")
|
||||
return
|
||||
}
|
||||
|
||||
// 打开文件
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
h.logger.Error("打开上传文件失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.response.BadRequest(c, "文件读取失败")
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// 读取文件内容
|
||||
imageBytes, err := io.ReadAll(src)
|
||||
if err != nil {
|
||||
h.logger.Error("读取文件内容失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.response.BadRequest(c, "文件读取失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 调用OCR服务识别营业执照
|
||||
result, err := h.appService.RecognizeBusinessLicense(c.Request.Context(), imageBytes)
|
||||
if err != nil {
|
||||
h.logger.Error("营业执照OCR识别失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.response.BadRequest(c, "营业执照识别失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("营业执照OCR识别成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("company_name", result.CompanyName),
|
||||
zap.Float64("confidence", result.Confidence),
|
||||
)
|
||||
|
||||
h.response.Success(c, result, "营业执照识别成功")
|
||||
}
|
||||
|
||||
// UploadCertificationFile 上传认证相关图片到七牛云(企业信息中的营业执照、办公场地、场景附件、授权代表身份证等)
|
||||
// @Summary 上传认证图片
|
||||
// @Description 上传企业信息中使用的图片到七牛云,返回可访问的 URL
|
||||
// @Tags 认证管理
|
||||
// @Accept multipart/form-data
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param file formData file true "图片文件"
|
||||
// @Success 200 {object} map[string]string "上传成功,返回 url 与 key"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certifications/upload [post]
|
||||
func (h *CertificationHandler) UploadCertificationFile(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
h.logger.Error("获取上传文件失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.response.BadRequest(c, "请选择要上传的图片文件")
|
||||
return
|
||||
}
|
||||
|
||||
allowedTypes := map[string]bool{
|
||||
"image/jpeg": true,
|
||||
"image/jpg": true,
|
||||
"image/png": true,
|
||||
"image/webp": true,
|
||||
}
|
||||
contentType := file.Header.Get("Content-Type")
|
||||
if !allowedTypes[contentType] {
|
||||
h.response.BadRequest(c, "只支持 JPG、PNG、WEBP 格式的图片")
|
||||
return
|
||||
}
|
||||
|
||||
if file.Size > 5*1024*1024 {
|
||||
h.response.BadRequest(c, "图片大小不能超过 5MB")
|
||||
return
|
||||
}
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
h.logger.Error("打开上传文件失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.response.BadRequest(c, "文件读取失败")
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
fileBytes, err := io.ReadAll(src)
|
||||
if err != nil {
|
||||
h.logger.Error("读取文件内容失败", zap.Error(err), zap.String("user_id", userID))
|
||||
h.response.BadRequest(c, "文件读取失败")
|
||||
return
|
||||
}
|
||||
|
||||
uploadResult, err := h.storageService.UploadFile(c.Request.Context(), fileBytes, file.Filename)
|
||||
if err != nil {
|
||||
h.logger.Error("上传文件到七牛云失败", zap.Error(err), zap.String("user_id", userID), zap.String("file_name", file.Filename))
|
||||
h.response.BadRequest(c, "图片上传失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, map[string]string{
|
||||
"url": uploadResult.URL,
|
||||
"key": uploadResult.Key,
|
||||
}, "上传成功")
|
||||
}
|
||||
|
||||
// ListCertifications 获取认证列表(管理员)
|
||||
// @Summary 获取认证列表
|
||||
// @Description 管理员获取认证申请列表
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param sort_by query string false "排序字段"
|
||||
// @Param sort_order query string false "排序方向" Enums(asc, desc)
|
||||
// @Param status query string false "认证状态"
|
||||
// @Param user_id query string false "用户ID"
|
||||
// @Param company_name query string false "公司名称"
|
||||
// @Param legal_person_name query string false "法人姓名"
|
||||
// @Param search_keyword query string false "搜索关键词"
|
||||
// @Success 200 {object} responses.CertificationListResponse "获取认证列表成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certifications [get]
|
||||
func (h *CertificationHandler) ListCertifications(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var query queries.ListCertificationsQuery
|
||||
if err := h.validator.BindAndValidate(c, &query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.appService.ListCertifications(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取认证列表失败", zap.Error(err))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "获取认证列表成功")
|
||||
}
|
||||
|
||||
// AdminCompleteCertificationWithoutContract 管理员代用户完成认证(暂不关联合同)
|
||||
// @Summary 管理员代用户完成认证(暂不关联合同)
|
||||
// @Description 后台补充企业信息并直接完成认证,暂时不要求上传合同
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.AdminCompleteCertificationCommand true "管理员代用户完成认证请求"
|
||||
// @Success 200 {object} responses.CertificationResponse "代用户完成认证成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certifications/admin/complete-without-contract [post]
|
||||
func (h *CertificationHandler) AdminCompleteCertificationWithoutContract(c *gin.Context) {
|
||||
adminID := h.getCurrentUserID(c)
|
||||
if adminID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.AdminCompleteCertificationCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
cmd.AdminID = adminID
|
||||
|
||||
result, err := h.appService.AdminCompleteCertificationWithoutContract(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("管理员代用户完成认证失败", zap.Error(err), zap.String("admin_id", adminID), zap.String("user_id", cmd.UserID))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "代用户完成认证成功")
|
||||
}
|
||||
|
||||
// AdminListSubmitRecords 管理端分页查询企业信息提交记录
|
||||
// @Summary 管理端企业审核列表
|
||||
// @Tags 认证管理
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页条数"
|
||||
// @Param certification_status query string false "按状态机筛选:info_pending_review/info_submitted/info_rejected,空为全部"
|
||||
// @Success 200 {object} responses.AdminSubmitRecordsListResponse
|
||||
// @Router /api/v1/certifications/admin/submit-records [get]
|
||||
func (h *CertificationHandler) AdminListSubmitRecords(c *gin.Context) {
|
||||
query := &queries.AdminListSubmitRecordsQuery{}
|
||||
if err := c.ShouldBindQuery(query); err != nil {
|
||||
h.response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
result, err := h.appService.AdminListSubmitRecords(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
h.response.Success(c, result, "获取成功")
|
||||
}
|
||||
|
||||
// AdminGetSubmitRecordByID 管理端获取单条提交记录详情
|
||||
// @Summary 管理端企业审核详情
|
||||
// @Tags 认证管理
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "记录ID"
|
||||
// @Success 200 {object} responses.AdminSubmitRecordDetail
|
||||
// @Router /api/v1/certifications/admin/submit-records/{id} [get]
|
||||
func (h *CertificationHandler) AdminGetSubmitRecordByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.response.BadRequest(c, "记录ID不能为空")
|
||||
return
|
||||
}
|
||||
result, err := h.appService.AdminGetSubmitRecordByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
h.response.Success(c, result, "获取成功")
|
||||
}
|
||||
|
||||
// AdminApproveSubmitRecord 管理端审核通过
|
||||
// @Summary 管理端企业审核通过
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "记录ID"
|
||||
// @Param request body object true "可选 remark"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/certifications/admin/submit-records/{id}/approve [post]
|
||||
func (h *CertificationHandler) AdminApproveSubmitRecord(c *gin.Context) {
|
||||
adminID := h.getCurrentUserID(c)
|
||||
if adminID == "" {
|
||||
h.response.Unauthorized(c, "未登录")
|
||||
return
|
||||
}
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.response.BadRequest(c, "记录ID不能为空")
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
_ = c.ShouldBindJSON(&body)
|
||||
if err := h.appService.AdminApproveSubmitRecord(c.Request.Context(), id, adminID, body.Remark); err != nil {
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
h.response.Success(c, nil, "审核通过")
|
||||
}
|
||||
|
||||
// AdminRejectSubmitRecord 管理端审核拒绝
|
||||
// @Summary 管理端企业审核拒绝
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "记录ID"
|
||||
// @Param request body object true "remark 必填"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/certifications/admin/submit-records/{id}/reject [post]
|
||||
func (h *CertificationHandler) AdminRejectSubmitRecord(c *gin.Context) {
|
||||
adminID := h.getCurrentUserID(c)
|
||||
if adminID == "" {
|
||||
h.response.Unauthorized(c, "未登录")
|
||||
return
|
||||
}
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.response.BadRequest(c, "记录ID不能为空")
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Remark string `json:"remark" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
h.response.BadRequest(c, "请填写拒绝原因(remark)")
|
||||
return
|
||||
}
|
||||
if err := h.appService.AdminRejectSubmitRecord(c.Request.Context(), id, adminID, body.Remark); err != nil {
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
h.response.Success(c, nil, "已拒绝")
|
||||
}
|
||||
|
||||
// AdminTransitionCertificationStatus 管理端按用户变更认证状态(以状态机为准)
|
||||
// @Summary 管理端变更认证状态
|
||||
// @Tags 认证管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.AdminTransitionCertificationStatusCommand true "user_id, target_status(info_submitted/info_rejected), remark"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/v1/certifications/admin/transition-status [post]
|
||||
func (h *CertificationHandler) AdminTransitionCertificationStatus(c *gin.Context) {
|
||||
adminID := h.getCurrentUserID(c)
|
||||
if adminID == "" {
|
||||
h.response.Unauthorized(c, "未登录")
|
||||
return
|
||||
}
|
||||
var cmd commands.AdminTransitionCertificationStatusCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
cmd.AdminID = adminID
|
||||
if err := h.appService.AdminTransitionCertificationStatus(c.Request.Context(), &cmd); err != nil {
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
h.response.Success(c, nil, "状态已更新")
|
||||
}
|
||||
|
||||
// ================ 回调处理 ================
|
||||
|
||||
// HandleEsignCallback 处理e签宝回调
|
||||
// @Summary 处理e签宝回调
|
||||
// @Description 处理e签宝的异步回调通知
|
||||
// @Tags 认证管理
|
||||
// @Accept application/json
|
||||
// @Produce text/plain
|
||||
// @Success 200 {string} string "success"
|
||||
// @Failure 400 {string} string "fail"
|
||||
// @Router /api/v1/certifications/esign/callback [post]
|
||||
func (h *CertificationHandler) HandleEsignCallback(c *gin.Context) {
|
||||
// 记录请求基本信息
|
||||
h.logger.Info("收到e签宝回调请求",
|
||||
zap.String("method", c.Request.Method),
|
||||
zap.String("url", c.Request.URL.String()),
|
||||
zap.String("remote_addr", c.ClientIP()),
|
||||
zap.String("user_agent", c.GetHeader("User-Agent")),
|
||||
)
|
||||
|
||||
// 记录所有请求头
|
||||
headers := make(map[string]string)
|
||||
for key, values := range c.Request.Header {
|
||||
if len(values) > 0 {
|
||||
headers[key] = values[0]
|
||||
}
|
||||
}
|
||||
h.logger.Info("回调请求头信息", zap.Any("headers", headers))
|
||||
|
||||
// 记录URL查询参数
|
||||
queryParams := make(map[string]string)
|
||||
for key, values := range c.Request.URL.Query() {
|
||||
if len(values) > 0 {
|
||||
queryParams[key] = values[0]
|
||||
}
|
||||
}
|
||||
if len(queryParams) > 0 {
|
||||
h.logger.Info("回调URL查询参数", zap.Any("query_params", queryParams))
|
||||
}
|
||||
|
||||
// 读取并记录请求体
|
||||
var callbackData *commands.EsignCallbackData
|
||||
if c.Request.Body != nil {
|
||||
bodyBytes, err := c.GetRawData()
|
||||
if err != nil {
|
||||
h.logger.Error("读取回调请求体失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "读取请求体失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(bodyBytes, &callbackData); err != nil {
|
||||
h.logger.Error("回调请求体不是有效的JSON格式", zap.Error(err))
|
||||
h.response.BadRequest(c, "请求体格式错误")
|
||||
return
|
||||
}
|
||||
h.logger.Info("回调请求体内容", zap.Any("body", callbackData))
|
||||
|
||||
// 如果后续还需要用 c.Request.Body
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
}
|
||||
|
||||
// 记录Content-Type
|
||||
contentType := c.GetHeader("Content-Type")
|
||||
h.logger.Info("回调请求Content-Type", zap.String("content_type", contentType))
|
||||
|
||||
// 记录Content-Length
|
||||
contentLength := c.GetHeader("Content-Length")
|
||||
if contentLength != "" {
|
||||
h.logger.Info("回调请求Content-Length", zap.String("content_length", contentLength))
|
||||
}
|
||||
|
||||
// 记录时间戳
|
||||
h.logger.Info("回调请求时间",
|
||||
zap.Time("request_time", time.Now()),
|
||||
zap.String("request_id", c.GetHeader("X-Request-ID")),
|
||||
)
|
||||
|
||||
// 记录完整的请求信息摘要
|
||||
h.logger.Info("e签宝回调完整信息摘要",
|
||||
zap.String("method", c.Request.Method),
|
||||
zap.String("url", c.Request.URL.String()),
|
||||
zap.String("client_ip", c.ClientIP()),
|
||||
zap.String("content_type", contentType),
|
||||
zap.Any("headers", headers),
|
||||
zap.Any("query_params", queryParams),
|
||||
zap.Any("body", callbackData),
|
||||
)
|
||||
|
||||
// 处理回调数据
|
||||
if callbackData != nil {
|
||||
// 构建请求头映射
|
||||
headers := make(map[string]string)
|
||||
for key, values := range c.Request.Header {
|
||||
if len(values) > 0 {
|
||||
headers[key] = values[0]
|
||||
}
|
||||
}
|
||||
|
||||
// 构建查询参数映射
|
||||
queryParams := make(map[string]string)
|
||||
for key, values := range c.Request.URL.Query() {
|
||||
if len(values) > 0 {
|
||||
queryParams[key] = values[0]
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.appService.HandleEsignCallback(c.Request.Context(), &commands.EsignCallbackCommand{
|
||||
Data: callbackData,
|
||||
Headers: headers,
|
||||
QueryParams: queryParams,
|
||||
}); err != nil {
|
||||
h.logger.Error("处理e签宝回调失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "回调处理失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"code": "200",
|
||||
"msg": "success",
|
||||
})
|
||||
}
|
||||
|
||||
// ================ 辅助方法 ================
|
||||
|
||||
// getCurrentUserID 获取当前用户ID
|
||||
func (h *CertificationHandler) getCurrentUserID(c *gin.Context) string {
|
||||
if userID, exists := c.Get("user_id"); exists {
|
||||
if id, ok := userID.(string); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/application/product"
|
||||
"hyapi-server/internal/config"
|
||||
financeRepositories "hyapi-server/internal/domains/finance/repositories"
|
||||
)
|
||||
|
||||
// ComponentReportOrderHandler 组件报告订单处理器
|
||||
type ComponentReportOrderHandler struct {
|
||||
service *product.ComponentReportOrderService
|
||||
purchaseOrderRepo financeRepositories.PurchaseOrderRepository
|
||||
config *config.Config
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewComponentReportOrderHandler 创建组件报告订单处理器
|
||||
func NewComponentReportOrderHandler(
|
||||
service *product.ComponentReportOrderService,
|
||||
purchaseOrderRepo financeRepositories.PurchaseOrderRepository,
|
||||
config *config.Config,
|
||||
logger *zap.Logger,
|
||||
) *ComponentReportOrderHandler {
|
||||
return &ComponentReportOrderHandler{
|
||||
service: service,
|
||||
purchaseOrderRepo: purchaseOrderRepo,
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CheckDownloadAvailability 检查下载可用性
|
||||
// GET /api/v1/products/:id/component-report/check
|
||||
func (h *ComponentReportOrderHandler) CheckDownloadAvailability(c *gin.Context) {
|
||||
h.logger.Info("开始检查下载可用性")
|
||||
|
||||
productID := c.Param("id")
|
||||
h.logger.Info("获取产品ID", zap.String("product_id", productID))
|
||||
|
||||
if productID == "" {
|
||||
h.logger.Error("产品ID不能为空")
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "产品ID不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
userID := c.GetString("user_id")
|
||||
h.logger.Info("获取用户ID", zap.String("user_id", userID))
|
||||
|
||||
if userID == "" {
|
||||
h.logger.Error("用户未登录")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "用户未登录",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 调用服务获取订单信息,检查是否可以下载
|
||||
orderInfo, err := h.service.GetOrderInfo(c.Request.Context(), userID, productID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取订单信息失败", zap.Error(err), zap.String("product_id", productID), zap.String("user_id", userID))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "获取订单信息失败",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("获取订单信息成功", zap.Bool("can_download", orderInfo.CanDownload), zap.Bool("is_package", orderInfo.IsPackage))
|
||||
|
||||
// 返回检查结果
|
||||
message := "需要购买"
|
||||
if orderInfo.CanDownload {
|
||||
message = "可以下载"
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": gin.H{
|
||||
"can_download": orderInfo.CanDownload,
|
||||
"is_package": orderInfo.IsPackage,
|
||||
"message": message,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetDownloadInfo 获取下载信息和价格计算
|
||||
// GET /api/v1/products/:id/component-report/info
|
||||
func (h *ComponentReportOrderHandler) GetDownloadInfo(c *gin.Context) {
|
||||
h.logger.Info("开始获取下载信息和价格计算")
|
||||
|
||||
productID := c.Param("id")
|
||||
h.logger.Info("获取产品ID", zap.String("product_id", productID))
|
||||
|
||||
if productID == "" {
|
||||
h.logger.Error("产品ID不能为空")
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "产品ID不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
userID := c.GetString("user_id")
|
||||
h.logger.Info("获取用户ID", zap.String("user_id", userID))
|
||||
|
||||
if userID == "" {
|
||||
h.logger.Error("用户未登录")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "用户未登录",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
orderInfo, err := h.service.GetOrderInfo(c.Request.Context(), userID, productID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取订单信息失败", zap.Error(err), zap.String("product_id", productID), zap.String("user_id", userID))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "获取订单信息失败",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录详细的订单信息
|
||||
h.logger.Info("获取订单信息成功",
|
||||
zap.String("product_id", orderInfo.ProductID),
|
||||
zap.String("product_code", orderInfo.ProductCode),
|
||||
zap.String("product_name", orderInfo.ProductName),
|
||||
zap.Bool("is_package", orderInfo.IsPackage),
|
||||
zap.Int("sub_products_count", len(orderInfo.SubProducts)),
|
||||
zap.String("price", orderInfo.Price),
|
||||
zap.Strings("purchased_product_codes", orderInfo.PurchasedProductCodes),
|
||||
zap.Bool("can_download", orderInfo.CanDownload),
|
||||
)
|
||||
|
||||
// 记录子产品详情
|
||||
for i, subProduct := range orderInfo.SubProducts {
|
||||
h.logger.Info("子产品信息",
|
||||
zap.Int("index", i),
|
||||
zap.String("sub_product_id", subProduct.ProductID),
|
||||
zap.String("sub_product_code", subProduct.ProductCode),
|
||||
zap.String("sub_product_name", subProduct.ProductName),
|
||||
zap.String("price", subProduct.Price),
|
||||
zap.Bool("is_purchased", subProduct.IsPurchased),
|
||||
)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": orderInfo,
|
||||
})
|
||||
}
|
||||
|
||||
// CreatePaymentOrder 创建支付订单
|
||||
// POST /api/v1/products/:id/component-report/create-order
|
||||
func (h *ComponentReportOrderHandler) CreatePaymentOrder(c *gin.Context) {
|
||||
h.logger.Info("开始创建支付订单")
|
||||
|
||||
productID := c.Param("id")
|
||||
h.logger.Info("获取产品ID", zap.String("product_id", productID))
|
||||
|
||||
if productID == "" {
|
||||
h.logger.Error("产品ID不能为空")
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "产品ID不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
userID := c.GetString("user_id")
|
||||
h.logger.Info("获取用户ID", zap.String("user_id", userID))
|
||||
|
||||
if userID == "" {
|
||||
h.logger.Error("用户未登录")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "用户未登录",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req product.CreatePaymentOrderRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Error("请求参数错误", zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "请求参数错误",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求参数
|
||||
h.logger.Info("支付订单请求参数",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("product_id", productID),
|
||||
zap.String("payment_type", req.PaymentType),
|
||||
zap.String("platform", req.Platform),
|
||||
zap.Strings("sub_product_codes", req.SubProductCodes),
|
||||
)
|
||||
|
||||
// 设置用户ID和产品ID
|
||||
req.UserID = userID
|
||||
req.ProductID = productID
|
||||
|
||||
// 如果未指定支付平台,根据User-Agent判断
|
||||
if req.Platform == "" {
|
||||
userAgent := c.GetHeader("User-Agent")
|
||||
req.Platform = h.detectPlatform(userAgent)
|
||||
h.logger.Info("根据User-Agent检测平台", zap.String("user_agent", userAgent), zap.String("detected_platform", req.Platform))
|
||||
}
|
||||
|
||||
response, err := h.service.CreatePaymentOrder(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("创建支付订单失败", zap.Error(err),
|
||||
zap.String("product_id", productID),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("payment_type", req.PaymentType))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "创建支付订单失败",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录创建订单成功响应
|
||||
h.logger.Info("创建支付订单成功",
|
||||
zap.String("order_id", response.OrderID),
|
||||
zap.String("order_no", response.OrderNo),
|
||||
zap.String("payment_type", response.PaymentType),
|
||||
zap.String("amount", response.Amount),
|
||||
zap.String("code_url", response.CodeURL),
|
||||
zap.String("pay_url", response.PayURL),
|
||||
)
|
||||
|
||||
// 开发环境下,自动将订单状态设置为已支付
|
||||
if h.config != nil && h.config.App.IsDevelopment() {
|
||||
h.logger.Info("开发环境:自动设置订单为已支付状态",
|
||||
zap.String("order_id", response.OrderID),
|
||||
zap.String("order_no", response.OrderNo))
|
||||
|
||||
// 获取订单信息
|
||||
purchaseOrder, err := h.purchaseOrderRepo.GetByID(c.Request.Context(), response.OrderID)
|
||||
if err != nil {
|
||||
h.logger.Error("开发环境:获取订单信息失败", zap.Error(err), zap.String("order_id", response.OrderID))
|
||||
} else {
|
||||
// 解析金额
|
||||
amount, err := decimal.NewFromString(response.Amount)
|
||||
if err != nil {
|
||||
h.logger.Error("开发环境:解析订单金额失败", zap.Error(err), zap.String("amount", response.Amount))
|
||||
} else {
|
||||
// 标记为已支付(使用开发环境的模拟交易号)
|
||||
tradeNo := "DEV_" + response.OrderNo
|
||||
purchaseOrder.MarkPaid(tradeNo, "", "", amount, amount)
|
||||
|
||||
// 更新订单状态
|
||||
err = h.purchaseOrderRepo.Update(c.Request.Context(), purchaseOrder)
|
||||
if err != nil {
|
||||
h.logger.Error("开发环境:更新订单状态失败", zap.Error(err), zap.String("order_id", response.OrderID))
|
||||
} else {
|
||||
h.logger.Info("开发环境:订单状态已自动设置为已支付",
|
||||
zap.String("order_id", response.OrderID),
|
||||
zap.String("order_no", response.OrderNo),
|
||||
zap.String("trade_no", tradeNo))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": response,
|
||||
})
|
||||
}
|
||||
|
||||
// CheckPaymentStatus 检查支付状态
|
||||
// GET /api/v1/component-report/check-payment/:orderId
|
||||
func (h *ComponentReportOrderHandler) CheckPaymentStatus(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "用户未登录",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
orderID := c.Param("orderId")
|
||||
if orderID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "订单ID不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.service.CheckPaymentStatus(c.Request.Context(), orderID)
|
||||
if err != nil {
|
||||
h.logger.Error("检查支付状态失败", zap.Error(err), zap.String("order_id", orderID))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "检查支付状态失败",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"data": response,
|
||||
"message": "查询支付状态成功",
|
||||
})
|
||||
}
|
||||
|
||||
// DownloadFile 下载文件
|
||||
// GET /api/v1/component-report/download/:orderId
|
||||
func (h *ComponentReportOrderHandler) DownloadFile(c *gin.Context) {
|
||||
h.logger.Info("开始处理文件下载请求")
|
||||
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.logger.Error("用户未登录")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "用户未登录",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("获取用户ID", zap.String("user_id", userID))
|
||||
|
||||
orderID := c.Param("orderId")
|
||||
if orderID == "" {
|
||||
h.logger.Error("订单ID不能为空")
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"message": "订单ID不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("获取订单ID", zap.String("order_id", orderID))
|
||||
|
||||
filePath, err := h.service.DownloadFile(c.Request.Context(), orderID)
|
||||
if err != nil {
|
||||
h.logger.Error("下载文件失败", zap.Error(err), zap.String("order_id", orderID), zap.String("user_id", userID))
|
||||
|
||||
// 根据错误类型返回不同的状态码和消息
|
||||
errorMessage := err.Error()
|
||||
statusCode := http.StatusInternalServerError
|
||||
|
||||
// 根据错误消息判断具体错误类型
|
||||
if strings.Contains(errorMessage, "购买订单不存在") {
|
||||
statusCode = http.StatusNotFound
|
||||
} else if strings.Contains(errorMessage, "订单未支付") || strings.Contains(errorMessage, "已过期") {
|
||||
statusCode = http.StatusForbidden
|
||||
} else if strings.Contains(errorMessage, "生成报告文件失败") {
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
c.JSON(statusCode, gin.H{
|
||||
"code": statusCode,
|
||||
"message": "下载文件失败",
|
||||
"error": errorMessage,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("成功获取文件路径",
|
||||
zap.String("order_id", orderID),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_path", filePath))
|
||||
|
||||
// 设置响应头
|
||||
c.Header("Content-Type", "application/zip")
|
||||
c.Header("Content-Disposition", "attachment; filename=component_report.zip")
|
||||
|
||||
// 发送文件
|
||||
h.logger.Info("开始发送文件", zap.String("file_path", filePath))
|
||||
c.File(filePath)
|
||||
h.logger.Info("文件发送成功", zap.String("file_path", filePath))
|
||||
}
|
||||
|
||||
// GetUserOrders 获取用户订单列表
|
||||
// GET /api/v1/component-report/orders
|
||||
func (h *ComponentReportOrderHandler) GetUserOrders(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": 401,
|
||||
"message": "用户未登录",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
orders, total, err := h.service.GetUserOrders(c.Request.Context(), userID, pageSize, offset)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户订单列表失败", zap.Error(err), zap.String("user_id", userID))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"message": "获取用户订单列表失败",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"data": gin.H{
|
||||
"list": orders,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// detectPlatform 根据 User-Agent 检测支付平台类型
|
||||
func (h *ComponentReportOrderHandler) detectPlatform(userAgent string) string {
|
||||
if userAgent == "" {
|
||||
return "h5" // 默认 H5
|
||||
}
|
||||
|
||||
ua := strings.ToLower(userAgent)
|
||||
|
||||
// 检测移动设备
|
||||
if strings.Contains(ua, "mobile") || strings.Contains(ua, "android") ||
|
||||
strings.Contains(ua, "iphone") || strings.Contains(ua, "ipad") {
|
||||
// 检测是否是支付宝或微信内置浏览器
|
||||
if strings.Contains(ua, "alipay") {
|
||||
return "app" // 支付宝 APP
|
||||
}
|
||||
if strings.Contains(ua, "micromessenger") {
|
||||
return "h5" // 微信 H5
|
||||
}
|
||||
return "h5" // 移动端默认 H5
|
||||
}
|
||||
|
||||
// PC 端
|
||||
return "pc"
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/application/product"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// FileDownloadHandler 文件下载处理器
|
||||
type FileDownloadHandler struct {
|
||||
uiComponentAppService product.UIComponentApplicationService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewFileDownloadHandler 创建文件下载处理器
|
||||
func NewFileDownloadHandler(
|
||||
uiComponentAppService product.UIComponentApplicationService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *FileDownloadHandler {
|
||||
return &FileDownloadHandler{
|
||||
uiComponentAppService: uiComponentAppService,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// DownloadUIComponentFile 下载UI组件文件
|
||||
// @Summary 下载UI组件文件
|
||||
// @Description 下载UI组件文件
|
||||
// @Tags 文件下载
|
||||
// @Accept json
|
||||
// @Produce application/octet-stream
|
||||
// @Param id path string true "UI组件ID"
|
||||
// @Success 200 {file} file "文件内容"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 404 {object} interfaces.Response "UI组件不存在或文件不存在"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/ui-components/{id}/download [get]
|
||||
func (h *FileDownloadHandler) DownloadUIComponentFile(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取UI组件信息
|
||||
component, err := h.uiComponentAppService.GetUIComponentByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("获取UI组件失败", zap.Error(err), zap.String("id", id))
|
||||
h.responseBuilder.InternalError(c, "获取UI组件失败")
|
||||
return
|
||||
}
|
||||
|
||||
if component == nil {
|
||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if component.FilePath == nil {
|
||||
h.responseBuilder.NotFound(c, "UI组件文件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件路径
|
||||
filePath, err := h.uiComponentAppService.DownloadUIComponentFile(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("获取UI组件文件路径失败", zap.Error(err), zap.String("id", id))
|
||||
h.responseBuilder.InternalError(c, "获取UI组件文件路径失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置下载文件名
|
||||
fileName := component.ComponentName
|
||||
if !strings.HasSuffix(strings.ToLower(fileName), ".zip") {
|
||||
fileName += ".zip"
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
c.Header("Content-Description", "File Transfer")
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
c.Header("Content-Disposition", "attachment; filename="+fileName)
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
|
||||
// 发送文件
|
||||
c.File(filePath)
|
||||
}
|
||||
1297
internal/infrastructure/http/handlers/finance_handler.go
Normal file
1297
internal/infrastructure/http/handlers/finance_handler.go
Normal file
File diff suppressed because it is too large
Load Diff
95
internal/infrastructure/http/handlers/pdfg_handler.go
Normal file
95
internal/infrastructure/http/handlers/pdfg_handler.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
"hyapi-server/internal/shared/pdf"
|
||||
)
|
||||
|
||||
// PDFGHandler PDFG处理器
|
||||
type PDFGHandler struct {
|
||||
cacheManager *pdf.PDFCacheManager
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewPDFGHandler 创建PDFG处理器
|
||||
func NewPDFGHandler(
|
||||
cacheManager *pdf.PDFCacheManager,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *PDFGHandler {
|
||||
return &PDFGHandler{
|
||||
cacheManager: cacheManager,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// DownloadPDF 下载PDF文件
|
||||
// GET /api/v1/pdfg/download?id=报告ID
|
||||
func (h *PDFGHandler) DownloadPDF(c *gin.Context) {
|
||||
reportID := c.Query("id")
|
||||
|
||||
if reportID == "" {
|
||||
h.responseBuilder.BadRequest(c, "报告ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 通过报告ID获取PDF文件
|
||||
pdfBytes, hit, createdAt, err := h.cacheManager.GetByReportID(reportID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取PDF缓存失败",
|
||||
zap.String("report_id", reportID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.InternalError(c, "获取PDF文件失败")
|
||||
return
|
||||
}
|
||||
|
||||
if !hit {
|
||||
h.logger.Warn("PDF文件不存在或已过期",
|
||||
zap.String("report_id", reportID),
|
||||
)
|
||||
h.responseBuilder.NotFound(c, "PDF文件不存在或已过期,请重新生成")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否过期(从文件生成时间开始算24小时)
|
||||
expiresAt := createdAt.Add(24 * time.Hour)
|
||||
if time.Now().After(expiresAt) {
|
||||
h.logger.Warn("PDF文件已过期",
|
||||
zap.String("report_id", reportID),
|
||||
zap.Time("expires_at", expiresAt),
|
||||
)
|
||||
h.responseBuilder.NotFound(c, "PDF文件已过期,请重新生成")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
c.Header("Content-Type", "application/pdf")
|
||||
// 使用报告ID前缀作为下载文件名的一部分
|
||||
filename := "大数据租赁风险报告.pdf"
|
||||
if idx := strings.LastIndex(reportID, "-"); idx > 0 {
|
||||
// 使用前缀(报告编号部分)作为文件名的一部分
|
||||
prefix := reportID[:idx]
|
||||
filename = fmt.Sprintf("大数据租赁风险报告_%s.pdf", prefix)
|
||||
}
|
||||
c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
||||
c.Header("Content-Length", fmt.Sprintf("%d", len(pdfBytes)))
|
||||
|
||||
// 发送PDF文件
|
||||
c.Data(http.StatusOK, "application/pdf", pdfBytes)
|
||||
|
||||
h.logger.Info("PDF文件下载成功",
|
||||
zap.String("report_id", reportID),
|
||||
zap.Int("file_size", len(pdfBytes)),
|
||||
)
|
||||
}
|
||||
1661
internal/infrastructure/http/handlers/product_admin_handler.go
Normal file
1661
internal/infrastructure/http/handlers/product_admin_handler.go
Normal file
File diff suppressed because it is too large
Load Diff
1015
internal/infrastructure/http/handlers/product_handler.go
Normal file
1015
internal/infrastructure/http/handlers/product_handler.go
Normal file
File diff suppressed because it is too large
Load Diff
301
internal/infrastructure/http/handlers/qygl_report_handler.go
Normal file
301
internal/infrastructure/http/handlers/qygl_report_handler.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/application/api/commands"
|
||||
"hyapi-server/internal/domains/api/dto"
|
||||
api_repositories "hyapi-server/internal/domains/api/repositories"
|
||||
api_services "hyapi-server/internal/domains/api/services"
|
||||
"hyapi-server/internal/domains/api/services/processors"
|
||||
"hyapi-server/internal/domains/api/services/processors/qygl"
|
||||
"hyapi-server/internal/shared/pdf"
|
||||
)
|
||||
|
||||
// QYGLReportHandler 企业全景报告页面渲染处理器
|
||||
// 使用 QYGLJ1U9 聚合接口生成企业报告数据,并通过模板引擎渲染 qiye.html
|
||||
type QYGLReportHandler struct {
|
||||
apiRequestService *api_services.ApiRequestService
|
||||
logger *zap.Logger
|
||||
|
||||
reportRepo api_repositories.ReportRepository
|
||||
pdfCacheManager *pdf.PDFCacheManager
|
||||
qyglPDFPregen *pdf.QYGLReportPDFPregen
|
||||
}
|
||||
|
||||
// NewQYGLReportHandler 创建企业报告页面处理器
|
||||
func NewQYGLReportHandler(
|
||||
apiRequestService *api_services.ApiRequestService,
|
||||
logger *zap.Logger,
|
||||
reportRepo api_repositories.ReportRepository,
|
||||
pdfCacheManager *pdf.PDFCacheManager,
|
||||
qyglPDFPregen *pdf.QYGLReportPDFPregen,
|
||||
) *QYGLReportHandler {
|
||||
return &QYGLReportHandler{
|
||||
apiRequestService: apiRequestService,
|
||||
logger: logger,
|
||||
reportRepo: reportRepo,
|
||||
pdfCacheManager: pdfCacheManager,
|
||||
qyglPDFPregen: qyglPDFPregen,
|
||||
}
|
||||
}
|
||||
|
||||
// GetQYGLReportPage 企业全景报告页面
|
||||
// GET /reports/qygl?ent_code=xxx&ent_name=yyy&ent_reg_no=zzz
|
||||
func (h *QYGLReportHandler) GetQYGLReportPage(c *gin.Context) {
|
||||
// 读取查询参数
|
||||
entCode := c.Query("ent_code")
|
||||
entName := c.Query("ent_name")
|
||||
entRegNo := c.Query("ent_reg_no")
|
||||
|
||||
// 组装 QYGLUY3S 入参
|
||||
req := dto.QYGLUY3SReq{
|
||||
EntName: entName,
|
||||
EntRegno: entRegNo,
|
||||
EntCode: entCode,
|
||||
}
|
||||
|
||||
params, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
h.logger.Error("序列化企业全景报告入参失败", zap.Error(err))
|
||||
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
|
||||
// 通过 ApiRequestService 调用 QYGLJ1U9 聚合处理器
|
||||
options := &commands.ApiCallOptions{}
|
||||
callCtx := &processors.CallContext{}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
respBytes, err := h.apiRequestService.PreprocessRequestApi(ctx, "QYGLJ1U9", params, options, callCtx)
|
||||
if err != nil {
|
||||
h.logger.Error("调用企业全景报告处理器失败", zap.Error(err))
|
||||
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
|
||||
var report map[string]interface{}
|
||||
if err := json.Unmarshal(respBytes, &report); err != nil {
|
||||
h.logger.Error("解析企业全景报告结果失败", zap.Error(err))
|
||||
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
|
||||
reportJSONBytes, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
h.logger.Error("序列化企业全景报告结果失败", zap.Error(err))
|
||||
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 template.JS 避免在脚本中被转义,直接作为 JS 对象字面量注入
|
||||
reportJSON := template.JS(reportJSONBytes)
|
||||
|
||||
c.HTML(http.StatusOK, "qiye.html", gin.H{
|
||||
"ReportJSON": reportJSON,
|
||||
})
|
||||
}
|
||||
|
||||
// GetQYGLReportPageByID 通过编号查看企业全景报告页面
|
||||
// GET /reports/qygl/:id
|
||||
func (h *QYGLReportHandler) GetQYGLReportPageByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.String(http.StatusBadRequest, "报告编号不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 优先从数据库中查询报告记录
|
||||
if h.reportRepo != nil {
|
||||
if entity, err := h.reportRepo.FindByReportID(c.Request.Context(), id); err == nil && entity != nil {
|
||||
h.maybeScheduleQYGLPDFPregen(c.Request.Context(), id)
|
||||
reportJSON := template.JS(entity.ReportData)
|
||||
c.HTML(http.StatusOK, "qiye.html", gin.H{
|
||||
"ReportJSON": reportJSON,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 回退到进程内存缓存(兼容老的访问方式)
|
||||
report, ok := qygl.GetQYGLReport(id)
|
||||
if !ok {
|
||||
c.String(http.StatusNotFound, "报告不存在或已过期")
|
||||
return
|
||||
}
|
||||
|
||||
reportJSONBytes, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
h.logger.Error("序列化企业全景报告结果失败", zap.Error(err))
|
||||
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后再试")
|
||||
return
|
||||
}
|
||||
|
||||
reportJSON := template.JS(reportJSONBytes)
|
||||
|
||||
h.maybeScheduleQYGLPDFPregen(c.Request.Context(), id)
|
||||
|
||||
c.HTML(http.StatusOK, "qiye.html", gin.H{
|
||||
"ReportJSON": reportJSON,
|
||||
})
|
||||
}
|
||||
|
||||
// qyglReportExists 报告是否仍在库或本进程内存中(用于决定是否补开预生成)
|
||||
func (h *QYGLReportHandler) qyglReportExists(ctx context.Context, id string) bool {
|
||||
if h.reportRepo != nil {
|
||||
if e, err := h.reportRepo.FindByReportID(ctx, id); err == nil && e != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
_, ok := qygl.GetQYGLReport(id)
|
||||
return ok
|
||||
}
|
||||
|
||||
// maybeScheduleQYGLPDFPregen 在已配置公网基址时异步预生成 PDF;Schedule 内部会去重。
|
||||
// 解决:服务重启 / 多实例后内存队列为空,用户打开报告页或轮询状态时仍应能启动预生成。
|
||||
func (h *QYGLReportHandler) maybeScheduleQYGLPDFPregen(ctx context.Context, id string) {
|
||||
if id == "" || h.qyglPDFPregen == nil || !h.qyglPDFPregen.Enabled() {
|
||||
return
|
||||
}
|
||||
if !h.qyglReportExists(ctx, id) {
|
||||
return
|
||||
}
|
||||
h.qyglPDFPregen.ScheduleQYGLReportPDF(context.Background(), id)
|
||||
}
|
||||
|
||||
// GetQYGLReportPDFStatusByID 查询企业报告 PDF 预生成状态(供前端轮询)
|
||||
// GET /reports/qygl/:id/pdf/status
|
||||
func (h *QYGLReportHandler) GetQYGLReportPDFStatusByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"status": "none", "message": "报告编号不能为空"})
|
||||
return
|
||||
}
|
||||
if h.pdfCacheManager != nil {
|
||||
if b, hit, _, err := h.pdfCacheManager.GetByReportID(id); hit && err == nil && len(b) > 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"status": string(pdf.QYGLReportPDFStatusReady), "message": ""})
|
||||
return
|
||||
}
|
||||
}
|
||||
if h.qyglPDFPregen == nil || !h.qyglPDFPregen.Enabled() {
|
||||
c.JSON(http.StatusOK, gin.H{"status": string(pdf.QYGLReportPDFStatusNone), "message": "未启用预生成,将在下载时现场生成"})
|
||||
return
|
||||
}
|
||||
st, msg := h.qyglPDFPregen.Status(id)
|
||||
if st == pdf.QYGLReportPDFStatusNone && h.qyglReportExists(c.Request.Context(), id) {
|
||||
h.qyglPDFPregen.ScheduleQYGLReportPDF(context.Background(), id)
|
||||
st, msg = h.qyglPDFPregen.Status(id)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"status": string(st), "message": userFacingPDFStatusMessage(st, msg)})
|
||||
}
|
||||
|
||||
func userFacingPDFStatusMessage(st pdf.QYGLReportPDFStatus, raw string) string {
|
||||
switch st {
|
||||
case pdf.QYGLReportPDFStatusPending:
|
||||
return "排队生成中"
|
||||
case pdf.QYGLReportPDFStatusGenerating:
|
||||
return "正在生成 PDF"
|
||||
case pdf.QYGLReportPDFStatusFailed:
|
||||
if raw != "" {
|
||||
return raw
|
||||
}
|
||||
return "预生成失败,下载时将重新生成"
|
||||
case pdf.QYGLReportPDFStatusReady:
|
||||
return ""
|
||||
case pdf.QYGLReportPDFStatusNone:
|
||||
return "尚未开始预生成"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// GetQYGLReportPDFByID 通过编号导出企业全景报告 PDF:优先读缓存;可短时等待预生成;否则 headless 现场生成并写入缓存
|
||||
// GET /reports/qygl/:id/pdf
|
||||
func (h *QYGLReportHandler) GetQYGLReportPDFByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.String(http.StatusBadRequest, "报告编号不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var fileName = "企业全景报告.pdf"
|
||||
if h.reportRepo != nil {
|
||||
if entity, err := h.reportRepo.FindByReportID(c.Request.Context(), id); err == nil && entity != nil {
|
||||
if entity.EntName != "" {
|
||||
fileName = fmt.Sprintf("%s_企业全景报告.pdf", entity.EntName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pdfBytes []byte
|
||||
if h.pdfCacheManager != nil {
|
||||
if b, hit, _, err := h.pdfCacheManager.GetByReportID(id); hit && err == nil && len(b) > 0 {
|
||||
pdfBytes = b
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存未命中时:若正在预生成,短时等待(与前端轮询互补)
|
||||
if len(pdfBytes) == 0 && h.qyglPDFPregen != nil && h.qyglPDFPregen.Enabled() && h.pdfCacheManager != nil {
|
||||
deadline := time.Now().Add(90 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
st, _ := h.qyglPDFPregen.Status(id)
|
||||
if st == pdf.QYGLReportPDFStatusFailed {
|
||||
break
|
||||
}
|
||||
if b, hit, _, err := h.pdfCacheManager.GetByReportID(id); hit && err == nil && len(b) > 0 {
|
||||
pdfBytes = b
|
||||
break
|
||||
}
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
if len(pdfBytes) == 0 {
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil {
|
||||
scheme = "https"
|
||||
} else if forwardedProto := c.Request.Header.Get("X-Forwarded-Proto"); forwardedProto != "" {
|
||||
scheme = forwardedProto
|
||||
}
|
||||
reportURL := fmt.Sprintf("%s://%s/reports/qygl/%s", scheme, c.Request.Host, id)
|
||||
|
||||
h.logger.Info("现场生成企业全景报告 PDF(headless Chrome)",
|
||||
zap.String("report_id", id),
|
||||
zap.String("url", reportURL),
|
||||
)
|
||||
|
||||
pdfGen := pdf.NewHTMLPDFGenerator(h.logger)
|
||||
var err error
|
||||
pdfBytes, err = pdfGen.GenerateFromURL(c.Request.Context(), reportURL)
|
||||
if err != nil {
|
||||
h.logger.Error("生成企业全景报告 PDF 失败", zap.String("report_id", id), zap.Error(err))
|
||||
c.String(http.StatusInternalServerError, "生成企业报告 PDF 失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
if len(pdfBytes) == 0 {
|
||||
h.logger.Error("生成的企业全景报告 PDF 为空", zap.String("report_id", id))
|
||||
c.String(http.StatusInternalServerError, "生成的企业报告 PDF 为空,请稍后重试")
|
||||
return
|
||||
}
|
||||
if h.pdfCacheManager != nil {
|
||||
_ = h.pdfCacheManager.SetByReportID(id, pdfBytes)
|
||||
}
|
||||
if h.qyglPDFPregen != nil {
|
||||
h.qyglPDFPregen.MarkReadyAfterUpload(id)
|
||||
}
|
||||
}
|
||||
|
||||
encodedFileName := url.QueryEscape(fileName)
|
||||
c.Header("Content-Type", "application/pdf")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", encodedFileName))
|
||||
c.Data(http.StatusOK, "application/pdf", pdfBytes)
|
||||
}
|
||||
1578
internal/infrastructure/http/handlers/statistics_handler.go
Normal file
1578
internal/infrastructure/http/handlers/statistics_handler.go
Normal file
File diff suppressed because it is too large
Load Diff
229
internal/infrastructure/http/handlers/sub_category_handler.go
Normal file
229
internal/infrastructure/http/handlers/sub_category_handler.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/application/product"
|
||||
"hyapi-server/internal/application/product/dto/commands"
|
||||
"hyapi-server/internal/application/product/dto/queries"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// SubCategoryHandler 二级分类HTTP处理器
|
||||
type SubCategoryHandler struct {
|
||||
subCategoryAppService product.SubCategoryApplicationService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
validator interfaces.RequestValidator
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewSubCategoryHandler 创建二级分类HTTP处理器
|
||||
func NewSubCategoryHandler(
|
||||
subCategoryAppService product.SubCategoryApplicationService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
validator interfaces.RequestValidator,
|
||||
logger *zap.Logger,
|
||||
) *SubCategoryHandler {
|
||||
return &SubCategoryHandler{
|
||||
subCategoryAppService: subCategoryAppService,
|
||||
responseBuilder: responseBuilder,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSubCategory 创建二级分类
|
||||
// @Summary 创建二级分类
|
||||
// @Description 管理员创建新二级分类
|
||||
// @Tags 二级分类管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.CreateSubCategoryCommand true "创建二级分类请求"
|
||||
// @Success 201 {object} map[string]interface{} "二级分类创建成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/sub-categories [post]
|
||||
func (h *SubCategoryHandler) CreateSubCategory(c *gin.Context) {
|
||||
var cmd commands.CreateSubCategoryCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.subCategoryAppService.CreateSubCategory(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("创建二级分类失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, nil, "二级分类创建成功")
|
||||
}
|
||||
|
||||
// UpdateSubCategory 更新二级分类
|
||||
// @Summary 更新二级分类
|
||||
// @Description 管理员更新二级分类信息
|
||||
// @Tags 二级分类管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "二级分类ID"
|
||||
// @Param request body commands.UpdateSubCategoryCommand true "更新二级分类请求"
|
||||
// @Success 200 {object} map[string]interface{} "二级分类更新成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "二级分类不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/sub-categories/{id} [put]
|
||||
func (h *SubCategoryHandler) UpdateSubCategory(c *gin.Context) {
|
||||
var cmd commands.UpdateSubCategoryCommand
|
||||
cmd.ID = c.Param("id")
|
||||
if cmd.ID == "" {
|
||||
h.responseBuilder.BadRequest(c, "二级分类ID不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.subCategoryAppService.UpdateSubCategory(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("更新二级分类失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "二级分类更新成功")
|
||||
}
|
||||
|
||||
// DeleteSubCategory 删除二级分类
|
||||
// @Summary 删除二级分类
|
||||
// @Description 管理员删除二级分类
|
||||
// @Tags 二级分类管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "二级分类ID"
|
||||
// @Success 200 {object} map[string]interface{} "二级分类删除成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "二级分类不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/sub-categories/{id} [delete]
|
||||
func (h *SubCategoryHandler) DeleteSubCategory(c *gin.Context) {
|
||||
cmd := commands.DeleteSubCategoryCommand{ID: c.Param("id")}
|
||||
if err := h.validator.ValidateParam(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.subCategoryAppService.DeleteSubCategory(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("删除二级分类失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "二级分类删除成功")
|
||||
}
|
||||
|
||||
// GetSubCategory 获取二级分类详情
|
||||
// @Summary 获取二级分类详情
|
||||
// @Description 获取指定二级分类的详细信息
|
||||
// @Tags 二级分类管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "二级分类ID"
|
||||
// @Success 200 {object} responses.SubCategoryInfoResponse "二级分类信息"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "二级分类不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/sub-categories/{id} [get]
|
||||
func (h *SubCategoryHandler) GetSubCategory(c *gin.Context) {
|
||||
query := &queries.GetSubCategoryQuery{ID: c.Param("id")}
|
||||
if err := h.validator.ValidateParam(c, query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.subCategoryAppService.GetSubCategoryByID(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取二级分类失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取二级分类成功")
|
||||
}
|
||||
|
||||
// ListSubCategories 获取二级分类列表
|
||||
// @Summary 获取二级分类列表
|
||||
// @Description 获取二级分类列表,支持筛选和分页
|
||||
// @Tags 二级分类管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param category_id query string false "一级分类ID"
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Param is_enabled query bool false "是否启用"
|
||||
// @Param is_visible query bool false "是否展示"
|
||||
// @Param sort_by query string false "排序字段"
|
||||
// @Param sort_order query string false "排序方向"
|
||||
// @Success 200 {object} responses.SubCategoryListResponse "二级分类列表"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/sub-categories [get]
|
||||
func (h *SubCategoryHandler) ListSubCategories(c *gin.Context) {
|
||||
query := &queries.ListSubCategoriesQuery{}
|
||||
if err := h.validator.ValidateQuery(c, query); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认分页参数
|
||||
if query.Page == 0 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize == 0 {
|
||||
query.PageSize = 20
|
||||
}
|
||||
|
||||
result, err := h.subCategoryAppService.ListSubCategories(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取二级分类列表失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取二级分类列表成功")
|
||||
}
|
||||
|
||||
// ListSubCategoriesByCategory 根据一级分类ID获取二级分类列表(级联选择用)
|
||||
// @Summary 根据一级分类获取二级分类列表
|
||||
// @Description 根据一级分类ID获取二级分类简单列表,用于级联选择
|
||||
// @Tags 二级分类管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "一级分类ID"
|
||||
// @Success 200 {object} []responses.SubCategorySimpleResponse "二级分类简单列表"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/categories/{id}/sub-categories [get]
|
||||
func (h *SubCategoryHandler) ListSubCategoriesByCategory(c *gin.Context) {
|
||||
categoryID := c.Param("id")
|
||||
if categoryID == "" {
|
||||
h.responseBuilder.BadRequest(c, "一级分类ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.subCategoryAppService.ListSubCategoriesByCategoryID(c.Request.Context(), categoryID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取二级分类列表失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取二级分类列表成功")
|
||||
}
|
||||
552
internal/infrastructure/http/handlers/ui_component_handler.go
Normal file
552
internal/infrastructure/http/handlers/ui_component_handler.go
Normal file
@@ -0,0 +1,552 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/application/product"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// UIComponentHandler UI组件HTTP处理器
|
||||
type UIComponentHandler struct {
|
||||
uiComponentAppService product.UIComponentApplicationService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
validator interfaces.RequestValidator
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUIComponentHandler 创建UI组件HTTP处理器
|
||||
func NewUIComponentHandler(
|
||||
uiComponentAppService product.UIComponentApplicationService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
validator interfaces.RequestValidator,
|
||||
logger *zap.Logger,
|
||||
) *UIComponentHandler {
|
||||
return &UIComponentHandler{
|
||||
uiComponentAppService: uiComponentAppService,
|
||||
responseBuilder: responseBuilder,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUIComponent 创建UI组件
|
||||
// @Summary 创建UI组件
|
||||
// @Description 管理员创建新的UI组件
|
||||
// @Tags UI组件管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body product.CreateUIComponentRequest true "创建UI组件请求"
|
||||
// @Success 200 {object} interfaces.Response{data=entities.UIComponent} "创建成功"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components [post]
|
||||
func (h *UIComponentHandler) CreateUIComponent(c *gin.Context) {
|
||||
var req product.CreateUIComponentRequest
|
||||
|
||||
// 一次性读取请求体并绑定到结构体
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Error("验证创建UI组件请求失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, fmt.Sprintf("请求参数错误: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 使用结构体数据记录日志
|
||||
h.logger.Info("创建UI组件请求数据",
|
||||
zap.String("component_code", req.ComponentCode),
|
||||
zap.String("component_name", req.ComponentName),
|
||||
zap.String("description", req.Description),
|
||||
zap.String("version", req.Version),
|
||||
zap.Bool("is_active", req.IsActive),
|
||||
zap.Int("sort_order", req.SortOrder))
|
||||
|
||||
component, err := h.uiComponentAppService.CreateUIComponent(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
h.logger.Error("创建UI组件失败", zap.Error(err), zap.String("component_code", req.ComponentCode))
|
||||
if err == product.ErrComponentCodeAlreadyExists {
|
||||
h.responseBuilder.BadRequest(c, "UI组件编码已存在")
|
||||
return
|
||||
}
|
||||
h.responseBuilder.InternalError(c, fmt.Sprintf("创建UI组件失败: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, component)
|
||||
}
|
||||
|
||||
// CreateUIComponentWithFile 创建UI组件并上传文件
|
||||
// @Summary 创建UI组件并上传文件
|
||||
// @Description 管理员创建新的UI组件并同时上传文件
|
||||
// @Tags UI组件管理
|
||||
// @Accept multipart/form-data
|
||||
// @Produce json
|
||||
// @Param component_code formData string true "组件编码"
|
||||
// @Param component_name formData string true "组件名称"
|
||||
// @Param description formData string false "组件描述"
|
||||
// @Param version formData string false "组件版本"
|
||||
// @Param is_active formData bool false "是否启用"
|
||||
// @Param sort_order formData int false "排序"
|
||||
// @Param file formData file true "组件文件"
|
||||
// @Success 200 {object} interfaces.Response{data=entities.UIComponent} "创建成功"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components/create-with-file [post]
|
||||
func (h *UIComponentHandler) CreateUIComponentWithFile(c *gin.Context) {
|
||||
// 创建请求结构体
|
||||
var req product.CreateUIComponentRequest
|
||||
|
||||
// 从表单数据中获取组件信息
|
||||
req.ComponentCode = c.PostForm("component_code")
|
||||
req.ComponentName = c.PostForm("component_name")
|
||||
req.Description = c.PostForm("description")
|
||||
req.Version = c.PostForm("version")
|
||||
req.IsActive = c.PostForm("is_active") == "true"
|
||||
|
||||
if sortOrderStr := c.PostForm("sort_order"); sortOrderStr != "" {
|
||||
if sortOrder, err := strconv.Atoi(sortOrderStr); err == nil {
|
||||
req.SortOrder = sortOrder
|
||||
}
|
||||
}
|
||||
|
||||
// 验证必需字段
|
||||
if req.ComponentCode == "" {
|
||||
h.responseBuilder.BadRequest(c, "组件编码不能为空")
|
||||
return
|
||||
}
|
||||
if req.ComponentName == "" {
|
||||
h.responseBuilder.BadRequest(c, "组件名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取上传的文件
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
h.logger.Error("获取表单数据失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "获取表单数据失败")
|
||||
return
|
||||
}
|
||||
|
||||
files := form.File["files"]
|
||||
if len(files) == 0 {
|
||||
h.responseBuilder.BadRequest(c, "请上传组件文件")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件大小(100MB)
|
||||
for _, fileHeader := range files {
|
||||
if fileHeader.Size > 100*1024*1024 {
|
||||
h.responseBuilder.BadRequest(c, fmt.Sprintf("文件 %s 大小不能超过100MB", fileHeader.Filename))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取路径信息
|
||||
paths := c.PostFormArray("paths")
|
||||
|
||||
// 记录请求日志
|
||||
h.logger.Info("创建UI组件并上传文件请求",
|
||||
zap.String("component_code", req.ComponentCode),
|
||||
zap.String("component_name", req.ComponentName),
|
||||
zap.String("description", req.Description),
|
||||
zap.String("version", req.Version),
|
||||
zap.Bool("is_active", req.IsActive),
|
||||
zap.Int("sort_order", req.SortOrder),
|
||||
zap.Int("files_count", len(files)),
|
||||
zap.Strings("paths", paths))
|
||||
|
||||
// 调用应用服务创建组件并上传文件
|
||||
component, err := h.uiComponentAppService.CreateUIComponentWithFilesAndPaths(c.Request.Context(), req, files, paths)
|
||||
if err != nil {
|
||||
h.logger.Error("创建UI组件并上传文件失败", zap.Error(err), zap.String("component_code", req.ComponentCode))
|
||||
if err == product.ErrComponentCodeAlreadyExists {
|
||||
h.responseBuilder.BadRequest(c, "UI组件编码已存在")
|
||||
return
|
||||
}
|
||||
h.responseBuilder.InternalError(c, fmt.Sprintf("创建UI组件并上传文件失败: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, component)
|
||||
}
|
||||
|
||||
// GetUIComponent 获取UI组件详情
|
||||
// @Summary 获取UI组件详情
|
||||
// @Description 根据ID获取UI组件详情
|
||||
// @Tags UI组件管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "UI组件ID"
|
||||
// @Success 200 {object} interfaces.Response{data=entities.UIComponent} "获取成功"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 404 {object} interfaces.Response "UI组件不存在"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components/{id} [get]
|
||||
func (h *UIComponentHandler) GetUIComponent(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
component, err := h.uiComponentAppService.GetUIComponentByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("获取UI组件失败", zap.Error(err), zap.String("id", id))
|
||||
h.responseBuilder.InternalError(c, "获取UI组件失败")
|
||||
return
|
||||
}
|
||||
|
||||
if component == nil {
|
||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, component)
|
||||
}
|
||||
|
||||
// UpdateUIComponent 更新UI组件
|
||||
// @Summary 更新UI组件
|
||||
// @Description 更新UI组件信息
|
||||
// @Tags UI组件管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "UI组件ID"
|
||||
// @Param request body product.UpdateUIComponentRequest true "更新UI组件请求"
|
||||
// @Success 200 {object} interfaces.Response "更新成功"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 404 {object} interfaces.Response "UI组件不存在"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components/{id} [put]
|
||||
func (h *UIComponentHandler) UpdateUIComponent(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var req product.UpdateUIComponentRequest
|
||||
|
||||
// 设置ID
|
||||
req.ID = id
|
||||
|
||||
// 验证请求
|
||||
if err := h.validator.Validate(c, &req); err != nil {
|
||||
h.logger.Error("验证更新UI组件请求失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
err := h.uiComponentAppService.UpdateUIComponent(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
h.logger.Error("更新UI组件失败", zap.Error(err), zap.String("id", id))
|
||||
if err == product.ErrComponentNotFound {
|
||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||
return
|
||||
}
|
||||
if err == product.ErrComponentCodeAlreadyExists {
|
||||
h.responseBuilder.BadRequest(c, "UI组件编码已存在")
|
||||
return
|
||||
}
|
||||
h.responseBuilder.InternalError(c, "更新UI组件失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil)
|
||||
}
|
||||
|
||||
// DeleteUIComponent 删除UI组件
|
||||
// @Summary 删除UI组件
|
||||
// @Description 删除UI组件
|
||||
// @Tags UI组件管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "UI组件ID"
|
||||
// @Success 200 {object} interfaces.Response "删除成功"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 404 {object} interfaces.Response "UI组件不存在"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components/{id} [delete]
|
||||
func (h *UIComponentHandler) DeleteUIComponent(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.uiComponentAppService.DeleteUIComponent(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("删除UI组件失败", zap.Error(err), zap.String("id", id))
|
||||
if err == product.ErrComponentNotFound {
|
||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||
return
|
||||
}
|
||||
// 提供更详细的错误信息
|
||||
h.responseBuilder.InternalError(c, fmt.Sprintf("删除UI组件失败: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil)
|
||||
}
|
||||
|
||||
// ListUIComponents 获取UI组件列表
|
||||
// @Summary 获取UI组件列表
|
||||
// @Description 获取UI组件列表,支持分页和筛选
|
||||
// @Tags UI组件管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param keyword query string false "关键词搜索"
|
||||
// @Param is_active query bool false "是否启用"
|
||||
// @Param sort_by query string false "排序字段" default(sort_order)
|
||||
// @Param sort_order query string false "排序方向" default(asc)
|
||||
// @Success 200 {object} interfaces.Response{data=product.ListUIComponentsResponse} "获取成功"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components [get]
|
||||
func (h *UIComponentHandler) ListUIComponents(c *gin.Context) {
|
||||
// 解析查询参数
|
||||
req := product.ListUIComponentsRequest{}
|
||||
|
||||
if pageStr := c.Query("page"); pageStr != "" {
|
||||
if page, err := strconv.Atoi(pageStr); err == nil {
|
||||
req.Page = page
|
||||
}
|
||||
}
|
||||
|
||||
if pageSizeStr := c.Query("page_size"); pageSizeStr != "" {
|
||||
if pageSize, err := strconv.Atoi(pageSizeStr); err == nil {
|
||||
req.PageSize = pageSize
|
||||
}
|
||||
}
|
||||
|
||||
req.Keyword = c.Query("keyword")
|
||||
|
||||
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
|
||||
if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
|
||||
req.IsActive = &isActive
|
||||
}
|
||||
}
|
||||
|
||||
req.SortBy = c.DefaultQuery("sort_by", "sort_order")
|
||||
req.SortOrder = c.DefaultQuery("sort_order", "asc")
|
||||
|
||||
response, err := h.uiComponentAppService.ListUIComponents(c.Request.Context(), req)
|
||||
if err != nil {
|
||||
h.logger.Error("获取UI组件列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取UI组件列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response)
|
||||
}
|
||||
|
||||
// UploadUIComponentFile 上传UI组件文件
|
||||
// @Summary 上传UI组件文件
|
||||
// @Description 上传UI组件文件
|
||||
// @Tags UI组件管理
|
||||
// @Accept multipart/form-data
|
||||
// @Produce json
|
||||
// @Param id path string true "UI组件ID"
|
||||
// @Param file formData file true "UI组件文件(ZIP格式)"
|
||||
// @Success 200 {object} interfaces.Response{data=string} "上传成功,返回文件路径"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 404 {object} interfaces.Response "UI组件不存在"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components/{id}/upload [post]
|
||||
func (h *UIComponentHandler) UploadUIComponentFile(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取上传的文件
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
h.logger.Error("获取上传文件失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "获取上传文件失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件大小(100MB)
|
||||
if file.Size > 100*1024*1024 {
|
||||
h.responseBuilder.BadRequest(c, "文件大小不能超过100MB")
|
||||
return
|
||||
}
|
||||
|
||||
filePath, err := h.uiComponentAppService.UploadUIComponentFile(c.Request.Context(), id, file)
|
||||
if err != nil {
|
||||
h.logger.Error("上传UI组件文件失败", zap.Error(err), zap.String("id", id))
|
||||
if err == product.ErrComponentNotFound {
|
||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||
return
|
||||
}
|
||||
if err == product.ErrInvalidFileType {
|
||||
h.responseBuilder.BadRequest(c, "文件类型错误")
|
||||
return
|
||||
}
|
||||
h.responseBuilder.InternalError(c, "上传UI组件文件失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, filePath)
|
||||
}
|
||||
|
||||
// UploadAndExtractUIComponentFile 上传并解压UI组件文件
|
||||
// @Summary 上传并解压UI组件文件
|
||||
// @Description 上传文件并自动解压到组件文件夹(仅ZIP文件支持解压)
|
||||
// @Tags UI组件管理
|
||||
// @Accept multipart/form-data
|
||||
// @Produce json
|
||||
// @Param id path string true "UI组件ID"
|
||||
// @Param file formData file true "UI组件文件(任意格式,ZIP格式支持自动解压)"
|
||||
// @Success 200 {object} interfaces.Response "上传成功"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 404 {object} interfaces.Response "UI组件不存在"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components/{id}/upload-extract [post]
|
||||
func (h *UIComponentHandler) UploadAndExtractUIComponentFile(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取上传的文件
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
h.logger.Error("获取上传文件失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "获取上传文件失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件大小(100MB)
|
||||
if file.Size > 100*1024*1024 {
|
||||
h.responseBuilder.BadRequest(c, "文件大小不能超过100MB")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.uiComponentAppService.UploadAndExtractUIComponentFile(c.Request.Context(), id, file)
|
||||
if err != nil {
|
||||
h.logger.Error("上传并解压UI组件文件失败", zap.Error(err), zap.String("id", id))
|
||||
if err == product.ErrComponentNotFound {
|
||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||
return
|
||||
}
|
||||
if err == product.ErrInvalidFileType {
|
||||
h.responseBuilder.BadRequest(c, "文件类型错误")
|
||||
return
|
||||
}
|
||||
h.responseBuilder.InternalError(c, "上传并解压UI组件文件失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil)
|
||||
}
|
||||
|
||||
// GetUIComponentFolderContent 获取UI组件文件夹内容
|
||||
// @Summary 获取UI组件文件夹内容
|
||||
// @Description 获取UI组件文件夹内容
|
||||
// @Tags UI组件管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "UI组件ID"
|
||||
// @Success 200 {object} interfaces.Response{data=[]FileInfo} "获取成功"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 404 {object} interfaces.Response "UI组件不存在"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components/{id}/folder-content [get]
|
||||
func (h *UIComponentHandler) GetUIComponentFolderContent(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
files, err := h.uiComponentAppService.GetUIComponentFolderContent(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("获取UI组件文件夹内容失败", zap.Error(err), zap.String("id", id))
|
||||
if err == product.ErrComponentNotFound {
|
||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||
return
|
||||
}
|
||||
h.responseBuilder.InternalError(c, "获取UI组件文件夹内容失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, files)
|
||||
}
|
||||
|
||||
// DeleteUIComponentFolder 删除UI组件文件夹
|
||||
// @Summary 删除UI组件文件夹
|
||||
// @Description 删除UI组件文件夹
|
||||
// @Tags UI组件管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "UI组件ID"
|
||||
// @Success 200 {object} interfaces.Response "删除成功"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 404 {object} interfaces.Response "UI组件不存在"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components/{id}/folder [delete]
|
||||
func (h *UIComponentHandler) DeleteUIComponentFolder(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.uiComponentAppService.DeleteUIComponentFolder(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("删除UI组件文件夹失败", zap.Error(err), zap.String("id", id))
|
||||
if err == product.ErrComponentNotFound {
|
||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||
return
|
||||
}
|
||||
h.responseBuilder.InternalError(c, "删除UI组件文件夹失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil)
|
||||
}
|
||||
|
||||
// DownloadUIComponentFile 下载UI组件文件
|
||||
// @Summary 下载UI组件文件
|
||||
// @Description 下载UI组件文件
|
||||
// @Tags UI组件管理
|
||||
// @Accept json
|
||||
// @Produce application/octet-stream
|
||||
// @Param id path string true "UI组件ID"
|
||||
// @Success 200 {file} file "文件内容"
|
||||
// @Failure 400 {object} interfaces.Response "请求参数错误"
|
||||
// @Failure 404 {object} interfaces.Response "UI组件不存在或文件不存在"
|
||||
// @Failure 500 {object} interfaces.Response "服务器内部错误"
|
||||
// @Router /api/v1/admin/ui-components/{id}/download [get]
|
||||
func (h *UIComponentHandler) DownloadUIComponentFile(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
filePath, err := h.uiComponentAppService.DownloadUIComponentFile(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("下载UI组件文件失败", zap.Error(err), zap.String("id", id))
|
||||
if err == product.ErrComponentNotFound {
|
||||
h.responseBuilder.NotFound(c, "UI组件不存在")
|
||||
return
|
||||
}
|
||||
if err == product.ErrComponentFileNotFound {
|
||||
h.responseBuilder.NotFound(c, "UI组件文件不存在")
|
||||
return
|
||||
}
|
||||
h.responseBuilder.InternalError(c, "下载UI组件文件失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 这里应该实现文件下载逻辑,返回文件内容
|
||||
// 由于我们使用的是本地文件存储,可以直接返回文件
|
||||
c.File(filePath)
|
||||
}
|
||||
531
internal/infrastructure/http/handlers/user_handler.go
Normal file
531
internal/infrastructure/http/handlers/user_handler.go
Normal file
@@ -0,0 +1,531 @@
|
||||
//nolint:unused
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/application/user"
|
||||
"hyapi-server/internal/application/user/dto/commands"
|
||||
"hyapi-server/internal/application/user/dto/queries"
|
||||
_ "hyapi-server/internal/application/user/dto/responses"
|
||||
"hyapi-server/internal/config"
|
||||
"hyapi-server/internal/shared/crypto"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
)
|
||||
|
||||
// UserHandler 用户HTTP处理器
|
||||
type UserHandler struct {
|
||||
appService user.UserApplicationService
|
||||
response interfaces.ResponseBuilder
|
||||
validator interfaces.RequestValidator
|
||||
logger *zap.Logger
|
||||
jwtAuth *middleware.JWTAuthMiddleware
|
||||
config *config.Config
|
||||
cache interfaces.CacheService
|
||||
}
|
||||
|
||||
// NewUserHandler 创建用户处理器
|
||||
func NewUserHandler(
|
||||
appService user.UserApplicationService,
|
||||
response interfaces.ResponseBuilder,
|
||||
validator interfaces.RequestValidator,
|
||||
logger *zap.Logger,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
cfg *config.Config,
|
||||
cache interfaces.CacheService,
|
||||
) *UserHandler {
|
||||
return &UserHandler{
|
||||
appService: appService,
|
||||
response: response,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
jwtAuth: jwtAuth,
|
||||
config: cfg,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// decodedSendCodeData 解码后的请求数据结构
|
||||
type decodedSendCodeData struct {
|
||||
Phone string `json:"phone"`
|
||||
Scene string `json:"scene"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Nonce string `json:"nonce"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
// SendCode 发送验证码
|
||||
// @Summary 发送短信验证码
|
||||
// @Description 向指定手机号发送验证码,支持注册、登录、修改密码等场景。需要提供有效的签名验证。只接收编码后的data字段(使用自定义编码方案)
|
||||
// @Tags 用户认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.SendCodeCommand true "发送验证码请求(包含data字段和可选的captchaVerifyParam字段)"
|
||||
// @Success 200 {object} map[string]interface{} "验证码发送成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 429 {object} map[string]interface{} "请求频率限制"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/send-code [post]
|
||||
func (h *UserHandler) SendCode(c *gin.Context) {
|
||||
var cmd commands.SendCodeCommand
|
||||
|
||||
// 绑定请求(包含data字段和可选的captchaVerifyParam字段)
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.response.BadRequest(c, "请求参数格式错误,必须提供data字段")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证data字段不为空
|
||||
if cmd.Data == "" {
|
||||
h.response.BadRequest(c, "data字段不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 解码自定义编码的数据
|
||||
decodedData, err := h.decodeRequestData(cmd.Data)
|
||||
if err != nil {
|
||||
h.logger.Warn("解码请求数据失败",
|
||||
zap.String("client_ip", c.ClientIP()),
|
||||
zap.Error(err))
|
||||
h.response.BadRequest(c, "请求数据解码失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证必要字段
|
||||
if decodedData.Phone == "" || decodedData.Scene == "" {
|
||||
h.response.BadRequest(c, "手机号和场景不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 如果启用了签名验证,进行签名校验(包含nonce唯一性检查,防止重放攻击)
|
||||
if h.config.SMS.SignatureEnabled {
|
||||
if err := h.verifyDecodedSignature(c.Request.Context(), decodedData); err != nil {
|
||||
h.logger.Warn("短信发送签名验证失败",
|
||||
zap.String("phone", decodedData.Phone),
|
||||
zap.String("scene", decodedData.Scene),
|
||||
zap.String("client_ip", c.ClientIP()),
|
||||
zap.Error(err))
|
||||
|
||||
// 根据错误类型返回不同的用户友好消息(不暴露技术细节)
|
||||
userMessage := h.getSignatureErrorMessage(err)
|
||||
h.response.BadRequest(c, userMessage)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 构建SendCodeCommand用于调用应用服务
|
||||
serviceCmd := &commands.SendCodeCommand{
|
||||
Phone: decodedData.Phone,
|
||||
Scene: decodedData.Scene,
|
||||
Timestamp: decodedData.Timestamp,
|
||||
Nonce: decodedData.Nonce,
|
||||
Signature: decodedData.Signature,
|
||||
CaptchaVerifyParam: cmd.CaptchaVerifyParam,
|
||||
}
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
userAgent := c.GetHeader("User-Agent")
|
||||
|
||||
if err := h.appService.SendCode(c.Request.Context(), serviceCmd, clientIP, userAgent); err != nil {
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, nil, "验证码发送成功")
|
||||
}
|
||||
|
||||
// decodeRequestData 解码自定义编码的请求数据
|
||||
func (h *UserHandler) decodeRequestData(encodedData string) (*decodedSendCodeData, error) {
|
||||
// 使用自定义编码方案解码
|
||||
decodedData, err := crypto.DecodeRequest(encodedData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("自定义编码解码失败: %w", err)
|
||||
}
|
||||
|
||||
// 解析JSON
|
||||
var decoded decodedSendCodeData
|
||||
if err := json.Unmarshal([]byte(decodedData), &decoded); err != nil {
|
||||
return nil, fmt.Errorf("JSON解析失败: %w", err)
|
||||
}
|
||||
|
||||
return &decoded, nil
|
||||
}
|
||||
|
||||
// verifyDecodedSignature 验证解码后的签名(包含nonce唯一性检查,防止重放攻击)
|
||||
func (h *UserHandler) verifyDecodedSignature(ctx context.Context, data *decodedSendCodeData) error {
|
||||
// 构建参数map(包含signature字段,VerifySignature会自动排除它)
|
||||
params := map[string]string{
|
||||
"phone": data.Phone,
|
||||
"scene": data.Scene,
|
||||
"signature": data.Signature,
|
||||
}
|
||||
|
||||
// 验证签名并检查nonce唯一性(防止重放攻击)
|
||||
return crypto.VerifySignatureWithNonceCheck(
|
||||
ctx,
|
||||
params,
|
||||
h.config.SMS.SignatureSecret,
|
||||
data.Timestamp,
|
||||
data.Nonce,
|
||||
h.cache,
|
||||
"sms:signature", // 缓存键前缀
|
||||
)
|
||||
}
|
||||
|
||||
// getSignatureErrorMessage 根据错误类型返回用户友好的错误消息(不暴露技术细节)
|
||||
func (h *UserHandler) getSignatureErrorMessage(err error) string {
|
||||
errMsg := err.Error()
|
||||
|
||||
// 根据错误消息内容判断错误类型,返回通用的用户友好消息
|
||||
if strings.Contains(errMsg, "请求已被使用") || strings.Contains(errMsg, "重复提交") {
|
||||
// 重放攻击:返回通用消息,不暴露具体原因
|
||||
return "请求无效,请重新操作"
|
||||
}
|
||||
if strings.Contains(errMsg, "时间戳") || strings.Contains(errMsg, "过期") {
|
||||
// 时间戳过期:返回通用消息
|
||||
return "请求已过期,请重新操作"
|
||||
}
|
||||
if strings.Contains(errMsg, "签名") {
|
||||
// 签名错误:返回通用消息
|
||||
return "请求验证失败,请重新操作"
|
||||
}
|
||||
|
||||
// 其他错误:返回通用消息
|
||||
return "请求验证失败,请重新操作"
|
||||
}
|
||||
|
||||
|
||||
// Register 用户注册
|
||||
// @Summary 用户注册
|
||||
// @Description 使用手机号、密码和验证码进行用户注册,需要确认密码
|
||||
// @Tags 用户认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.RegisterUserCommand true "用户注册请求"
|
||||
// @Success 201 {object} responses.RegisterUserResponse "注册成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效"
|
||||
// @Failure 409 {object} map[string]interface{} "手机号已存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/register [post]
|
||||
func (h *UserHandler) Register(c *gin.Context) {
|
||||
var cmd commands.RegisterUserCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.appService.Register(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("注册用户失败", zap.Error(err))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Created(c, resp, "用户注册成功")
|
||||
}
|
||||
|
||||
// LoginWithPassword 密码登录
|
||||
// @Summary 用户密码登录
|
||||
// @Description 使用手机号和密码进行用户登录,返回JWT令牌
|
||||
// @Tags 用户认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.LoginWithPasswordCommand true "密码登录请求"
|
||||
// @Success 200 {object} responses.LoginUserResponse "登录成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "用户名或密码错误"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/login-password [post]
|
||||
func (h *UserHandler) LoginWithPassword(c *gin.Context) {
|
||||
var cmd commands.LoginWithPasswordCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.appService.LoginWithPassword(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("密码登录失败", zap.Error(err))
|
||||
h.response.Unauthorized(c, "用户名或密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, resp, "登录成功")
|
||||
}
|
||||
|
||||
// LoginWithSMS 短信验证码登录
|
||||
// @Summary 用户短信验证码登录
|
||||
// @Description 使用手机号和短信验证码进行用户登录,返回JWT令牌
|
||||
// @Tags 用户认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.LoginWithSMSCommand true "短信登录请求"
|
||||
// @Success 200 {object} responses.LoginUserResponse "登录成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效"
|
||||
// @Failure 401 {object} map[string]interface{} "认证失败"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/login-sms [post]
|
||||
func (h *UserHandler) LoginWithSMS(c *gin.Context) {
|
||||
var cmd commands.LoginWithSMSCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.appService.LoginWithSMS(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("短信登录失败", zap.Error(err))
|
||||
h.response.Unauthorized(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, resp, "登录成功")
|
||||
}
|
||||
|
||||
// GetProfile 获取当前用户信息
|
||||
// @Summary 获取当前用户信息
|
||||
// @Description 根据JWT令牌获取当前登录用户的详细信息
|
||||
// @Tags 用户管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.UserProfileResponse "用户信息"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "用户不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/me [get]
|
||||
func (h *UserHandler) GetProfile(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.appService.GetUserProfile(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户资料失败", zap.Error(err))
|
||||
h.response.NotFound(c, "用户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, resp, "获取用户资料成功")
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
// @Summary 修改密码
|
||||
// @Description 使用旧密码、新密码确认和验证码修改当前用户的密码
|
||||
// @Tags 用户管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.ChangePasswordCommand true "修改密码请求"
|
||||
// @Success 200 {object} map[string]interface{} "密码修改成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/me/password [put]
|
||||
func (h *UserHandler) ChangePassword(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.ChangePasswordCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
cmd.UserID = userID
|
||||
|
||||
if err := h.appService.ChangePassword(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("修改密码失败", zap.Error(err))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, nil, "密码修改成功")
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
// @Summary 重置密码
|
||||
// @Description 使用手机号、验证码和新密码重置用户密码(忘记密码时使用)
|
||||
// @Tags 用户认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.ResetPasswordCommand true "重置密码请求"
|
||||
// @Success 200 {object} map[string]interface{} "密码重置成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效"
|
||||
// @Failure 404 {object} map[string]interface{} "用户不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/reset-password [post]
|
||||
func (h *UserHandler) ResetPassword(c *gin.Context) {
|
||||
var cmd commands.ResetPasswordCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.appService.ResetPassword(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("重置密码失败", zap.Error(err))
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, nil, "密码重置成功")
|
||||
}
|
||||
|
||||
// ListUsers 管理员查看用户列表
|
||||
// @Summary 管理员查看用户列表
|
||||
// @Description 管理员查看用户列表,支持分页和筛选
|
||||
// @Tags 用户管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param phone query string false "手机号筛选"
|
||||
// @Param user_type query string false "用户类型筛选" Enums(user,admin)
|
||||
// @Param is_active query bool false "是否激活筛选"
|
||||
// @Param is_certified query bool false "是否已认证筛选"
|
||||
// @Param company_name query string false "企业名称筛选"
|
||||
// @Param start_date query string false "开始日期" format(date)
|
||||
// @Param end_date query string false "结束日期" format(date)
|
||||
// @Success 200 {object} responses.UserListResponse "用户列表"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/admin/list [get]
|
||||
func (h *UserHandler) ListUsers(c *gin.Context) {
|
||||
// 检查管理员权限
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
// 构建查询参数
|
||||
query := &queries.ListUsersQuery{
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
}
|
||||
|
||||
// 从查询参数中获取筛选条件
|
||||
if page := c.Query("page"); page != "" {
|
||||
if pageNum, err := strconv.Atoi(page); err == nil && pageNum > 0 {
|
||||
query.Page = pageNum
|
||||
}
|
||||
}
|
||||
|
||||
if pageSize := c.Query("page_size"); pageSize != "" {
|
||||
if size, err := strconv.Atoi(pageSize); err == nil && size > 0 && size <= 1000 {
|
||||
query.PageSize = size
|
||||
}
|
||||
}
|
||||
|
||||
query.Phone = c.Query("phone")
|
||||
query.UserType = c.Query("user_type")
|
||||
query.CompanyName = c.Query("company_name")
|
||||
query.StartDate = c.Query("start_date")
|
||||
query.EndDate = c.Query("end_date")
|
||||
|
||||
// 处理布尔值参数
|
||||
if isActive := c.Query("is_active"); isActive != "" {
|
||||
if active, err := strconv.ParseBool(isActive); err == nil {
|
||||
query.IsActive = &active
|
||||
}
|
||||
}
|
||||
|
||||
if isCertified := c.Query("is_certified"); isCertified != "" {
|
||||
if certified, err := strconv.ParseBool(isCertified); err == nil {
|
||||
query.IsCertified = &certified
|
||||
}
|
||||
}
|
||||
|
||||
// 调用应用服务
|
||||
resp, err := h.appService.ListUsers(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户列表失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "获取用户列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, resp, "获取用户列表成功")
|
||||
}
|
||||
|
||||
// GetUserDetail 管理员获取用户详情
|
||||
// @Summary 管理员获取用户详情
|
||||
// @Description 管理员获取指定用户的详细信息
|
||||
// @Tags 用户管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param user_id path string true "用户ID"
|
||||
// @Success 200 {object} responses.UserDetailResponse "用户详情"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||
// @Failure 404 {object} map[string]interface{} "用户不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/admin/{user_id} [get]
|
||||
func (h *UserHandler) GetUserDetail(c *gin.Context) {
|
||||
// 检查管理员权限
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取路径参数中的用户ID
|
||||
targetUserID := c.Param("user_id")
|
||||
if targetUserID == "" {
|
||||
h.response.BadRequest(c, "用户ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 调用应用服务
|
||||
resp, err := h.appService.GetUserDetail(c.Request.Context(), targetUserID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户详情失败", zap.Error(err), zap.String("target_user_id", targetUserID))
|
||||
h.response.BadRequest(c, "获取用户详情失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, resp, "获取用户详情成功")
|
||||
}
|
||||
|
||||
// GetUserStats 获取用户统计信息
|
||||
// @Summary 获取用户统计信息
|
||||
// @Description 管理员获取用户相关的统计信息
|
||||
// @Tags 用户管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.UserStatsResponse "用户统计信息"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/users/admin/stats [get]
|
||||
func (h *UserHandler) GetUserStats(c *gin.Context) {
|
||||
// 调用应用服务
|
||||
resp, err := h.appService.GetUserStats(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户统计信息失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "获取用户统计信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, resp, "获取用户统计信息成功")
|
||||
}
|
||||
|
||||
// getCurrentUserID 获取当前用户ID
|
||||
func (h *UserHandler) getCurrentUserID(c *gin.Context) string {
|
||||
if userID, exists := c.Get("user_id"); exists {
|
||||
if id, ok := userID.(string); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
39
internal/infrastructure/http/routes/admin_security_routes.go
Normal file
39
internal/infrastructure/http/routes/admin_security_routes.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AdminSecurityRoutes 管理端安全路由
|
||||
type AdminSecurityRoutes struct {
|
||||
handler *handlers.AdminSecurityHandler
|
||||
admin *middleware.AdminAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewAdminSecurityRoutes(
|
||||
handler *handlers.AdminSecurityHandler,
|
||||
admin *middleware.AdminAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *AdminSecurityRoutes {
|
||||
return &AdminSecurityRoutes{
|
||||
handler: handler,
|
||||
admin: admin,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *AdminSecurityRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
group := engine.Group("/api/v1/admin/security")
|
||||
group.Use(r.admin.Handle())
|
||||
{
|
||||
group.GET("/suspicious-ip/list", r.handler.ListSuspiciousIPs)
|
||||
group.GET("/suspicious-ip/geo-stream", r.handler.GetSuspiciousIPGeoStream)
|
||||
}
|
||||
r.logger.Info("管理员安全路由注册完成")
|
||||
}
|
||||
73
internal/infrastructure/http/routes/announcement_routes.go
Normal file
73
internal/infrastructure/http/routes/announcement_routes.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AnnouncementRoutes 公告路由
|
||||
type AnnouncementRoutes struct {
|
||||
handler *handlers.AnnouncementHandler
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
admin *middleware.AdminAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAnnouncementRoutes 创建公告路由
|
||||
func NewAnnouncementRoutes(
|
||||
handler *handlers.AnnouncementHandler,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
admin *middleware.AdminAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *AnnouncementRoutes {
|
||||
return &AnnouncementRoutes{
|
||||
handler: handler,
|
||||
auth: auth,
|
||||
admin: admin,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册路由
|
||||
func (r *AnnouncementRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
|
||||
// ==================== 用户端路由 ====================
|
||||
// 公告相关路由 - 用户端(只显示已发布的公告)
|
||||
announcementGroup := engine.Group("/api/v1/announcements")
|
||||
{
|
||||
// 公开路由 - 不需要认证
|
||||
announcementGroup.GET("/:id", r.handler.GetAnnouncementByID) // 获取公告详情
|
||||
announcementGroup.GET("", r.handler.ListAnnouncements) // 获取公告列表
|
||||
}
|
||||
|
||||
// ==================== 管理员端路由 ====================
|
||||
// 管理员公告管理路由
|
||||
adminAnnouncementGroup := engine.Group("/api/v1/admin/announcements")
|
||||
adminAnnouncementGroup.Use(r.admin.Handle())
|
||||
{
|
||||
// 统计信息
|
||||
adminAnnouncementGroup.GET("/stats", r.handler.GetAnnouncementStats) // 获取公告统计
|
||||
|
||||
// 公告列表查询
|
||||
adminAnnouncementGroup.GET("", r.handler.ListAnnouncements) // 获取公告列表(管理员端,包含所有状态)
|
||||
|
||||
// 公告管理
|
||||
adminAnnouncementGroup.POST("", r.handler.CreateAnnouncement) // 创建公告
|
||||
adminAnnouncementGroup.PUT("/:id", r.handler.UpdateAnnouncement) // 更新公告
|
||||
adminAnnouncementGroup.DELETE("/:id", r.handler.DeleteAnnouncement) // 删除公告
|
||||
|
||||
// 公告状态管理
|
||||
adminAnnouncementGroup.POST("/:id/publish", r.handler.PublishAnnouncement) // 发布公告
|
||||
adminAnnouncementGroup.POST("/:id/withdraw", r.handler.WithdrawAnnouncement) // 撤回公告
|
||||
adminAnnouncementGroup.POST("/:id/archive", r.handler.ArchiveAnnouncement) // 归档公告
|
||||
adminAnnouncementGroup.POST("/:id/schedule-publish", r.handler.SchedulePublishAnnouncement) // 定时发布公告
|
||||
adminAnnouncementGroup.POST("/:id/update-schedule-publish", r.handler.UpdateSchedulePublishAnnouncement) // 修改定时发布时间
|
||||
adminAnnouncementGroup.POST("/:id/cancel-schedule", r.handler.CancelSchedulePublishAnnouncement) // 取消定时发布
|
||||
}
|
||||
|
||||
r.logger.Info("公告路由注册完成")
|
||||
}
|
||||
74
internal/infrastructure/http/routes/api_routes.go
Normal file
74
internal/infrastructure/http/routes/api_routes.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ApiRoutes API路由注册器
|
||||
type ApiRoutes struct {
|
||||
apiHandler *handlers.ApiHandler
|
||||
authMiddleware *middleware.JWTAuthMiddleware
|
||||
domainAuthMiddleware *middleware.DomainAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewApiRoutes 创建API路由注册器
|
||||
func NewApiRoutes(
|
||||
apiHandler *handlers.ApiHandler,
|
||||
authMiddleware *middleware.JWTAuthMiddleware,
|
||||
domainAuthMiddleware *middleware.DomainAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *ApiRoutes {
|
||||
return &ApiRoutes{
|
||||
apiHandler: apiHandler,
|
||||
authMiddleware: authMiddleware,
|
||||
domainAuthMiddleware: domainAuthMiddleware,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册相关路由
|
||||
func (r *ApiRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
// API路由组,需要用户认证
|
||||
engine := router.GetEngine()
|
||||
apiGroup := engine.Group("/api/v1")
|
||||
|
||||
{
|
||||
// API调用接口 - 不受频率限制(业务核心接口)
|
||||
apiGroup.POST("/:api_name", r.domainAuthMiddleware.Handle(""), r.apiHandler.HandleApiCall)
|
||||
|
||||
// Console专用接口 - 使用JWT认证,不需要域名认证
|
||||
apiGroup.POST("/console/:api_name", r.authMiddleware.Handle(), r.apiHandler.HandleApiCall)
|
||||
|
||||
// 表单配置接口(用于前端动态生成表单)
|
||||
apiGroup.GET("/form-config/:api_code", r.authMiddleware.Handle(), r.apiHandler.GetFormConfig)
|
||||
|
||||
// 加密接口(用于前端调试)
|
||||
apiGroup.POST("/encrypt", r.authMiddleware.Handle(), r.apiHandler.EncryptParams)
|
||||
|
||||
// 解密接口(用于前端调试)
|
||||
apiGroup.POST("/decrypt", r.authMiddleware.Handle(), r.apiHandler.DecryptParams)
|
||||
|
||||
// API密钥管理接口
|
||||
apiGroup.GET("/api-keys", r.authMiddleware.Handle(), r.apiHandler.GetUserApiKeys)
|
||||
|
||||
// 白名单管理接口
|
||||
apiGroup.GET("/white-list", r.authMiddleware.Handle(), r.apiHandler.GetUserWhiteList)
|
||||
apiGroup.POST("/white-list", r.authMiddleware.Handle(), r.apiHandler.AddWhiteListIP)
|
||||
apiGroup.DELETE("/white-list/:ip", r.authMiddleware.Handle(), r.apiHandler.DeleteWhiteListIP)
|
||||
|
||||
// API调用记录接口
|
||||
apiGroup.GET("/my/api-calls", r.authMiddleware.Handle(), r.apiHandler.GetUserApiCalls)
|
||||
|
||||
// 余额预警设置接口
|
||||
apiGroup.GET("/user/balance-alert/settings", r.authMiddleware.Handle(), r.apiHandler.GetUserBalanceAlertSettings)
|
||||
apiGroup.PUT("/user/balance-alert/settings", r.authMiddleware.Handle(), r.apiHandler.UpdateUserBalanceAlertSettings)
|
||||
apiGroup.POST("/user/balance-alert/test-sms", r.authMiddleware.Handle(), r.apiHandler.TestBalanceAlertSms)
|
||||
}
|
||||
|
||||
r.logger.Info("API路由注册完成")
|
||||
}
|
||||
109
internal/infrastructure/http/routes/article_routes.go
Normal file
109
internal/infrastructure/http/routes/article_routes.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ArticleRoutes 文章路由
|
||||
type ArticleRoutes struct {
|
||||
handler *handlers.ArticleHandler
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
admin *middleware.AdminAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewArticleRoutes 创建文章路由
|
||||
func NewArticleRoutes(
|
||||
handler *handlers.ArticleHandler,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
admin *middleware.AdminAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *ArticleRoutes {
|
||||
return &ArticleRoutes{
|
||||
handler: handler,
|
||||
auth: auth,
|
||||
admin: admin,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册路由
|
||||
func (r *ArticleRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
|
||||
// ==================== 用户端路由 ====================
|
||||
// 文章相关路由 - 用户端
|
||||
articleGroup := engine.Group("/api/v1/articles")
|
||||
{
|
||||
// 公开路由 - 不需要认证
|
||||
articleGroup.GET("/:id", r.handler.GetArticleByID) // 获取文章详情
|
||||
articleGroup.GET("", r.handler.ListArticles) // 获取文章列表(支持筛选:标题、分类、摘要、标签、推荐状态)
|
||||
}
|
||||
|
||||
// 分类相关路由 - 用户端
|
||||
categoryGroup := engine.Group("/api/v1/article-categories")
|
||||
{
|
||||
// 公开路由 - 不需要认证
|
||||
categoryGroup.GET("", r.handler.ListCategories) // 获取分类列表
|
||||
categoryGroup.GET("/:id", r.handler.GetCategoryByID) // 获取分类详情
|
||||
}
|
||||
|
||||
// 标签相关路由 - 用户端
|
||||
tagGroup := engine.Group("/api/v1/article-tags")
|
||||
{
|
||||
// 公开路由 - 不需要认证
|
||||
tagGroup.GET("", r.handler.ListTags) // 获取标签列表
|
||||
tagGroup.GET("/:id", r.handler.GetTagByID) // 获取标签详情
|
||||
}
|
||||
|
||||
// ==================== 管理员端路由 ====================
|
||||
// 管理员文章管理路由
|
||||
adminArticleGroup := engine.Group("/api/v1/admin/articles")
|
||||
adminArticleGroup.Use(r.admin.Handle())
|
||||
{
|
||||
// 统计信息
|
||||
adminArticleGroup.GET("/stats", r.handler.GetArticleStats) // 获取文章统计
|
||||
|
||||
// 文章列表查询
|
||||
adminArticleGroup.GET("", r.handler.ListArticlesForAdmin) // 获取文章列表(管理员端,包含所有状态)
|
||||
|
||||
// 文章管理
|
||||
adminArticleGroup.POST("", r.handler.CreateArticle) // 创建文章
|
||||
adminArticleGroup.PUT("/:id", r.handler.UpdateArticle) // 更新文章
|
||||
adminArticleGroup.DELETE("/:id", r.handler.DeleteArticle) // 删除文章
|
||||
|
||||
// 文章状态管理
|
||||
adminArticleGroup.POST("/:id/publish", r.handler.PublishArticle) // 发布文章
|
||||
adminArticleGroup.POST("/:id/schedule-publish", r.handler.SchedulePublishArticle) // 定时发布文章
|
||||
adminArticleGroup.POST("/:id/update-schedule-publish", r.handler.UpdateSchedulePublishArticle) // 修改定时发布时间
|
||||
adminArticleGroup.POST("/:id/cancel-schedule", r.handler.CancelSchedulePublishArticle) // 取消定时发布
|
||||
adminArticleGroup.POST("/:id/archive", r.handler.ArchiveArticle) // 归档文章
|
||||
adminArticleGroup.PUT("/:id/featured", r.handler.SetFeatured) // 设置推荐状态
|
||||
}
|
||||
|
||||
// 管理员分类管理路由
|
||||
adminCategoryGroup := engine.Group("/api/v1/admin/article-categories")
|
||||
adminCategoryGroup.Use(r.admin.Handle())
|
||||
{
|
||||
// 分类管理
|
||||
adminCategoryGroup.POST("", r.handler.CreateCategory) // 创建分类
|
||||
adminCategoryGroup.PUT("/:id", r.handler.UpdateCategory) // 更新分类
|
||||
adminCategoryGroup.DELETE("/:id", r.handler.DeleteCategory) // 删除分类
|
||||
}
|
||||
|
||||
// 管理员标签管理路由
|
||||
adminTagGroup := engine.Group("/api/v1/admin/article-tags")
|
||||
adminTagGroup.Use(r.admin.Handle())
|
||||
{
|
||||
// 标签管理
|
||||
adminTagGroup.POST("", r.handler.CreateTag) // 创建标签
|
||||
adminTagGroup.PUT("/:id", r.handler.UpdateTag) // 更新标签
|
||||
adminTagGroup.DELETE("/:id", r.handler.DeleteTag) // 删除标签
|
||||
}
|
||||
|
||||
r.logger.Info("文章路由注册完成")
|
||||
}
|
||||
33
internal/infrastructure/http/routes/captcha_routes.go
Normal file
33
internal/infrastructure/http/routes/captcha_routes.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// CaptchaRoutes 验证码路由
|
||||
type CaptchaRoutes struct {
|
||||
handler *handlers.CaptchaHandler
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCaptchaRoutes 创建验证码路由
|
||||
func NewCaptchaRoutes(handler *handlers.CaptchaHandler, logger *zap.Logger) *CaptchaRoutes {
|
||||
return &CaptchaRoutes{
|
||||
handler: handler,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册验证码相关路由
|
||||
func (r *CaptchaRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
captchaGroup := engine.Group("/api/v1/captcha")
|
||||
{
|
||||
captchaGroup.POST("/encryptedSceneId", r.handler.GetEncryptedSceneId)
|
||||
captchaGroup.GET("/config", r.handler.GetConfig)
|
||||
}
|
||||
r.logger.Info("验证码路由注册完成")
|
||||
}
|
||||
129
internal/infrastructure/http/routes/certification_routes.go
Normal file
129
internal/infrastructure/http/routes/certification_routes.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
"hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
)
|
||||
|
||||
// CertificationRoutes 认证路由
|
||||
type CertificationRoutes struct {
|
||||
handler *handlers.CertificationHandler
|
||||
router *http.GinRouter
|
||||
logger *zap.Logger
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
admin *middleware.AdminAuthMiddleware
|
||||
optional *middleware.OptionalAuthMiddleware
|
||||
dailyRateLimit *middleware.DailyRateLimitMiddleware
|
||||
}
|
||||
|
||||
// NewCertificationRoutes 创建认证路由
|
||||
func NewCertificationRoutes(
|
||||
handler *handlers.CertificationHandler,
|
||||
router *http.GinRouter,
|
||||
logger *zap.Logger,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
admin *middleware.AdminAuthMiddleware,
|
||||
optional *middleware.OptionalAuthMiddleware,
|
||||
dailyRateLimit *middleware.DailyRateLimitMiddleware,
|
||||
) *CertificationRoutes {
|
||||
return &CertificationRoutes{
|
||||
handler: handler,
|
||||
router: router,
|
||||
logger: logger,
|
||||
auth: auth,
|
||||
admin: admin,
|
||||
optional: optional,
|
||||
dailyRateLimit: dailyRateLimit,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册认证路由
|
||||
func (r *CertificationRoutes) Register(router *http.GinRouter) {
|
||||
// 认证管理路由组
|
||||
certificationGroup := router.GetEngine().Group("/api/v1/certifications")
|
||||
{
|
||||
// 需要认证的路由
|
||||
authGroup := certificationGroup.Group("")
|
||||
authGroup.Use(r.auth.Handle())
|
||||
{
|
||||
authGroup.GET("", r.handler.ListCertifications) // 查询认证列表(管理员)
|
||||
|
||||
// 1. 获取认证详情
|
||||
authGroup.GET("/details", r.handler.GetCertification)
|
||||
|
||||
// 2. 提交企业信息(应用每日限流)
|
||||
authGroup.POST("/enterprise-info", r.dailyRateLimit.Handle(), r.handler.SubmitEnterpriseInfo)
|
||||
|
||||
// OCR营业执照识别接口
|
||||
authGroup.POST("/ocr/business-license", r.handler.RecognizeBusinessLicense)
|
||||
|
||||
// 认证图片上传(七牛云,用于企业信息中的各类图片)
|
||||
authGroup.POST("/upload", r.handler.UploadCertificationFile)
|
||||
|
||||
// 3. 申请合同签署
|
||||
authGroup.POST("/apply-contract", r.handler.ApplyContract)
|
||||
|
||||
// 前端确认是否完成认证
|
||||
authGroup.POST("/confirm-auth", r.handler.ConfirmAuth)
|
||||
|
||||
// 前端确认是否完成签署
|
||||
authGroup.POST("/confirm-sign", r.handler.ConfirmSign)
|
||||
|
||||
// 管理员代用户完成认证(暂不关联合同)
|
||||
authGroup.POST("/admin/complete-without-contract", r.handler.AdminCompleteCertificationWithoutContract)
|
||||
|
||||
}
|
||||
|
||||
// 管理端企业审核(需管理员权限,以状态机状态为准)
|
||||
adminGroup := certificationGroup.Group("/admin")
|
||||
adminGroup.Use(r.auth.Handle())
|
||||
adminGroup.Use(r.admin.Handle())
|
||||
{
|
||||
adminGroup.POST("/transition-status", r.handler.AdminTransitionCertificationStatus)
|
||||
}
|
||||
adminCertGroup := adminGroup.Group("/submit-records")
|
||||
{
|
||||
adminCertGroup.GET("", r.handler.AdminListSubmitRecords)
|
||||
adminCertGroup.GET("/:id", r.handler.AdminGetSubmitRecordByID)
|
||||
adminCertGroup.POST("/:id/approve", r.handler.AdminApproveSubmitRecord)
|
||||
adminCertGroup.POST("/:id/reject", r.handler.AdminRejectSubmitRecord)
|
||||
}
|
||||
|
||||
// 回调路由(不需要认证,但需要验证签名)
|
||||
callbackGroup := certificationGroup.Group("/callbacks")
|
||||
{
|
||||
callbackGroup.POST("/esign", r.handler.HandleEsignCallback) // e签宝回调(统一处理企业认证和合同签署回调)
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info("认证路由注册完成")
|
||||
}
|
||||
|
||||
// GetRoutes 获取路由信息(用于调试)
|
||||
func (r *CertificationRoutes) GetRoutes() []RouteInfo {
|
||||
return []RouteInfo{
|
||||
{Method: "POST", Path: "/api/v1/certifications", Handler: "CreateCertification", Auth: true},
|
||||
{Method: "GET", Path: "/api/v1/certifications/:id", Handler: "GetCertification", Auth: true},
|
||||
{Method: "GET", Path: "/api/v1/certifications/user", Handler: "GetUserCertifications", Auth: true},
|
||||
{Method: "GET", Path: "/api/v1/certifications", Handler: "ListCertifications", Auth: true},
|
||||
{Method: "GET", Path: "/api/v1/certifications/statistics", Handler: "GetCertificationStatistics", Auth: true},
|
||||
{Method: "POST", Path: "/api/v1/certifications/:id/enterprise-info", Handler: "SubmitEnterpriseInfo", Auth: true},
|
||||
{Method: "POST", Path: "/api/v1/certifications/ocr/business-license", Handler: "RecognizeBusinessLicense", Auth: true},
|
||||
{Method: "POST", Path: "/api/v1/certifications/apply-contract", Handler: "ApplyContract", Auth: true},
|
||||
{Method: "POST", Path: "/api/v1/certifications/retry", Handler: "RetryOperation", Auth: true},
|
||||
{Method: "POST", Path: "/api/v1/certifications/force-transition", Handler: "ForceTransitionStatus", Auth: true},
|
||||
{Method: "GET", Path: "/api/v1/certifications/monitoring", Handler: "GetSystemMonitoring", Auth: true},
|
||||
{Method: "POST", Path: "/api/v1/certifications/callbacks/esign", Handler: "HandleEsignCallback", Auth: false},
|
||||
}
|
||||
}
|
||||
|
||||
// RouteInfo 路由信息
|
||||
type RouteInfo struct {
|
||||
Method string
|
||||
Path string
|
||||
Handler string
|
||||
Auth bool
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ComponentReportOrderRoutes 组件报告订单路由
|
||||
type ComponentReportOrderRoutes struct {
|
||||
componentReportOrderHandler *handlers.ComponentReportOrderHandler
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewComponentReportOrderRoutes 创建组件报告订单路由
|
||||
func NewComponentReportOrderRoutes(
|
||||
componentReportOrderHandler *handlers.ComponentReportOrderHandler,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *ComponentReportOrderRoutes {
|
||||
return &ComponentReportOrderRoutes{
|
||||
componentReportOrderHandler: componentReportOrderHandler,
|
||||
auth: auth,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册组件报告订单相关路由
|
||||
func (r *ComponentReportOrderRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
|
||||
// 产品组件报告相关接口 - 需要认证
|
||||
componentReportGroup := engine.Group("/api/v1/products/:id/component-report", r.auth.Handle())
|
||||
{
|
||||
// 检查下载可用性
|
||||
componentReportGroup.GET("/check", r.componentReportOrderHandler.CheckDownloadAvailability)
|
||||
// 获取下载信息
|
||||
componentReportGroup.GET("/info", r.componentReportOrderHandler.GetDownloadInfo)
|
||||
// 创建支付订单
|
||||
componentReportGroup.POST("/create-order", r.componentReportOrderHandler.CreatePaymentOrder)
|
||||
}
|
||||
|
||||
// 组件报告订单相关接口 - 需要认证
|
||||
componentReportOrder := engine.Group("/api/v1/component-report", r.auth.Handle())
|
||||
{
|
||||
// 检查支付状态
|
||||
componentReportOrder.GET("/check-payment/:orderId", r.componentReportOrderHandler.CheckPaymentStatus)
|
||||
// 下载文件
|
||||
componentReportOrder.GET("/download/:orderId", r.componentReportOrderHandler.DownloadFile)
|
||||
// 获取用户订单列表
|
||||
componentReportOrder.GET("/orders", r.componentReportOrderHandler.GetUserOrders)
|
||||
}
|
||||
|
||||
r.logger.Info("组件报告订单路由注册完成")
|
||||
}
|
||||
109
internal/infrastructure/http/routes/finance_routes.go
Normal file
109
internal/infrastructure/http/routes/finance_routes.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// FinanceRoutes 财务路由注册器
|
||||
type FinanceRoutes struct {
|
||||
financeHandler *handlers.FinanceHandler
|
||||
authMiddleware *middleware.JWTAuthMiddleware
|
||||
adminAuthMiddleware *middleware.AdminAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewFinanceRoutes 创建财务路由注册器
|
||||
func NewFinanceRoutes(
|
||||
financeHandler *handlers.FinanceHandler,
|
||||
authMiddleware *middleware.JWTAuthMiddleware,
|
||||
adminAuthMiddleware *middleware.AdminAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *FinanceRoutes {
|
||||
return &FinanceRoutes{
|
||||
financeHandler: financeHandler,
|
||||
authMiddleware: authMiddleware,
|
||||
adminAuthMiddleware: adminAuthMiddleware,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册财务相关路由
|
||||
func (r *FinanceRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
|
||||
// 支付宝回调路由(不需要认证)
|
||||
alipayGroup := engine.Group("/api/v1/finance/alipay")
|
||||
{
|
||||
alipayGroup.POST("/callback", r.financeHandler.HandleAlipayCallback) // 支付宝异步回调
|
||||
alipayGroup.GET("/return", r.financeHandler.HandleAlipayReturn) // 支付宝同步回调
|
||||
}
|
||||
|
||||
// 微信支付回调路由(不需要认证)
|
||||
wechatPayGroup := engine.Group("/api/v1/pay/wechat")
|
||||
{
|
||||
wechatPayGroup.POST("/callback", r.financeHandler.HandleWechatPayCallback) // 微信支付异步回调
|
||||
}
|
||||
|
||||
// 微信退款回调路由(不需要认证)
|
||||
wechatRefundGroup := engine.Group("/api/v1/wechat")
|
||||
{
|
||||
wechatRefundGroup.POST("/refund_callback", r.financeHandler.HandleWechatRefundCallback) // 微信退款异步回调
|
||||
}
|
||||
|
||||
// 财务路由组,需要用户认证
|
||||
financeGroup := engine.Group("/api/v1/finance")
|
||||
financeGroup.Use(r.authMiddleware.Handle())
|
||||
{
|
||||
// 钱包相关路由
|
||||
walletGroup := financeGroup.Group("/wallet")
|
||||
{
|
||||
walletGroup.GET("", r.financeHandler.GetWallet) // 获取钱包信息
|
||||
walletGroup.GET("/transactions", r.financeHandler.GetUserWalletTransactions) // 获取钱包交易记录
|
||||
walletGroup.GET("/recharge-config", r.financeHandler.GetRechargeConfig) // 获取充值配置
|
||||
walletGroup.POST("/alipay-recharge", r.financeHandler.CreateAlipayRecharge) // 创建支付宝充值订单
|
||||
walletGroup.POST("/wechat-recharge", r.financeHandler.CreateWechatRecharge) // 创建微信充值订单
|
||||
walletGroup.GET("/recharge-records", r.financeHandler.GetUserRechargeRecords) // 用户充值记录分页
|
||||
walletGroup.GET("/alipay-order-status", r.financeHandler.GetAlipayOrderStatus) // 获取支付宝订单状态
|
||||
walletGroup.GET("/wechat-order-status", r.financeHandler.GetWechatOrderStatus) // 获取微信订单状态
|
||||
financeGroup.GET("/purchase-records", r.financeHandler.GetUserPurchaseRecords) // 用户购买记录分页
|
||||
}
|
||||
}
|
||||
|
||||
// 发票相关路由,需要用户认证
|
||||
invoiceGroup := engine.Group("/api/v1/invoices")
|
||||
invoiceGroup.Use(r.authMiddleware.Handle())
|
||||
{
|
||||
invoiceGroup.POST("/apply", r.financeHandler.ApplyInvoice) // 申请开票
|
||||
invoiceGroup.GET("/info", r.financeHandler.GetUserInvoiceInfo) // 获取用户发票信息
|
||||
invoiceGroup.PUT("/info", r.financeHandler.UpdateUserInvoiceInfo) // 更新用户发票信息
|
||||
invoiceGroup.GET("/records", r.financeHandler.GetUserInvoiceRecords) // 获取用户开票记录
|
||||
invoiceGroup.GET("/available-amount", r.financeHandler.GetAvailableAmount) // 获取可开票金额
|
||||
invoiceGroup.GET("/:application_id/download", r.financeHandler.DownloadInvoiceFile) // 下载发票文件
|
||||
}
|
||||
|
||||
// 管理员财务路由组
|
||||
adminFinanceGroup := engine.Group("/api/v1/admin/finance")
|
||||
adminFinanceGroup.Use(r.adminAuthMiddleware.Handle())
|
||||
{
|
||||
adminFinanceGroup.POST("/transfer-recharge", r.financeHandler.TransferRecharge) // 对公转账充值
|
||||
adminFinanceGroup.POST("/gift-recharge", r.financeHandler.GiftRecharge) // 赠送充值
|
||||
adminFinanceGroup.GET("/recharge-records", r.financeHandler.GetAdminRechargeRecords) // 管理员充值记录分页
|
||||
adminFinanceGroup.GET("/purchase-records", r.financeHandler.GetAdminPurchaseRecords) // 管理员购买记录分页
|
||||
}
|
||||
|
||||
// 管理员发票相关路由组
|
||||
adminInvoiceGroup := engine.Group("/api/v1/admin/invoices")
|
||||
adminInvoiceGroup.Use(r.adminAuthMiddleware.Handle())
|
||||
{
|
||||
adminInvoiceGroup.GET("/pending", r.financeHandler.GetPendingApplications) // 获取待处理申请列表
|
||||
adminInvoiceGroup.POST("/:application_id/approve", r.financeHandler.ApproveInvoiceApplication) // 通过发票申请
|
||||
adminInvoiceGroup.POST("/:application_id/reject", r.financeHandler.RejectInvoiceApplication) // 拒绝发票申请
|
||||
adminInvoiceGroup.GET("/:application_id/download", r.financeHandler.AdminDownloadInvoiceFile) // 下载发票文件
|
||||
}
|
||||
|
||||
r.logger.Info("财务路由注册完成")
|
||||
}
|
||||
38
internal/infrastructure/http/routes/pdfg_routes.go
Normal file
38
internal/infrastructure/http/routes/pdfg_routes.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
)
|
||||
|
||||
// PDFGRoutes PDFG路由
|
||||
type PDFGRoutes struct {
|
||||
pdfgHandler *handlers.PDFGHandler
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewPDFGRoutes 创建PDFG路由
|
||||
func NewPDFGRoutes(
|
||||
pdfgHandler *handlers.PDFGHandler,
|
||||
logger *zap.Logger,
|
||||
) *PDFGRoutes {
|
||||
return &PDFGRoutes{
|
||||
pdfgHandler: pdfgHandler,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册相关路由
|
||||
func (r *PDFGRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
apiGroup := engine.Group("/api/v1")
|
||||
|
||||
{
|
||||
// PDF下载接口 - 不需要认证(因为下载链接已经包含了验证信息)
|
||||
apiGroup.GET("/pdfg/download", r.pdfgHandler.DownloadPDF)
|
||||
}
|
||||
|
||||
r.logger.Info("PDFG路由注册完成")
|
||||
}
|
||||
105
internal/infrastructure/http/routes/product_admin_routes.go
Normal file
105
internal/infrastructure/http/routes/product_admin_routes.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
)
|
||||
|
||||
// ProductAdminRoutes 产品管理员路由
|
||||
type ProductAdminRoutes struct {
|
||||
handler *handlers.ProductAdminHandler
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
admin *middleware.AdminAuthMiddleware
|
||||
}
|
||||
|
||||
// NewProductAdminRoutes 创建产品管理员路由
|
||||
func NewProductAdminRoutes(
|
||||
handler *handlers.ProductAdminHandler,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
admin *middleware.AdminAuthMiddleware,
|
||||
) *ProductAdminRoutes {
|
||||
return &ProductAdminRoutes{
|
||||
handler: handler,
|
||||
auth: auth,
|
||||
admin: admin,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册路由
|
||||
func (r *ProductAdminRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
// 管理员路由组
|
||||
engine := router.GetEngine()
|
||||
adminGroup := engine.Group("/api/v1/admin")
|
||||
adminGroup.Use(r.admin.Handle()) // 管理员权限验证
|
||||
{
|
||||
// 产品管理
|
||||
products := adminGroup.Group("/products")
|
||||
{
|
||||
products.GET("", r.handler.ListProducts)
|
||||
products.GET("/available", r.handler.GetAvailableProducts)
|
||||
products.GET("/:id", r.handler.GetProductDetail)
|
||||
products.POST("", r.handler.CreateProduct)
|
||||
products.PUT("/:id", r.handler.UpdateProduct)
|
||||
products.DELETE("/:id", r.handler.DeleteProduct)
|
||||
|
||||
// 组合包管理
|
||||
products.POST("/:id/package-items", r.handler.AddPackageItem)
|
||||
products.PUT("/:id/package-items/:item_id", r.handler.UpdatePackageItem)
|
||||
products.DELETE("/:id/package-items/:item_id", r.handler.RemovePackageItem)
|
||||
products.PUT("/:id/package-items/reorder", r.handler.ReorderPackageItems)
|
||||
products.PUT("/:id/package-items/batch", r.handler.UpdatePackageItems)
|
||||
|
||||
// API配置管理
|
||||
products.GET("/:id/api-config", r.handler.GetProductApiConfig)
|
||||
products.POST("/:id/api-config", r.handler.CreateProductApiConfig)
|
||||
products.PUT("/:id/api-config", r.handler.UpdateProductApiConfig)
|
||||
products.DELETE("/:id/api-config", r.handler.DeleteProductApiConfig)
|
||||
|
||||
// 文档管理
|
||||
products.GET("/:id/documentation", r.handler.GetProductDocumentation)
|
||||
products.POST("/:id/documentation", r.handler.CreateOrUpdateProductDocumentation)
|
||||
products.DELETE("/:id/documentation", r.handler.DeleteProductDocumentation)
|
||||
}
|
||||
|
||||
// 分类管理
|
||||
categories := adminGroup.Group("/product-categories")
|
||||
{
|
||||
categories.GET("", r.handler.ListCategories)
|
||||
categories.GET("/:id", r.handler.GetCategoryDetail)
|
||||
categories.POST("", r.handler.CreateCategory)
|
||||
categories.PUT("/:id", r.handler.UpdateCategory)
|
||||
categories.DELETE("/:id", r.handler.DeleteCategory)
|
||||
}
|
||||
|
||||
// 订阅管理
|
||||
subscriptions := adminGroup.Group("/subscriptions")
|
||||
{
|
||||
subscriptions.GET("", r.handler.ListSubscriptions)
|
||||
subscriptions.GET("/stats", r.handler.GetSubscriptionStats)
|
||||
subscriptions.PUT("/:id/price", r.handler.UpdateSubscriptionPrice)
|
||||
subscriptions.POST("/batch-update-prices", r.handler.BatchUpdateSubscriptionPrices)
|
||||
}
|
||||
|
||||
// 消费记录管理
|
||||
walletTransactions := adminGroup.Group("/wallet-transactions")
|
||||
{
|
||||
walletTransactions.GET("", r.handler.GetAdminWalletTransactions)
|
||||
walletTransactions.GET("/export", r.handler.ExportAdminWalletTransactions)
|
||||
}
|
||||
|
||||
// API调用记录管理
|
||||
apiCalls := adminGroup.Group("/api-calls")
|
||||
{
|
||||
apiCalls.GET("", r.handler.GetAdminApiCalls)
|
||||
apiCalls.GET("/export", r.handler.ExportAdminApiCalls)
|
||||
}
|
||||
|
||||
// 充值记录管理
|
||||
rechargeRecords := adminGroup.Group("/recharge-records")
|
||||
{
|
||||
rechargeRecords.GET("", r.handler.GetAdminRechargeRecords)
|
||||
rechargeRecords.GET("/export", r.handler.ExportAdminRechargeRecords)
|
||||
}
|
||||
}
|
||||
}
|
||||
108
internal/infrastructure/http/routes/product_routes.go
Normal file
108
internal/infrastructure/http/routes/product_routes.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
component_report "hyapi-server/internal/shared/component_report"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProductRoutes 产品路由
|
||||
type ProductRoutes struct {
|
||||
productHandler *handlers.ProductHandler
|
||||
componentReportHandler *component_report.ComponentReportHandler
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
optionalAuth *middleware.OptionalAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductRoutes 创建产品路由
|
||||
func NewProductRoutes(
|
||||
productHandler *handlers.ProductHandler,
|
||||
componentReportHandler *component_report.ComponentReportHandler,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
optionalAuth *middleware.OptionalAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *ProductRoutes {
|
||||
return &ProductRoutes{
|
||||
productHandler: productHandler,
|
||||
componentReportHandler: componentReportHandler,
|
||||
auth: auth,
|
||||
optionalAuth: optionalAuth,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册产品相关路由
|
||||
func (r *ProductRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
|
||||
// 数据大厅 - 公开接口
|
||||
products := engine.Group("/api/v1/products")
|
||||
{
|
||||
// 获取产品列表(分页+筛选)
|
||||
products.GET("", r.optionalAuth.Handle(), r.productHandler.ListProducts)
|
||||
|
||||
// 获取产品统计
|
||||
products.GET("/stats", r.productHandler.GetProductStats)
|
||||
|
||||
// 根据产品代码获取API配置
|
||||
products.GET("/code/:product_code/api-config", r.productHandler.GetProductApiConfigByCode)
|
||||
|
||||
// 产品详情和API配置 - 使用具体路径避免冲突
|
||||
products.GET("/:id", r.productHandler.GetProductDetail)
|
||||
products.GET("/:id/api-config", r.productHandler.GetProductApiConfig)
|
||||
products.GET("/:id/documentation", r.productHandler.GetProductDocumentation)
|
||||
products.GET("/:id/documentation/download", r.productHandler.DownloadProductDocumentation)
|
||||
|
||||
// 订阅产品(需要认证)
|
||||
products.POST("/:id/subscribe", r.auth.Handle(), r.productHandler.SubscribeProduct)
|
||||
}
|
||||
|
||||
// 组件报告 - 需要认证
|
||||
componentReport := engine.Group("/api/v1/component-report", r.auth.Handle())
|
||||
{
|
||||
// 生成并下载 example.json 文件
|
||||
componentReport.POST("/download-example-json", r.componentReportHandler.DownloadExampleJSON)
|
||||
// 生成并下载示例报告ZIP文件
|
||||
componentReport.POST("/generate-and-download", r.componentReportHandler.GenerateAndDownloadZip)
|
||||
}
|
||||
|
||||
// 产品组件报告相关接口 - 已迁移到 ComponentReportOrderRoutes
|
||||
|
||||
// 分类 - 公开接口
|
||||
categories := engine.Group("/api/v1/categories")
|
||||
{
|
||||
// 获取分类列表
|
||||
categories.GET("", r.productHandler.ListCategories)
|
||||
|
||||
// 获取分类详情
|
||||
categories.GET("/:id", r.productHandler.GetCategoryDetail)
|
||||
}
|
||||
|
||||
// 我的订阅 - 需要认证
|
||||
my := engine.Group("/api/v1/my", r.auth.Handle())
|
||||
{
|
||||
subscriptions := my.Group("/subscriptions")
|
||||
{
|
||||
// 获取我的订阅列表
|
||||
subscriptions.GET("", r.productHandler.ListMySubscriptions)
|
||||
|
||||
// 获取我的订阅统计
|
||||
subscriptions.GET("/stats", r.productHandler.GetMySubscriptionStats)
|
||||
|
||||
// 获取订阅详情
|
||||
subscriptions.GET("/:id", r.productHandler.GetMySubscriptionDetail)
|
||||
|
||||
// 获取订阅使用情况
|
||||
subscriptions.GET("/:id/usage", r.productHandler.GetMySubscriptionUsage)
|
||||
|
||||
// 取消订阅
|
||||
subscriptions.POST("/:id/cancel", r.productHandler.CancelMySubscription)
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info("产品路由注册完成")
|
||||
}
|
||||
37
internal/infrastructure/http/routes/qygl_report_routes.go
Normal file
37
internal/infrastructure/http/routes/qygl_report_routes.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
)
|
||||
|
||||
// QYGLReportRoutes 企业报告页面路由注册器
|
||||
type QYGLReportRoutes struct {
|
||||
handler *handlers.QYGLReportHandler
|
||||
}
|
||||
|
||||
// NewQYGLReportRoutes 创建企业报告页面路由注册器
|
||||
func NewQYGLReportRoutes(
|
||||
handler *handlers.QYGLReportHandler,
|
||||
) *QYGLReportRoutes {
|
||||
return &QYGLReportRoutes{
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册企业报告页面路由
|
||||
func (r *QYGLReportRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
|
||||
// 企业全景报告页面(实时生成)
|
||||
engine.GET("/reports/qygl", r.handler.GetQYGLReportPage)
|
||||
|
||||
// 企业全景报告页面(通过编号查看)
|
||||
engine.GET("/reports/qygl/:id", r.handler.GetQYGLReportPageByID)
|
||||
|
||||
// 企业全景报告 PDF 预生成状态(通过编号,供前端轮询)
|
||||
engine.GET("/reports/qygl/:id/pdf/status", r.handler.GetQYGLReportPDFStatusByID)
|
||||
|
||||
// 企业全景报告 PDF 导出(通过编号)
|
||||
engine.GET("/reports/qygl/:id/pdf", r.handler.GetQYGLReportPDFByID)
|
||||
}
|
||||
168
internal/infrastructure/http/routes/statistics_routes.go
Normal file
168
internal/infrastructure/http/routes/statistics_routes.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// StatisticsRoutes 统计路由
|
||||
type StatisticsRoutes struct {
|
||||
statisticsHandler *handlers.StatisticsHandler
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
optionalAuth *middleware.OptionalAuthMiddleware
|
||||
admin *middleware.AdminAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewStatisticsRoutes 创建统计路由
|
||||
func NewStatisticsRoutes(
|
||||
statisticsHandler *handlers.StatisticsHandler,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
optionalAuth *middleware.OptionalAuthMiddleware,
|
||||
admin *middleware.AdminAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *StatisticsRoutes {
|
||||
return &StatisticsRoutes{
|
||||
statisticsHandler: statisticsHandler,
|
||||
auth: auth,
|
||||
optionalAuth: optionalAuth,
|
||||
admin: admin,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册统计相关路由
|
||||
func (r *StatisticsRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
|
||||
// ================ 用户端统计路由 ================
|
||||
|
||||
// 统计公开接口
|
||||
statistics := engine.Group("/api/v1/statistics")
|
||||
{
|
||||
// 获取公开统计信息
|
||||
statistics.GET("/public", r.statisticsHandler.GetPublicStatistics)
|
||||
|
||||
// 获取API受欢迎榜单(公开接口)
|
||||
statistics.GET("/api-popularity-ranking", r.statisticsHandler.GetPublicApiPopularityRanking)
|
||||
|
||||
// 获取最新产品推荐(公开接口)
|
||||
statistics.GET("/latest-products", r.statisticsHandler.GetLatestProducts)
|
||||
}
|
||||
|
||||
// 用户统计接口 - 需要认证
|
||||
userStats := engine.Group("/api/v1/statistics", r.auth.Handle())
|
||||
{
|
||||
// 获取用户统计信息
|
||||
userStats.GET("/user", r.statisticsHandler.GetUserStatistics)
|
||||
|
||||
// 独立统计接口(用户只能查询自己的数据)
|
||||
userStats.GET("/api-calls", r.statisticsHandler.GetApiCallsStatistics)
|
||||
userStats.GET("/consumption", r.statisticsHandler.GetConsumptionStatistics)
|
||||
userStats.GET("/recharge", r.statisticsHandler.GetRechargeStatistics)
|
||||
|
||||
// 获取指标列表
|
||||
userStats.GET("/metrics", r.statisticsHandler.GetMetrics)
|
||||
|
||||
// 获取指标详情
|
||||
userStats.GET("/metrics/:id", r.statisticsHandler.GetMetricDetail)
|
||||
|
||||
// 获取仪表板列表
|
||||
userStats.GET("/dashboards", r.statisticsHandler.GetDashboards)
|
||||
|
||||
// 获取仪表板详情
|
||||
userStats.GET("/dashboards/:id", r.statisticsHandler.GetDashboardDetail)
|
||||
|
||||
// 获取仪表板数据
|
||||
userStats.GET("/dashboards/:id/data", r.statisticsHandler.GetDashboardData)
|
||||
|
||||
// 获取报告列表
|
||||
userStats.GET("/reports", r.statisticsHandler.GetReports)
|
||||
|
||||
// 获取报告详情
|
||||
userStats.GET("/reports/:id", r.statisticsHandler.GetReportDetail)
|
||||
|
||||
// 创建报告
|
||||
userStats.POST("/reports", r.statisticsHandler.CreateReport)
|
||||
}
|
||||
|
||||
// ================ 管理员统计路由 ================
|
||||
|
||||
// 管理员路由组
|
||||
adminGroup := engine.Group("/api/v1/admin")
|
||||
adminGroup.Use(r.admin.Handle()) // 管理员权限验证
|
||||
{
|
||||
// 统计指标管理
|
||||
metrics := adminGroup.Group("/statistics/metrics")
|
||||
{
|
||||
metrics.GET("", r.statisticsHandler.AdminGetMetrics)
|
||||
metrics.POST("", r.statisticsHandler.AdminCreateMetric)
|
||||
metrics.PUT("/:id", r.statisticsHandler.AdminUpdateMetric)
|
||||
metrics.DELETE("/:id", r.statisticsHandler.AdminDeleteMetric)
|
||||
}
|
||||
|
||||
// 仪表板管理
|
||||
dashboards := adminGroup.Group("/statistics/dashboards")
|
||||
{
|
||||
dashboards.GET("", r.statisticsHandler.AdminGetDashboards)
|
||||
dashboards.POST("", r.statisticsHandler.AdminCreateDashboard)
|
||||
dashboards.PUT("/:id", r.statisticsHandler.AdminUpdateDashboard)
|
||||
dashboards.DELETE("/:id", r.statisticsHandler.AdminDeleteDashboard)
|
||||
}
|
||||
|
||||
// 报告管理
|
||||
reports := adminGroup.Group("/statistics/reports")
|
||||
{
|
||||
reports.GET("", r.statisticsHandler.AdminGetReports)
|
||||
}
|
||||
|
||||
// 系统统计
|
||||
system := adminGroup.Group("/statistics/system")
|
||||
{
|
||||
system.GET("", r.statisticsHandler.AdminGetSystemStatistics)
|
||||
}
|
||||
|
||||
// 独立域统计接口
|
||||
domainStats := adminGroup.Group("/statistics")
|
||||
{
|
||||
domainStats.GET("/user-domain", r.statisticsHandler.AdminGetUserDomainStatistics)
|
||||
domainStats.GET("/api-domain", r.statisticsHandler.AdminGetApiDomainStatistics)
|
||||
domainStats.GET("/consumption-domain", r.statisticsHandler.AdminGetConsumptionDomainStatistics)
|
||||
domainStats.GET("/recharge-domain", r.statisticsHandler.AdminGetRechargeDomainStatistics)
|
||||
}
|
||||
|
||||
// 排行榜接口
|
||||
rankings := adminGroup.Group("/statistics")
|
||||
{
|
||||
rankings.GET("/user-call-ranking", r.statisticsHandler.AdminGetUserCallRanking)
|
||||
rankings.GET("/recharge-ranking", r.statisticsHandler.AdminGetRechargeRanking)
|
||||
rankings.GET("/api-popularity-ranking", r.statisticsHandler.AdminGetApiPopularityRanking)
|
||||
rankings.GET("/today-certified-enterprises", r.statisticsHandler.AdminGetTodayCertifiedEnterprises)
|
||||
}
|
||||
|
||||
// 用户统计
|
||||
userStats := adminGroup.Group("/statistics/users")
|
||||
{
|
||||
userStats.GET("/:user_id", r.statisticsHandler.AdminGetUserStatistics)
|
||||
}
|
||||
|
||||
// 独立统计接口(管理员可查询任意用户)
|
||||
independentStats := adminGroup.Group("/statistics")
|
||||
{
|
||||
independentStats.GET("/api-calls", r.statisticsHandler.GetApiCallsStatistics)
|
||||
independentStats.GET("/consumption", r.statisticsHandler.GetConsumptionStatistics)
|
||||
independentStats.GET("/recharge", r.statisticsHandler.GetRechargeStatistics)
|
||||
}
|
||||
|
||||
// 数据聚合
|
||||
aggregation := adminGroup.Group("/statistics/aggregation")
|
||||
{
|
||||
aggregation.POST("/trigger", r.statisticsHandler.AdminTriggerAggregation)
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info("统计路由注册完成")
|
||||
}
|
||||
52
internal/infrastructure/http/routes/sub_category_routes.go
Normal file
52
internal/infrastructure/http/routes/sub_category_routes.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
)
|
||||
|
||||
// SubCategoryRoutes 二级分类路由
|
||||
type SubCategoryRoutes struct {
|
||||
handler *handlers.SubCategoryHandler
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
admin *middleware.AdminAuthMiddleware
|
||||
}
|
||||
|
||||
// NewSubCategoryRoutes 创建二级分类路由
|
||||
func NewSubCategoryRoutes(
|
||||
handler *handlers.SubCategoryHandler,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
admin *middleware.AdminAuthMiddleware,
|
||||
) *SubCategoryRoutes {
|
||||
return &SubCategoryRoutes{
|
||||
handler: handler,
|
||||
auth: auth,
|
||||
admin: admin,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册路由
|
||||
func (r *SubCategoryRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
adminGroup := engine.Group("/api/v1/admin")
|
||||
adminGroup.Use(r.auth.Handle())
|
||||
adminGroup.Use(r.admin.Handle())
|
||||
{
|
||||
// 二级分类管理
|
||||
subCategories := adminGroup.Group("/sub-categories")
|
||||
{
|
||||
subCategories.POST("", r.handler.CreateSubCategory) // 创建二级分类
|
||||
subCategories.PUT("/:id", r.handler.UpdateSubCategory) // 更新二级分类
|
||||
subCategories.DELETE("/:id", r.handler.DeleteSubCategory) // 删除二级分类
|
||||
subCategories.GET("/:id", r.handler.GetSubCategory) // 获取二级分类详情
|
||||
subCategories.GET("", r.handler.ListSubCategories) // 获取二级分类列表
|
||||
}
|
||||
|
||||
// 一级分类下的二级分类路由(级联选择)
|
||||
categoryAdmin := adminGroup.Group("/product-categories")
|
||||
{
|
||||
categoryAdmin.GET("/:id/sub-categories", r.handler.ListSubCategoriesByCategory) // 根据一级分类获取二级分类列表
|
||||
}
|
||||
}
|
||||
}
|
||||
56
internal/infrastructure/http/routes/ui_component_routes.go
Normal file
56
internal/infrastructure/http/routes/ui_component_routes.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
)
|
||||
|
||||
// UIComponentRoutes UI组件路由
|
||||
type UIComponentRoutes struct {
|
||||
uiComponentHandler *handlers.UIComponentHandler
|
||||
logger *zap.Logger
|
||||
auth *middleware.JWTAuthMiddleware
|
||||
admin *middleware.AdminAuthMiddleware
|
||||
}
|
||||
|
||||
// NewUIComponentRoutes 创建UI组件路由
|
||||
func NewUIComponentRoutes(
|
||||
uiComponentHandler *handlers.UIComponentHandler,
|
||||
logger *zap.Logger,
|
||||
auth *middleware.JWTAuthMiddleware,
|
||||
admin *middleware.AdminAuthMiddleware,
|
||||
) *UIComponentRoutes {
|
||||
return &UIComponentRoutes{
|
||||
uiComponentHandler: uiComponentHandler,
|
||||
logger: logger,
|
||||
auth: auth,
|
||||
admin: admin,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册UI组件路由
|
||||
func (r *UIComponentRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
// 管理员路由组
|
||||
engine := router.GetEngine()
|
||||
uiComponentGroup := engine.Group("/api/v1/admin/ui-components")
|
||||
uiComponentGroup.Use(r.admin.Handle()) // 管理员权限验证
|
||||
{
|
||||
// UI组件管理
|
||||
uiComponentGroup.POST("", r.uiComponentHandler.CreateUIComponent) // 创建UI组件
|
||||
uiComponentGroup.POST("/create-with-file", r.uiComponentHandler.CreateUIComponentWithFile) // 创建UI组件并上传文件
|
||||
uiComponentGroup.GET("", r.uiComponentHandler.ListUIComponents) // 获取UI组件列表
|
||||
uiComponentGroup.GET("/:id", r.uiComponentHandler.GetUIComponent) // 获取UI组件详情
|
||||
uiComponentGroup.PUT("/:id", r.uiComponentHandler.UpdateUIComponent) // 更新UI组件
|
||||
uiComponentGroup.DELETE("/:id", r.uiComponentHandler.DeleteUIComponent) // 删除UI组件
|
||||
|
||||
// 文件操作
|
||||
uiComponentGroup.POST("/:id/upload", r.uiComponentHandler.UploadUIComponentFile) // 上传UI组件文件
|
||||
uiComponentGroup.POST("/:id/upload-extract", r.uiComponentHandler.UploadAndExtractUIComponentFile) // 上传并解压UI组件文件
|
||||
uiComponentGroup.GET("/:id/folder-content", r.uiComponentHandler.GetUIComponentFolderContent) // 获取UI组件文件夹内容
|
||||
uiComponentGroup.DELETE("/:id/folder", r.uiComponentHandler.DeleteUIComponentFolder) // 删除UI组件文件夹
|
||||
uiComponentGroup.GET("/:id/download", r.uiComponentHandler.DownloadUIComponentFile) // 下载UI组件文件
|
||||
}
|
||||
}
|
||||
67
internal/infrastructure/http/routes/user_routes.go
Normal file
67
internal/infrastructure/http/routes/user_routes.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"hyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "hyapi-server/internal/shared/http"
|
||||
"hyapi-server/internal/shared/middleware"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// UserRoutes 用户路由注册器
|
||||
type UserRoutes struct {
|
||||
handler *handlers.UserHandler
|
||||
authMiddleware *middleware.JWTAuthMiddleware
|
||||
adminAuthMiddleware *middleware.AdminAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserRoutes 创建用户路由注册器
|
||||
func NewUserRoutes(
|
||||
handler *handlers.UserHandler,
|
||||
authMiddleware *middleware.JWTAuthMiddleware,
|
||||
adminAuthMiddleware *middleware.AdminAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *UserRoutes {
|
||||
return &UserRoutes{
|
||||
handler: handler,
|
||||
authMiddleware: authMiddleware,
|
||||
adminAuthMiddleware: adminAuthMiddleware,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册用户相关路由
|
||||
func (r *UserRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
// 用户域路由组
|
||||
engine := router.GetEngine()
|
||||
usersGroup := engine.Group("/api/v1/users")
|
||||
{
|
||||
// 公开路由(不需要认证)
|
||||
usersGroup.POST("/send-code", r.handler.SendCode) // 发送验证码
|
||||
usersGroup.POST("/register", r.handler.Register) // 用户注册
|
||||
usersGroup.POST("/login-password", r.handler.LoginWithPassword) // 密码登录
|
||||
usersGroup.POST("/login-sms", r.handler.LoginWithSMS) // 短信验证码登录
|
||||
usersGroup.POST("/reset-password", r.handler.ResetPassword) // 重置密码
|
||||
|
||||
// 需要认证的路由
|
||||
authenticated := usersGroup.Group("")
|
||||
authenticated.Use(r.authMiddleware.Handle())
|
||||
{
|
||||
authenticated.GET("/me", r.handler.GetProfile) // 获取当前用户信息
|
||||
authenticated.PUT("/me/password", r.handler.ChangePassword) // 修改密码
|
||||
}
|
||||
|
||||
// 管理员路由
|
||||
adminGroup := usersGroup.Group("/admin")
|
||||
adminGroup.Use(r.adminAuthMiddleware.Handle())
|
||||
{
|
||||
adminGroup.GET("/list", r.handler.ListUsers) // 管理员查看用户列表
|
||||
adminGroup.GET("/:user_id", r.handler.GetUserDetail) // 管理员获取用户详情
|
||||
adminGroup.GET("/stats", r.handler.GetUserStats) // 管理员获取用户统计信息
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info("用户路由注册完成")
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user