From 79972e963c1d332c097223f45560a24ef8190a03 Mon Sep 17 00:00:00 2001 From: yanghao05 Date: Fri, 11 Apr 2025 14:48:36 +0800 Subject: [PATCH] complete backend --- backend/app/http/admin/routes.gen.go | 10 +++++ backend/app/http/admin/uploads.go | 42 ++++++++++++++++++- backend/app/models/medias.go | 38 +++++++++++++++++ .../20250321112535_create_medias.sql | 3 +- .../database/schemas/public/model/medias.go | 1 + .../database/schemas/public/table/medias.go | 7 +++- backend/providers/ali/credential.go | 2 +- frontend/admin/src/api/mediaService.js | 11 +++-- frontend/admin/src/pages/MediaPage.vue | 5 +-- frontend/admin/src/pages/MediaUploadPage.vue | 33 +++++++++++---- 10 files changed, 131 insertions(+), 21 deletions(-) diff --git a/backend/app/http/admin/routes.gen.go b/backend/app/http/admin/routes.gen.go index d74bc37..f554143 100644 --- a/backend/app/http/admin/routes.gen.go +++ b/backend/app/http/admin/routes.gen.go @@ -92,6 +92,16 @@ func (r *Routes) Register(router fiber.Router) { r.uploads.Token, )) + router.Get("/v1/admin/uploads/pre-uploaded-check/:md5", Func1( + r.uploads.PreUploadCheck, + PathParam[string]("md5"), + )) + + router.Post("/v1/admin/uploads/post-uploaded-action", Func1( + r.uploads.PostUploadedAction, + Body[PostUploadedForm]("body"), + )) + // 注册路由组: users router.Get("/v1/admin/users", DataFunc2( r.users.List, diff --git a/backend/app/http/admin/uploads.go b/backend/app/http/admin/uploads.go index 7cee484..1850642 100644 --- a/backend/app/http/admin/uploads.go +++ b/backend/app/http/admin/uploads.go @@ -14,10 +14,13 @@ import ( "quyun/providers/ali" "quyun/providers/app" + "github.com/go-jet/jet/v2/qrm" "github.com/gofiber/fiber/v3" log "github.com/sirupsen/logrus" ) +const UPLOAD_PATH = "quyun" + // @provider type uploads struct { app *app.Config @@ -143,5 +146,42 @@ func (up *uploads) Complete(ctx fiber.Ctx, md5 string, body *UploadFileInfo) err // Token // @Router /v1/admin/uploads/token [get] func (up *uploads) Token(ctx fiber.Ctx) (*ali.PolicyToken, error) { - return up.ali.GetToken("quyun") + return up.ali.GetToken(UPLOAD_PATH) +} + +// PreUploadCheck +// @Router /v1/admin/uploads/pre-uploaded-check/:md5 [get] +// @Bind md5 path +func (up *uploads) PreUploadCheck(ctx fiber.Ctx, md5 string) error { + _, err := models.Medias.GetByHash(ctx.Context(), md5) + if err != nil && errors.Is(err, qrm.ErrNoRows) { + return ctx.SendString("ok") + } + return ctx.SendString("exists") +} + +type PostUploadedForm struct { + OriginalName string `json:"originalName"` + Md5 string `json:"md5"` + MimeType string `json:"mimeType"` + Size int64 `json:"size"` +} + +// PostUploadedAction +// @Router /v1/admin/uploads/post-uploaded-action [post] +// @Bind body body +func (up *uploads) PostUploadedAction(ctx fiber.Ctx, body *PostUploadedForm) error { + m, err := models.Medias.GetByHash(ctx.Context(), body.Md5) + if err != nil && !errors.Is(err, qrm.ErrNoRows) { + return err + } + + m = &model.Medias{ + Name: body.OriginalName, + MimeType: body.MimeType, + Size: body.Size, + Hash: body.Md5, + Path: filepath.Join(UPLOAD_PATH, body.Md5+filepath.Ext(body.OriginalName)), + } + return models.Medias.Create(ctx.Context(), m) } diff --git a/backend/app/models/medias.go b/backend/app/models/medias.go index 4397e39..ec3766c 100644 --- a/backend/app/models/medias.go +++ b/backend/app/models/medias.go @@ -2,6 +2,7 @@ package models import ( "context" + "time" "quyun/app/requests" "quyun/database/fields" @@ -127,6 +128,7 @@ func (m *mediasModel) BatchCreate(ctx context.Context, models []*model.Medias) e } func (m *mediasModel) Create(ctx context.Context, model *model.Medias) error { + model.CreatedAt = time.Now() stmt := table.Medias.INSERT(table.Medias.MutableColumns).MODEL(model) m.log.Infof("sql: %s", stmt.DebugSql()) @@ -193,3 +195,39 @@ func (m *mediasModel) GetByIds(ctx context.Context, ids []int64) ([]*model.Media return &media }), nil } + +// GetByHash +func (m *mediasModel) GetByHash(ctx context.Context, hash string) (*model.Medias, error) { + tbl := table.Medias + stmt := tbl. + SELECT(tbl.AllColumns). + WHERE(tbl.Hash.EQ(String(hash))) + m.log.Infof("sql: %s", stmt.DebugSql()) + + var media model.Medias + err := stmt.QueryContext(ctx, db, &media) + if err != nil { + m.log.Errorf("error querying media item by hash: %v", err) + return nil, err + } + + return &media, nil +} + +// Update +func (m *mediasModel) Update(ctx context.Context, hash string, model *model.Medias) error { + tbl := table.Medias + stmt := tbl. + UPDATE(tbl.MutableColumns.Except(tbl.CreatedAt)). + MODEL(model). + WHERE(table.Medias.Hash.EQ(String(hash))) + m.log.Infof("sql: %s", stmt.DebugSql()) + + if _, err := stmt.ExecContext(ctx, db); err != nil { + m.log.Errorf("error updating media item: %v", err) + return err + } + + m.log.Infof("media item updated successfully") + return nil +} diff --git a/backend/database/migrations/20250321112535_create_medias.sql b/backend/database/migrations/20250321112535_create_medias.sql index 838e9e4..328d0ad 100644 --- a/backend/database/migrations/20250321112535_create_medias.sql +++ b/backend/database/migrations/20250321112535_create_medias.sql @@ -6,7 +6,8 @@ CREATE TABLE medias( name varchar(255) NOT NULL DEFAULT '', mime_type varchar(128) NOT NULL DEFAULT '', size int8 NOT NULL DEFAULT 0, - path varchar(255) NOT NULL DEFAULT '' + path varchar(255) NOT NULL DEFAULT '', + hash varchar(64) NOT NULL DEFAULT '' ); -- +goose StatementEnd diff --git a/backend/database/schemas/public/model/medias.go b/backend/database/schemas/public/model/medias.go index 8a38196..d8de890 100644 --- a/backend/database/schemas/public/model/medias.go +++ b/backend/database/schemas/public/model/medias.go @@ -18,4 +18,5 @@ type Medias struct { MimeType string `json:"mime_type"` Size int64 `json:"size"` Path string `json:"path"` + Hash string `json:"hash"` } diff --git a/backend/database/schemas/public/table/medias.go b/backend/database/schemas/public/table/medias.go index b43c13a..cf43687 100644 --- a/backend/database/schemas/public/table/medias.go +++ b/backend/database/schemas/public/table/medias.go @@ -23,6 +23,7 @@ type mediasTable struct { MimeType postgres.ColumnString Size postgres.ColumnInteger Path postgres.ColumnString + Hash postgres.ColumnString AllColumns postgres.ColumnList MutableColumns postgres.ColumnList @@ -69,8 +70,9 @@ func newMediasTableImpl(schemaName, tableName, alias string) mediasTable { MimeTypeColumn = postgres.StringColumn("mime_type") SizeColumn = postgres.IntegerColumn("size") PathColumn = postgres.StringColumn("path") - allColumns = postgres.ColumnList{IDColumn, CreatedAtColumn, NameColumn, MimeTypeColumn, SizeColumn, PathColumn} - mutableColumns = postgres.ColumnList{CreatedAtColumn, NameColumn, MimeTypeColumn, SizeColumn, PathColumn} + HashColumn = postgres.StringColumn("hash") + allColumns = postgres.ColumnList{IDColumn, CreatedAtColumn, NameColumn, MimeTypeColumn, SizeColumn, PathColumn, HashColumn} + mutableColumns = postgres.ColumnList{CreatedAtColumn, NameColumn, MimeTypeColumn, SizeColumn, PathColumn, HashColumn} ) return mediasTable{ @@ -83,6 +85,7 @@ func newMediasTableImpl(schemaName, tableName, alias string) mediasTable { MimeType: MimeTypeColumn, Size: SizeColumn, Path: PathColumn, + Hash: HashColumn, AllColumns: allColumns, MutableColumns: mutableColumns, diff --git a/backend/providers/ali/credential.go b/backend/providers/ali/credential.go index 7461251..d91414a 100644 --- a/backend/providers/ali/credential.go +++ b/backend/providers/ali/credential.go @@ -101,7 +101,7 @@ func (c *Config) GetToken(path string) (*PolicyToken, error) { var callbackParam CallbackParam callbackParam.CallbackUrl = c.CallbackURL - callbackParam.CallbackBody = "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}" + callbackParam.CallbackBody = "filename=${object}&size=${size}&mimeType=${mimeType}" callbackParam.CallbackBodyType = "application/x-www-form-urlencoded" callback_str, err := json.Marshal(callbackParam) if err != nil { diff --git a/frontend/admin/src/api/mediaService.js b/frontend/admin/src/api/mediaService.js index 6f55f84..551a1b9 100644 --- a/frontend/admin/src/api/mediaService.js +++ b/frontend/admin/src/api/mediaService.js @@ -7,15 +7,18 @@ export const mediaService = { }); }, + createMedia(mediaInfo) { + return httpClient.post('/admin/medias', mediaInfo); + }, + getUploadToken() { return httpClient.get('/admin/uploads/token'); }, + preUploadedCheck(md5) { + return httpClient.get(`/admin/uploads/pre-uploaded-check/${md5}`); + }, uploadedSuccess(data) { return httpClient.post('/admin/uploads/post-uploaded-action', data); }, - - createMedia(mediaInfo) { - return httpClient.post('/admin/medias', mediaInfo); - } }; \ No newline at end of file diff --git a/frontend/admin/src/pages/MediaPage.vue b/frontend/admin/src/pages/MediaPage.vue index 25db690..c90be2d 100644 --- a/frontend/admin/src/pages/MediaPage.vue +++ b/frontend/admin/src/pages/MediaPage.vue @@ -203,11 +203,10 @@ const formatDate = (date) => { - + diff --git a/frontend/admin/src/pages/MediaUploadPage.vue b/frontend/admin/src/pages/MediaUploadPage.vue index b8968f2..fa3b04d 100644 --- a/frontend/admin/src/pages/MediaUploadPage.vue +++ b/frontend/admin/src/pages/MediaUploadPage.vue @@ -106,11 +106,28 @@ const processNextUpload = async () => { currentUploadIndex.value = 0; try { - await uploadFile(nextFile.file); - uploadQueue.value.shift(); - addToHistory(nextFile.file, 'completed'); + const md5Hash = await calculateMD5(nextFile.file); + // Check if file exists before upload + const checkResult = await mediaService.preUploadedCheck(md5Hash); + console.log(checkResult) + if (checkResult.data === 'exists') { + // Skip upload and mark as completed + uploadQueue.value.shift(); + addToHistory(nextFile.file, 'completed'); + toast.add({ + severity: 'info', + summary: '提示', + detail: '文件已存在,已跳过上传', + life: 3000 + }); + } else { + // Proceed with upload + await uploadFile(nextFile.file, md5Hash); + uploadQueue.value.shift(); + addToHistory(nextFile.file, 'completed'); + } } catch (error) { - console.error('Failed to upload file:', nextFile.file.name, error); + console.error('Failed to process file:', nextFile.file.name, error); uploadQueue.value.shift(); addToHistory(nextFile.file, 'error'); } @@ -159,7 +176,7 @@ const calculateMD5 = (file) => { }; // Modify uploadFile function -const uploadFile = async (file) => { +const uploadFile = async (file, md5Hash) => { try { currentFile.value = file; isUploading.value = true; @@ -169,8 +186,6 @@ const uploadFile = async (file) => { await getOssToken(); } - // Calculate MD5 before upload - const md5Hash = await calculateMD5(file); const fileExt = file.name.split('.').pop(); const newFileName = `${md5Hash}.${fileExt}`; @@ -292,9 +307,9 @@ onMounted(() => {
-
+
-

拖拽文件到此处或点击上传

+

拖拽文件到此处或点击上传

支持: MP4, JPG, PNG, GIF, MP4, PDF, DOC