mirror of
https://gitee.com/xiangheng/x_admin.git
synced 2025-10-05 16:17:00 +08:00
优化
This commit is contained in:
@@ -3,7 +3,6 @@ import axios from 'axios'
|
||||
|
||||
export interface FileUploaderOptions {
|
||||
chunkSize?: number
|
||||
fileName?: string
|
||||
onSuccess?: (filePath: string) => void
|
||||
onError?: (error: Error) => void
|
||||
}
|
||||
@@ -16,41 +15,65 @@ export default class FileUploader {
|
||||
chunkSize: number = 1024 * 1024 // 1MB
|
||||
chunkCount: number = 0
|
||||
|
||||
private uploading = false
|
||||
private startChunkIndex = -1
|
||||
onSuccess: FileUploaderOptions['onSuccess'] = () => {}
|
||||
onError: FileUploaderOptions['onError'] = (error: Error) => {
|
||||
console.log(error)
|
||||
|
||||
// 上传完成
|
||||
success(filePath: string) {
|
||||
this.uploading = false
|
||||
this.startChunkIndex = -1
|
||||
this.onSuccess(filePath)
|
||||
}
|
||||
onUploadProgress(chunkIndex: number, chunkLoaded: number, chunkTotal: number, loaded: number) {
|
||||
onSuccess: FileUploaderOptions['onSuccess'] = (filePath) => {
|
||||
console.log('上传完成', filePath)
|
||||
}
|
||||
error(error: Error) {
|
||||
this.uploading = false
|
||||
this.startChunkIndex = -1
|
||||
this.onError(error)
|
||||
}
|
||||
onError: FileUploaderOptions['onError'] = (error: Error) => {
|
||||
console.log('error', error)
|
||||
}
|
||||
onUploadProgress(chunkIndex: number, chunkLoaded: number, chunkTotal: number) {
|
||||
console.log(
|
||||
`当前分片: ${chunkIndex}/${this.chunkCount},分片进度${chunkLoaded}/${chunkTotal},总进度: ${loaded}/${this.fileSize}`
|
||||
`当前分片: ${chunkIndex}/${this.chunkCount},分片进度${chunkLoaded}/${chunkTotal}}`
|
||||
)
|
||||
}
|
||||
|
||||
constructor(file: File, options: FileUploaderOptions) {
|
||||
this.file = file
|
||||
this.fileName = file.name
|
||||
this.fileSize = file.size
|
||||
|
||||
constructor(options: FileUploaderOptions, file?: File) {
|
||||
if (options?.chunkSize) {
|
||||
this.chunkSize = options.chunkSize
|
||||
}
|
||||
this.chunkCount = Math.ceil(this.file.size / this.chunkSize)
|
||||
if (options?.fileName) {
|
||||
this.fileName = options.fileName
|
||||
}
|
||||
if (options?.onSuccess) {
|
||||
this.onSuccess = options.onSuccess
|
||||
}
|
||||
if (options?.onError) {
|
||||
this.onError = options.onError
|
||||
}
|
||||
if (file) {
|
||||
this.loadFile(file)
|
||||
}
|
||||
}
|
||||
public loadFile(file: File, fileName?: string) {
|
||||
if (this.uploading) {
|
||||
this.error(new Error('请等待上一个文件上传完成'))
|
||||
return
|
||||
}
|
||||
|
||||
this.file = file
|
||||
if (fileName) {
|
||||
this.fileName = fileName
|
||||
} else {
|
||||
this.fileName = file.name
|
||||
}
|
||||
this.fileSize = file.size
|
||||
this.chunkCount = Math.ceil(this.file.size / this.chunkSize)
|
||||
}
|
||||
readerFile(file: File): Promise<ArrayBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!file) {
|
||||
return reject(new Error('文件不存在'))
|
||||
return reject(new Error('读取文件失败'))
|
||||
}
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
@@ -62,35 +85,39 @@ export default class FileUploader {
|
||||
reader.readAsArrayBuffer(file)
|
||||
})
|
||||
}
|
||||
// 开始/恢复上传
|
||||
async start() {
|
||||
// 开始上传
|
||||
public async start() {
|
||||
try {
|
||||
if (!this.file) {
|
||||
this.error(new Error('请选择文件后上传'))
|
||||
return
|
||||
}
|
||||
if (this.uploading) {
|
||||
this.error(new Error('正在上传中'))
|
||||
return
|
||||
}
|
||||
this.uploading = true
|
||||
|
||||
const arrayBuffer = await this.readerFile(this.file)
|
||||
const fileMd5 = this.getMd5(arrayBuffer)
|
||||
this.fileMd5 = fileMd5 + '_' + this.fileSize
|
||||
const isExistFilePath = await this.checkFileExist()
|
||||
|
||||
if (isExistFilePath) {
|
||||
this.complete(isExistFilePath)
|
||||
this.success(isExistFilePath)
|
||||
return
|
||||
}
|
||||
const hasChunk = await this.getHasChunk()
|
||||
this.startChunkIndex = hasChunk && hasChunk.length ? Math.max(...hasChunk) : 0
|
||||
this.startChunkIndex = hasChunk && hasChunk.length ? Math.max(...hasChunk) : -1
|
||||
console.log('hasChunk', hasChunk)
|
||||
|
||||
await this.splitChunks()
|
||||
await this.mergeChunk()
|
||||
} catch (error) {
|
||||
this.onError(error)
|
||||
this.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传完成
|
||||
complete(filePath) {
|
||||
/* 合并分片 */
|
||||
console.log('complete:', filePath)
|
||||
this.onSuccess(filePath)
|
||||
}
|
||||
// 检查上传状态
|
||||
getMd5(arrayBuffer: ArrayBuffer): string {
|
||||
console.time('SparkMD5')
|
||||
@@ -131,7 +158,7 @@ export default class FileUploader {
|
||||
}
|
||||
|
||||
async splitChunks() {
|
||||
for (let index = this.startChunkIndex; index < this.chunkCount; index++) {
|
||||
for (let index = this.startChunkIndex + 1; index < this.chunkCount; index++) {
|
||||
const chunkStart = index * this.chunkSize
|
||||
const chunkEnd = Math.min(chunkStart + this.chunkSize, this.file.size)
|
||||
const chunk = this.file.slice(chunkStart, chunkEnd)
|
||||
@@ -152,9 +179,9 @@ export default class FileUploader {
|
||||
// ((this.chunkSize * index + progressEvent.loaded) * 100) /
|
||||
// this.fileSize
|
||||
// ).toFixed(3)
|
||||
const loaded = this.chunkSize * index + progressEvent.loaded //TODO progressEvent.loaded体积比文件大,不能直接相加
|
||||
// const loaded = this.chunkSize * index + progressEvent.loaded //TODO progressEvent.loaded体积比文件大,不能直接相加
|
||||
|
||||
this.onUploadProgress(index, progressEvent.loaded, progressEvent.total, loaded)
|
||||
this.onUploadProgress(index, progressEvent.loaded, progressEvent.total)
|
||||
}
|
||||
})
|
||||
chunk = null
|
||||
@@ -169,7 +196,7 @@ export default class FileUploader {
|
||||
} catch (error) {
|
||||
chunk = null
|
||||
console.error(`分片 ${index + 1}/${this.chunkCount} 上传失败: ${error}`)
|
||||
this.onError(error)
|
||||
this.error(error)
|
||||
}
|
||||
}
|
||||
async mergeChunk() {
|
||||
@@ -182,14 +209,14 @@ export default class FileUploader {
|
||||
})
|
||||
if (res.data.code === 200) {
|
||||
console.log('合并分片成功')
|
||||
this.complete(res.data.data)
|
||||
this.success(res.data.data)
|
||||
} else {
|
||||
console.log('MergeChunk', res)
|
||||
this.onError(new Error('合并分片失败'))
|
||||
this.error(new Error('合并分片失败'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`合并分片失败: ${error}`)
|
||||
this.onError(error)
|
||||
this.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,17 +8,27 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import FileUploader from '@/utils/FileUploader'
|
||||
|
||||
const fileInput = ref<HTMLInputElement>()
|
||||
|
||||
let fileUploader: FileUploader | null = null
|
||||
const fileUploader = new FileUploader({
|
||||
chunkSize: 1024 * 1024 * 1,
|
||||
onSuccess(filePath) {
|
||||
ElMessage.success('上传成功:' + filePath)
|
||||
},
|
||||
onError(error) {
|
||||
// console.error('error', error)
|
||||
ElMessage.error(error.message)
|
||||
}
|
||||
})
|
||||
function handleChange(e) {
|
||||
const files = (e.target as HTMLInputElement).files
|
||||
// console.log('e.target', e.target)
|
||||
console.log('files', files)
|
||||
if (files) {
|
||||
fileUploader = new FileUploader(files[0], { chunkSize: 1024 * 1024 * 1 })
|
||||
fileUploader.loadFile(files[0])
|
||||
}
|
||||
}
|
||||
function btn() {
|
||||
|
1
server/.gitignore
vendored
1
server/.gitignore
vendored
@@ -58,3 +58,4 @@ main
|
||||
# air
|
||||
tmp
|
||||
dist/
|
||||
uploads/
|
@@ -41,24 +41,40 @@ var AdminConfig = adminConfig{
|
||||
|
||||
// 管理员账号id
|
||||
SuperAdminId: 1,
|
||||
// 管理员账号key
|
||||
ReqAdminIdKey: "admin_id",
|
||||
// 角色key
|
||||
ReqRoleIdKey: "role",
|
||||
// 用户名key
|
||||
ReqUsernameKey: "username",
|
||||
// 昵称key
|
||||
ReqNicknameKey: "nickname",
|
||||
}
|
||||
|
||||
type adminConfig struct {
|
||||
// 管理缓存键
|
||||
BackstageManageKey string
|
||||
// 角色缓存键
|
||||
BackstageRolesKey string
|
||||
// 令牌缓存键
|
||||
BackstageTokenKey string
|
||||
// 令牌的集合
|
||||
BackstageTokenSet string
|
||||
// 免登录验证
|
||||
NotLoginUri []string
|
||||
// 免权限验证
|
||||
NotAuthUri []string
|
||||
// 演示模式白名单
|
||||
ShowWhitelistUri []string
|
||||
// 管理员账号id
|
||||
SuperAdminId uint
|
||||
// 管理员账号key
|
||||
ReqAdminIdKey string
|
||||
// 角色key
|
||||
ReqRoleIdKey string
|
||||
// 用户名key
|
||||
ReqUsernameKey string
|
||||
// 昵称key
|
||||
ReqNicknameKey string
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ package commonController
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"x_admin/core/response"
|
||||
@@ -33,8 +34,11 @@ type uploadChunkHandler struct {
|
||||
tmpPath string
|
||||
}
|
||||
|
||||
func (uh uploadChunkHandler) getFilePath(fileMd5 string) string {
|
||||
return fmt.Sprintf("%s/%s", uh.uploadPath, fileMd5)
|
||||
func (uh uploadChunkHandler) getFilePath(fileMd5 string, fileName string) string {
|
||||
// 获取文件后缀
|
||||
ext := filepath.Ext(fileName)
|
||||
|
||||
return fmt.Sprintf("%s/%s%s", uh.uploadPath, fileMd5, ext)
|
||||
}
|
||||
func (uh uploadChunkHandler) getChunkDir(fileMd5 string, chunkSize string) string {
|
||||
return fmt.Sprintf("%s/%s_%s", uh.tmpPath, fileMd5, chunkSize)
|
||||
@@ -91,6 +95,15 @@ func (uh uploadChunkHandler) CheckFileExist(c *gin.Context) {
|
||||
".gz",
|
||||
".bz2",
|
||||
".xz",
|
||||
".msi",
|
||||
".exe",
|
||||
".dmg",
|
||||
".iso",
|
||||
".app",
|
||||
".deb",
|
||||
".rpm",
|
||||
".pkg",
|
||||
".apk",
|
||||
}
|
||||
var fileExt = ""
|
||||
for _, ext := range whiteList {
|
||||
@@ -111,7 +124,7 @@ func (uh uploadChunkHandler) CheckFileExist(c *gin.Context) {
|
||||
response.FailWithMsg(c, response.SystemError, "文件hash错误")
|
||||
return
|
||||
}
|
||||
var filePath = uh.getFilePath(fileMd5)
|
||||
var filePath = uh.getFilePath(fileMd5, fileName)
|
||||
// 检查文件是否存在
|
||||
if commonService.UploadChunkService.CheckFileExist(filePath) {
|
||||
response.OkWithData(c, filePath)
|
||||
@@ -209,7 +222,7 @@ func (uh uploadChunkHandler) MergeChunk(c *gin.Context) {
|
||||
response.FailWithMsg(c, response.SystemError, "分片大小错误")
|
||||
return
|
||||
}
|
||||
var filePath = uh.getFilePath(MergeChunk.FileMd5)
|
||||
var filePath = uh.getFilePath(MergeChunk.FileMd5, MergeChunk.FileName)
|
||||
var chunkDir = uh.getChunkDir(MergeChunk.FileMd5, fmt.Sprintf("%d", MergeChunk.ChunkSize))
|
||||
err := commonService.UploadChunkService.MergeChunk(chunkDir, filePath, MergeChunk.ChunkCount)
|
||||
if err != nil {
|
||||
|
@@ -27,23 +27,30 @@ type Response struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// code 200成功
|
||||
Success = RespType{code: 200, message: "成功"}
|
||||
// code 300失败
|
||||
Failed = RespType{code: 300, message: "失败"}
|
||||
|
||||
// code 310参数校验错误
|
||||
ParamsValidError = RespType{code: 310, message: "参数校验错误"}
|
||||
// code 311参数类型错误
|
||||
ParamsTypeError = RespType{code: 311, message: "参数类型错误"}
|
||||
|
||||
RequestMethodError = RespType{code: 312, message: "请求方法错误"}
|
||||
AssertArgumentError = RespType{code: 313, message: "断言参数错误"}
|
||||
|
||||
// code 330登录账号或密码错误
|
||||
LoginAccountError = RespType{code: 330, message: "登录账号或密码错误"}
|
||||
// code 331登录账号已被禁用了
|
||||
LoginDisableError = RespType{code: 331, message: "登录账号已被禁用了"}
|
||||
// code 332 token参数为空
|
||||
TokenEmpty = RespType{code: 332, message: "token参数为空"}
|
||||
// code 333 登录失效
|
||||
TokenInvalid = RespType{code: 333, message: "登录失效"}
|
||||
|
||||
// 无相关权限
|
||||
NoPermission = RespType{code: 403, message: "无相关权限"}
|
||||
Request404Error = RespType{code: 404, message: "请求接口不存在"}
|
||||
Request405Error = RespType{code: 405, message: "请求方法不允许"}
|
||||
|
||||
// code 500系统错误
|
||||
SystemError = RespType{code: 500, message: "系统错误"}
|
||||
)
|
||||
|
||||
|
@@ -111,7 +111,7 @@ func TokenAuth() gin.HandlerFunc {
|
||||
c.Set(config.AdminConfig.ReqNicknameKey, mapping.Nickname)
|
||||
|
||||
// 免权限验证接口
|
||||
if util.ToolsUtil.Contains(config.AdminConfig.NotAuthUri, auths) || uid == 1 {
|
||||
if util.ToolsUtil.Contains(config.AdminConfig.NotAuthUri, auths) || uid == config.AdminConfig.SuperAdminId {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@@ -49,3 +50,9 @@ func (su stringUtil) ToUpperCamelCase(s string) string {
|
||||
}
|
||||
return strings.Join(words, "")
|
||||
}
|
||||
|
||||
// 检查字符串只能包含字母、数字和下划线
|
||||
func (su stringUtil) CheckSafeString(s string) bool {
|
||||
reg := regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||
return !reg.MatchString(s)
|
||||
}
|
||||
|
16
server/util/string_test.go
Normal file
16
server/util/string_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckSafeString(t *testing.T) {
|
||||
// 测试正常字符串
|
||||
if StringUtil.CheckSafeString("abc123") {
|
||||
t.Log("正常字符串")
|
||||
}
|
||||
// 测试包含特殊字符的字符串
|
||||
if !StringUtil.CheckSafeString("abc123!") {
|
||||
t.Log("包含特殊字符的字符串")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user