统一上传文件不区分图片、视频接口,素材中心私有化

This commit is contained in:
xh
2025-08-27 03:17:40 +08:00
parent a58c1360d7
commit 606bcbef0a
17 changed files with 296 additions and 213 deletions

View File

@@ -8,7 +8,7 @@ import { getToken } from '@/utils/auth'
type album_cate = { type album_cate = {
id?: number id?: number
pid?: number pid?: number
type?: number // type?: number
name?: string name?: string
isDelete?: number isDelete?: number
createTime?: string createTime?: string
@@ -18,7 +18,7 @@ type album_cate = {
// 查询 // 查询
type album_cate_query = { type album_cate_query = {
pid?: number pid?: number
type?: number // type?: number
name?: string name?: string
createTimeStart?: string createTimeStart?: string
createTimeEnd?: string createTimeEnd?: string
@@ -29,7 +29,7 @@ type album_cate_query = {
type album_cate_edit = { type album_cate_edit = {
id?: number id?: number
pid?: number pid?: number
type?: number // type?: number
name?: string name?: string
} }

View File

@@ -28,12 +28,25 @@ export default defineComponent({
default: '100px' default: '100px'
}, },
// 文件类型 // 文件类型
type: { ext: {
type: String, type: String,
default: 'image' default: ''
} }
}, },
emits: ['close'] emits: ['close'],
computed: {
type() {
const imageExt = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
const videoExt = ['mp4', 'avi', 'mov']
if (imageExt.includes(this.ext)) {
return 'image'
}
if (videoExt.includes(this.ext)) {
return 'video'
}
return 'file'
}
}
}) })
</script> </script>

View File

