complete backend

This commit is contained in:
yanghao05
2025-04-11 14:48:36 +08:00
parent 736991e3ea
commit 79972e963c
10 changed files with 131 additions and 21 deletions

View File

@@ -92,6 +92,16 @@ func (r *Routes) Register(router fiber.Router) {
r.uploads.Token, 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 // 注册路由组: users
router.Get("/v1/admin/users", DataFunc2( router.Get("/v1/admin/users", DataFunc2(
r.users.List, r.users.List,

View File

@@ -14,10 +14,13 @@ import (
"quyun/providers/ali" "quyun/providers/ali"
"quyun/providers/app" "quyun/providers/app"
"github.com/go-jet/jet/v2/qrm"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
const UPLOAD_PATH = "quyun"
// @provider // @provider
type uploads struct { type uploads struct {
app *app.Config app *app.Config
@@ -143,5 +146,42 @@ func (up *uploads) Complete(ctx fiber.Ctx, md5 string, body *UploadFileInfo) err
// Token // Token
// @Router /v1/admin/uploads/token [get] // @Router /v1/admin/uploads/token [get]
func (up *uploads) Token(ctx fiber.Ctx) (*ali.PolicyToken, error) { 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)
} }

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"context" "context"
"time"
"quyun/app/requests" "quyun/app/requests"
"quyun/database/fields" "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 { func (m *mediasModel) Create(ctx context.Context, model *model.Medias) error {
model.CreatedAt = time.Now()
stmt := table.Medias.INSERT(table.Medias.MutableColumns).MODEL(model) stmt := table.Medias.INSERT(table.Medias.MutableColumns).MODEL(model)
m.log.Infof("sql: %s", stmt.DebugSql()) m.log.Infof("sql: %s", stmt.DebugSql())
@@ -193,3 +195,39 @@ func (m *mediasModel) GetByIds(ctx context.Context, ids []int64) ([]*model.Media
return &media return &media
}), nil }), 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
}

View File

@@ -6,7 +6,8 @@ CREATE TABLE medias(
name varchar(255) NOT NULL DEFAULT '', name varchar(255) NOT NULL DEFAULT '',
mime_type varchar(128) NOT NULL DEFAULT '', mime_type varchar(128) NOT NULL DEFAULT '',
size int8 NOT NULL DEFAULT 0, 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 -- +goose StatementEnd

View File

@@ -18,4 +18,5 @@ type Medias struct {
MimeType string `json:"mime_type"` MimeType string `json:"mime_type"`
Size int64 `json:"size"` Size int64 `json:"size"`
Path string `json:"path"` Path string `json:"path"`
Hash string `json:"hash"`
} }

View File

@@ -23,6 +23,7 @@ type mediasTable struct {
MimeType postgres.ColumnString MimeType postgres.ColumnString
Size postgres.ColumnInteger Size postgres.ColumnInteger
Path postgres.ColumnString Path postgres.ColumnString
Hash postgres.ColumnString
AllColumns postgres.ColumnList AllColumns postgres.ColumnList
MutableColumns postgres.ColumnList MutableColumns postgres.ColumnList
@@ -69,8 +70,9 @@ func newMediasTableImpl(schemaName, tableName, alias string) mediasTable {
MimeTypeColumn = postgres.StringColumn("mime_type") MimeTypeColumn = postgres.StringColumn("mime_type")
SizeColumn = postgres.IntegerColumn("size") SizeColumn = postgres.IntegerColumn("size")
PathColumn = postgres.StringColumn("path") PathColumn = postgres.StringColumn("path")
allColumns = postgres.ColumnList{IDColumn, CreatedAtColumn, NameColumn, MimeTypeColumn, SizeColumn, PathColumn} HashColumn = postgres.StringColumn("hash")
mutableColumns = postgres.ColumnList{CreatedAtColumn, NameColumn, MimeTypeColumn, SizeColumn, PathColumn} allColumns = postgres.ColumnList{IDColumn, CreatedAtColumn, NameColumn, MimeTypeColumn, SizeColumn, PathColumn, HashColumn}
mutableColumns = postgres.ColumnList{CreatedAtColumn, NameColumn, MimeTypeColumn, SizeColumn, PathColumn, HashColumn}
) )
return mediasTable{ return mediasTable{
@@ -83,6 +85,7 @@ func newMediasTableImpl(schemaName, tableName, alias string) mediasTable {
MimeType: MimeTypeColumn, MimeType: MimeTypeColumn,
Size: SizeColumn, Size: SizeColumn,
Path: PathColumn, Path: PathColumn,
Hash: HashColumn,
AllColumns: allColumns, AllColumns: allColumns,
MutableColumns: mutableColumns, MutableColumns: mutableColumns,

View File

@@ -101,7 +101,7 @@ func (c *Config) GetToken(path string) (*PolicyToken, error) {
var callbackParam CallbackParam var callbackParam CallbackParam
callbackParam.CallbackUrl = c.CallbackURL 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" callbackParam.CallbackBodyType = "application/x-www-form-urlencoded"
callback_str, err := json.Marshal(callbackParam) callback_str, err := json.Marshal(callbackParam)
if err != nil { if err != nil {

View File

@@ -7,15 +7,18 @@ export const mediaService = {
}); });
}, },
createMedia(mediaInfo) {
return httpClient.post('/admin/medias', mediaInfo);
},
getUploadToken() { getUploadToken() {
return httpClient.get('/admin/uploads/token'); return httpClient.get('/admin/uploads/token');
}, },
preUploadedCheck(md5) {
return httpClient.get(`/admin/uploads/pre-uploaded-check/${md5}`);
},
uploadedSuccess(data) { uploadedSuccess(data) {
return httpClient.post('/admin/uploads/post-uploaded-action', data); return httpClient.post('/admin/uploads/post-uploaded-action', data);
}, },
createMedia(mediaInfo) {
return httpClient.post('/admin/medias', mediaInfo);
}
}; };

View File

@@ -203,11 +203,10 @@ const formatDate = (date) => {
</template> </template>
</Column> </Column>
<Column field="upload_time" header="时间信息"> <Column field="upload_time" header="上传时间">
<template #body="{ data }"> <template #body="{ data }">
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-gray-500">更新: {{ formatDate(data.updated_at) }}</span> <span class="text-gray-400"> {{ formatDate(data.upload_time) }}</span>
<span class="text-gray-400">上传: {{ formatDate(data.created_at) }}</span>
</div> </div>
</template> </template>
</Column> </Column>

View File

@@ -106,11 +106,28 @@ const processNextUpload = async () => {
currentUploadIndex.value = 0; currentUploadIndex.value = 0;
try { try {
await uploadFile(nextFile.file); const md5Hash = await calculateMD5(nextFile.file);
uploadQueue.value.shift(); // Check if file exists before upload
addToHistory(nextFile.file, 'completed'); 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) { } 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(); uploadQueue.value.shift();
addToHistory(nextFile.file, 'error'); addToHistory(nextFile.file, 'error');
} }
@@ -159,7 +176,7 @@ const calculateMD5 = (file) => {
}; };
// Modify uploadFile function // Modify uploadFile function
const uploadFile = async (file) => { const uploadFile = async (file, md5Hash) => {
try { try {
currentFile.value = file; currentFile.value = file;
isUploading.value = true; isUploading.value = true;
@@ -169,8 +186,6 @@ const uploadFile = async (file) => {
await getOssToken(); await getOssToken();
} }
// Calculate MD5 before upload
const md5Hash = await calculateMD5(file);
const fileExt = file.name.split('.').pop(); const fileExt = file.name.split('.').pop();
const newFileName = `${md5Hash}.${fileExt}`; const newFileName = `${md5Hash}.${fileExt}`;
@@ -292,9 +307,9 @@ onMounted(() => {
<div ref="dropZone" @drop="onDrop" @dragover="onDragOver" @dragleave="onDragLeave" @click="onClick" <div ref="dropZone" @drop="onDrop" @dragover="onDragOver" @dragleave="onDragLeave" @click="onClick"
class="border-2 border-dashed border-gray-300 rounded-lg transition-all duration-200 hover:border-blue-500 hover:bg-blue-50 cursor-pointer"> class="border-2 border-dashed border-gray-300 rounded-lg transition-all duration-200 hover:border-blue-500 hover:bg-blue-50 cursor-pointer">
<div class="flex flex-col items-center justify-center p-8"> <div class="flex flex-col items-center justify-center p-8 py-36">
<i class="pi pi-cloud-upload text-6xl! text-gray-500 mb-4"></i> <i class="pi pi-cloud-upload text-6xl! text-gray-500 mb-4"></i>
<p class="text-gray-600 text-center mb-2">拖拽文件到此处或点击上传</p> <p class="text-gray-600 text-2xl! font-bold text-center mb-2">拖拽文件到此处或点击上传</p>
<p class="text-gray-400 text-sm text-center">支持: MP4, JPG, PNG, GIF, MP4, PDF, DOC</p> <p class="text-gray-400 text-sm text-center">支持: MP4, JPG, PNG, GIF, MP4, PDF, DOC</p>
</div> </div>
</div> </div>