init
This commit is contained in:
@@ -0,0 +1,600 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
apperrors "goravel/app/errors"
|
||||
"goravel/app/http/helpers"
|
||||
"goravel/app/http/response"
|
||||
"goravel/app/models"
|
||||
"goravel/app/services"
|
||||
"goravel/app/utils/errorlog"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
type AttachmentController struct {
|
||||
attachmentService services.AttachmentService
|
||||
}
|
||||
|
||||
func NewAttachmentController() *AttachmentController {
|
||||
return &AttachmentController{}
|
||||
}
|
||||
|
||||
// Index 附件列表
|
||||
func (r *AttachmentController) Index(ctx http.Context) http.Response {
|
||||
page := helpers.GetIntQuery(ctx, "page", 1)
|
||||
pageSize := helpers.GetIntQuery(ctx, "page_size", 10)
|
||||
|
||||
filters := r.buildFilters(ctx)
|
||||
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
attachments, total, err := attachmentService.GetList(filters, page, pageSize)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err)
|
||||
}
|
||||
|
||||
type AttachmentWithURL struct {
|
||||
models.Attachment
|
||||
FileURL string `json:"file_url"`
|
||||
}
|
||||
|
||||
var resultWithURL []AttachmentWithURL
|
||||
for _, a := range attachments {
|
||||
fileURL := attachmentService.GetFileURL(&a)
|
||||
resultWithURL = append(resultWithURL, AttachmentWithURL{
|
||||
Attachment: a,
|
||||
FileURL: fileURL,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, http.Json{
|
||||
"list": resultWithURL,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
// buildFilters 构建查询过滤器
|
||||
func (r *AttachmentController) buildFilters(ctx http.Context) services.AttachmentFilters {
|
||||
adminID := ctx.Request().Query("admin_id", "")
|
||||
filename := ctx.Request().Query("filename", "")
|
||||
displayName := ctx.Request().Query("display_name", "")
|
||||
fileType := ctx.Request().Query("file_type", "")
|
||||
extension := ctx.Request().Query("extension", "")
|
||||
startTime := helpers.GetTimeQueryParam(ctx, "start_time")
|
||||
endTime := helpers.GetTimeQueryParam(ctx, "end_time")
|
||||
orderBy := ctx.Request().Query("order_by", "")
|
||||
|
||||
return services.AttachmentFilters{
|
||||
AdminID: adminID,
|
||||
Filename: filename,
|
||||
DisplayName: displayName,
|
||||
FileType: fileType,
|
||||
Extension: extension,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
OrderBy: orderBy,
|
||||
}
|
||||
}
|
||||
|
||||
// Upload 普通文件上传(小文件)
|
||||
func (r *AttachmentController) Upload(ctx http.Context) http.Response {
|
||||
file, err := ctx.Request().File("file")
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrFileRequired.Code)
|
||||
}
|
||||
|
||||
filename := file.GetClientOriginalName()
|
||||
if filename == "" {
|
||||
filename = "uploaded_file"
|
||||
}
|
||||
|
||||
// 读取文件内容:先将文件保存到临时位置,然后读取
|
||||
storage := facades.Storage().Disk("local")
|
||||
|
||||
// 保存文件到临时位置,PutFile 返回保存后的路径
|
||||
savedPath, err := storage.PutFile("", file)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"filename": filename,
|
||||
})
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
fileDataStr, err := storage.Get(savedPath)
|
||||
if err != nil {
|
||||
// 清理临时文件
|
||||
_ = storage.Delete(savedPath)
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"filename": filename,
|
||||
})
|
||||
}
|
||||
|
||||
// 清理临时文件
|
||||
_ = storage.Delete(savedPath)
|
||||
|
||||
// 转换为字节数组
|
||||
fileData := []byte(fileDataStr)
|
||||
|
||||
// 获取MIME类型:直接根据文件扩展名推断(multipart/form-data 的 Content-Type 不是文件本身的 MIME 类型)
|
||||
ext := filepath.Ext(filename)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if mimeType == "" {
|
||||
mimeType = "application/octet-stream"
|
||||
}
|
||||
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
attachment, err := attachmentService.UploadFile(fileData, filename, mimeType)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"filename": filename,
|
||||
})
|
||||
}
|
||||
|
||||
fileURL := attachmentService.GetFileURL(attachment)
|
||||
|
||||
return response.Success(ctx, "upload_success", http.Json{
|
||||
"id": attachment.ID,
|
||||
"filename": attachment.Filename,
|
||||
"size": attachment.Size,
|
||||
"mime_type": attachment.MimeType,
|
||||
"file_type": attachment.FileType,
|
||||
"file_url": fileURL,
|
||||
})
|
||||
}
|
||||
|
||||
// ChunkUpload 大文件分片上传统一接口
|
||||
// 通过 action 参数区分不同操作:init(初始化)、upload(上传分片)、merge(合并分片)、progress(获取进度)
|
||||
func (r *AttachmentController) ChunkUpload(ctx http.Context) http.Response {
|
||||
action := ctx.Request().Input("action", "")
|
||||
if action == "" {
|
||||
// 兼容 GET 请求获取进度
|
||||
action = ctx.Request().Query("action", "progress")
|
||||
}
|
||||
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
|
||||
switch action {
|
||||
case "init":
|
||||
// 初始化分片上传
|
||||
filename := ctx.Request().Input("filename", "")
|
||||
if filename == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrFilenameRequired.Code)
|
||||
}
|
||||
|
||||
totalSizeStr := ctx.Request().Input("total_size", "0")
|
||||
totalSize, err := strconv.ParseInt(totalSizeStr, 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidTotalSize.Code)
|
||||
}
|
||||
if totalSize <= 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidTotalSize.Code)
|
||||
}
|
||||
|
||||
chunkSizeStr := ctx.Request().Input("chunk_size", "0")
|
||||
chunkSize, err := strconv.ParseInt(chunkSizeStr, 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidChunkSize.Code)
|
||||
}
|
||||
if chunkSize <= 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidChunkSize.Code)
|
||||
}
|
||||
|
||||
totalChunksStr := ctx.Request().Input("total_chunks", "0")
|
||||
totalChunks, err := strconv.Atoi(totalChunksStr)
|
||||
if err != nil {
|
||||
// 尝试作为浮点数解析(以防传入 "5.0" 格式)
|
||||
if floatVal, floatErr := strconv.ParseFloat(totalChunksStr, 64); floatErr == nil {
|
||||
totalChunks = int(floatVal)
|
||||
} else {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidTotalChunks.Code)
|
||||
}
|
||||
}
|
||||
if totalChunks <= 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidTotalChunks.Code)
|
||||
}
|
||||
|
||||
// 验证分片数量计算的合理性
|
||||
expectedChunks := int((totalSize + chunkSize - 1) / chunkSize) // 向上取整
|
||||
if totalChunks != expectedChunks {
|
||||
// 不返回错误,使用客户端提供的值(可能是由于浮点数计算差异)
|
||||
}
|
||||
|
||||
chunkID, err := attachmentService.InitChunkUpload(filename, totalSize, chunkSize, totalChunks)
|
||||
if err != nil {
|
||||
// 使用业务错误类型,直接提取错误码
|
||||
if businessErr, ok := apperrors.GetBusinessError(err); ok {
|
||||
return response.Error(ctx, http.StatusBadRequest, businessErr.Code)
|
||||
}
|
||||
|
||||
// 返回详细的错误信息
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"filename": filename,
|
||||
"total_size": totalSize,
|
||||
"chunk_size": chunkSize,
|
||||
"total_chunks": totalChunks,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, "init_chunk_upload_success", http.Json{
|
||||
"chunk_id": chunkID,
|
||||
})
|
||||
|
||||
case "upload":
|
||||
// 上传分片
|
||||
chunkID := ctx.Request().Input("chunk_id", "")
|
||||
if chunkID == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrChunkIDRequired.Code)
|
||||
}
|
||||
|
||||
chunkIndex, err := strconv.Atoi(ctx.Request().Input("chunk_index", "-1"))
|
||||
if err != nil || chunkIndex < 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidChunkIndex.Code)
|
||||
}
|
||||
|
||||
file, err := ctx.Request().File("chunk")
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrChunkFileRequired.Code)
|
||||
}
|
||||
|
||||
// 读取分片数据:先将文件保存到临时位置,然后读取
|
||||
storage := facades.Storage().Disk("local")
|
||||
|
||||
// 保存文件到临时位置
|
||||
savedPath, err := storage.PutFile("", file)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"chunk_id": chunkID,
|
||||
"chunk_index": chunkIndex,
|
||||
})
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
chunkDataStr, err := storage.Get(savedPath)
|
||||
if err != nil {
|
||||
// 清理临时文件
|
||||
_ = storage.Delete(savedPath)
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"chunk_id": chunkID,
|
||||
"chunk_index": chunkIndex,
|
||||
})
|
||||
}
|
||||
|
||||
// 清理临时文件
|
||||
_ = storage.Delete(savedPath)
|
||||
|
||||
// 转换为字节数组
|
||||
chunkData := []byte(chunkDataStr)
|
||||
|
||||
if err := attachmentService.UploadChunk(chunkID, chunkIndex, chunkData); err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"chunk_id": chunkID,
|
||||
"chunk_index": chunkIndex,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, "upload_chunk_success")
|
||||
|
||||
case "merge":
|
||||
// 合并分片
|
||||
chunkID := ctx.Request().Input("chunk_id", "")
|
||||
if chunkID == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrChunkIDRequired.Code)
|
||||
}
|
||||
|
||||
filename := ctx.Request().Input("filename", "")
|
||||
if filename == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrFilenameRequired.Code)
|
||||
}
|
||||
|
||||
totalChunksStr := ctx.Request().Input("total_chunks", "0")
|
||||
totalChunks, err := strconv.Atoi(totalChunksStr)
|
||||
if err != nil {
|
||||
// 尝试作为浮点数解析(以防传入 "5.0" 格式)
|
||||
if floatVal, floatErr := strconv.ParseFloat(totalChunksStr, 64); floatErr == nil {
|
||||
totalChunks = int(floatVal)
|
||||
} else {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidTotalChunks.Code)
|
||||
}
|
||||
}
|
||||
if totalChunks <= 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidTotalChunks.Code)
|
||||
}
|
||||
|
||||
// 获取MIME类型:直接根据文件扩展名推断(前端传递的 mime_type 可能不准确)
|
||||
ext := filepath.Ext(filename)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if mimeType == "" {
|
||||
mimeType = "application/octet-stream"
|
||||
}
|
||||
|
||||
attachment, err := attachmentService.MergeChunks(chunkID, filename, mimeType, totalChunks)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"chunk_id": chunkID,
|
||||
"filename": filename,
|
||||
"total_chunks": totalChunks,
|
||||
})
|
||||
}
|
||||
|
||||
// 合并成功后,记录日志(仅 Debug 模式)
|
||||
facades.Log().Debugf("Successfully merged chunks for chunkID %s, filename: %s, total_chunks: %d", chunkID, filename, totalChunks)
|
||||
|
||||
fileURL := attachmentService.GetFileURL(attachment)
|
||||
|
||||
return response.Success(ctx, "merge_chunks_success", http.Json{
|
||||
"id": attachment.ID,
|
||||
"filename": attachment.Filename,
|
||||
"size": attachment.Size,
|
||||
"mime_type": attachment.MimeType,
|
||||
"file_type": attachment.FileType,
|
||||
"file_url": fileURL,
|
||||
})
|
||||
|
||||
case "progress":
|
||||
// 获取分片上传进度
|
||||
chunkID := ctx.Request().Query("chunk_id", "")
|
||||
if chunkID == "" {
|
||||
chunkID = ctx.Request().Input("chunk_id", "")
|
||||
}
|
||||
if chunkID == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrChunkIDRequired.Code)
|
||||
}
|
||||
|
||||
totalChunks, err := strconv.Atoi(ctx.Request().Query("total_chunks", "0"))
|
||||
if totalChunks == 0 {
|
||||
totalChunks, err = strconv.Atoi(ctx.Request().Input("total_chunks", "0"))
|
||||
}
|
||||
if err != nil || totalChunks <= 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidTotalChunks.Code)
|
||||
}
|
||||
|
||||
progress, err := attachmentService.GetChunkProgress(chunkID, totalChunks)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"chunk_id": chunkID,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx, progress)
|
||||
|
||||
default:
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrInvalidAction.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// Download 下载附件文件
|
||||
func (r *AttachmentController) Download(ctx http.Context) http.Response {
|
||||
id := helpers.GetUintRoute(ctx, "id")
|
||||
if id == 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrIDRequired.Code)
|
||||
}
|
||||
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
attachment, err := attachmentService.GetByID(id)
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrRecordNotFound.Code)
|
||||
}
|
||||
|
||||
if attachment.Path == "" || attachment.Disk == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrFilePathRequired.Code)
|
||||
}
|
||||
|
||||
// 对于云存储,尝试生成临时URL并重定向,避免通过服务器中转
|
||||
if attachment.Disk != "local" && attachment.Disk != "public" {
|
||||
storage := facades.Storage().Disk(attachment.Disk)
|
||||
|
||||
// 尝试生成临时URL(24小时有效)
|
||||
if url, err := storage.TemporaryUrl(attachment.Path, time.Now().Add(24*time.Hour)); err == nil {
|
||||
return ctx.Response().Redirect(http.StatusFound, url)
|
||||
}
|
||||
|
||||
// 如果生成临时URL失败,尝试从配置获取基础URL
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
directURL := attachmentService.GetFileURL(attachment)
|
||||
if directURL != "" && directURL != fmt.Sprintf("/api/admin/attachments/%d/preview", attachment.ID) {
|
||||
return ctx.Response().Redirect(http.StatusFound, directURL)
|
||||
}
|
||||
// 如果都失败,继续使用服务器中转方式
|
||||
}
|
||||
|
||||
// 对于本地存储或临时URL生成失败的情况,使用服务器中转
|
||||
storage := facades.Storage().Disk(attachment.Disk)
|
||||
|
||||
// 读取文件内容
|
||||
content, err := storage.Get(attachment.Path)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"disk": attachment.Disk,
|
||||
"path": attachment.Path,
|
||||
})
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
filename := attachment.Filename
|
||||
if filename == "" {
|
||||
filename = attachment.Path
|
||||
}
|
||||
|
||||
// 根据MIME类型设置 Content-Type
|
||||
contentType := attachment.MimeType
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
// 设置响应头,使用链式调用确保顺序正确
|
||||
response := ctx.Response().
|
||||
Header("Content-Type", contentType).
|
||||
Header("Content-Length", fmt.Sprintf("%d", len(content))).
|
||||
Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)).
|
||||
Header("Cache-Control", "no-cache, no-store, must-revalidate").
|
||||
Header("Pragma", "no-cache").
|
||||
Header("Expires", "0")
|
||||
|
||||
return response.String(http.StatusOK, content)
|
||||
}
|
||||
|
||||
// Preview 预览文件(图片、视频、文档)
|
||||
func (r *AttachmentController) Preview(ctx http.Context) http.Response {
|
||||
id := helpers.GetUintRoute(ctx, "id")
|
||||
if id == 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrIDRequired.Code)
|
||||
}
|
||||
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
attachment, err := attachmentService.GetByID(id)
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrRecordNotFound.Code)
|
||||
}
|
||||
|
||||
if attachment.Path == "" || attachment.Disk == "" {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrFilePathRequired.Code)
|
||||
}
|
||||
|
||||
// 对于云存储,尝试生成临时URL并重定向,避免通过服务器中转
|
||||
// 这样可以减少服务器带宽和内存占用,提高性能
|
||||
if attachment.Disk != "local" && attachment.Disk != "public" {
|
||||
storage := facades.Storage().Disk(attachment.Disk)
|
||||
|
||||
// 尝试生成临时URL(24小时有效)
|
||||
if url, err := storage.TemporaryUrl(attachment.Path, time.Now().Add(24*time.Hour)); err == nil {
|
||||
return ctx.Response().Redirect(http.StatusFound, url)
|
||||
}
|
||||
|
||||
// 如果生成临时URL失败,尝试从配置获取基础URL
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
directURL := attachmentService.GetFileURL(attachment)
|
||||
if directURL != "" && directURL != fmt.Sprintf("/api/admin/attachments/%d/preview", attachment.ID) {
|
||||
return ctx.Response().Redirect(http.StatusFound, directURL)
|
||||
}
|
||||
// 如果都失败,继续使用服务器中转方式
|
||||
}
|
||||
|
||||
// 对于本地存储或临时URL生成失败的情况,使用服务器中转
|
||||
storage := facades.Storage().Disk(attachment.Disk)
|
||||
|
||||
// 读取文件内容
|
||||
content, err := storage.Get(attachment.Path)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"disk": attachment.Disk,
|
||||
"path": attachment.Path,
|
||||
})
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
mimeType := attachment.MimeType
|
||||
if mimeType == "" {
|
||||
mimeType = "application/octet-stream"
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
response := ctx.Response().
|
||||
Header("Content-Type", mimeType).
|
||||
Header("Content-Length", fmt.Sprintf("%d", len(content))).
|
||||
Header("Cache-Control", "public, max-age=3600")
|
||||
|
||||
// 对于图片和视频,支持范围请求(Range request)
|
||||
if attachment.FileType == "image" || attachment.FileType == "video" {
|
||||
response = response.Header("Accept-Ranges", "bytes")
|
||||
}
|
||||
|
||||
return response.String(http.StatusOK, content)
|
||||
}
|
||||
|
||||
// Destroy 删除附件
|
||||
func (r *AttachmentController) Destroy(ctx http.Context) http.Response {
|
||||
id := helpers.GetUintRoute(ctx, "id")
|
||||
if id == 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrIDRequired.Code)
|
||||
}
|
||||
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
attachment, err := attachmentService.GetByID(id)
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrRecordNotFound.Code)
|
||||
}
|
||||
|
||||
if err := attachmentService.DeleteFile(attachment); err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"attachId": attachment.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(ctx)
|
||||
}
|
||||
|
||||
type AttachmentBatchDestroyRequest struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
|
||||
// BatchDestroy 批量删除附件
|
||||
func (r *AttachmentController) BatchDestroy(ctx http.Context) http.Response {
|
||||
var req AttachmentBatchDestroyRequest
|
||||
|
||||
if err := ctx.Request().Bind(&req); err != nil {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrParamsError.Code)
|
||||
}
|
||||
|
||||
if len(req.IDs) == 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrIDsRequired.Code)
|
||||
}
|
||||
|
||||
ids := req.IDs
|
||||
|
||||
// 查询要删除的附件
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
attachments, err := attachmentService.GetByIDs(ids)
|
||||
if err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"ids": ids,
|
||||
})
|
||||
}
|
||||
|
||||
// 删除文件和记录
|
||||
for _, attachment := range attachments {
|
||||
if err := attachmentService.DeleteFile(&attachment); err != nil {
|
||||
// 批量删除中单个文件删除失败只记录日志,不影响主流程
|
||||
errorlog.RecordHTTP(ctx, "attachment", "Failed to delete attachment in batch delete", map[string]any{
|
||||
"error": err.Error(),
|
||||
"attachId": attachment.ID,
|
||||
}, "Delete attachment in batch delete error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return response.Success(ctx)
|
||||
}
|
||||
|
||||
// UpdateDisplayName 更新显示名称
|
||||
func (r *AttachmentController) UpdateDisplayName(ctx http.Context) http.Response {
|
||||
id := helpers.GetUintRoute(ctx, "id")
|
||||
if id == 0 {
|
||||
return response.Error(ctx, http.StatusBadRequest, apperrors.ErrIDRequired.Code)
|
||||
}
|
||||
|
||||
attachmentService := services.NewAttachmentService(ctx)
|
||||
displayName := ctx.Request().Input("display_name", "")
|
||||
|
||||
if err := attachmentService.UpdateDisplayName(id, displayName); err != nil {
|
||||
return response.ErrorWithLog(ctx, "attachment", err, map[string]any{
|
||||
"attachId": id,
|
||||
})
|
||||
}
|
||||
|
||||
// 重新获取更新后的附件
|
||||
attachment, err := attachmentService.GetByID(id)
|
||||
if err != nil {
|
||||
return response.Error(ctx, http.StatusNotFound, apperrors.ErrRecordNotFound.Code)
|
||||
}
|
||||
|
||||
return response.Success(ctx, http.Json{
|
||||
"attachment": attachment,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user