Files
agent/backend/internal/logic/files/handlers.go

102 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package files
import (
"errors"
"net/http"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"senlinai-agent/backend/internal/httpx"
"senlinai-agent/backend/internal/logic/auth"
"senlinai-agent/backend/internal/logic/projects"
"senlinai-agent/backend/internal/models"
)
// SourceDTO 是文件资料写接口的稳定响应,仅包含可公开的相对存储路径。
type SourceDTO struct {
ID string `json:"id"`
ProjectID string `json:"projectId"`
Kind string `json:"kind"`
Title string `json:"title"`
FilePath string `json:"filePath"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// Handler 负责 multipart 边界,文件名清理和本地路径构造始终委托给 Service。
type Handler struct {
service *Service
}
// NewHandler 创建文件资料 HTTP registrar。
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
// Register 将文件资料上传接口注册到上层提供的 /api/v1 路由组。
func (h *Handler) Register(router gin.IRouter) {
router.POST("/projects/:id/sources", h.upload)
}
func (h *Handler) upload(c *gin.Context) {
userID, ok := auth.CurrentUserID(c)
if !ok {
httpx.Error(c, http.StatusUnauthorized, "unauthorized", "未登录或登录已失效")
return
}
projectIdentity, ok := httpx.IdentityParam(c, "id")
if !ok {
return
}
// 所有权必须在写磁盘前校验,防止越权请求留下孤立文件。
project, err := projects.FindOwnedProject(userID, projectIdentity)
if err != nil {
writeSourceError(c, err)
return
}
fileHeader, err := c.FormFile("file")
if err != nil {
httpx.Error(c, http.StatusBadRequest, "invalid_request", "请选择要上传的文件")
return
}
file, err := fileHeader.Open()
if err != nil {
httpx.Error(c, http.StatusBadRequest, "invalid_request", "文件读取失败")
return
}
defer file.Close()
// 先由文件服务保存内容再用其返回的相对路径创建资料记录handler 不拼接任何本地路径。
stored, err := h.service.Save(project.ID, fileHeader.Filename, file)
if err != nil {
httpx.Error(c, http.StatusInternalServerError, "internal_error", "文件保存失败")
return
}
source, err := h.service.CreateSource(userID, project, c.PostForm("title"), stored)
if err != nil {
writeSourceError(c, err)
return
}
c.JSON(http.StatusCreated, sourceDTO(*source))
}
func sourceDTO(source models.SenlinAgentSource) SourceDTO {
return SourceDTO{
ID: source.Identity, ProjectID: source.ProjectIdentity, Kind: source.Kind,
Title: source.Title, FilePath: source.FilePath,
CreatedAt: source.CreatedAt.UTC(), UpdatedAt: source.UpdatedAt.UTC(),
}
}
func writeSourceError(c *gin.Context, err error) {
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
httpx.Error(c, http.StatusNotFound, "not_found", "项目不存在")
case errors.Is(err, ErrSourceTitleRequired), errors.Is(err, ErrSourcePathRequired):
httpx.Error(c, http.StatusBadRequest, "invalid_request", "资料参数无效")
default:
httpx.Error(c, http.StatusInternalServerError, "internal_error", "资料创建失败")
}
}