@@ -15,7 +15,7 @@ import { shallowRef, ref, reactive } from 'vue'
import type { Ref } from 'vue' import type { Ref } from 'vue'
// 左侧分组的钩子函数 // 左侧分组的钩子函数
export function useCate(type: number) { export function useCate() {
const treeRef = shallowRef<InstanceType<typeof ElTree>>() const treeRef = shallowRef<InstanceType<typeof ElTree>>()
// 分组列表 // 分组列表
const cateLists = ref<any[]>([]) const cateLists = ref<any[]>([])
@@ -25,18 +25,12 @@ export function useCate(type: number) {
// 获取分组列表 // 获取分组列表
const getCateLists = async () => { const getCateLists = async () => {
const data = await fileCateLists({ const data = await fileCateLists({})
type
})
const item: any[] = [ const item: any[] = [
{ {
name: '全部', name: '全部',
id: 0 id: 0
} }
// {
// name: '未分组',
// id: 0
// }
] ]
cateLists.value = data cateLists.value = data
cateLists.value.unshift(...item) cateLists.value.unshift(...item)
@@ -48,7 +42,6 @@ export function useCate(type: number) {
// 添加分组 // 添加分组
const handleAddCate = async (value: string) => { const handleAddCate = async (value: string) => {
await fileCateAdd({ await fileCateAdd({
type,
name: value, name: value,
pid: 0 pid: 0
}) })
@@ -92,7 +85,7 @@ export function useCate(type: number) {
// 处理文件的钩子函数 // 处理文件的钩子函数
export function useFile( export function useFile(
cateId: Ref<string | number>, cateId: Ref<string | number>,
type: Ref<number>, ext: string[],
limit: Ref<number>, limit: Ref<number>,
size: number size: number
) { ) {
@@ -103,7 +96,7 @@ export function useFile(
const isIndeterminate = ref(false) const isIndeterminate = ref(false)
const fileParams = reactive({ const fileParams = reactive({
name: '', name: '',
type: type, ext: ext,
cid: cateId cid: cateId
}) })
const { pager, getLists, resetPage } = usePaging({ const { pager, getLists, resetPage } = usePaging({

View File

@@ -85,22 +85,10 @@
<div class="operate-btn flex"> <div class="operate-btn flex">
<div class="flex-1 flex"> <div class="flex-1 flex">
<upload <upload
v-if="type == 'image'"
v-perms="['admin:common:upload:image']" v-perms="['admin:common:upload:image']"
class="mr-3" class="mr-3"
:data="{ cid: cateId }" :data="{ cid: cateId }"
:type="type" :ext="ext"
:show-progress="true"
@change="refresh"
>
<el-button type="primary">本地上传</el-button>
</upload>
<upload
v-if="type == 'video'"
v-perms="['admin:common:upload:video']"
class="mr-3"
:data="{ cid: cateId }"
:type="type"
:show-progress="true" :show-progress="true"
@change="refresh" @change="refresh"
> >
@@ -139,7 +127,7 @@
<file-item <file-item
:uri="row.uri" :uri="row.uri"
file-size="50px" file-size="50px"
:type="type" :ext="row.ext"
@click.stop="handlePreview(row.uri)" @click.stop="handlePreview(row.uri)"
></file-item> ></file-item>
</template> </template>
@@ -151,6 +139,9 @@
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="大小" prop="size" min-width="100"> </el-table-column>
<el-table-column label="格式" prop="ext" min-width="100"></el-table-column>
<el-table-column prop="createTime" label="上传时间" min-width="100" /> <el-table-column prop="createTime" label="上传时间" min-width="100" />
<el-table-column label="操作" width="150" fixed="right"> <el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
@@ -259,7 +250,7 @@
<file-item <file-item
:uri="item.uri" :uri="item.uri"
file-size="100px" file-size="100px"
:type="type" :ext="item.ext"
></file-item> ></file-item>
</del-wrap> </del-wrap>
</div> </div>
@@ -268,12 +259,14 @@
</el-scrollbar> </el-scrollbar>
</div> </div>
</div> </div>
<preview v-model="showPreview" :url="previewUrl" :type="type" /> <preview v-model="showPreview" :url="previewUrl" />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, toRefs, ref, watch } from 'vue' import { onMounted, toRefs, ref, watch } from 'vue'
import type { PropType } from 'vue'
import { useCate, useFile } from './hook' import { useCate, useFile } from './hook'
import FileItem from './file.vue' import FileItem from './file.vue'
import Preview from './preview.vue' import Preview from './preview.vue'
@@ -286,10 +279,14 @@ const props = defineProps({
type: Number, type: Number,
default: 1 default: 1
}, },
type: { ext: {
type: String, type: Array as PropType<string[]>,
default: 'image' default: () => []
}, },
// type: {
// type: String,
// default: 'image'
// },
mode: { mode: {
type: String, type: String,
default: 'picker' default: 'picker'
@@ -301,18 +298,18 @@ const props = defineProps({
}) })
const emit = defineEmits(['change']) const emit = defineEmits(['change'])
const { limit } = toRefs(props) const { limit } = toRefs(props)
const typeValue = computed<number>(() => { // const typeValue = computed<number>(() => {
switch (props.type) { // switch (props.type) {
case 'image': // case 'image':
return 10 // return 10
case 'video': // case 'video':
return 20 // return 20
case 'file': // case 'file':
return 30 // return 30
default: // default:
return 0 // return 0
} // }
}) // })
// const visible: Ref<boolean> = inject('visible') // const visible: Ref<boolean> = inject('visible')
const previewUrl = ref('') const previewUrl = ref('')
const showPreview = ref(false) const showPreview = ref(false)
@@ -325,7 +322,7 @@ const {
handleDeleteCate, handleDeleteCate,
getCateLists, getCateLists,
handleCatSelect handleCatSelect
} = useCate(typeValue.value) } = useCate()
const { const {
tableRef, tableRef,
@@ -345,7 +342,7 @@ const {
selectItems, selectItems,
handleFileRename handleFileRename
} = useFile(cateId, typeValue, limit, props.pageSize) } = useFile(cateId, props.ext, limit, props.pageSize)
function handleSelectionChange(val: any[]) { function handleSelectionChange(val: any[]) {
console.log('handleSelectionChange', val) console.log('handleSelectionChange', val)
selectItems(val) selectItems(val)
@@ -356,22 +353,12 @@ const getData = async () => {
treeRef.value?.setCurrentKey(cateId.value) treeRef.value?.setCurrentKey(cateId.value)
getFileList() getFileList()
} }
// getData()
const handlePreview = (url: string) => { const handlePreview = (url: string) => {
previewUrl.value = url previewUrl.value = url
showPreview.value = true showPreview.value = true
} }
// watch(
// () => visible,
// (val) => {
// if (val) {
// getData()
// }
// },
// {
// immediate: true
// }
// )
watch(cateId, () => { watch(cateId, () => {
fileParams.name = '' fileParams.name = ''
refresh() refresh()

View File

@@ -17,7 +17,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, useTemplateRef, watch, nextTick } from 'vue' import { ref, useTemplateRef, watch, computed, nextTick } from 'vue'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Boolean, type: Boolean,
@@ -26,11 +26,24 @@ const props = defineProps({
url: { url: {
type: String, type: String,
default: '' default: ''
},
type: {
type: String,
default: 'image'
} }
// type: {
// type: String,
// default: 'image'
// }
})
const type = computed(() => {
const imageExt = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
const videoExt = ['mp4', 'avi', 'mov']
const ext = props.url.split('.').pop()
if (imageExt.includes(ext)) {
return 'image'
}
if (videoExt.includes(ext)) {
return 'video'
}
return 'file'
}) })
const playerRef = useTemplateRef('playerRef') const playerRef = useTemplateRef('playerRef')

View File

@@ -14,7 +14,7 @@
:on-error="handleError" :on-error="handleError"
:accept="getAccept" :accept="getAccept"
> >
<slot></slot> <slot></slot>{{ getAccept }}
</el-upload> </el-upload>
<el-dialog <el-dialog
v-if="showProgress && fileList.length" v-if="showProgress && fileList.length"
@@ -41,6 +41,7 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, ref, toRaw, useTemplateRef } from 'vue' import { computed, defineComponent, ref, toRaw, useTemplateRef } from 'vue'
import type { PropType } from 'vue'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import config from '@/config' import config from '@/config'
import feedback from '@/utils/feedback' import feedback from '@/utils/feedback'
@@ -54,6 +55,10 @@ export default defineComponent({
type: String, type: String,
default: '' default: ''
}, },
ext: {
type: Array as PropType<string[]>,
default: () => []
},
// 上传文件类型 // 上传文件类型
type: { type: {
type: String, type: String,
@@ -94,7 +99,7 @@ export default defineComponent({
action = `${config.baseUrl}${config.urlPrefix}${props.url}` action = `${config.baseUrl}${config.urlPrefix}${props.url}`
} }
} else { } else {
action = `${config.baseUrl}${config.urlPrefix}/common/upload/${props.type}` action = `${config.baseUrl}${config.urlPrefix}/common/upload/file`
} }
const headers = computed(() => ({ const headers = computed(() => ({
token: userStore.token, token: userStore.token,
@@ -135,14 +140,15 @@ export default defineComponent({
} }
const getAccept = computed(() => { const getAccept = computed(() => {
switch (props.type) { if (props.ext.length) {
case 'image': // 补充前缀
return '.jpg,.png,.gif,.webp,.jpeg,.ico,.bmp' return props.ext
case 'video': .map((item) => {
return '.wmv,.avi,.mov,.mp4,.flv,.rmvb' return `.${item}`
default: })
return '*' .join(',')
} }
return '*'
}) })
return { return {
uploadRefs, uploadRefs,

View File

@@ -5,13 +5,13 @@
<el-tab-pane <el-tab-pane
v-for="item in tabsMap" v-for="item in tabsMap"
:label="item.name" :label="item.name"
:name="item.type" :name="item.name"
:index="item.type" :index="item.name"
:key="item.type" :key="item.name"
lazy lazy
> >
<material <material
:type="item.type" :ext="item.ext"
mode="page" mode="page"
file-size="120px" file-size="120px"
:limit="-1" :limit="-1"
@@ -31,12 +31,24 @@ defineOptions({
}) })
const tabsMap = [ const tabsMap = [
{ {
type: 'image', // type: '',
name: '图片' name: '全部',
ext: []
}, },
{ {
type: 'video', // type: 'image',
name: '视频' name: '图片',
ext: ['jpg', 'jpeg', 'png', 'gif', 'bmp']
},
{
// type: 'video',
name: '视频',
ext: ['mp4', 'avi', 'mov']
},
{
// type: 'pdf',
name: 'pdf',
ext: ['pdf']
} }
] ]
const activeTab = ref('image') const activeTab = ref('image')
@@ -47,11 +59,7 @@ const activeTab = ref('image')
min-width: 700px; min-width: 700px;
:deep(.el-tabs) { :deep(.el-tabs) {
height: calc(100vh - 180px); height: calc(100vh - 180px);
// display: flex;
// flex-direction: column;
// .el-tabs__header {
// margin-bottom: 0 !important;
// }
.el-tabs__content, .el-tabs__content,
.el-tab-pane { .el-tab-pane {
min-height: 0; min-height: 0;

View File

@@ -13,3 +13,11 @@ REDIS.URL='redis://localhost:6379'
# 上传文件目录 # 上传文件目录
FILE.UPLOAD_DIRECTORY='/www/wwwroot/x_admin_go/public/uploads/' FILE.UPLOAD_DIRECTORY='/www/wwwroot/x_admin_go/public/uploads/'
GeTui.HOST=''
GeTui.APPID=''
GeTui.APPKEY=''
GeTui.APPSECRET=''
GeTui.MASTERSECRET=''
GeTui.PackName=''

View File

@@ -3,21 +3,30 @@ package config
type fileConfig struct { type fileConfig struct {
UploadDirectory string `mapstructure:"UPLOAD_DIRECTORY"` // 文件目录 UploadDirectory string `mapstructure:"UPLOAD_DIRECTORY"` // 文件目录
PublicPrefix string `mapstructure:"PUBLIC_PREFIX"` // 资源访问前缀 PublicPrefix string `mapstructure:"PUBLIC_PREFIX"` // 资源访问前缀
UploadImageSize int64 `mapstructure:"UPLOAD_IMAGE_SIZE"` // 上传图片大小限制 UploadImageSize int64 `mapstructure:"UPLOAD_IMAGE_SIZE"` // 上传图片大小限制
UploadVideoSize int64 `mapstructure:"UPLOAD_VIDEO_SIZE"` // 上传视频大小限制 UploadVideoSize int64 `mapstructure:"UPLOAD_VIDEO_SIZE"` // 上传视频大小限制
UploadFileSize int64 `mapstructure:"UPLOAD_FILE_SIZE"` // 上传文件大小限制
UploadImageExt []string `mapstructure:"UPLOAD_IMAGE_EXT"` // 上传图片扩展 UploadImageExt []string `mapstructure:"UPLOAD_IMAGE_EXT"` // 上传图片扩展
UploadVideoExt []string `mapstructure:"UPLOAD_VIDEO_EXT"` // 上传视频扩展 UploadVideoExt []string `mapstructure:"UPLOAD_VIDEO_EXT"` // 上传视频扩展
UploadFileExt []string `mapstructure:"UPLOAD_FILE_EXT"` // 上传文件扩展
} }
// var uploadImageExtDefault = []string{"png", "jpg", "jpeg", "gif", "ico", "bmp", "webp", "avif"}
var FileConfig = fileConfig{ var FileConfig = fileConfig{
// 资源访问前缀 // 资源访问前缀
PublicPrefix: "/api/uploads", PublicPrefix: "/api/uploads",
// 上传文件路径 // 上传文件路径
UploadDirectory: "/tmp/uploads/x_admin_go/", UploadDirectory: "/tmp/uploads/x_admin_go/",
UploadImageSize: 10 * 1024 * 1024, UploadImageSize: 10 * 1024 * 1024, // 10MB
UploadVideoSize: 30 * 1024 * 1024, UploadVideoSize: 500 * 1024 * 1024, // 500MB
UploadFileSize: 1024 * 1024 * 1024, //1GB
// 上传图片扩展 // 上传图片扩展
UploadImageExt: []string{"png", "jpg", "jpeg", "gif", "ico", "bmp", "webp", "avif"}, UploadImageExt: []string{"png", "jpg", "jpeg", "gif", "ico", "bmp", "webp", "avif"},
// 上传视频扩展 // 上传视频扩展
UploadVideoExt: []string{"mp4", "mp3", "avi", "flv", "rmvb", "mov"}, UploadVideoExt: []string{"mp4", "mp3", "avi", "flv", "rmvb", "mov"},
UploadFileExt: []string{"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "zip", "rar", "7z", "txt"},
} }

View File

@@ -1,6 +1,7 @@
package commonController package commonController
import ( import (
"x_admin/config"
"x_admin/core/request" "x_admin/core/request"
"x_admin/core/response" "x_admin/core/response"
"x_admin/middleware" "x_admin/middleware"
@@ -39,7 +40,8 @@ func (ah albumHandler) albumList(c *gin.Context) {
if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) {
return return
} }
res, err := commonService.AlbumService.AlbumList(page, listReq) var adminId = config.AdminConfig.GetAdminId(c)
res, err := commonService.AlbumService.AlbumList(adminId, page, listReq)
response.CheckAndRespWithData(c, res, err) response.CheckAndRespWithData(c, res, err)
} }
@@ -76,7 +78,8 @@ func (ah albumHandler) cateList(c *gin.Context) {
if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) { if response.IsFailWithResp(c, util.VerifyUtil.VerifyQuery(c, &listReq)) {
return return
} }
res, err := commonService.AlbumService.CateList(listReq) var adminId = config.AdminConfig.GetAdminId(c)
res, err := commonService.AlbumService.CateList(adminId, listReq)
response.CheckAndRespWithData(c, res, err) response.CheckAndRespWithData(c, res, err)
} }
@@ -86,7 +89,8 @@ func (ah albumHandler) cateAdd(c *gin.Context) {
if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) { if response.IsFailWithResp(c, util.VerifyUtil.VerifyJSON(c, &addReq)) {
return return
} }
response.CheckAndResp(c, commonService.AlbumService.CateAdd(addReq)) var adminId = config.AdminConfig.GetAdminId(c)
response.CheckAndResp(c, commonService.AlbumService.CateAdd(adminId, addReq))
} }
// cateRename 类目命名 // cateRename 类目命名

View File

@@ -15,28 +15,26 @@ func UploadRoute(rg *gin.RouterGroup) {
handle := uploadHandler{} handle := uploadHandler{}
rg = rg.Group("/common", middleware.TokenAuth()) rg = rg.Group("/common", middleware.TokenAuth())
rg.POST("/upload/image", middleware.RecordLog("上传图片", middleware.RequestFile), handle.uploadImage) rg.POST("/upload/preUploadFile", middleware.RecordLog("文件预上传", middleware.RequestFile), handle.preUploadFile)
rg.POST("/upload/video", middleware.RecordLog("上传视频", middleware.RequestFile), handle.uploadVideo) rg.POST("/upload/file", middleware.RecordLog("上传文件", middleware.RequestFile), handle.uploadFile)
} }
type uploadHandler struct{} type uploadHandler struct{}
// uploadImage 上传图片 // 文件预上传
func (uh uploadHandler) uploadImage(c *gin.Context) { func (uh uploadHandler) preUploadFile(c *gin.Context) {
var uReq commonSchema.CommonUploadImageReq // md5,fileName,fileSize,cid
if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &uReq)) { // 检查MD5是否已存在
return // 检查名称是否合规安全
} // 检查文件大小是否超过限制
file, ve := util.VerifyUtil.VerifyFile(c, "file") // 检查文件类型是否符合要求
if response.IsFailWithResp(c, ve) {
return // 如果文件存在复制到cid对应目录
}
res, err := commonService.UploadService.UploadImage(file, uReq.Cid, config.AdminConfig.GetAdminId(c))
response.CheckAndRespWithData(c, res, err)
} }
// uploadVideo 上传视频 // uploadFile 上传文件
func (uh uploadHandler) uploadVideo(c *gin.Context) { func (uh uploadHandler) uploadFile(c *gin.Context) {
var uReq commonSchema.CommonUploadImageReq var uReq commonSchema.CommonUploadImageReq
if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &uReq)) { if response.IsFailWithResp(c, util.VerifyUtil.VerifyBody(c, &uReq)) {
return return
@@ -45,6 +43,6 @@ func (uh uploadHandler) uploadVideo(c *gin.Context) {
if response.IsFailWithResp(c, ve) { if response.IsFailWithResp(c, ve) {
return return
} }
res, err := commonService.UploadService.UploadVideo(file, uReq.Cid, config.AdminConfig.GetAdminId(c)) res, err := commonService.UploadService.UploadFile(file, uReq.Cid, config.AdminConfig.GetAdminId(c))
response.CheckAndRespWithData(c, res, err) response.CheckAndRespWithData(c, res, err)
} }

View File

@@ -10,12 +10,13 @@ import (
type Album struct { type Album struct {
ID uint `gorm:"primarykey;comment:'主键ID'"` ID uint `gorm:"primarykey;comment:'主键ID'"`
Cid uint `gorm:"not null;default:0;comment:'类目ID'"` Cid uint `gorm:"not null;default:0;comment:'类目ID'"`
Aid uint `gorm:"not null;default:0;comment:'管理ID'"` AdminId uint `gorm:"not null;default:0;comment:'管理ID'"`
Uid uint `gorm:"not null;default:0;comment:'用户ID'"` Uid uint `gorm:"not null;default:0;comment:'用户ID'"`
Type int `gorm:"not null;default:10;comment:'文件类型: [10=图片, 20=视频]''"` // Type int `gorm:"not null;default:10;comment:'文件类型: [10=图片, 20=视频]''"`
Name string `gorm:"not null;default:'';comment:'文件名称''"` Name string `gorm:"not null;default:'';comment:'文件名称''"`
Uri string `gorm:"not null;comment:'文件路径'"` Uri string `gorm:"not null;comment:'文件路径'"`
Ext string `gorm:"not null;default:'';comment:'文件扩展'"` Ext string `gorm:"not null;default:'';comment:'文件扩展'"`
Hash string `gorm:"not null;default:'';comment:'文件hash'"`
Size int64 `gorm:"not null;default:0;comment:文件大小"` Size int64 `gorm:"not null;default:0;comment:文件大小"`
IsDelete soft_delete.DeletedAt `gorm:"not null;default:0;softDelete:flag,DeletedAtField:DeleteTime;comment:'是否删除: 0=否, 1=是'"` IsDelete soft_delete.DeletedAt `gorm:"not null;default:0;softDelete:flag,DeletedAtField:DeleteTime;comment:'是否删除: 0=否, 1=是'"`
CreateTime core.NullTime `gorm:"autoCreateTime;not null;comment:'创建时间'"` CreateTime core.NullTime `gorm:"autoCreateTime;not null;comment:'创建时间'"`
@@ -26,8 +27,10 @@ type Album struct {
// AlbumCate 相册分类实体 // AlbumCate 相册分类实体
type AlbumCate struct { type AlbumCate struct {
ID uint `gorm:"primarykey;comment:'主键ID'"` ID uint `gorm:"primarykey;comment:'主键ID'"`
AdminId uint `gorm:"not null;default:0;comment:'管理员ID'"`
Pid uint `gorm:"not null;default:0;comment:'父级ID'"` Pid uint `gorm:"not null;default:0;comment:'父级ID'"`
Type int `gorm:"not null;default:10;comment:'文件类型: [10=图片, 20=视频]''"` // Type int `gorm:"not null;default:10;comment:'文件类型: [10=图片, 20=视频]''"`
Name string `gorm:"not null;default:'';comment:'分类名称''"` Name string `gorm:"not null;default:'';comment:'分类名称''"`
IsDelete soft_delete.DeletedAt `gorm:"not null;default:0;softDelete:flag,DeletedAtField:DeleteTime;comment:'是否删除: 0=否, 1=是'"` IsDelete soft_delete.DeletedAt `gorm:"not null;default:0;softDelete:flag,DeletedAtField:DeleteTime;comment:'是否删除: 0=否, 1=是'"`
CreateTime core.NullTime `gorm:"autoCreateTime;not null;comment:'创建时间'"` CreateTime core.NullTime `gorm:"autoCreateTime;not null;comment:'创建时间'"`

View File

@@ -20,7 +20,7 @@ var StorageDriver = storageDriver{}
// UploadFile 文件对象 // UploadFile 文件对象
type UploadFile struct { type UploadFile struct {
Name string // 文件名称 Name string // 文件名称
Type int // 文件类型 // Type int // 文件类型
Size int64 // 文件大小 Size int64 // 文件大小
Ext string // 文件扩展 Ext string // 文件扩展
Uri string // 文件路径 Uri string // 文件路径
@@ -31,25 +31,34 @@ type UploadFile struct {
type storageDriver struct{} type storageDriver struct{}
// Upload 根据引擎类型上传文件 // Upload 根据引擎类型上传文件
func (sd storageDriver) Upload(file *multipart.FileHeader, folder string, fileType int) (uf *UploadFile, e error) { func (sd storageDriver) Upload(file *multipart.FileHeader) (uf *UploadFile, e error) {
// TODO: engine默认local // TODO: engine默认local
if e = sd.checkFile(file, fileType); e != nil {
fileExt := sd.getFileExt(file.Filename)
if fileExt == "" {
return nil, response.AssertArgumentError.SetMessage("文件类型错误!")
}
if e = sd.checkFile(file.Filename, file.Size); e != nil {
return return
} }
key := sd.buildSaveName(file) var folder string = fileExt
saveName := sd.buildSaveName(file)
engine := "local" engine := "local"
if engine == "local" { if engine == "local" {
if e = sd.localUpload(file, key, folder); e != nil { if e = sd.localUpload(file, folder, saveName); e != nil {
return return
} }
} else { } else {
core.Logger.Errorf("storageDriver.Upload engine err: err=[unsupported engine]") core.Logger.Errorf("storageDriver.Upload engine err: err=[unsupported engine]")
return nil, response.Failed.SetMessage(fmt.Sprintf("engine:%s 暂时不支持", engine)) return nil, response.Failed.SetMessage(fmt.Sprintf("engine:%s 暂时不支持", engine))
} }
fileRelPath := path.Join(folder, key)
fileRelPath := path.Join(folder, saveName)
return &UploadFile{ return &UploadFile{
Name: file.Filename, Name: file.Filename,
Type: fileType, // Type: int(fileType),
Size: file.Size, Size: file.Size,
Ext: strings.ToLower(strings.Replace(path.Ext(file.Filename), ".", "", 1)), Ext: strings.ToLower(strings.Replace(path.Ext(file.Filename), ".", "", 1)),
Uri: fileRelPath, Uri: fileRelPath,
@@ -58,7 +67,7 @@ func (sd storageDriver) Upload(file *multipart.FileHeader, folder string, fileTy
} }
// localUpload 本地上传 (临时方法) // localUpload 本地上传 (临时方法)
func (sd storageDriver) localUpload(file *multipart.FileHeader, key string, folder string) (e error) { func (sd storageDriver) localUpload(file *multipart.FileHeader, folder string, saveName string) (e error) {
// TODO: 临时方法,后续调整 // TODO: 临时方法,后续调整
// 映射目录 // 映射目录
directory := config.FileConfig.UploadDirectory directory := config.FileConfig.UploadDirectory
@@ -70,8 +79,8 @@ func (sd storageDriver) localUpload(file *multipart.FileHeader, key string, fold
} }
defer src.Close() defer src.Close()
// 文件信息 // 文件信息
savePath := path.Join(directory, folder, path.Dir(key)) savePath := path.Join(directory, folder, path.Dir(saveName))
saveFilePath := path.Join(directory, folder, key) saveFilePath := path.Join(directory, folder, saveName)
// 创建目录 // 创建目录
err = os.MkdirAll(savePath, 0755) err = os.MkdirAll(savePath, 0755)
if err != nil && !os.IsExist(err) { if err != nil && !os.IsExist(err) {
@@ -94,6 +103,7 @@ func (sd storageDriver) localUpload(file *multipart.FileHeader, key string, fold
"storageDriver.localUpload Copy err: file=[%s], err=[%+v]", saveFilePath, err) "storageDriver.localUpload Copy err: file=[%s], err=[%+v]", saveFilePath, err)
return response.Failed.SetMessage("上传文件失败: " + err.Error()) return response.Failed.SetMessage("上传文件失败: " + err.Error())
} }
return nil return nil
} }
@@ -101,35 +111,40 @@ func (sd storageDriver) localUpload(file *multipart.FileHeader, key string, fold
func (sd storageDriver) buildSaveName(file *multipart.FileHeader) string { func (sd storageDriver) buildSaveName(file *multipart.FileHeader) string {
name := file.Filename name := file.Filename
ext := strings.ToLower(path.Ext(name)) ext := strings.ToLower(path.Ext(name))
date := time.Now().Format("20060201") date := time.Now().Format("20060102")
return path.Join(date, util.ToolsUtil.MakeUuid()+ext) return path.Join(date, util.ToolsUtil.MakeUuid()+ext)
} }
// checkFile 文件验证 // getFileExt 获取文件扩展名
func (sd storageDriver) checkFile(file *multipart.FileHeader, fileType int) (e error) { func (sd storageDriver) getFileExt(fileName string) string {
fileName := file.Filename
fileExt := strings.ToLower(strings.Replace(path.Ext(fileName), ".", "", 1)) fileExt := strings.ToLower(strings.Replace(path.Ext(fileName), ".", "", 1))
fileSize := file.Size return fileExt
switch fileType {
case 10:
// 图片文件
if !util.ToolsUtil.Contains(config.FileConfig.UploadImageExt, fileExt) {
return response.Failed.SetMessage("不被支持的图片扩展: " + fileExt)
} }
// checkFile 文件验证
func (sd storageDriver) checkFile(fileName string, fileSize int64) (e error) {
fileExt := sd.getFileExt(fileName)
if util.ToolsUtil.Contains(config.FileConfig.UploadImageExt, fileExt) {
// 图片文件
if fileSize > config.FileConfig.UploadImageSize { if fileSize > config.FileConfig.UploadImageSize {
return response.Failed.SetMessage("上传图片不能超出限制: " + strconv.FormatInt(config.FileConfig.UploadImageSize/1024/1024, 10) + "M") return response.Failed.SetMessage("上传图片不能超出限制: " + strconv.FormatInt(config.FileConfig.UploadImageSize/1024/1024, 10) + "M")
} }
case 20: } else if util.ToolsUtil.Contains(config.FileConfig.UploadVideoExt, fileExt) {
// 视频文件 // 视频文件
if !util.ToolsUtil.Contains(config.FileConfig.UploadVideoExt, fileExt) {
return response.Failed.SetMessage("不被支持的视频扩展: " + fileExt)
}
if fileSize > config.FileConfig.UploadVideoSize { if fileSize > config.FileConfig.UploadVideoSize {
return response.Failed.SetMessage("上传视频不能超出限制: " + strconv.FormatInt(config.FileConfig.UploadVideoSize/1024/1024, 10) + "M") return response.Failed.SetMessage("上传视频不能超出限制: " + strconv.FormatInt(config.FileConfig.UploadVideoSize/1024/1024, 10) + "M")
} }
default: } else if util.ToolsUtil.Contains(config.FileConfig.UploadFileExt, fileExt) {
// 文件
if fileSize > config.FileConfig.UploadFileSize {
return response.Failed.SetMessage("上传文件不能超出限制: " + strconv.FormatInt(config.FileConfig.UploadFileSize/1024/1024, 10) + "M")
}
} else {
core.Logger.Errorf("storageDriver.checkFile fileType err: err=[unsupported fileType]") core.Logger.Errorf("storageDriver.checkFile fileType err: err=[unsupported fileType]")
return response.Failed.SetMessage("上传文件类型错误") return response.Failed.SetMessage("上传文件类型错误")
} }
return nil return nil
} }

View File

@@ -9,8 +9,10 @@ type CommonUploadImageReq struct {
//CommonAlbumListReq 相册文件列表参数 //CommonAlbumListReq 相册文件列表参数
type CommonAlbumListReq struct { type CommonAlbumListReq struct {
Cid int `form:"cid,default=-1"` // 类目ID Cid int `form:"cid,default=-1"` // 类目ID
Type int `form:"type" binding:"omitempty,oneof=10 20 30"` // 文件类型: [10=图片, 20=视频] // Type int `form:"type" binding:"omitempty,oneof=10 20 30"` // 文件类型: [10=图片, 20=视频]
Name string `form:"name"` // 文件名称 Name string `form:"name"` // 文件名称
Ext []string `form:"ext[]"` // 文件扩展
} }
//CommonAlbumRenameReq 相册文件重命名参数 //CommonAlbumRenameReq 相册文件重命名参数
@@ -28,13 +30,14 @@ type CommonAlbumMoveReq struct {
//CommonAlbumAddReq 相册文件新增参数 //CommonAlbumAddReq 相册文件新增参数
type CommonAlbumAddReq struct { type CommonAlbumAddReq struct {
Cid uint `form:"cid" binding:"gte=0"` // 类目ID Cid uint `form:"cid" binding:"gte=0"` // 类目ID
Aid uint `form:"aid" binding:"gte=0"` // 管理ID AdminId uint `form:"admin_id" binding:"gte=0"` // 管理ID
Uid uint `form:"uid" binding:"gte=0"` // 用户ID // Uid uint `form:"uid" binding:"gte=0"` // 用户ID
Type int `form:"type" binding:"oneof=10 20 30"` // 文件类型: [10=图片, 20=视频,30文件] // Type int `form:"type" binding:"oneof=10 20 30"` // 文件类型: [10=图片, 20=视频,30文件]
Name string `form:"name"` // 文件名称 Name string `form:"name"` // 文件名称
Uri string `form:"uri"` // 文件路径 Uri string `form:"uri"` // 文件路径
Ext string `form:"ext"` // 文件扩展 Ext string `form:"ext"` // 文件扩展
Size int64 `form:"size"` // 文件大小 Size int64 `form:"size"` // 文件大小
Hash string `form:"hash"`
} }
//CommonAlbumDelReq 相册文件删除参数 //CommonAlbumDelReq 相册文件删除参数
@@ -44,14 +47,14 @@ type CommonAlbumDelReq struct {
//CommonCateListReq 相册分类列表参数 //CommonCateListReq 相册分类列表参数
type CommonCateListReq struct { type CommonCateListReq struct {
Type int `form:"type" binding:"omitempty,oneof=10 20 30"` // 分类类型: [10=图片,20=视频,30文件] // Type int `form:"type" binding:"omitempty,oneof=10 20 30"` // 分类类型: [10=图片,20=视频,30文件]
Name string `form:"name"` // 分类名称 Name string `form:"name"` // 分类名称
} }
//CommonCateAddReq 相册分类新增参数 //CommonCateAddReq 相册分类新增参数
type CommonCateAddReq struct { type CommonCateAddReq struct {
Pid uint `form:"pid" binding:"gte=0"` // 父级ID Pid uint `form:"pid" binding:"gte=0"` // 父级ID
Type int `form:"type" binding:"required,oneof=10 20 30"` // 分类类型: [10=图片,20=视频,30文件] // Type int `form:"type" binding:"required,oneof=10 20 30"` // 分类类型: [10=图片,20=视频,30文件]
Name string `form:"name" binding:"required,min=1,max=30"` // 分类名称 Name string `form:"name" binding:"required,min=1,max=30"` // 分类名称
} }
@@ -70,9 +73,9 @@ type CommonCateDelReq struct {
type CommonUploadFileResp struct { type CommonUploadFileResp struct {
ID uint `json:"id" structs:"id"` // 主键 ID uint `json:"id" structs:"id"` // 主键
Cid uint `json:"cid" structs:"cid"` // 类目ID Cid uint `json:"cid" structs:"cid"` // 类目ID
Aid uint `json:"aid" structs:"aid"` // 管理ID AdminId uint `json:"admin_id" structs:"admin_id"` // 管理ID
Uid uint `json:"uid" structs:"uid"` // 用户ID Uid uint `json:"uid" structs:"uid"` // 用户ID
Type int `json:"type" structs:"type"` // 文件类型: [10=图片, 20=视频] // Type int `json:"type" structs:"type"` // 文件类型: [10=图片, 20=视频]
Name string `json:"name" structs:"name"` // 文件名称 Name string `json:"name" structs:"name"` // 文件名称
Uri string `json:"url" structs:"url"` // 文件路径 Uri string `json:"url" structs:"url"` // 文件路径
Path string `json:"path" structs:"path"` // 访问地址 Path string `json:"path" structs:"path"` // 访问地址

View File

@@ -14,22 +14,10 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
type IAlbumService interface {
AlbumList(page request.PageReq, listReq commonSchema.CommonAlbumListReq) (res response.PageResp, e error)
AlbumRename(id uint, name string) (e error)
AlbumMove(ids []uint, cid int) (e error)
AlbumAdd(addReq commonSchema.CommonAlbumAddReq) (res uint, e error)
AlbumDel(ids []uint) (e error)
CateList(listReq commonSchema.CommonCateListReq) (mapList []commonSchema.CommonCateListResp, e error)
CateAdd(addReq commonSchema.CommonCateAddReq) (e error)
CateRename(id uint, name string) (e error)
CateDel(id uint) (e error)
}
var AlbumService = NewAlbumService() var AlbumService = NewAlbumService()
// NewAlbumService 初始化 // NewAlbumService 初始化
func NewAlbumService() IAlbumService { func NewAlbumService() *albumService {
db := core.GetDB() db := core.GetDB()
return &albumService{db: db} return &albumService{db: db}
} }
@@ -40,21 +28,29 @@ type albumService struct {
} }
// AlbumList 相册文件列表 // AlbumList 相册文件列表
func (albSrv albumService) AlbumList(page request.PageReq, listReq commonSchema.CommonAlbumListReq) (res response.PageResp, e error) { func (albSrv albumService) AlbumList(adminId uint, page request.PageReq, listReq commonSchema.CommonAlbumListReq) (res response.PageResp, e error) {
// 分页信息 // 分页信息
limit := page.PageSize limit := page.PageSize
offset := page.PageSize * (page.PageNo - 1) offset := page.PageSize * (page.PageNo - 1)
// 查询 // 查询
albumModel := albSrv.db.Model(&common_model.Album{}).Where("is_delete = ?", 0) albumModel := albSrv.db.Model(&common_model.Album{}).Where("is_delete = ?", 0)
albumModel = albumModel.Where("admin_id = ?", adminId)
if listReq.Cid > 0 { if listReq.Cid > 0 {
albumModel = albumModel.Where("cid = ?", listReq.Cid) albumModel = albumModel.Where("cid = ?", listReq.Cid)
} }
if listReq.Name != "" { if listReq.Name != "" {
albumModel = albumModel.Where("name like ?", "%"+listReq.Name+"%") albumModel = albumModel.Where("name like ?", "%"+listReq.Name+"%")
} }
if listReq.Type > 0 { if len(listReq.Ext) > 0 {
albumModel = albumModel.Where("type = ?", listReq.Type) albumModel = albumModel.Where("ext in ?", listReq.Ext)
} }
// if listReq.Type > 0 {
// albumModel = albumModel.Where("type = ?", listReq.Type)
// }
// 总数 // 总数
var count int64 var count int64
err := albumModel.Count(&count).Error err := albumModel.Count(&count).Error
@@ -161,12 +157,14 @@ func (albSrv albumService) AlbumDel(ids []uint) (e error) {
} }
// CateList 相册分类列表 // CateList 相册分类列表
func (albSrv albumService) CateList(listReq commonSchema.CommonCateListReq) (mapList []commonSchema.CommonCateListResp, e error) { func (albSrv albumService) CateList(adminId uint, listReq commonSchema.CommonCateListReq) (mapList []commonSchema.CommonCateListResp, e error) {
var cates []common_model.AlbumCate var cates []common_model.AlbumCate
cateModel := albSrv.db.Where("is_delete = ?", 0).Order("id desc") cateModel := albSrv.db.Where("is_delete = ?", 0).Order("id desc")
if listReq.Type > 0 { // if listReq.Type > 0 {
cateModel = cateModel.Where("type = ?", listReq.Type) // cateModel = cateModel.Where("type = ?", listReq.Type)
} // }
cateModel = cateModel.Where("admin_id = ?", adminId)
if listReq.Name != "" { if listReq.Name != "" {
cateModel = cateModel.Where("name like ?", "%"+listReq.Name+"%") cateModel = cateModel.Where("name like ?", "%"+listReq.Name+"%")
} }
@@ -180,9 +178,12 @@ func (albSrv albumService) CateList(listReq commonSchema.CommonCateListReq) (map
} }
// CateAdd 分类新增 // CateAdd 分类新增
func (albSrv albumService) CateAdd(addReq commonSchema.CommonCateAddReq) (e error) { func (albSrv albumService) CateAdd(adminId uint, addReq commonSchema.CommonCateAddReq) (e error) {
var cate common_model.AlbumCate var cate common_model.AlbumCate
convert_util.Copy(&cate, addReq) convert_util.Copy(&cate, addReq)
cate.AdminId = adminId
err := albSrv.db.Create(&cate).Error err := albSrv.db.Create(&cate).Error
e = response.CheckErr(err, "Cate添加失败") e = response.CheckErr(err, "Cate添加失败")
return return

View File

@@ -1,18 +1,16 @@
package commonService package commonService
import ( import (
"fmt"
"mime/multipart" "mime/multipart"
"time"
"x_admin/plugin" "x_admin/plugin"
"x_admin/schema/commonSchema" "x_admin/schema/commonSchema"
"x_admin/util"
"x_admin/util/convert_util" "x_admin/util/convert_util"
) )
type IUploadService interface {
UploadImage(file *multipart.FileHeader, cid uint, aid uint) (res commonSchema.CommonUploadFileResp, e error)
UploadVideo(file *multipart.FileHeader, cid uint, aid uint) (res commonSchema.CommonUploadFileResp, e error)
}
var UploadService = NewUploadService() var UploadService = NewUploadService()
// NewUploadService 初始化 // NewUploadService 初始化
@@ -23,26 +21,34 @@ func NewUploadService() *uploadService {
// uploadService 上传服务实现类 // uploadService 上传服务实现类
type uploadService struct{} type uploadService struct{}
// UploadImage 上传图片 // UploadFile 上传
func (upSrv uploadService) UploadImage(file *multipart.FileHeader, cid uint, aid uint) (res commonSchema.CommonUploadFileResp, e error) { // cid 分类id
return upSrv.uploadFile(file, "image", 10, cid, aid) // AdminId 用户id
} func (upSrv uploadService) UploadFile(file *multipart.FileHeader, cid uint, AdminId uint) (res commonSchema.CommonUploadFileResp, e error) {
// UploadVideo 上传视频
func (upSrv uploadService) UploadVideo(file *multipart.FileHeader, cid uint, aid uint) (res commonSchema.CommonUploadFileResp, e error) {
return upSrv.uploadFile(file, "video", 20, cid, aid)
}
// uploadFile 上传文件
func (upSrv uploadService) uploadFile(file *multipart.FileHeader, folder string, fileType int, cid uint, aid uint) (res commonSchema.CommonUploadFileResp, e error) {
var upRes *plugin.UploadFile var upRes *plugin.UploadFile
if upRes, e = plugin.StorageDriver.Upload(file, folder, fileType); e != nil { if upRes, e = plugin.StorageDriver.Upload(file); e != nil {
return return
} }
var startTime = time.Now()
// 计算文件MD5
md5, e := util.ToolsUtil.GetFileMD5(file)
if e != nil {
return
}
var endTime = time.Now()
var costTime = endTime.UnixNano() - startTime.UnixNano()
// 毫秒
costTime = costTime / 1000000
fmt.Printf("\n文件大小%d , md5时间 %d毫秒\n", file.Size, costTime)
var addReq commonSchema.CommonAlbumAddReq var addReq commonSchema.CommonAlbumAddReq
convert_util.Copy(&addReq, upRes) convert_util.Copy(&addReq, upRes)
addReq.Aid = aid addReq.AdminId = AdminId //管理员
addReq.Cid = cid addReq.Cid = cid // 分类id
addReq.Hash = md5
var albumId uint var albumId uint
if albumId, e = AlbumService.AlbumAdd(addReq); e != nil { if albumId, e = AlbumService.AlbumAdd(addReq); e != nil {
return return

View File

@@ -4,8 +4,10 @@ import (
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"io"
"math" "math"
"math/rand" "math/rand"
"mime/multipart"
"os" "os"
"reflect" "reflect"
"strconv" "strconv"
@@ -45,6 +47,20 @@ func (tu toolsUtil) MakeMd5(data string) string {
return hex.EncodeToString(sum[:]) return hex.EncodeToString(sum[:])
} }
// GetFileMD5 获取文件MD5
func (tu toolsUtil) GetFileMD5(file *multipart.FileHeader) (string, error) {
f, err := file.Open()
if err != nil {
return "", err
}
defer f.Close()
hash := md5.New()
if _, err := io.Copy(hash, f); err != nil {
return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
// MakeToken 生成唯一Token // MakeToken 生成唯一Token
func (tu toolsUtil) MakeToken() string { func (tu toolsUtil) MakeToken() string {
ms := time.Now().UnixMilli() ms := time.Now().UnixMilli()