73 changed files with 595 additions and 5102 deletions
@ -1,28 +0,0 @@ |
|||||
import request from '@/utils/request'; |
|
||||
import { OssQuery, OssVO } from './types'; |
|
||||
import { AxiosPromise } from 'axios'; |
|
||||
|
|
||||
// 查询OSS对象存储列表
|
|
||||
export function listOss(query: OssQuery): AxiosPromise<OssVO[]> { |
|
||||
return request({ |
|
||||
url: '/resource/oss/list', |
|
||||
method: 'get', |
|
||||
params: query |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 查询OSS对象基于id串
|
|
||||
export function listByIds(ossId: string | number): AxiosPromise<OssVO[]> { |
|
||||
return request({ |
|
||||
url: '/resource/oss/listByIds/' + ossId, |
|
||||
method: 'get' |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 删除OSS对象存储
|
|
||||
export function delOss(ossId: string | number | Array<string | number>) { |
|
||||
return request({ |
|
||||
url: '/resource/oss/' + ossId, |
|
||||
method: 'delete' |
|
||||
}); |
|
||||
} |
|
||||
@ -1,22 +0,0 @@ |
|||||
export interface OssVO extends BaseEntity { |
|
||||
ossId: string | number; |
|
||||
fileName: string; |
|
||||
originalName: string; |
|
||||
fileSuffix: string; |
|
||||
url: string; |
|
||||
createByName: string; |
|
||||
service: string; |
|
||||
} |
|
||||
|
|
||||
export interface OssQuery extends PageQuery { |
|
||||
fileName: string; |
|
||||
originalName: string; |
|
||||
fileSuffix: string; |
|
||||
createTime: string; |
|
||||
service: string; |
|
||||
orderByColumn: string; |
|
||||
isAsc: string; |
|
||||
} |
|
||||
export interface OssForm { |
|
||||
file: undefined | string; |
|
||||
} |
|
||||
@ -1,60 +0,0 @@ |
|||||
import request from '@/utils/request'; |
|
||||
import { OssConfigForm, OssConfigQuery, OssConfigVO } from './types'; |
|
||||
import { AxiosPromise } from 'axios'; |
|
||||
|
|
||||
// 查询对象存储配置列表
|
|
||||
export function listOssConfig(query: OssConfigQuery): AxiosPromise<OssConfigVO[]> { |
|
||||
return request({ |
|
||||
url: '/resource/oss/config/list', |
|
||||
method: 'get', |
|
||||
params: query |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 查询对象存储配置详细
|
|
||||
export function getOssConfig(ossConfigId: string | number): AxiosPromise<OssConfigVO> { |
|
||||
return request({ |
|
||||
url: '/resource/oss/config/' + ossConfigId, |
|
||||
method: 'get' |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 新增对象存储配置
|
|
||||
export function addOssConfig(data: OssConfigForm) { |
|
||||
return request({ |
|
||||
url: '/resource/oss/config', |
|
||||
method: 'post', |
|
||||
data: data |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 修改对象存储配置
|
|
||||
export function updateOssConfig(data: OssConfigForm) { |
|
||||
return request({ |
|
||||
url: '/resource/oss/config', |
|
||||
method: 'put', |
|
||||
data: data |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 删除对象存储配置
|
|
||||
export function delOssConfig(ossConfigId: string | number | Array<string | number>) { |
|
||||
return request({ |
|
||||
url: '/resource/oss/config/' + ossConfigId, |
|
||||
method: 'delete' |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 对象存储状态修改
|
|
||||
export function changeOssConfigStatus(ossConfigId: string | number, status: string, configKey: string) { |
|
||||
const data = { |
|
||||
ossConfigId, |
|
||||
status, |
|
||||
configKey |
|
||||
}; |
|
||||
return request({ |
|
||||
url: '/resource/oss/config/changeStatus', |
|
||||
method: 'put', |
|
||||
data: data |
|
||||
}); |
|
||||
} |
|
||||
@ -1,38 +0,0 @@ |
|||||
export interface OssConfigVO extends BaseEntity { |
|
||||
ossConfigId: number | string; |
|
||||
configKey: string; |
|
||||
accessKey: string; |
|
||||
secretKey: string; |
|
||||
bucketName: string; |
|
||||
prefix: string; |
|
||||
endpoint: string; |
|
||||
domain: string; |
|
||||
isHttps: string; |
|
||||
region: string; |
|
||||
status: string; |
|
||||
ext1: string; |
|
||||
remark: string; |
|
||||
accessPolicy: string; |
|
||||
} |
|
||||
|
|
||||
export interface OssConfigQuery extends PageQuery { |
|
||||
configKey: string; |
|
||||
bucketName: string; |
|
||||
status: string; |
|
||||
} |
|
||||
|
|
||||
export interface OssConfigForm { |
|
||||
ossConfigId: string | number | undefined; |
|
||||
configKey: string; |
|
||||
accessKey: string; |
|
||||
secretKey: string; |
|
||||
bucketName: string; |
|
||||
prefix: string; |
|
||||
endpoint: string; |
|
||||
domain: string; |
|
||||
isHttps: string; |
|
||||
accessPolicy: string; |
|
||||
region: string; |
|
||||
status: string; |
|
||||
remark: string; |
|
||||
} |
|
||||
@ -1,229 +0,0 @@ |
|||||
<template> |
|
||||
<div class="upload-file"> |
|
||||
<el-upload |
|
||||
ref="fileUploadRef" |
|
||||
multiple |
|
||||
:action="uploadFileUrl" |
|
||||
:before-upload="handleBeforeUpload" |
|
||||
:file-list="fileList" |
|
||||
:limit="limit" |
|
||||
:on-error="handleUploadError" |
|
||||
:on-exceed="handleExceed" |
|
||||
:on-success="handleUploadSuccess" |
|
||||
:show-file-list="false" |
|
||||
:headers="headers" |
|
||||
class="upload-file-uploader" |
|
||||
> |
|
||||
<!-- 上传按钮 --> |
|
||||
<el-button type="primary">选取文件</el-button> |
|
||||
</el-upload> |
|
||||
<!-- 上传提示 --> |
|
||||
<div v-if="showTip" class="el-upload__tip"> |
|
||||
请上传 |
|
||||
<template v-if="fileSize"> |
|
||||
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> |
|
||||
</template> |
|
||||
<template v-if="fileType"> |
|
||||
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> |
|
||||
</template> |
|
||||
的文件 |
|
||||
</div> |
|
||||
<!-- 文件列表 --> |
|
||||
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> |
|
||||
<li v-for="(file, index) in fileList" :key="file.uid" class="el-upload-list__item ele-upload-list__item-content"> |
|
||||
<el-link :href="`${file.url}`" :underline="false" target="_blank"> |
|
||||
<span class="el-icon-document"> {{ getFileName(file.name) }} </span> |
|
||||
</el-link> |
|
||||
<div class="ele-upload-list__item-content-action"> |
|
||||
<el-button type="danger" link @click="handleDelete(index)">删除</el-button> |
|
||||
</div> |
|
||||
</li> |
|
||||
</transition-group> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { propTypes } from '@/utils/propTypes'; |
|
||||
import { delOss, listByIds } from '@/api/system/oss'; |
|
||||
import { globalHeaders } from '@/utils/request'; |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
modelValue: { |
|
||||
type: [String, Object, Array], |
|
||||
default: () => [] |
|
||||
}, |
|
||||
// 数量限制 |
|
||||
limit: propTypes.number.def(5), |
|
||||
// 大小限制(MB) |
|
||||
fileSize: propTypes.number.def(5), |
|
||||
// 文件类型, 例如['png', 'jpg', 'jpeg'] |
|
||||
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), |
|
||||
// 是否显示提示 |
|
||||
isShowTip: propTypes.bool.def(true) |
|
||||
}); |
|
||||
|
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|
||||
const emit = defineEmits(['update:modelValue']); |
|
||||
const number = ref(0); |
|
||||
const uploadList = ref<any[]>([]); |
|
||||
|
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API; |
|
||||
const uploadFileUrl = ref(baseUrl + '/resource/oss/upload'); // 上传文件服务器地址 |
|
||||
const headers = ref(globalHeaders()); |
|
||||
|
|
||||
const fileList = ref<any[]>([]); |
|
||||
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize)); |
|
||||
|
|
||||
const fileUploadRef = ref<ElUploadInstance>(); |
|
||||
|
|
||||
watch( |
|
||||
() => props.modelValue, |
|
||||
async (val) => { |
|
||||
if (val) { |
|
||||
let temp = 1; |
|
||||
// 首先将值转为数组 |
|
||||
let list: any[] = []; |
|
||||
if (Array.isArray(val)) { |
|
||||
list = val; |
|
||||
} else { |
|
||||
const res = await listByIds(val); |
|
||||
list = res.data.map((oss) => { |
|
||||
return { |
|
||||
name: oss.originalName, |
|
||||
url: oss.url, |
|
||||
ossId: oss.ossId |
|
||||
}; |
|
||||
}); |
|
||||
} |
|
||||
// 然后将数组转为对象数组 |
|
||||
fileList.value = list.map((item) => { |
|
||||
item = { name: item.name, url: item.url, ossId: item.ossId }; |
|
||||
item.uid = item.uid || new Date().getTime() + temp++; |
|
||||
return item; |
|
||||
}); |
|
||||
} else { |
|
||||
fileList.value = []; |
|
||||
return []; |
|
||||
} |
|
||||
}, |
|
||||
{ deep: true, immediate: true } |
|
||||
); |
|
||||
|
|
||||
// 上传前校检格式和大小 |
|
||||
const handleBeforeUpload = (file: any) => { |
|
||||
// 校检文件类型 |
|
||||
if (props.fileType.length) { |
|
||||
const fileName = file.name.split('.'); |
|
||||
const fileExt = fileName[fileName.length - 1]; |
|
||||
const isTypeOk = props.fileType.indexOf(fileExt) >= 0; |
|
||||
if (!isTypeOk) { |
|
||||
proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
// 校检文件大小 |
|
||||
if (props.fileSize) { |
|
||||
const isLt = file.size / 1024 / 1024 < props.fileSize; |
|
||||
if (!isLt) { |
|
||||
proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
proxy?.$modal.loading('正在上传文件,请稍候...'); |
|
||||
number.value++; |
|
||||
return true; |
|
||||
}; |
|
||||
|
|
||||
// 文件个数超出 |
|
||||
const handleExceed = () => { |
|
||||
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`); |
|
||||
}; |
|
||||
|
|
||||
// 上传失败 |
|
||||
const handleUploadError = () => { |
|
||||
proxy?.$modal.msgError('上传文件失败'); |
|
||||
}; |
|
||||
|
|
||||
// 上传成功回调 |
|
||||
const handleUploadSuccess = (res: any, file: UploadFile) => { |
|
||||
if (res.code === 200) { |
|
||||
uploadList.value.push({ |
|
||||
name: res.data.fileName, |
|
||||
url: res.data.url, |
|
||||
ossId: res.data.ossId |
|
||||
}); |
|
||||
uploadedSuccessfully(); |
|
||||
} else { |
|
||||
number.value--; |
|
||||
proxy?.$modal.closeLoading(); |
|
||||
proxy?.$modal.msgError(res.msg); |
|
||||
fileUploadRef.value?.handleRemove(file); |
|
||||
uploadedSuccessfully(); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// 删除文件 |
|
||||
const handleDelete = (index: number) => { |
|
||||
let ossId = fileList.value[index].ossId; |
|
||||
delOss(ossId); |
|
||||
fileList.value.splice(index, 1); |
|
||||
emit('update:modelValue', listToString(fileList.value)); |
|
||||
}; |
|
||||
|
|
||||
// 上传结束处理 |
|
||||
const uploadedSuccessfully = () => { |
|
||||
if (number.value > 0 && uploadList.value.length === number.value) { |
|
||||
fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value); |
|
||||
uploadList.value = []; |
|
||||
number.value = 0; |
|
||||
emit('update:modelValue', listToString(fileList.value)); |
|
||||
proxy?.$modal.closeLoading(); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// 获取文件名称 |
|
||||
const getFileName = (name: string) => { |
|
||||
// 如果是url那么取最后的名字 如果不是直接返回 |
|
||||
if (name.lastIndexOf('/') > -1) { |
|
||||
return name.slice(name.lastIndexOf('/') + 1); |
|
||||
} else { |
|
||||
return name; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// 对象转成指定字符串分隔 |
|
||||
const listToString = (list: any[], separator?: string) => { |
|
||||
let strs = ''; |
|
||||
separator = separator || ','; |
|
||||
list.forEach((item) => { |
|
||||
if (item.ossId) { |
|
||||
strs += item.ossId + separator; |
|
||||
} |
|
||||
}); |
|
||||
return strs != '' ? strs.substring(0, strs.length - 1) : ''; |
|
||||
}; |
|
||||
</script> |
|
||||
|
|
||||
<style scoped lang="scss"> |
|
||||
.upload-file-uploader { |
|
||||
margin-bottom: 5px; |
|
||||
} |
|
||||
|
|
||||
.upload-file-list .el-upload-list__item { |
|
||||
border: 1px solid #e4e7ed; |
|
||||
line-height: 2; |
|
||||
margin-bottom: 10px; |
|
||||
position: relative; |
|
||||
} |
|
||||
|
|
||||
.upload-file-list .ele-upload-list__item-content { |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
color: inherit; |
|
||||
} |
|
||||
|
|
||||
.ele-upload-list__item-content-action .el-link { |
|
||||
margin-right: 10px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,234 +0,0 @@ |
|||||
<template> |
|
||||
<div class="component-upload-image"> |
|
||||
<el-upload |
|
||||
ref="imageUpload" |
|
||||
multiple |
|
||||
:action="uploadImgUrl" |
|
||||
list-type="picture-card" |
|
||||
:on-success="handleUploadSuccess" |
|
||||
:before-upload="handleBeforeUpload" |
|
||||
:limit="limit" |
|
||||
:on-error="handleUploadError" |
|
||||
:on-exceed="handleExceed" |
|
||||
:before-remove="handleDelete" |
|
||||
:show-file-list="true" |
|
||||
:headers="headers" |
|
||||
:file-list="fileList" |
|
||||
:on-preview="handlePictureCardPreview" |
|
||||
:class="{ hide: fileList.length >= limit }" |
|
||||
> |
|
||||
<el-icon class="avatar-uploader-icon"> |
|
||||
<plus /> |
|
||||
</el-icon> |
|
||||
</el-upload> |
|
||||
<!-- 上传提示 --> |
|
||||
<div v-if="showTip" class="el-upload__tip"> |
|
||||
请上传 |
|
||||
<template v-if="fileSize"> |
|
||||
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> |
|
||||
</template> |
|
||||
<template v-if="fileType"> |
|
||||
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> |
|
||||
</template> |
|
||||
的文件 |
|
||||
</div> |
|
||||
|
|
||||
<el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body> |
|
||||
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" /> |
|
||||
</el-dialog> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { listByIds, delOss } from '@/api/system/oss'; |
|
||||
import { OssVO } from '@/api/system/oss/types'; |
|
||||
import { propTypes } from '@/utils/propTypes'; |
|
||||
import { globalHeaders } from '@/utils/request'; |
|
||||
import { compressAccurately } from 'image-conversion'; |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
modelValue: { |
|
||||
type: [String, Object, Array], |
|
||||
default: () => [] |
|
||||
}, |
|
||||
// 图片数量限制 |
|
||||
limit: propTypes.number.def(5), |
|
||||
// 大小限制(MB) |
|
||||
fileSize: propTypes.number.def(5), |
|
||||
// 文件类型, 例如['png', 'jpg', 'jpeg'] |
|
||||
fileType: propTypes.array.def(['png', 'jpg', 'jpeg']), |
|
||||
// 是否显示提示 |
|
||||
isShowTip: { |
|
||||
type: Boolean, |
|
||||
default: true |
|
||||
}, |
|
||||
// 是否支持压缩,默认否 |
|
||||
compressSupport: { |
|
||||
type: Boolean, |
|
||||
default: false |
|
||||
}, |
|
||||
// 压缩目标大小,单位KB。默认300KB以上文件才压缩,并压缩至300KB以内 |
|
||||
compressTargetSize: propTypes.number.def(300) |
|
||||
}); |
|
||||
|
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|
||||
const emit = defineEmits(['update:modelValue']); |
|
||||
const number = ref(0); |
|
||||
const uploadList = ref<any[]>([]); |
|
||||
const dialogImageUrl = ref(''); |
|
||||
const dialogVisible = ref(false); |
|
||||
|
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API; |
|
||||
const uploadImgUrl = ref(baseUrl + '/resource/oss/upload'); // 上传的图片服务器地址 |
|
||||
const headers = ref(globalHeaders()); |
|
||||
|
|
||||
const fileList = ref<any[]>([]); |
|
||||
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize)); |
|
||||
|
|
||||
const imageUploadRef = ref<ElUploadInstance>(); |
|
||||
|
|
||||
watch( |
|
||||
() => props.modelValue, |
|
||||
async (val: string) => { |
|
||||
if (val) { |
|
||||
// 首先将值转为数组 |
|
||||
let list: OssVO[] = []; |
|
||||
if (Array.isArray(val)) { |
|
||||
list = val as OssVO[]; |
|
||||
} else { |
|
||||
const res = await listByIds(val); |
|
||||
list = res.data; |
|
||||
} |
|
||||
// 然后将数组转为对象数组 |
|
||||
fileList.value = list.map((item) => { |
|
||||
// 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来 |
|
||||
let itemData; |
|
||||
if (typeof item === 'string') { |
|
||||
itemData = { name: item, url: item }; |
|
||||
} else { |
|
||||
// 此处name使用ossId 防止删除出现重名 |
|
||||
itemData = { name: item.ossId, url: item.url, ossId: item.ossId }; |
|
||||
} |
|
||||
return itemData; |
|
||||
}); |
|
||||
} else { |
|
||||
fileList.value = []; |
|
||||
return []; |
|
||||
} |
|
||||
}, |
|
||||
{ deep: true, immediate: true } |
|
||||
); |
|
||||
|
|
||||
/** 上传前loading加载 */ |
|
||||
const handleBeforeUpload = (file: any) => { |
|
||||
let isImg = false; |
|
||||
if (props.fileType.length) { |
|
||||
let fileExtension = ''; |
|
||||
if (file.name.lastIndexOf('.') > -1) { |
|
||||
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1); |
|
||||
} |
|
||||
isImg = props.fileType.some((type: any) => { |
|
||||
if (file.type.indexOf(type) > -1) return true; |
|
||||
if (fileExtension && fileExtension.indexOf(type) > -1) return true; |
|
||||
return false; |
|
||||
}); |
|
||||
} else { |
|
||||
isImg = file.type.indexOf('image') > -1; |
|
||||
} |
|
||||
if (!isImg) { |
|
||||
proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}图片格式文件!`); |
|
||||
return false; |
|
||||
} |
|
||||
if (props.fileSize) { |
|
||||
const isLt = file.size / 1024 / 1024 < props.fileSize; |
|
||||
if (!isLt) { |
|
||||
proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//压缩图片,开启压缩并且大于指定的压缩大小时才压缩 |
|
||||
if (props.compressSupport && file.size / 1024 > props.compressTargetSize) { |
|
||||
proxy?.$modal.loading('正在上传图片,请稍候...'); |
|
||||
number.value++; |
|
||||
return compressAccurately(file, props.compressTargetSize); |
|
||||
} else { |
|
||||
proxy?.$modal.loading('正在上传图片,请稍候...'); |
|
||||
number.value++; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// 文件个数超出 |
|
||||
const handleExceed = () => { |
|
||||
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`); |
|
||||
}; |
|
||||
|
|
||||
// 上传成功回调 |
|
||||
const handleUploadSuccess = (res: any, file: UploadFile) => { |
|
||||
if (res.code === 200) { |
|
||||
uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId }); |
|
||||
uploadedSuccessfully(); |
|
||||
} else { |
|
||||
number.value--; |
|
||||
proxy?.$modal.closeLoading(); |
|
||||
proxy?.$modal.msgError(res.msg); |
|
||||
imageUploadRef.value?.handleRemove(file); |
|
||||
uploadedSuccessfully(); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// 删除图片 |
|
||||
const handleDelete = (file: UploadFile): boolean => { |
|
||||
const findex = fileList.value.map((f) => f.name).indexOf(file.name); |
|
||||
if (findex > -1 && uploadList.value.length === number.value) { |
|
||||
let ossId = fileList.value[findex].ossId; |
|
||||
delOss(ossId); |
|
||||
fileList.value.splice(findex, 1); |
|
||||
emit('update:modelValue', listToString(fileList.value)); |
|
||||
return false; |
|
||||
} |
|
||||
return true; |
|
||||
}; |
|
||||
|
|
||||
// 上传结束处理 |
|
||||
const uploadedSuccessfully = () => { |
|
||||
if (number.value > 0 && uploadList.value.length === number.value) { |
|
||||
fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value); |
|
||||
uploadList.value = []; |
|
||||
number.value = 0; |
|
||||
emit('update:modelValue', listToString(fileList.value)); |
|
||||
proxy?.$modal.closeLoading(); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// 上传失败 |
|
||||
const handleUploadError = () => { |
|
||||
proxy?.$modal.msgError('上传图片失败'); |
|
||||
proxy?.$modal.closeLoading(); |
|
||||
}; |
|
||||
|
|
||||
// 预览 |
|
||||
const handlePictureCardPreview = (file: any) => { |
|
||||
dialogImageUrl.value = file.url; |
|
||||
dialogVisible.value = true; |
|
||||
}; |
|
||||
|
|
||||
// 对象转成指定字符串分隔 |
|
||||
const listToString = (list: any[], separator?: string) => { |
|
||||
let strs = ''; |
|
||||
separator = separator || ','; |
|
||||
for (let i in list) { |
|
||||
if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) { |
|
||||
strs += list[i].ossId + separator; |
|
||||
} |
|
||||
} |
|
||||
return strs != '' ? strs.substring(0, strs.length - 1) : ''; |
|
||||
}; |
|
||||
</script> |
|
||||
|
|
||||
<style scoped lang="scss"> |
|
||||
// .el-upload--picture-card 控制加号部分 |
|
||||
:deep(.hide .el-upload--picture-card) { |
|
||||
display: none; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,116 +0,0 @@ |
|||||
<template> |
|
||||
<div class="container"> |
|
||||
<el-dialog v-model="visible" draggable title="审批记录" :width="props.width" :height="props.height" :close-on-click-modal="false"> |
|
||||
<el-tabs v-model="tabActiveName" class="demo-tabs"> |
|
||||
<el-tab-pane label="流程图" name="bpmn"> |
|
||||
<BpmnView ref="bpmnViewRef"></BpmnView> |
|
||||
</el-tab-pane> |
|
||||
<el-tab-pane v-loading="loading" label="审批信息" name="info"> |
|
||||
<div> |
|
||||
<el-table :data="historyList" style="width: 100%" border fit> |
|
||||
<el-table-column type="index" label="序号" align="center" width="60"></el-table-column> |
|
||||
<el-table-column prop="name" label="任务名称" sortable align="center"></el-table-column> |
|
||||
<el-table-column prop="nickName" :show-overflow-tooltip="true" label="办理人" sortable align="center"> |
|
||||
<template #default="scope"> |
|
||||
<el-tag type="success">{{ scope.row.nickName || '无' }}</el-tag> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column label="状态" sortable align="center"> |
|
||||
<template #default="scope"> |
|
||||
<el-tag type="success">{{ scope.row.statusName }}</el-tag> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column prop="comment" label="审批意见" sortable align="center"></el-table-column> |
|
||||
<el-table-column prop="startTime" label="开始时间" sortable align="center"></el-table-column> |
|
||||
<el-table-column prop="endTime" label="结束时间" sortable align="center"></el-table-column> |
|
||||
<el-table-column prop="runDuration" label="运行时长" sortable align="center"></el-table-column> |
|
||||
<el-table-column prop="attachmentList" label="附件" sortable align="center"> |
|
||||
<template #default="scope"> |
|
||||
<el-popover v-if="scope.row.attachmentList && scope.row.attachmentList.length > 0" placement="right" :width="310" trigger="click"> |
|
||||
<template #reference> |
|
||||
<el-button style="margin-right: 16px">附件</el-button> |
|
||||
</template> |
|
||||
<el-table border :data="scope.row.attachmentList"> |
|
||||
<el-table-column prop="name" width="202" :show-overflow-tooltip="true" label="附件名称"></el-table-column> |
|
||||
<el-table-column prop="name" width="80" align="center" :show-overflow-tooltip="true" label="操作"> |
|
||||
<template #default="tool"> |
|
||||
<el-button type="text" @click="handleDownload(tool.row.contentId)">下载</el-button> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
</el-table> |
|
||||
</el-popover> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
</el-table> |
|
||||
</div> |
|
||||
</el-tab-pane> |
|
||||
</el-tabs> |
|
||||
</el-dialog> |
|
||||
</div> |
|
||||
</template> |
|
||||
<script lang="ts" setup> |
|
||||
import BpmnView from '@/components/BpmnView/index.vue'; |
|
||||
import processApi from '@/api/workflow/processInstance'; |
|
||||
import { propTypes } from '@/utils/propTypes'; |
|
||||
|
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
width: propTypes.string.def('70%'), |
|
||||
height: propTypes.string.def('100%') |
|
||||
}); |
|
||||
const loading = ref(false); |
|
||||
const visible = ref(false); |
|
||||
const historyList = ref<Array<any>>([]); |
|
||||
const tabActiveName = ref('bpmn'); |
|
||||
|
|
||||
const bpmnViewRef = ref<BpmnView>(); |
|
||||
|
|
||||
//初始化查询审批记录 |
|
||||
const init = async (businessKey: string | number) => { |
|
||||
visible.value = true; |
|
||||
loading.value = true; |
|
||||
tabActiveName.value = 'bpmn'; |
|
||||
historyList.value = []; |
|
||||
processApi.getHistoryRecord(businessKey).then((resp) => { |
|
||||
historyList.value = resp.data; |
|
||||
loading.value = false; |
|
||||
}); |
|
||||
await nextTick(() => { |
|
||||
bpmnViewRef.value.init(businessKey); |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
/** 下载按钮操作 */ |
|
||||
const handleDownload = (ossId: string) => { |
|
||||
proxy?.$download.oss(ossId); |
|
||||
}; |
|
||||
/** |
|
||||
* 对外暴露子组件方法 |
|
||||
*/ |
|
||||
defineExpose({ |
|
||||
init |
|
||||
}); |
|
||||
</script> |
|
||||
<style lang="scss" scoped> |
|
||||
.triangle { |
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); |
|
||||
border-radius: 6px; |
|
||||
} |
|
||||
|
|
||||
.triangle::after { |
|
||||
content: ' '; |
|
||||
position: absolute; |
|
||||
top: 8em; |
|
||||
right: 215px; |
|
||||
border: 15px solid; |
|
||||
border-color: transparent #fff transparent transparent; |
|
||||
} |
|
||||
|
|
||||
.container { |
|
||||
:deep(.el-dialog .el-dialog__body) { |
|
||||
max-height: calc(100vh - 170px) !important; |
|
||||
min-height: calc(100vh - 170px) !important; |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,378 +0,0 @@ |
|||||
<template> |
|
||||
<el-dialog v-model="visible" draggable :title="title" :width="width" :height="height" append-to-body :close-on-click-modal="false"> |
|
||||
<div v-if="multiInstance === 'add'" class="p-2"> |
|
||||
<el-row :gutter="20"> |
|
||||
<!-- 部门树 --> |
|
||||
<el-col :lg="4" :xs="24" style=""> |
|
||||
<el-card shadow="hover"> |
|
||||
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable /> |
|
||||
<el-tree |
|
||||
ref="deptTreeRef" |
|
||||
class="mt-2" |
|
||||
node-key="id" |
|
||||
:data="deptOptions" |
|
||||
:props="{ label: 'label', children: 'children' }" |
|
||||
:expand-on-click-node="false" |
|
||||
:filter-node-method="filterNode" |
|
||||
highlight-current |
|
||||
default-expand-all |
|
||||
@node-click="handleNodeClick" |
|
||||
></el-tree> |
|
||||
</el-card> |
|
||||
</el-col> |
|
||||
<el-col :lg="20" :xs="24"> |
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> |
|
||||
<div v-show="showSearch" class="search"> |
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> |
|
||||
<el-form-item label="用户名称" prop="userName"> |
|
||||
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="手机号码" prop="phonenumber"> |
|
||||
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item> |
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> |
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
</div> |
|
||||
</transition> |
|
||||
|
|
||||
<el-card shadow="hover"> |
|
||||
<template #header> |
|
||||
<el-row :gutter="10"> |
|
||||
<right-toolbar v-model:showSearch="showSearch" :search="true" @query-table="handleQuery"></right-toolbar> |
|
||||
</el-row> |
|
||||
</template> |
|
||||
|
|
||||
<el-table ref="multipleTableRef" v-loading="loading" :data="userList" row-key="userId" @selection-change="handleSelectionChange"> |
|
||||
<el-table-column type="selection" width="50" align="center" /> |
|
||||
<el-table-column key="userId" label="用户编号" align="center" prop="userId" /> |
|
||||
<el-table-column key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" /> |
|
||||
<el-table-column key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" /> |
|
||||
<el-table-column key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" /> |
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160"> |
|
||||
<template #default="scope"> |
|
||||
<span>{{ scope.row.createTime }}</span> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
</el-table> |
|
||||
|
|
||||
<pagination |
|
||||
v-show="total > 0" |
|
||||
v-model:page="queryParams.pageNum" |
|
||||
v-model:limit="queryParams.pageSize" |
|
||||
:total="total" |
|
||||
@pagination="handleQuery" |
|
||||
/> |
|
||||
</el-card> |
|
||||
<el-card shadow="hover"> |
|
||||
<el-tag v-for="(user, index) in chooseUserList" :key="user.userId" style="margin: 2px" closable @close="handleCloseTag(user, index)" |
|
||||
>{{ user.userName }} |
|
||||
</el-tag> |
|
||||
</el-card> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</div> |
|
||||
<div v-if="multiInstance === 'delete'" class="p-2"> |
|
||||
<el-table v-loading="loading" :data="taskList" @selection-change="handleTaskSelection"> |
|
||||
<el-table-column type="selection" width="55" /> |
|
||||
<el-table-column prop="name" label="任务名称" /> |
|
||||
<el-table-column prop="assigneeName" label="办理人" /> |
|
||||
</el-table> |
|
||||
</div> |
|
||||
<template #footer> |
|
||||
<div class="dialog-footer"> |
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button> |
|
||||
<el-button @click="visible = false">取 消</el-button> |
|
||||
</div> |
|
||||
</template> |
|
||||
</el-dialog> |
|
||||
</template> |
|
||||
|
|
||||
<script setup name="User" lang="ts"> |
|
||||
import { deptTreeSelect, listUser, optionSelect } from '@/api/system/user'; |
|
||||
import { |
|
||||
addMultiInstanceExecution, |
|
||||
deleteMultiInstanceExecution, |
|
||||
getTaskUserIdsByAddMultiInstance, |
|
||||
getListByDeleteMultiInstance |
|
||||
} from '@/api/workflow/task'; |
|
||||
import { UserVO } from '@/api/system/user/types'; |
|
||||
import { DeptVO } from '@/api/system/dept/types'; |
|
||||
import { ComponentInternalInstance } from 'vue'; |
|
||||
import { ElTree, ElTable } from 'element-plus'; |
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
// 宽 |
|
||||
width: { |
|
||||
type: String, |
|
||||
default: '70%' |
|
||||
}, |
|
||||
// 高 |
|
||||
height: { |
|
||||
type: String, |
|
||||
default: '100%' |
|
||||
}, |
|
||||
// 标题 |
|
||||
title: { |
|
||||
type: String, |
|
||||
default: '加签人员' |
|
||||
}, |
|
||||
//是否多选 |
|
||||
multiple: { |
|
||||
type: Boolean, |
|
||||
default: true |
|
||||
}, |
|
||||
//回显用户id |
|
||||
userIdList: { |
|
||||
type: Array, |
|
||||
default: () => [] |
|
||||
} |
|
||||
}); |
|
||||
const deptTreeRef = ref(ElTree); |
|
||||
const multipleTableRef = ref(ElTable); |
|
||||
|
|
||||
const userList = ref<UserVO[]>(); |
|
||||
const taskList = ref<Array<any>[]>(); |
|
||||
const loading = ref(true); |
|
||||
const showSearch = ref(true); |
|
||||
const selectionTask = ref<Array<any>[]>(); |
|
||||
const visible = ref(false); |
|
||||
const total = ref(0); |
|
||||
const deptName = ref(''); |
|
||||
const deptOptions = ref<DeptVO[]>([]); |
|
||||
const chooseUserList = ref(ref<UserVO[]>()); |
|
||||
const userIds = ref<Array<number | string>>([]); |
|
||||
//加签或者减签 |
|
||||
const multiInstance = ref(''); |
|
||||
const queryParams = ref<Record<string, any>>({ |
|
||||
pageNum: 1, |
|
||||
pageSize: 10, |
|
||||
userName: '', |
|
||||
nickName: '', |
|
||||
taskId: '' |
|
||||
}); |
|
||||
/** 查询用户列表 */ |
|
||||
const getAddMultiInstanceList = async (taskId: string, userIdList: Array<number | string>) => { |
|
||||
deptOptions.value = []; |
|
||||
getTreeSelect(); |
|
||||
multiInstance.value = 'add'; |
|
||||
userIds.value = userIdList; |
|
||||
visible.value = true; |
|
||||
queryParams.value.taskId = taskId; |
|
||||
loading.value = true; |
|
||||
const res1 = await getTaskUserIdsByAddMultiInstance(taskId); |
|
||||
queryParams.value.excludeUserIds = res1.data; |
|
||||
const res = await listUser(queryParams.value); |
|
||||
loading.value = false; |
|
||||
userList.value = res.rows; |
|
||||
total.value = res.total; |
|
||||
if (userList.value && userIds.value.length > 0) { |
|
||||
const data = await optionSelect(userIds.value); |
|
||||
if (data.data && data.data.length > 0) { |
|
||||
chooseUserList.value = data.data; |
|
||||
data.data.forEach((user: UserVO) => { |
|
||||
multipleTableRef.value!.toggleRowSelection( |
|
||||
userList.value.find((item) => { |
|
||||
return item.userId == user.userId; |
|
||||
}), |
|
||||
true |
|
||||
); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const getList = async () => { |
|
||||
loading.value = true; |
|
||||
const res1 = await getTaskUserIdsByAddMultiInstance(queryParams.value.taskId); |
|
||||
queryParams.value.excludeUserIds = res1.data; |
|
||||
const res = await listUser(queryParams.value); |
|
||||
loading.value = false; |
|
||||
userList.value = res.rows; |
|
||||
total.value = res.total; |
|
||||
if (userList.value && userIds.value.length > 0) { |
|
||||
const data = await optionSelect(userIds.value); |
|
||||
if (data.data && data.data.length > 0) { |
|
||||
chooseUserList.value = data.data; |
|
||||
data.data.forEach((user: UserVO) => { |
|
||||
multipleTableRef.value!.toggleRowSelection( |
|
||||
userList.value.find((item) => { |
|
||||
return item.userId == user.userId; |
|
||||
}), |
|
||||
true |
|
||||
); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const getDeleteMultiInstanceList = async (taskId: string) => { |
|
||||
deptOptions.value = []; |
|
||||
loading.value = true; |
|
||||
queryParams.value.taskId = taskId; |
|
||||
multiInstance.value = 'delete'; |
|
||||
visible.value = true; |
|
||||
const res = await getListByDeleteMultiInstance(taskId); |
|
||||
taskList.value = res.data; |
|
||||
loading.value = false; |
|
||||
}; |
|
||||
/** 搜索按钮操作 */ |
|
||||
const handleQuery = () => { |
|
||||
queryParams.value.pageNum = 1; |
|
||||
getAddMultiInstanceList(queryParams.value.taskId, userIds.value); |
|
||||
}; |
|
||||
|
|
||||
/** 重置按钮操作 */ |
|
||||
const resetQuery = () => { |
|
||||
queryParams.value.pageNum = 1; |
|
||||
queryParams.value.deptId = undefined; |
|
||||
queryParams.value.userName = undefined; |
|
||||
queryParams.value.nickName = undefined; |
|
||||
deptTreeRef.value.setCurrentKey(null); |
|
||||
handleQuery(); |
|
||||
}; |
|
||||
|
|
||||
/** 选择条数 */ |
|
||||
const handleSelectionChange = (selection: UserVO[]) => { |
|
||||
if (props.multiple) { |
|
||||
chooseUserList.value = selection.filter((element, index, self) => { |
|
||||
return self.findIndex((x) => x.userId === element.userId) === index; |
|
||||
}); |
|
||||
selection.forEach((u) => { |
|
||||
if (chooseUserList.value && !chooseUserList.value.includes(u)) { |
|
||||
multipleTableRef.value!.toggleRowSelection(u, undefined); |
|
||||
} |
|
||||
}); |
|
||||
userIds.value = chooseUserList.value.map((item) => { |
|
||||
return item.userId; |
|
||||
}); |
|
||||
} else { |
|
||||
chooseUserList.value = selection; |
|
||||
if (selection.length > 1) { |
|
||||
let delRow = selection.shift(); |
|
||||
multipleTableRef.value!.toggleRowSelection(delRow, undefined); |
|
||||
} |
|
||||
if (selection.length === 0) { |
|
||||
chooseUserList.value = []; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
/** 选择条数 */ |
|
||||
const handleTaskSelection = (selection: any) => { |
|
||||
selectionTask.value = selection; |
|
||||
}; |
|
||||
|
|
||||
/** 查询部门下拉树结构 */ |
|
||||
const getTreeSelect = async () => { |
|
||||
const res = await deptTreeSelect(); |
|
||||
deptOptions.value = res.data; |
|
||||
}; |
|
||||
|
|
||||
/** 通过条件过滤节点 */ |
|
||||
const filterNode = (value: string, data: any) => { |
|
||||
if (!value) return true; |
|
||||
return data.label.indexOf(value) !== -1; |
|
||||
}; |
|
||||
/** 根据名称筛选部门树 */ |
|
||||
watchEffect( |
|
||||
() => { |
|
||||
if (visible.value && deptOptions.value && deptOptions.value.length > 0) { |
|
||||
deptTreeRef.value.filter(deptName.value); |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行 |
|
||||
} |
|
||||
); |
|
||||
/** 节点单击事件 */ |
|
||||
const handleNodeClick = (data: DeptVO) => { |
|
||||
queryParams.value.deptId = data.id; |
|
||||
getList(); |
|
||||
}; |
|
||||
//删除tag |
|
||||
const handleCloseTag = (user: UserVO, index: any) => { |
|
||||
if (multipleTableRef.value.selection && multipleTableRef.value.selection.length > 0) { |
|
||||
multipleTableRef.value.selection.forEach((u: UserVO, i: number) => { |
|
||||
if (user.userId === u.userId) { |
|
||||
multipleTableRef.value.selection.splice(i, 1); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
if (chooseUserList.value && chooseUserList.value.length > 0) { |
|
||||
chooseUserList.value.splice(index, 1); |
|
||||
} |
|
||||
multipleTableRef.value.toggleRowSelection(user, undefined); |
|
||||
|
|
||||
if (userIds.value && userIds.value.length > 0) { |
|
||||
userIds.value.forEach((userId, i) => { |
|
||||
if (userId === user.userId) { |
|
||||
userIds.value.splice(i, 1); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
}; |
|
||||
const submitFileForm = async () => { |
|
||||
if (multiInstance.value === 'add') { |
|
||||
if (chooseUserList.value && chooseUserList.value.length > 0) { |
|
||||
loading.value = true; |
|
||||
let userIds = chooseUserList.value.map((item) => { |
|
||||
return item.userId; |
|
||||
}); |
|
||||
let nickNames = chooseUserList.value.map((item) => { |
|
||||
return item.nickName; |
|
||||
}); |
|
||||
let params = { |
|
||||
taskId: queryParams.value.taskId, |
|
||||
assignees: userIds, |
|
||||
assigneeNames: nickNames |
|
||||
}; |
|
||||
await addMultiInstanceExecution(params); |
|
||||
emits('submitCallback'); |
|
||||
loading.value = false; |
|
||||
proxy?.$modal.msgSuccess('操作成功'); |
|
||||
visible.value = false; |
|
||||
} |
|
||||
} else { |
|
||||
if (selectionTask.value && selectionTask.value.length > 0) { |
|
||||
loading.value = true; |
|
||||
let taskIds = selectionTask.value.map((item: any) => { |
|
||||
return item.id; |
|
||||
}); |
|
||||
let executionIds = selectionTask.value.map((item: any) => { |
|
||||
return item.executionId; |
|
||||
}); |
|
||||
let assigneeIds = selectionTask.value.map((item: any) => { |
|
||||
return item.assignee; |
|
||||
}); |
|
||||
let assigneeNames = selectionTask.value.map((item: any) => { |
|
||||
return item.assigneeName; |
|
||||
}); |
|
||||
let params = { |
|
||||
taskId: queryParams.value.taskId, |
|
||||
taskIds: taskIds, |
|
||||
executionIds: executionIds, |
|
||||
assigneeIds: assigneeIds, |
|
||||
assigneeNames: assigneeNames |
|
||||
}; |
|
||||
await deleteMultiInstanceExecution(params); |
|
||||
emits('submitCallback'); |
|
||||
loading.value = false; |
|
||||
proxy?.$modal.msgSuccess('操作成功'); |
|
||||
visible.value = false; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
//事件 |
|
||||
const emits = defineEmits(['submitCallback']); |
|
||||
|
|
||||
/** |
|
||||
* 对外暴露子组件方法 |
|
||||
*/ |
|
||||
defineExpose({ |
|
||||
getAddMultiInstanceList, |
|
||||
getDeleteMultiInstanceList |
|
||||
}); |
|
||||
</script> |
|
||||
@ -1,366 +0,0 @@ |
|||||
<template> |
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="50%" draggable :before-close="cancel" center :close-on-click-modal="false"> |
|
||||
<el-form v-loading="loading" :model="form" label-width="120px"> |
|
||||
<el-form-item label="消息提醒"> |
|
||||
<el-checkbox-group v-model="form.messageType"> |
|
||||
<el-checkbox label="1" name="type" disabled>站内信</el-checkbox> |
|
||||
<el-checkbox label="2" name="type">邮件</el-checkbox> |
|
||||
<el-checkbox label="3" name="type">短信</el-checkbox> |
|
||||
</el-checkbox-group> |
|
||||
</el-form-item> |
|
||||
<el-form-item v-if="task.businessStatus === 'waiting'" label="附件"> |
|
||||
<fileUpload v-model="form.fileId" :file-type="['doc', 'xls', 'ppt', 'txt', 'pdf', 'xlsx', 'docx', 'zip']" :file-size="'20'" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="抄送"> |
|
||||
<el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" /> |
|
||||
<el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)"> |
|
||||
{{ user.userName }} |
|
||||
</el-tag> |
|
||||
</el-form-item> |
|
||||
<el-form-item v-if="task.businessStatus === 'waiting'" label="审批意见"> |
|
||||
<el-input v-model="form.message" type="textarea" resize="none" /> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
<template #footer> |
|
||||
<span class="dialog-footer"> |
|
||||
<el-button :disabled="buttonDisabled" type="primary" @click="handleCompleteTask"> 提交 </el-button> |
|
||||
<el-button v-if="task.businessStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openDelegateTask"> 委托 </el-button> |
|
||||
<el-button v-if="task.businessStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openTransferTask"> 转办 </el-button> |
|
||||
<el-button |
|
||||
v-if="task.businessStatus === 'waiting' && task.multiInstance" |
|
||||
:disabled="buttonDisabled" |
|
||||
type="primary" |
|
||||
@click="addMultiInstanceUser" |
|
||||
> |
|
||||
加签 |
|
||||
</el-button> |
|
||||
<el-button |
|
||||
v-if="task.businessStatus === 'waiting' && task.multiInstance" |
|
||||
:disabled="buttonDisabled" |
|
||||
type="primary" |
|
||||
@click="deleteMultiInstanceUser" |
|
||||
> |
|
||||
减签 |
|
||||
</el-button> |
|
||||
<el-button v-if="task.businessStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleTerminationTask"> 终止 </el-button> |
|
||||
<el-button v-if="task.businessStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleBackProcessOpen"> 退回 </el-button> |
|
||||
<el-button :disabled="buttonDisabled" @click="cancel">取消</el-button> |
|
||||
</span> |
|
||||
</template> |
|
||||
<!-- 抄送 --> |
|
||||
<UserSelect ref="userSelectCopyRef" :multiple="true" :data="selectCopyUserIds" @confirm-call-back="userSelectCopyCallBack"></UserSelect> |
|
||||
<!-- 转办 --> |
|
||||
<UserSelect ref="transferTaskRef" :multiple="false" @confirm-call-back="handleTransferTask"></UserSelect> |
|
||||
<!-- 委托 --> |
|
||||
<UserSelect ref="delegateTaskRef" :multiple="false" @confirm-call-back="handleDelegateTask"></UserSelect> |
|
||||
<!-- 加签组件 --> |
|
||||
<multiInstanceUser ref="multiInstanceUserRef" :title="title" @submit-callback="closeDialog" /> |
|
||||
|
|
||||
<!-- 驳回开始 --> |
|
||||
<el-dialog v-model="backVisible" draggable title="驳回" width="40%" :close-on-click-modal="false"> |
|
||||
<el-form v-if="task.businessStatus === 'waiting'" v-loading="backLoading" :model="backForm" label-width="120px"> |
|
||||
<el-form-item label="驳回节点"> |
|
||||
<el-select v-model="backForm.targetActivityId" clearable placeholder="请选择" style="width: 300px"> |
|
||||
<el-option v-for="item in taskNodeList" :key="item.nodeId" :label="item.nodeName" :value="item.nodeId" /> |
|
||||
</el-select> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="消息提醒"> |
|
||||
<el-checkbox-group v-model="backForm.messageType"> |
|
||||
<el-checkbox label="1" name="type" disabled>站内信</el-checkbox> |
|
||||
<el-checkbox label="2" name="type">邮件</el-checkbox> |
|
||||
<el-checkbox label="3" name="type">短信</el-checkbox> |
|
||||
</el-checkbox-group> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="审批意见"> |
|
||||
<el-input v-model="backForm.message" type="textarea" resize="none" /> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
<template #footer> |
|
||||
<div class="dialog-footer" style="float: right; padding-bottom: 20px"> |
|
||||
<el-button :disabled="backButtonDisabled" type="primary" @click="handleBackProcess">确认</el-button> |
|
||||
<el-button :disabled="backButtonDisabled" @click="backVisible = false">取消</el-button> |
|
||||
</div> |
|
||||
</template> |
|
||||
</el-dialog> |
|
||||
<!-- 驳回结束 --> |
|
||||
</el-dialog> |
|
||||
</template> |
|
||||
|
|
||||
<script lang="ts" setup> |
|
||||
import { ref } from 'vue'; |
|
||||
import { ComponentInternalInstance } from 'vue'; |
|
||||
import { ElForm } from 'element-plus'; |
|
||||
import { completeTask, backProcess, getTaskById, transferTask, terminationTask, getTaskNodeList, delegateTask } from '@/api/workflow/task'; |
|
||||
import UserSelect from '@/components/UserSelect'; |
|
||||
import MultiInstanceUser from '@/components/Process/multiInstanceUser.vue'; |
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|
||||
import { UserVO } from '@/api/system/user/types'; |
|
||||
import { TaskVO } from '@/api/workflow/task/types'; |
|
||||
const userSelectCopyRef = ref<InstanceType<typeof UserSelect>>(); |
|
||||
const transferTaskRef = ref<InstanceType<typeof UserSelect>>(); |
|
||||
const delegateTaskRef = ref<InstanceType<typeof UserSelect>>(); |
|
||||
|
|
||||
//加签组件 |
|
||||
const multiInstanceUserRef = ref<InstanceType<typeof MultiInstanceUser>>(); |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
taskVariables: { |
|
||||
type: Object as () => Record<string, any>, |
|
||||
default: () => {} |
|
||||
} |
|
||||
}); |
|
||||
//遮罩层 |
|
||||
const loading = ref(true); |
|
||||
//按钮 |
|
||||
const buttonDisabled = ref(true); |
|
||||
//任务id |
|
||||
const taskId = ref<string>(''); |
|
||||
//抄送人 |
|
||||
const selectCopyUserList = ref<UserVO[]>([]); |
|
||||
//抄送人id |
|
||||
const selectCopyUserIds = ref<string>(undefined); |
|
||||
// 驳回是否显示 |
|
||||
const backVisible = ref(false); |
|
||||
const backLoading = ref(true); |
|
||||
const backButtonDisabled = ref(true); |
|
||||
// 可驳回得任务节点 |
|
||||
const taskNodeList = ref([]); |
|
||||
//任务 |
|
||||
const task = ref<TaskVO>({ |
|
||||
id: undefined, |
|
||||
name: undefined, |
|
||||
description: undefined, |
|
||||
priority: undefined, |
|
||||
owner: undefined, |
|
||||
assignee: undefined, |
|
||||
assigneeName: undefined, |
|
||||
processInstanceId: undefined, |
|
||||
executionId: undefined, |
|
||||
taskDefinitionId: undefined, |
|
||||
processDefinitionId: undefined, |
|
||||
endTime: undefined, |
|
||||
taskDefinitionKey: undefined, |
|
||||
dueDate: undefined, |
|
||||
category: undefined, |
|
||||
parentTaskId: undefined, |
|
||||
tenantId: undefined, |
|
||||
claimTime: undefined, |
|
||||
businessStatus: undefined, |
|
||||
businessStatusName: undefined, |
|
||||
processDefinitionName: undefined, |
|
||||
processDefinitionKey: undefined, |
|
||||
participantVo: undefined, |
|
||||
multiInstance: undefined, |
|
||||
businessKey: undefined, |
|
||||
wfNodeConfigVo: undefined |
|
||||
}); |
|
||||
//加签 减签标题 |
|
||||
const title = ref(''); |
|
||||
const dialog = reactive<DialogOption>({ |
|
||||
visible: false, |
|
||||
title: '提示' |
|
||||
}); |
|
||||
|
|
||||
const form = ref<Record<string, any>>({ |
|
||||
taskId: undefined, |
|
||||
message: undefined, |
|
||||
variables: {}, |
|
||||
messageType: ['1'], |
|
||||
wfCopyList: [] |
|
||||
}); |
|
||||
const backForm = ref<Record<string, any>>({ |
|
||||
taskId: undefined, |
|
||||
targetActivityId: undefined, |
|
||||
message: undefined, |
|
||||
variables: {}, |
|
||||
messageType: ['1'] |
|
||||
}); |
|
||||
const closeDialog = () => { |
|
||||
dialog.visible = false; |
|
||||
}; |
|
||||
//打开弹窗 |
|
||||
const openDialog = (id?: string) => { |
|
||||
selectCopyUserIds.value = undefined; |
|
||||
selectCopyUserList.value = []; |
|
||||
form.value.fileId = undefined; |
|
||||
taskId.value = id; |
|
||||
form.value.message = undefined; |
|
||||
dialog.visible = true; |
|
||||
loading.value = true; |
|
||||
buttonDisabled.value = true; |
|
||||
nextTick(() => { |
|
||||
getTaskById(taskId.value).then((response) => { |
|
||||
task.value = response.data; |
|
||||
loading.value = false; |
|
||||
buttonDisabled.value = false; |
|
||||
}); |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
onMounted(() => {}); |
|
||||
const emits = defineEmits(['submitCallback', 'cancelCallback']); |
|
||||
|
|
||||
/** 办理流程 */ |
|
||||
const handleCompleteTask = async () => { |
|
||||
form.value.taskId = taskId.value; |
|
||||
form.value.taskVariables = props.taskVariables; |
|
||||
if (selectCopyUserList.value && selectCopyUserList.value.length > 0) { |
|
||||
let wfCopyList = []; |
|
||||
selectCopyUserList.value.forEach((e) => { |
|
||||
let copyUser = { |
|
||||
userId: e.userId, |
|
||||
userName: e.nickName |
|
||||
}; |
|
||||
wfCopyList.push(copyUser); |
|
||||
}); |
|
||||
form.value.wfCopyList = wfCopyList; |
|
||||
} |
|
||||
await proxy?.$modal.confirm('是否确认提交?'); |
|
||||
loading.value = true; |
|
||||
buttonDisabled.value = true; |
|
||||
try { |
|
||||
await completeTask(form.value); |
|
||||
dialog.visible = false; |
|
||||
emits('submitCallback'); |
|
||||
proxy?.$modal.msgSuccess('操作成功'); |
|
||||
} finally { |
|
||||
loading.value = false; |
|
||||
buttonDisabled.value = false; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/** 驳回弹窗打开 */ |
|
||||
const handleBackProcessOpen = async () => { |
|
||||
backForm.value = {}; |
|
||||
backForm.value.messageType = ['1']; |
|
||||
backVisible.value = true; |
|
||||
backLoading.value = true; |
|
||||
backButtonDisabled.value = true; |
|
||||
let data = await getTaskNodeList(task.value.processInstanceId); |
|
||||
taskNodeList.value = data.data; |
|
||||
backLoading.value = false; |
|
||||
backButtonDisabled.value = false; |
|
||||
backForm.value.targetActivityId = taskNodeList.value[0].nodeId; |
|
||||
}; |
|
||||
/** 驳回流程 */ |
|
||||
const handleBackProcess = async () => { |
|
||||
backForm.value.taskId = taskId.value; |
|
||||
await proxy?.$modal.confirm('是否确认驳回到申请人?'); |
|
||||
loading.value = true; |
|
||||
backLoading.value = true; |
|
||||
backButtonDisabled.value = true; |
|
||||
await backProcess(backForm.value).finally(() => (loading.value = false)); |
|
||||
dialog.visible = false; |
|
||||
backLoading.value = false; |
|
||||
backButtonDisabled.value = false; |
|
||||
emits('submitCallback'); |
|
||||
proxy?.$modal.msgSuccess('操作成功'); |
|
||||
}; |
|
||||
//取消 |
|
||||
const cancel = async () => { |
|
||||
dialog.visible = false; |
|
||||
buttonDisabled.value = false; |
|
||||
emits('cancelCallback'); |
|
||||
}; |
|
||||
//打开抄送人员 |
|
||||
const openUserSelectCopy = () => { |
|
||||
userSelectCopyRef.value.open(); |
|
||||
}; |
|
||||
//确认抄送人员 |
|
||||
const userSelectCopyCallBack = (data: UserVO[]) => { |
|
||||
if (data && data.length > 0) { |
|
||||
selectCopyUserList.value = data; |
|
||||
selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(','); |
|
||||
} |
|
||||
}; |
|
||||
//删除抄送人员 |
|
||||
const handleCopyCloseTag = (user: UserVO) => { |
|
||||
const userId = user.userId; |
|
||||
// 使用split删除用户 |
|
||||
const index = selectCopyUserList.value.findIndex((item) => item.userId === userId); |
|
||||
selectCopyUserList.value.splice(index, 1); |
|
||||
selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(','); |
|
||||
}; |
|
||||
//加签 |
|
||||
const addMultiInstanceUser = () => { |
|
||||
if (multiInstanceUserRef.value) { |
|
||||
title.value = '加签人员'; |
|
||||
multiInstanceUserRef.value.getAddMultiInstanceList(taskId.value, []); |
|
||||
} |
|
||||
}; |
|
||||
//减签 |
|
||||
const deleteMultiInstanceUser = () => { |
|
||||
if (multiInstanceUserRef.value) { |
|
||||
title.value = '减签人员'; |
|
||||
multiInstanceUserRef.value.getDeleteMultiInstanceList(taskId.value); |
|
||||
} |
|
||||
}; |
|
||||
//打开转办 |
|
||||
const openTransferTask = () => { |
|
||||
transferTaskRef.value.open(); |
|
||||
}; |
|
||||
//转办 |
|
||||
const handleTransferTask = async (data) => { |
|
||||
if (data && data.length > 0) { |
|
||||
let params = { |
|
||||
taskId: taskId.value, |
|
||||
userId: data[0].userId, |
|
||||
comment: form.value.message |
|
||||
}; |
|
||||
await proxy?.$modal.confirm('是否确认提交?'); |
|
||||
loading.value = true; |
|
||||
buttonDisabled.value = true; |
|
||||
await transferTask(params).finally(() => (loading.value = false)); |
|
||||
dialog.visible = false; |
|
||||
emits('submitCallback'); |
|
||||
proxy?.$modal.msgSuccess('操作成功'); |
|
||||
} else { |
|
||||
proxy?.$modal.msgWarning('请选择用户!'); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
//打开委托 |
|
||||
const openDelegateTask = () => { |
|
||||
delegateTaskRef.value.open(); |
|
||||
}; |
|
||||
//委托 |
|
||||
const handleDelegateTask = async (data) => { |
|
||||
if (data && data.length > 0) { |
|
||||
let params = { |
|
||||
taskId: taskId.value, |
|
||||
userId: data[0].userId, |
|
||||
nickName: data[0].nickName |
|
||||
}; |
|
||||
await proxy?.$modal.confirm('是否确认提交?'); |
|
||||
loading.value = true; |
|
||||
buttonDisabled.value = true; |
|
||||
await delegateTask(params).finally(() => (loading.value = false)); |
|
||||
dialog.visible = false; |
|
||||
emits('submitCallback'); |
|
||||
proxy?.$modal.msgSuccess('操作成功'); |
|
||||
} else { |
|
||||
proxy?.$modal.msgWarning('请选择用户!'); |
|
||||
} |
|
||||
}; |
|
||||
//终止任务 |
|
||||
const handleTerminationTask = async (data) => { |
|
||||
let params = { |
|
||||
taskId: taskId.value, |
|
||||
comment: form.value.message |
|
||||
}; |
|
||||
await proxy?.$modal.confirm('是否确认终止?'); |
|
||||
loading.value = true; |
|
||||
buttonDisabled.value = true; |
|
||||
await terminationTask(params).finally(() => (loading.value = false)); |
|
||||
dialog.visible = false; |
|
||||
emits('submitCallback'); |
|
||||
proxy?.$modal.msgSuccess('操作成功'); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* 对外暴露子组件方法 |
|
||||
*/ |
|
||||
defineExpose({ |
|
||||
openDialog |
|
||||
}); |
|
||||
</script> |
|
||||
@ -1,339 +0,0 @@ |
|||||
<template> |
|
||||
<div class="p-2"> |
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> |
|
||||
<div v-show="showSearch" class="mb-[10px]"> |
|
||||
<el-card shadow="hover"> |
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> |
|
||||
<el-form-item label="群id" prop="groupId"> |
|
||||
<el-input v-model="queryParams.groupId" placeholder="请输入群id" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="用户id" prop="userId"> |
|
||||
<el-input v-model="queryParams.userId" placeholder="请输入用户id" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="组内显示名称" prop="remarkNickName"> |
|
||||
<el-input v-model="queryParams.remarkNickName" placeholder="请输入组内显示名称" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="群名备注" prop="remarkGroupName"> |
|
||||
<el-input v-model="queryParams.remarkGroupName" placeholder="请输入群名备注" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="是否已退出" prop="quit"> |
|
||||
<el-input v-model="queryParams.quit" placeholder="请输入是否已退出" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="创建时间" prop="createdTime"> |
|
||||
<el-date-picker clearable |
|
||||
v-model="queryParams.createdTime" |
|
||||
type="date" |
|
||||
value-format="YYYY-MM-DD" |
|
||||
placeholder="请选择创建时间" |
|
||||
/> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="退出时间" prop="quitTime"> |
|
||||
<el-date-picker clearable |
|
||||
v-model="queryParams.quitTime" |
|
||||
type="date" |
|
||||
value-format="YYYY-MM-DD" |
|
||||
placeholder="请选择退出时间" |
|
||||
/> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="用户昵称" prop="userNickName"> |
|
||||
<el-input v-model="queryParams.userNickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item> |
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> |
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
</el-card> |
|
||||
</div> |
|
||||
</transition> |
|
||||
|
|
||||
<el-card shadow="never"> |
|
||||
<template #header> |
|
||||
<el-row :gutter="10" class="mb8"> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['im:groupMember:add']">新增</el-button> |
|
||||
</el-col> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['im:groupMember:edit']">修改</el-button> |
|
||||
</el-col> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['im:groupMember:remove']">删除</el-button> |
|
||||
</el-col> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['im:groupMember:export']">导出</el-button> |
|
||||
</el-col> |
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> |
|
||||
</el-row> |
|
||||
</template> |
|
||||
|
|
||||
<el-table v-loading="loading" :data="groupMemberList" @selection-change="handleSelectionChange"> |
|
||||
<el-table-column type="selection" width="55" align="center" /> |
|
||||
<el-table-column label="id" align="center" prop="id" v-if="true" /> |
|
||||
<el-table-column label="群id" align="center" prop="groupId" /> |
|
||||
<el-table-column label="用户id" align="center" prop="userId" /> |
|
||||
<el-table-column label="组内显示名称" align="center" prop="remarkNickName" /> |
|
||||
<el-table-column label="用户头像" align="center" prop="headImageUrl" width="100"> |
|
||||
<template #default="scope"> |
|
||||
<image-preview :src="scope.row.headImageUrl" :width="50" :height="50"/> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column label="群名备注" align="center" prop="remarkGroupName" /> |
|
||||
<el-table-column label="是否已退出" align="center" prop="quit" /> |
|
||||
<el-table-column label="创建时间" align="center" prop="createdTime" width="180"> |
|
||||
<template #default="scope"> |
|
||||
<span>{{ parseTime(scope.row.createdTime, '{y}-{m}-{d}') }}</span> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column label="退出时间" align="center" prop="quitTime" width="180"> |
|
||||
<template #default="scope"> |
|
||||
<span>{{ parseTime(scope.row.quitTime, '{y}-{m}-{d}') }}</span> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column label="用户昵称" align="center" prop="userNickName" /> |
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> |
|
||||
<template #default="scope"> |
|
||||
<el-tooltip content="修改" placement="top"> |
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['im:groupMember:edit']"></el-button> |
|
||||
</el-tooltip> |
|
||||
<el-tooltip content="删除" placement="top"> |
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['im:groupMember:remove']"></el-button> |
|
||||
</el-tooltip> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
</el-table> |
|
||||
|
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> |
|
||||
</el-card> |
|
||||
<!-- 添加或修改群成员对话框 --> |
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body> |
|
||||
<el-form ref="groupMemberFormRef" :model="form" :rules="rules" label-width="80px"> |
|
||||
<el-form-item label="群id" prop="groupId"> |
|
||||
<el-input v-model="form.groupId" placeholder="请输入群id" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="用户id" prop="userId"> |
|
||||
<el-input v-model="form.userId" placeholder="请输入用户id" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="组内显示名称" prop="remarkNickName"> |
|
||||
<el-input v-model="form.remarkNickName" placeholder="请输入组内显示名称" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="用户头像" prop="headImage"> |
|
||||
<image-upload v-model="form.headImage"/> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="群名备注" prop="remarkGroupName"> |
|
||||
<el-input v-model="form.remarkGroupName" placeholder="请输入群名备注" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="是否已退出" prop="quit"> |
|
||||
<el-input v-model="form.quit" placeholder="请输入是否已退出" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="创建时间" prop="createdTime"> |
|
||||
<el-date-picker clearable |
|
||||
v-model="form.createdTime" |
|
||||
type="datetime" |
|
||||
value-format="YYYY-MM-DD HH:mm:ss" |
|
||||
placeholder="请选择创建时间"> |
|
||||
</el-date-picker> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="退出时间" prop="quitTime"> |
|
||||
<el-date-picker clearable |
|
||||
v-model="form.quitTime" |
|
||||
type="datetime" |
|
||||
value-format="YYYY-MM-DD HH:mm:ss" |
|
||||
placeholder="请选择退出时间"> |
|
||||
</el-date-picker> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="用户昵称" prop="userNickName"> |
|
||||
<el-input v-model="form.userNickName" placeholder="请输入用户昵称" /> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
<template #footer> |
|
||||
<div class="dialog-footer"> |
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button> |
|
||||
<el-button @click="cancel">取 消</el-button> |
|
||||
</div> |
|
||||
</template> |
|
||||
</el-dialog> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup name="GroupMember" lang="ts"> |
|
||||
import { listGroupMember, getGroupMember, delGroupMember, addGroupMember, updateGroupMember } from '@/api/im/groupMember'; |
|
||||
import { GroupMemberVO, GroupMemberQuery, GroupMemberForm } from '@/api/im/groupMember/types'; |
|
||||
|
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|
||||
|
|
||||
const groupMemberList = ref<GroupMemberVO[]>([]); |
|
||||
const buttonLoading = ref(false); |
|
||||
const loading = ref(true); |
|
||||
const showSearch = ref(true); |
|
||||
const ids = ref<Array<string | number>>([]); |
|
||||
const single = ref(true); |
|
||||
const multiple = ref(true); |
|
||||
const total = ref(0); |
|
||||
|
|
||||
const queryFormRef = ref<ElFormInstance>(); |
|
||||
const groupMemberFormRef = ref<ElFormInstance>(); |
|
||||
|
|
||||
const dialog = reactive<DialogOption>({ |
|
||||
visible: false, |
|
||||
title: '' |
|
||||
}); |
|
||||
|
|
||||
const initFormData: GroupMemberForm = { |
|
||||
id: undefined, |
|
||||
groupId: undefined, |
|
||||
userId: undefined, |
|
||||
remarkNickName: undefined, |
|
||||
headImage: undefined, |
|
||||
remarkGroupName: undefined, |
|
||||
quit: undefined, |
|
||||
createdTime: undefined, |
|
||||
quitTime: undefined, |
|
||||
userNickName: undefined |
|
||||
} |
|
||||
const data = reactive<PageData<GroupMemberForm, GroupMemberQuery>>({ |
|
||||
form: {...initFormData}, |
|
||||
queryParams: { |
|
||||
pageNum: 1, |
|
||||
pageSize: 10, |
|
||||
groupId: undefined, |
|
||||
userId: undefined, |
|
||||
remarkNickName: undefined, |
|
||||
headImage: undefined, |
|
||||
remarkGroupName: undefined, |
|
||||
quit: undefined, |
|
||||
createdTime: undefined, |
|
||||
quitTime: undefined, |
|
||||
userNickName: undefined, |
|
||||
params: { |
|
||||
} |
|
||||
}, |
|
||||
rules: { |
|
||||
id: [ |
|
||||
{ required: true, message: "id不能为空", trigger: "blur" } |
|
||||
], |
|
||||
groupId: [ |
|
||||
{ required: true, message: "群id不能为空", trigger: "blur" } |
|
||||
], |
|
||||
userId: [ |
|
||||
{ required: true, message: "用户id不能为空", trigger: "blur" } |
|
||||
], |
|
||||
remarkNickName: [ |
|
||||
{ required: true, message: "组内显示名称不能为空", trigger: "blur" } |
|
||||
], |
|
||||
headImage: [ |
|
||||
{ required: true, message: "用户头像不能为空", trigger: "blur" } |
|
||||
], |
|
||||
remarkGroupName: [ |
|
||||
{ required: true, message: "群名备注不能为空", trigger: "blur" } |
|
||||
], |
|
||||
quit: [ |
|
||||
{ required: true, message: "是否已退出不能为空", trigger: "blur" } |
|
||||
], |
|
||||
createdTime: [ |
|
||||
{ required: true, message: "创建时间不能为空", trigger: "blur" } |
|
||||
], |
|
||||
quitTime: [ |
|
||||
{ required: true, message: "退出时间不能为空", trigger: "blur" } |
|
||||
], |
|
||||
userNickName: [ |
|
||||
{ required: true, message: "用户昵称不能为空", trigger: "blur" } |
|
||||
] |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
const { queryParams, form, rules } = toRefs(data); |
|
||||
|
|
||||
/** 查询群成员列表 */ |
|
||||
const getList = async () => { |
|
||||
loading.value = true; |
|
||||
const res = await listGroupMember(queryParams.value); |
|
||||
groupMemberList.value = res.rows; |
|
||||
total.value = res.total; |
|
||||
loading.value = false; |
|
||||
} |
|
||||
|
|
||||
/** 取消按钮 */ |
|
||||
const cancel = () => { |
|
||||
reset(); |
|
||||
dialog.visible = false; |
|
||||
} |
|
||||
|
|
||||
/** 表单重置 */ |
|
||||
const reset = () => { |
|
||||
form.value = {...initFormData}; |
|
||||
groupMemberFormRef.value?.resetFields(); |
|
||||
} |
|
||||
|
|
||||
/** 搜索按钮操作 */ |
|
||||
const handleQuery = () => { |
|
||||
queryParams.value.pageNum = 1; |
|
||||
getList(); |
|
||||
} |
|
||||
|
|
||||
/** 重置按钮操作 */ |
|
||||
const resetQuery = () => { |
|
||||
queryFormRef.value?.resetFields(); |
|
||||
handleQuery(); |
|
||||
} |
|
||||
|
|
||||
/** 多选框选中数据 */ |
|
||||
const handleSelectionChange = (selection: GroupMemberVO[]) => { |
|
||||
ids.value = selection.map(item => item.id); |
|
||||
single.value = selection.length != 1; |
|
||||
multiple.value = !selection.length; |
|
||||
} |
|
||||
|
|
||||
/** 新增按钮操作 */ |
|
||||
const handleAdd = () => { |
|
||||
reset(); |
|
||||
dialog.visible = true; |
|
||||
dialog.title = "添加群成员"; |
|
||||
} |
|
||||
|
|
||||
/** 修改按钮操作 */ |
|
||||
const handleUpdate = async (row?: GroupMemberVO) => { |
|
||||
reset(); |
|
||||
const _id = row?.id || ids.value[0] |
|
||||
const res = await getGroupMember(_id); |
|
||||
Object.assign(form.value, res.data); |
|
||||
dialog.visible = true; |
|
||||
dialog.title = "修改群成员"; |
|
||||
} |
|
||||
|
|
||||
/** 提交按钮 */ |
|
||||
const submitForm = () => { |
|
||||
groupMemberFormRef.value?.validate(async (valid: boolean) => { |
|
||||
if (valid) { |
|
||||
buttonLoading.value = true; |
|
||||
if (form.value.id) { |
|
||||
await updateGroupMember(form.value).finally(() => buttonLoading.value = false); |
|
||||
} else { |
|
||||
await addGroupMember(form.value).finally(() => buttonLoading.value = false); |
|
||||
} |
|
||||
proxy?.$modal.msgSuccess("操作成功"); |
|
||||
dialog.visible = false; |
|
||||
await getList(); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/** 删除按钮操作 */ |
|
||||
const handleDelete = async (row?: GroupMemberVO) => { |
|
||||
const _ids = row?.id || ids.value; |
|
||||
await proxy?.$modal.confirm('是否确认删除群成员编号为"' + _ids + '"的数据项?').finally(() => loading.value = false); |
|
||||
await delGroupMember(_ids); |
|
||||
proxy?.$modal.msgSuccess("删除成功"); |
|
||||
await getList(); |
|
||||
} |
|
||||
|
|
||||
/** 导出按钮操作 */ |
|
||||
const handleExport = () => { |
|
||||
proxy?.download('im/groupMember/export', { |
|
||||
...queryParams.value |
|
||||
}, `groupMember_${new Date().getTime()}.xlsx`) |
|
||||
} |
|
||||
|
|
||||
onMounted(() => { |
|
||||
getList(); |
|
||||
}); |
|
||||
</script> |
|
||||
@ -1,334 +0,0 @@ |
|||||
<template> |
|
||||
<div class="p-2"> |
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> |
|
||||
<div v-show="showSearch" class="mb-[10px]"> |
|
||||
<el-card shadow="hover"> |
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> |
|
||||
<el-form-item label="配置key" prop="configKey"> |
|
||||
<el-input v-model="queryParams.configKey" placeholder="配置key" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="桶名称" prop="bucketName"> |
|
||||
<el-input v-model="queryParams.bucketName" placeholder="请输入桶名称" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="是否默认" prop="status"> |
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable> |
|
||||
<el-option key="0" label="是" value="0" /> |
|
||||
<el-option key="1" label="否" value="1" /> |
|
||||
</el-select> |
|
||||
</el-form-item> |
|
||||
<el-form-item> |
|
||||
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button> |
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
</el-card> |
|
||||
</div> |
|
||||
</transition> |
|
||||
|
|
||||
<el-card shadow="hover"> |
|
||||
<template #header> |
|
||||
<el-row :gutter="10" class="mb8"> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button v-hasPermi="['system:ossConfig:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button> |
|
||||
</el-col> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button v-hasPermi="['system:ossConfig:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" |
|
||||
>修改</el-button |
|
||||
> |
|
||||
</el-col> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button v-hasPermi="['system:ossConfig:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"> |
|
||||
删除 |
|
||||
</el-button> |
|
||||
</el-col> |
|
||||
<right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar> |
|
||||
</el-row> |
|
||||
</template> |
|
||||
|
|
||||
<el-table v-loading="loading" :data="ossConfigList" @selection-change="handleSelectionChange"> |
|
||||
<el-table-column type="selection" width="55" align="center" /> |
|
||||
<el-table-column v-if="columns[0].visible" label="主建" align="center" prop="ossConfigId" /> |
|
||||
<el-table-column v-if="columns[1].visible" label="配置key" align="center" prop="configKey" /> |
|
||||
<el-table-column v-if="columns[2].visible" label="访问站点" align="center" prop="endpoint" width="200" /> |
|
||||
<el-table-column v-if="columns[3].visible" label="自定义域名" align="center" prop="domain" width="200" /> |
|
||||
<el-table-column v-if="columns[4].visible" label="桶名称" align="center" prop="bucketName" /> |
|
||||
<el-table-column v-if="columns[5].visible" label="前缀" align="center" prop="prefix" /> |
|
||||
<el-table-column v-if="columns[6].visible" label="域" align="center" prop="region" /> |
|
||||
<el-table-column v-if="columns[7].visible" label="桶权限类型" align="center" prop="accessPolicy"> |
|
||||
<template #default="scope"> |
|
||||
<el-tag v-if="scope.row.accessPolicy === '0'" type="warning">private</el-tag> |
|
||||
<el-tag v-if="scope.row.accessPolicy === '1'" type="success">public</el-tag> |
|
||||
<el-tag v-if="scope.row.accessPolicy === '2'" type="info">custom</el-tag> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column v-if="columns[8].visible" label="是否默认" align="center" prop="status"> |
|
||||
<template #default="scope"> |
|
||||
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding"> |
|
||||
<template #default="scope"> |
|
||||
<el-tooltip content="修改" placement="top"> |
|
||||
<el-button v-hasPermi="['system:ossConfig:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> |
|
||||
</el-tooltip> |
|
||||
<el-tooltip content="删除" placement="top"> |
|
||||
<el-button v-hasPermi="['system:ossConfig:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> |
|
||||
</el-tooltip> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
</el-table> |
|
||||
|
|
||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> |
|
||||
</el-card> |
|
||||
<!-- 添加或修改对象存储配置对话框 --> |
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="800px" append-to-body> |
|
||||
<el-form ref="ossConfigFormRef" :model="form" :rules="rules" label-width="120px"> |
|
||||
<el-form-item label="配置key" prop="configKey"> |
|
||||
<el-input v-model="form.configKey" placeholder="请输入配置key" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="访问站点" prop="endpoint"> |
|
||||
<el-input v-model="form.endpoint" placeholder="请输入访问站点" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="自定义域名" prop="domain"> |
|
||||
<el-input v-model="form.domain" placeholder="请输入自定义域名" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="accessKey" prop="accessKey"> |
|
||||
<el-input v-model="form.accessKey" placeholder="请输入accessKey" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="secretKey" prop="secretKey"> |
|
||||
<el-input v-model="form.secretKey" placeholder="请输入秘钥" show-password /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="桶名称" prop="bucketName"> |
|
||||
<el-input v-model="form.bucketName" placeholder="请输入桶名称" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="前缀" prop="prefix"> |
|
||||
<el-input v-model="form.prefix" placeholder="请输入前缀" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="是否HTTPS"> |
|
||||
<el-radio-group v-model="form.isHttps"> |
|
||||
<el-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio> |
|
||||
</el-radio-group> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="桶权限类型"> |
|
||||
<el-radio-group v-model="form.accessPolicy"> |
|
||||
<el-radio value="0">private</el-radio> |
|
||||
<el-radio value="1">public</el-radio> |
|
||||
<el-radio value="2">custom</el-radio> |
|
||||
</el-radio-group> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="域" prop="region"> |
|
||||
<el-input v-model="form.region" placeholder="请输入域" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="备注" prop="remark"> |
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
<template #footer> |
|
||||
<div class="dialog-footer"> |
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button> |
|
||||
<el-button @click="cancel">取 消</el-button> |
|
||||
</div> |
|
||||
</template> |
|
||||
</el-dialog> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup name="OssConfig" lang="ts"> |
|
||||
import { listOssConfig, getOssConfig, delOssConfig, addOssConfig, updateOssConfig, changeOssConfigStatus } from '@/api/system/ossConfig'; |
|
||||
import { OssConfigForm, OssConfigQuery, OssConfigVO } from '@/api/system/ossConfig/types'; |
|
||||
|
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|
||||
const { sys_yes_no } = toRefs<any>(proxy?.useDict('sys_yes_no')); |
|
||||
|
|
||||
const ossConfigList = ref<OssConfigVO[]>([]); |
|
||||
const buttonLoading = ref(false); |
|
||||
const loading = ref(true); |
|
||||
const showSearch = ref(true); |
|
||||
const ids = ref<Array<number | string>>([]); |
|
||||
const single = ref(true); |
|
||||
const multiple = ref(true); |
|
||||
const total = ref(0); |
|
||||
|
|
||||
const queryFormRef = ref<ElFormInstance>(); |
|
||||
const ossConfigFormRef = ref<ElFormInstance>(); |
|
||||
|
|
||||
const dialog = reactive<DialogOption>({ |
|
||||
visible: false, |
|
||||
title: '' |
|
||||
}); |
|
||||
|
|
||||
// 列显隐信息 |
|
||||
const columns = ref<FieldOption[]>([ |
|
||||
{ key: 0, label: `主建`, visible: true }, |
|
||||
{ key: 1, label: `配置key`, visible: false }, |
|
||||
{ key: 2, label: `访问站点`, visible: true }, |
|
||||
{ key: 3, label: `自定义域名`, visible: true }, |
|
||||
{ key: 4, label: `桶名称`, visible: true }, |
|
||||
{ key: 5, label: `前缀`, visible: true }, |
|
||||
{ key: 6, label: `域`, visible: true }, |
|
||||
{ key: 7, label: `桶权限类型`, visible: true }, |
|
||||
{ key: 8, label: `状态`, visible: true } |
|
||||
]); |
|
||||
|
|
||||
const initFormData: OssConfigForm = { |
|
||||
ossConfigId: undefined, |
|
||||
configKey: '', |
|
||||
accessKey: '', |
|
||||
secretKey: '', |
|
||||
bucketName: '', |
|
||||
prefix: '', |
|
||||
endpoint: '', |
|
||||
domain: '', |
|
||||
isHttps: 'N', |
|
||||
accessPolicy: '1', |
|
||||
region: '', |
|
||||
status: '1', |
|
||||
remark: '' |
|
||||
}; |
|
||||
const data = reactive<PageData<OssConfigForm, OssConfigQuery>>({ |
|
||||
form: { ...initFormData }, |
|
||||
// 查询参数 |
|
||||
queryParams: { |
|
||||
pageNum: 1, |
|
||||
pageSize: 10, |
|
||||
configKey: '', |
|
||||
bucketName: '', |
|
||||
status: '' |
|
||||
}, |
|
||||
rules: { |
|
||||
configKey: [{ required: true, message: 'configKey不能为空', trigger: 'blur' }], |
|
||||
accessKey: [ |
|
||||
{ required: true, message: 'accessKey不能为空', trigger: 'blur' }, |
|
||||
{ |
|
||||
min: 2, |
|
||||
max: 200, |
|
||||
message: 'accessKey长度必须介于 2 和 100 之间', |
|
||||
trigger: 'blur' |
|
||||
} |
|
||||
], |
|
||||
secretKey: [ |
|
||||
{ required: true, message: 'secretKey不能为空', trigger: 'blur' }, |
|
||||
{ |
|
||||
min: 2, |
|
||||
max: 100, |
|
||||
message: 'secretKey长度必须介于 2 和 100 之间', |
|
||||
trigger: 'blur' |
|
||||
} |
|
||||
], |
|
||||
bucketName: [ |
|
||||
{ required: true, message: 'bucketName不能为空', trigger: 'blur' }, |
|
||||
{ |
|
||||
min: 2, |
|
||||
max: 100, |
|
||||
message: 'bucketName长度必须介于 2 和 100 之间', |
|
||||
trigger: 'blur' |
|
||||
} |
|
||||
], |
|
||||
endpoint: [ |
|
||||
{ required: true, message: 'endpoint不能为空', trigger: 'blur' }, |
|
||||
{ |
|
||||
min: 2, |
|
||||
max: 100, |
|
||||
message: 'endpoint名称长度必须介于 2 和 100 之间', |
|
||||
trigger: 'blur' |
|
||||
} |
|
||||
], |
|
||||
accessPolicy: [{ required: true, message: 'accessPolicy不能为空', trigger: 'blur' }] |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
const { queryParams, form, rules } = toRefs(data); |
|
||||
|
|
||||
/** 查询对象存储配置列表 */ |
|
||||
const getList = async () => { |
|
||||
loading.value = true; |
|
||||
const res = await listOssConfig(queryParams.value); |
|
||||
ossConfigList.value = res.rows; |
|
||||
total.value = res.total; |
|
||||
loading.value = false; |
|
||||
}; |
|
||||
/** 取消按钮 */ |
|
||||
const cancel = () => { |
|
||||
dialog.visible = false; |
|
||||
reset(); |
|
||||
}; |
|
||||
/** 表单重置 */ |
|
||||
const reset = () => { |
|
||||
form.value = { ...initFormData }; |
|
||||
ossConfigFormRef.value?.resetFields(); |
|
||||
}; |
|
||||
/** 搜索按钮操作 */ |
|
||||
const handleQuery = () => { |
|
||||
queryParams.value.pageNum = 1; |
|
||||
getList(); |
|
||||
}; |
|
||||
/** 重置按钮操作 */ |
|
||||
const resetQuery = () => { |
|
||||
queryFormRef.value?.resetFields(); |
|
||||
handleQuery(); |
|
||||
}; |
|
||||
/** 选择条数 */ |
|
||||
const handleSelectionChange = (selection: OssConfigVO[]) => { |
|
||||
ids.value = selection.map((item) => item.ossConfigId); |
|
||||
single.value = selection.length != 1; |
|
||||
multiple.value = !selection.length; |
|
||||
}; |
|
||||
/** 新增按钮操作 */ |
|
||||
const handleAdd = () => { |
|
||||
reset(); |
|
||||
dialog.visible = true; |
|
||||
dialog.title = '添加对象存储配置'; |
|
||||
}; |
|
||||
/** 修改按钮操作 */ |
|
||||
const handleUpdate = async (row?: OssConfigVO) => { |
|
||||
reset(); |
|
||||
const ossConfigId = row?.ossConfigId || ids.value[0]; |
|
||||
const res = await getOssConfig(ossConfigId); |
|
||||
Object.assign(form.value, res.data); |
|
||||
dialog.visible = true; |
|
||||
dialog.title = '修改对象存储配置'; |
|
||||
}; |
|
||||
/** 提交按钮 */ |
|
||||
const submitForm = () => { |
|
||||
ossConfigFormRef.value?.validate(async (valid: boolean) => { |
|
||||
if (valid) { |
|
||||
buttonLoading.value = true; |
|
||||
if (form.value.ossConfigId) { |
|
||||
await updateOssConfig(form.value).finally(() => (buttonLoading.value = false)); |
|
||||
} else { |
|
||||
await addOssConfig(form.value).finally(() => (buttonLoading.value = false)); |
|
||||
} |
|
||||
proxy?.$modal.msgSuccess('新增成功'); |
|
||||
dialog.visible = false; |
|
||||
await getList(); |
|
||||
} |
|
||||
}); |
|
||||
}; |
|
||||
/** 状态修改 */ |
|
||||
const handleStatusChange = async (row: OssConfigVO) => { |
|
||||
let text = row.status === '0' ? '启用' : '停用'; |
|
||||
try { |
|
||||
await proxy?.$modal.confirm('确认要"' + text + '""' + row.configKey + '"配置吗?'); |
|
||||
await changeOssConfigStatus(row.ossConfigId, row.status, row.configKey); |
|
||||
await getList(); |
|
||||
proxy?.$modal.msgSuccess(text + '成功'); |
|
||||
} catch { |
|
||||
return; |
|
||||
} finally { |
|
||||
row.status = row.status === '0' ? '1' : '0'; |
|
||||
} |
|
||||
}; |
|
||||
/** 删除按钮操作 */ |
|
||||
const handleDelete = async (row?: OssConfigVO) => { |
|
||||
const ossConfigIds = row?.ossConfigId || ids.value; |
|
||||
await proxy?.$modal.confirm('是否确认删除OSS配置编号为"' + ossConfigIds + '"的数据项?'); |
|
||||
loading.value = true; |
|
||||
await delOssConfig(ossConfigIds).finally(() => (loading.value = false)); |
|
||||
await getList(); |
|
||||
proxy?.$modal.msgSuccess('删除成功'); |
|
||||
}; |
|
||||
|
|
||||
onMounted(() => { |
|
||||
getList(); |
|
||||
}); |
|
||||
</script> |
|
||||
@ -1,332 +0,0 @@ |
|||||
<template> |
|
||||
<div class="p-2"> |
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> |
|
||||
<div v-show="showSearch" class="mb-[10px]"> |
|
||||
<el-card shadow="hover"> |
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> |
|
||||
<el-form-item label="文件名" prop="fileName"> |
|
||||
<el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="原名" prop="originalName"> |
|
||||
<el-input v-model="queryParams.originalName" placeholder="请输入原名" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="文件后缀" prop="fileSuffix"> |
|
||||
<el-input v-model="queryParams.fileSuffix" placeholder="请输入文件后缀" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="创建时间" style="width: 308px"> |
|
||||
<el-date-picker |
|
||||
v-model="dateRangeCreateTime" |
|
||||
value-format="YYYY-MM-DD HH:mm:ss" |
|
||||
type="daterange" |
|
||||
range-separator="-" |
|
||||
start-placeholder="开始日期" |
|
||||
end-placeholder="结束日期" |
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" |
|
||||
></el-date-picker> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="服务商" prop="service"> |
|
||||
<el-input v-model="queryParams.service" placeholder="请输入服务商" clearable @keyup.enter="handleQuery" /> |
|
||||
</el-form-item> |
|
||||
<el-form-item> |
|
||||
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button> |
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
</el-card> |
|
||||
</div> |
|
||||
</transition> |
|
||||
|
|
||||
<el-card shadow="hover"> |
|
||||
<template #header> |
|
||||
<el-row :gutter="10" class="mb8"> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleFile">上传文件</el-button> |
|
||||
</el-col> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleImage">上传图片</el-button> |
|
||||
</el-col> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button v-hasPermi="['system:oss:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"> |
|
||||
删除 |
|
||||
</el-button> |
|
||||
</el-col> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button |
|
||||
v-hasPermi="['system:oss:edit']" |
|
||||
:type="previewListResource ? 'danger' : 'warning'" |
|
||||
plain |
|
||||
@click="handlePreviewListResource(!previewListResource)" |
|
||||
>预览开关 : {{ previewListResource ? '禁用' : '启用' }}</el-button |
|
||||
> |
|
||||
</el-col> |
|
||||
<el-col :span="1.5"> |
|
||||
<el-button v-hasPermi="['system:ossConfig:list']" type="info" plain icon="Operation" @click="handleOssConfig">配置管理</el-button> |
|
||||
</el-col> |
|
||||
<right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar> |
|
||||
</el-row> |
|
||||
</template> |
|
||||
|
|
||||
<el-table |
|
||||
v-if="showTable" |
|
||||
v-loading="loading" |
|
||||
:data="ossList" |
|
||||
:header-cell-class-name="handleHeaderClass" |
|
||||
@selection-change="handleSelectionChange" |
|
||||
@header-click="handleHeaderCLick" |
|
||||
> |
|
||||
<el-table-column type="selection" width="55" align="center" /> |
|
||||
<el-table-column v-if="false" label="对象存储主键" align="center" prop="ossId" /> |
|
||||
<el-table-column label="文件名" align="center" prop="fileName" /> |
|
||||
<el-table-column label="原名" align="center" prop="originalName" /> |
|
||||
<el-table-column label="文件后缀" align="center" prop="fileSuffix" /> |
|
||||
<el-table-column label="文件展示" align="center" prop="url"> |
|
||||
<template #default="scope"> |
|
||||
<ImagePreview |
|
||||
v-if="previewListResource && checkFileSuffix(scope.row.fileSuffix)" |
|
||||
:width="100" |
|
||||
:height="100" |
|
||||
:src="scope.row.url" |
|
||||
:preview-src-list="[scope.row.url]" |
|
||||
/> |
|
||||
<span v-if="!checkFileSuffix(scope.row.fileSuffix) || !previewListResource" v-text="scope.row.url" /> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180" sortable="custom"> |
|
||||
<template #default="scope"> |
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column label="上传人" align="center" prop="createByName" /> |
|
||||
<el-table-column label="服务商" align="center" prop="service" sortable="custom" /> |
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> |
|
||||
<template #default="scope"> |
|
||||
<el-tooltip content="下载" placement="top"> |
|
||||
<el-button v-hasPermi="['system:oss:download']" link type="primary" icon="Download" @click="handleDownload(scope.row)"></el-button> |
|
||||
</el-tooltip> |
|
||||
<el-tooltip content="删除" placement="top"> |
|
||||
<el-button v-hasPermi="['system:oss:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> |
|
||||
</el-tooltip> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
</el-table> |
|
||||
|
|
||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> |
|
||||
</el-card> |
|
||||
<!-- 添加或修改OSS对象存储对话框 --> |
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body> |
|
||||
<el-form ref="ossFormRef" :model="form" :rules="rules" label-width="80px"> |
|
||||
<el-form-item label="文件名"> |
|
||||
<fileUpload v-if="type === 0" v-model="form.file" /> |
|
||||
<imageUpload v-if="type === 1" v-model="form.file" /> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
<template #footer> |
|
||||
<div class="dialog-footer"> |
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button> |
|
||||
<el-button @click="cancel">取 消</el-button> |
|
||||
</div> |
|
||||
</template> |
|
||||
</el-dialog> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup name="Oss" lang="ts"> |
|
||||
import { listOss, delOss } from '@/api/system/oss'; |
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue'; |
|
||||
import { OssForm, OssQuery, OssVO } from '@/api/system/oss/types'; |
|
||||
|
|
||||
const router = useRouter(); |
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|
||||
|
|
||||
const ossList = ref<OssVO[]>([]); |
|
||||
const showTable = ref(true); |
|
||||
const buttonLoading = ref(false); |
|
||||
const loading = ref(true); |
|
||||
const showSearch = ref(true); |
|
||||
const ids = ref<Array<string | number>>([]); |
|
||||
const single = ref(true); |
|
||||
const multiple = ref(true); |
|
||||
const total = ref(0); |
|
||||
const type = ref(0); |
|
||||
const previewListResource = ref(true); |
|
||||
const dateRangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']); |
|
||||
|
|
||||
const dialog = reactive<DialogOption>({ |
|
||||
visible: false, |
|
||||
title: '' |
|
||||
}); |
|
||||
|
|
||||
// 默认排序 |
|
||||
const defaultSort = ref({ prop: 'createTime', order: 'ascending' }); |
|
||||
|
|
||||
const ossFormRef = ref<ElFormInstance>(); |
|
||||
const queryFormRef = ref<ElFormInstance>(); |
|
||||
|
|
||||
const initFormData = { |
|
||||
file: undefined |
|
||||
}; |
|
||||
const data = reactive<PageData<OssForm, OssQuery>>({ |
|
||||
form: { ...initFormData }, |
|
||||
// 查询参数 |
|
||||
queryParams: { |
|
||||
pageNum: 1, |
|
||||
pageSize: 10, |
|
||||
fileName: '', |
|
||||
originalName: '', |
|
||||
fileSuffix: '', |
|
||||
createTime: '', |
|
||||
service: '', |
|
||||
orderByColumn: defaultSort.value.prop, |
|
||||
isAsc: defaultSort.value.order |
|
||||
}, |
|
||||
rules: { |
|
||||
file: [{ required: true, message: '文件不能为空', trigger: 'blur' }] |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
const { queryParams, form, rules } = toRefs(data); |
|
||||
|
|
||||
/** 查询OSS对象存储列表 */ |
|
||||
const getList = async () => { |
|
||||
loading.value = true; |
|
||||
const res = await proxy?.getConfigKey('sys.oss.previewListResource'); |
|
||||
previewListResource.value = res?.data === undefined ? true : res.data === 'true'; |
|
||||
const response = await listOss(proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, 'CreateTime')); |
|
||||
ossList.value = response.rows; |
|
||||
total.value = response.total; |
|
||||
loading.value = false; |
|
||||
showTable.value = true; |
|
||||
}; |
|
||||
function checkFileSuffix(fileSuffix: string | string[]) { |
|
||||
const arr = ['.png', '.jpg', '.jpeg']; |
|
||||
const suffixArray = Array.isArray(fileSuffix) ? fileSuffix : [fileSuffix]; |
|
||||
return suffixArray.some((suffix) => arr.includes(suffix.toLowerCase())); |
|
||||
} |
|
||||
/** 取消按钮 */ |
|
||||
function cancel() { |
|
||||
dialog.visible = false; |
|
||||
reset(); |
|
||||
} |
|
||||
/** 表单重置 */ |
|
||||
function reset() { |
|
||||
form.value = { ...initFormData }; |
|
||||
ossFormRef.value?.resetFields(); |
|
||||
} |
|
||||
/** 搜索按钮操作 */ |
|
||||
function handleQuery() { |
|
||||
queryParams.value.pageNum = 1; |
|
||||
getList(); |
|
||||
} |
|
||||
/** 重置按钮操作 */ |
|
||||
function resetQuery() { |
|
||||
showTable.value = false; |
|
||||
dateRangeCreateTime.value = ['', '']; |
|
||||
queryFormRef.value?.resetFields(); |
|
||||
queryParams.value.orderByColumn = defaultSort.value.prop; |
|
||||
queryParams.value.isAsc = defaultSort.value.order; |
|
||||
handleQuery(); |
|
||||
} |
|
||||
/** 选择条数 */ |
|
||||
function handleSelectionChange(selection: OssVO[]) { |
|
||||
ids.value = selection.map((item) => item.ossId); |
|
||||
single.value = selection.length != 1; |
|
||||
multiple.value = !selection.length; |
|
||||
} |
|
||||
/** 设置列的排序为我们自定义的排序 */ |
|
||||
const handleHeaderClass = ({ column }: any): any => { |
|
||||
column.order = column.multiOrder; |
|
||||
}; |
|
||||
/** 点击表头进行排序 */ |
|
||||
const handleHeaderCLick = (column: any) => { |
|
||||
if (column.sortable !== 'custom') { |
|
||||
return; |
|
||||
} |
|
||||
switch (column.multiOrder) { |
|
||||
case 'descending': |
|
||||
column.multiOrder = 'ascending'; |
|
||||
break; |
|
||||
case 'ascending': |
|
||||
column.multiOrder = ''; |
|
||||
break; |
|
||||
default: |
|
||||
column.multiOrder = 'descending'; |
|
||||
break; |
|
||||
} |
|
||||
handleOrderChange(column.property, column.multiOrder); |
|
||||
}; |
|
||||
const handleOrderChange = (prop: string, order: string) => { |
|
||||
let orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(',') : []; |
|
||||
let isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(',') : []; |
|
||||
let propIndex = orderByArr.indexOf(prop); |
|
||||
if (propIndex !== -1) { |
|
||||
if (order) { |
|
||||
//排序里已存在 只修改排序 |
|
||||
isAscArr[propIndex] = order; |
|
||||
} else { |
|
||||
//如果order为null 则删除排序字段和属性 |
|
||||
isAscArr.splice(propIndex, 1); //删除排序 |
|
||||
orderByArr.splice(propIndex, 1); //删除属性 |
|
||||
} |
|
||||
} else { |
|
||||
//排序里不存在则新增排序 |
|
||||
orderByArr.push(prop); |
|
||||
isAscArr.push(order); |
|
||||
} |
|
||||
//合并排序 |
|
||||
queryParams.value.orderByColumn = orderByArr.join(','); |
|
||||
queryParams.value.isAsc = isAscArr.join(','); |
|
||||
getList(); |
|
||||
}; |
|
||||
/** 任务日志列表查询 */ |
|
||||
const handleOssConfig = () => { |
|
||||
router.push('/system/oss-config/index'); |
|
||||
}; |
|
||||
/** 文件按钮操作 */ |
|
||||
const handleFile = () => { |
|
||||
reset(); |
|
||||
type.value = 0; |
|
||||
dialog.visible = true; |
|
||||
dialog.title = '上传文件'; |
|
||||
}; |
|
||||
/** 图片按钮操作 */ |
|
||||
const handleImage = () => { |
|
||||
reset(); |
|
||||
type.value = 1; |
|
||||
dialog.visible = true; |
|
||||
dialog.title = '上传图片'; |
|
||||
}; |
|
||||
/** 提交按钮 */ |
|
||||
const submitForm = () => { |
|
||||
dialog.visible = false; |
|
||||
getList(); |
|
||||
}; |
|
||||
/** 下载按钮操作 */ |
|
||||
const handleDownload = (row: OssVO) => { |
|
||||
proxy?.$download.oss(row.ossId); |
|
||||
}; |
|
||||
/** 预览开关按钮 */ |
|
||||
const handlePreviewListResource = async (preview: boolean) => { |
|
||||
let text = preview ? '启用' : '停用'; |
|
||||
try { |
|
||||
await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?'); |
|
||||
await proxy?.updateConfigByKey('sys.oss.previewListResource', preview); |
|
||||
await getList(); |
|
||||
proxy?.$modal.msgSuccess(text + '成功'); |
|
||||
} catch { |
|
||||
return; |
|
||||
} |
|
||||
}; |
|
||||
/** 删除按钮操作 */ |
|
||||
const handleDelete = async (row?: OssVO) => { |
|
||||
const ossIds = row?.ossId || ids.value; |
|
||||
await proxy?.$modal.confirm('是否确认删除OSS对象存储编号为"' + ossIds + '"的数据项?'); |
|
||||
loading.value = true; |
|
||||
await delOss(ossIds).finally(() => (loading.value = false)); |
|
||||
await getList(); |
|
||||
proxy?.$modal.msgSuccess('删除成功'); |
|
||||
}; |
|
||||
|
|
||||
onMounted(() => { |
|
||||
getList(); |
|
||||
}); |
|
||||
</script> |
|
||||
@ -1,29 +0,0 @@ |
|||||
package org.dromara.common.core.service; |
|
||||
|
|
||||
import org.dromara.common.core.domain.dto.OssDTO; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* 通用 OSS服务 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
public interface OssService { |
|
||||
|
|
||||
/** |
|
||||
* 通过ossId查询对应的url |
|
||||
* |
|
||||
* @param ossIds ossId串逗号分隔 |
|
||||
* @return url串逗号分隔 |
|
||||
*/ |
|
||||
String selectUrlByIds(String ossIds); |
|
||||
|
|
||||
/** |
|
||||
* 通过ossId查询列表 |
|
||||
* |
|
||||
* @param ossIds ossId串逗号分隔 |
|
||||
* @return 列表 |
|
||||
*/ |
|
||||
List<OssDTO> selectByIds(String ossIds); |
|
||||
} |
|
||||
@ -0,0 +1,44 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
|
||||
|
<parent> |
||||
|
<groupId>org.dromara</groupId> |
||||
|
<artifactId>ruoyi-common</artifactId> |
||||
|
<version>${revision}</version> |
||||
|
</parent> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<artifactId>ruoyi-common-minio</artifactId> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>io.minio</groupId> |
||||
|
<artifactId>minio</artifactId> |
||||
|
<version>8.5.1</version> |
||||
|
<exclusions> |
||||
|
<!-- 跟easy-excel的commons-compress版本冲突了 --> |
||||
|
<exclusion> |
||||
|
<groupId>org.apache.commons</groupId> |
||||
|
<artifactId>commons-compress</artifactId> |
||||
|
</exclusion> |
||||
|
</exclusions> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.dromara</groupId> |
||||
|
<artifactId>ruoyi-common-doc</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.dromara</groupId> |
||||
|
<artifactId>ruoyi-common-core</artifactId> |
||||
|
</dependency> |
||||
|
<!--thumbnailator图片处理--> |
||||
|
<dependency> |
||||
|
<groupId>net.coobird</groupId> |
||||
|
<artifactId>thumbnailator</artifactId> |
||||
|
<version>0.4.8</version> |
||||
|
</dependency> |
||||
|
|
||||
|
</dependencies> |
||||
|
|
||||
|
</project> |
||||
@ -0,0 +1,147 @@ |
|||||
|
package org.dromara.common.minio.client; |
||||
|
|
||||
|
import io.minio.*; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.dromara.common.core.utils.DateUtils; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
|
||||
|
import java.io.ByteArrayInputStream; |
||||
|
import java.io.InputStream; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@RequiredArgsConstructor |
||||
|
public class MinioService { |
||||
|
|
||||
|
private final MinioClient minioClient; |
||||
|
|
||||
|
/** |
||||
|
* 查看存储bucket是否存在 |
||||
|
* |
||||
|
* @return boolean |
||||
|
*/ |
||||
|
public Boolean bucketExists(String bucketName) { |
||||
|
try { |
||||
|
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); |
||||
|
} catch (Exception e) { |
||||
|
log.error("查询bucket失败", e); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建存储bucket |
||||
|
*/ |
||||
|
public void makeBucket(String bucketName) { |
||||
|
try { |
||||
|
minioClient.makeBucket(MakeBucketArgs.builder() |
||||
|
.bucket(bucketName) |
||||
|
.build()); |
||||
|
} catch (Exception e) { |
||||
|
log.error("创建bucket失败,", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置bucket权限为public |
||||
|
*/ |
||||
|
public void setBucketPublic(String bucketName) { |
||||
|
try { |
||||
|
// 设置公开
|
||||
|
String sb = "{\"Version\":\"2012-10-17\"," + |
||||
|
"\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" + |
||||
|
"{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," + |
||||
|
"\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + |
||||
|
"\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" + |
||||
|
bucketName + |
||||
|
"/*\"]}]}"; |
||||
|
minioClient.setBucketPolicy( |
||||
|
SetBucketPolicyArgs.builder() |
||||
|
.bucket(bucketName) |
||||
|
.config(sb) |
||||
|
.build()); |
||||
|
} catch (Exception e) { |
||||
|
log.error("创建bucket失败,", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 文件上传 |
||||
|
* |
||||
|
* @param bucketName bucket名称 |
||||
|
* @param path 路径 |
||||
|
* @param file 文件 |
||||
|
* @return Boolean |
||||
|
*/ |
||||
|
public String upload(String bucketName, String path, MultipartFile file) { |
||||
|
String originalFilename = file.getOriginalFilename(); |
||||
|
if (StringUtils.isBlank(originalFilename)) { |
||||
|
throw new RuntimeException(); |
||||
|
} |
||||
|
String fileName = System.currentTimeMillis() + ""; |
||||
|
if (originalFilename.lastIndexOf(".") >= 0) { |
||||
|
fileName += originalFilename.substring(originalFilename.lastIndexOf(".")); |
||||
|
} |
||||
|
String objectName = DateUtils.dateTimeNow(DateUtils.YYYYMMDD) + "/" + fileName; |
||||
|
try { |
||||
|
InputStream stream = new ByteArrayInputStream(file.getBytes()); |
||||
|
PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(path + "/" + objectName) |
||||
|
.stream(stream, file.getSize(), -1).contentType(file.getContentType()).build(); |
||||
|
//文件名称相同会覆盖
|
||||
|
minioClient.putObject(objectArgs); |
||||
|
} catch (Exception e) { |
||||
|
log.error("上传图片失败,", e); |
||||
|
return null; |
||||
|
} |
||||
|
return objectName; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 文件上传 |
||||
|
* |
||||
|
* @param bucketName bucket名称 |
||||
|
* @param path 路径 |
||||
|
* @param name 文件名 |
||||
|
* @param fileByte 文件内容 |
||||
|
* @param contentType contentType |
||||
|
* @return objectName |
||||
|
*/ |
||||
|
public String upload(String bucketName, String path, String name, byte[] fileByte, String contentType) { |
||||
|
|
||||
|
String fileName = System.currentTimeMillis() + name.substring(name.lastIndexOf(".")); |
||||
|
String objectName = DateUtils.dateTimeNow(DateUtils.YYYYMMDD) + "/" + fileName; |
||||
|
try { |
||||
|
InputStream stream = new ByteArrayInputStream(fileByte); |
||||
|
PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(path + "/" + objectName) |
||||
|
.stream(stream, fileByte.length, -1).contentType(contentType).build(); |
||||
|
//文件名称相同会覆盖
|
||||
|
minioClient.putObject(objectArgs); |
||||
|
} catch (Exception e) { |
||||
|
log.error("上传文件失败,", e); |
||||
|
return null; |
||||
|
} |
||||
|
return objectName; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 删除 |
||||
|
* |
||||
|
* @param bucketName bucket名称 |
||||
|
* @param path 路径 |
||||
|
* @param fileName 文件名 |
||||
|
* @return true/false |
||||
|
*/ |
||||
|
public boolean remove(String bucketName, String path, String fileName) { |
||||
|
try { |
||||
|
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(path + fileName).build()); |
||||
|
} catch (Exception e) { |
||||
|
log.error("删除文件失败,", e); |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
package org.dromara.common.minio.config; |
||||
|
|
||||
|
|
||||
|
import io.minio.MinioClient; |
||||
|
import org.dromara.common.minio.properties.MinioProperties; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
|
||||
|
@Configuration |
||||
|
public class MinIoClientConfig { |
||||
|
|
||||
|
@Bean |
||||
|
public MinioClient minioClient(MinioProperties minioProps) { |
||||
|
// 注入minio 客户端
|
||||
|
return MinioClient.builder() |
||||
|
.endpoint(minioProps.getEndpoint()) |
||||
|
.credentials(minioProps.getAccessKey(), minioProps.getSecretKey()) |
||||
|
.build(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
package org.dromara.common.minio.enums; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
|
||||
|
@AllArgsConstructor |
||||
|
public enum FileType { |
||||
|
|
||||
|
/** |
||||
|
* 文件 |
||||
|
*/ |
||||
|
FILE(0, "文件"), |
||||
|
/** |
||||
|
* 图片 |
||||
|
*/ |
||||
|
IMAGE(1, "图片"), |
||||
|
/** |
||||
|
* 视频 |
||||
|
*/ |
||||
|
VIDEO(2, "视频"), |
||||
|
/** |
||||
|
* 声音 |
||||
|
*/ |
||||
|
AUDIO(3, "声音"); |
||||
|
|
||||
|
private final Integer code; |
||||
|
|
||||
|
private final String desc; |
||||
|
|
||||
|
|
||||
|
public Integer code() { |
||||
|
return this.code; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
@ -0,0 +1,32 @@ |
|||||
|
package org.dromara.common.minio.properties; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
/** |
||||
|
* @author: Blue |
||||
|
* @date: 2024-09-28 |
||||
|
* @version: 1.0 |
||||
|
*/ |
||||
|
@Data |
||||
|
@Component |
||||
|
@ConfigurationProperties(prefix = "minio") |
||||
|
public class MinioProperties { |
||||
|
|
||||
|
private String endpoint; |
||||
|
|
||||
|
private String accessKey; |
||||
|
|
||||
|
private String secretKey; |
||||
|
|
||||
|
private String domain; |
||||
|
|
||||
|
private String bucketName; |
||||
|
|
||||
|
private String imagePath; |
||||
|
|
||||
|
private String filePath; |
||||
|
|
||||
|
private String videoPath; |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
package org.dromara.common.minio.service; |
||||
|
|
||||
|
|
||||
|
import org.dromara.common.minio.vo.UploadImageVO; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
|
||||
|
public interface FileService { |
||||
|
|
||||
|
String uploadFile(MultipartFile file); |
||||
|
|
||||
|
UploadImageVO uploadImage(MultipartFile file,boolean withThumb); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,110 @@ |
|||||
|
package org.dromara.common.minio.service.impl; |
||||
|
|
||||
|
|
||||
|
import org.dromara.common.minio.client.MinioService; |
||||
|
import org.dromara.common.minio.enums.FileType; |
||||
|
import org.dromara.common.minio.properties.MinioProperties; |
||||
|
import org.dromara.common.minio.service.FileService; |
||||
|
import org.dromara.common.minio.util.FileUtil; |
||||
|
import org.dromara.common.minio.util.ImageUtil; |
||||
|
import org.dromara.common.minio.vo.UploadImageVO; |
||||
|
import jakarta.annotation.PostConstruct; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.dromara.common.core.exception.ServiceException; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
|
||||
|
/** |
||||
|
* 文件上传服务 |
||||
|
* |
||||
|
* @author Blue |
||||
|
* @version 1.0 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Service |
||||
|
@RequiredArgsConstructor |
||||
|
public class FileServiceImpl implements FileService { |
||||
|
|
||||
|
private final MinioService minioSerivce; |
||||
|
|
||||
|
private final MinioProperties minioProps; |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init() { |
||||
|
if (!minioSerivce.bucketExists(minioProps.getBucketName())) { |
||||
|
// 创建bucket
|
||||
|
minioSerivce.makeBucket(minioProps.getBucketName()); |
||||
|
// 公开bucket
|
||||
|
minioSerivce.setBucketPublic(minioProps.getBucketName()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String uploadFile(MultipartFile file) { |
||||
|
// 上传
|
||||
|
String fileName = minioSerivce.upload(minioProps.getBucketName(), minioProps.getFilePath(), file); |
||||
|
if (StringUtils.isEmpty(fileName)) { |
||||
|
throw new ServiceException("文件上传失败"); |
||||
|
} |
||||
|
String url = generUrl(FileType.FILE, fileName); |
||||
|
log.info("文件文件成功,url:{}", url); |
||||
|
return url; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public UploadImageVO uploadImage(MultipartFile file,boolean withThumb) { |
||||
|
try { |
||||
|
// 上传原图
|
||||
|
UploadImageVO vo = new UploadImageVO(); |
||||
|
// 图片格式校验
|
||||
|
if (!FileUtil.isImage(file.getOriginalFilename())) { |
||||
|
throw new ServiceException("图片格式不合法"); |
||||
|
} |
||||
|
String fileName = minioSerivce.upload(minioProps.getBucketName(), minioProps.getImagePath(), file); |
||||
|
if (StringUtils.isEmpty(fileName)) { |
||||
|
throw new ServiceException( "图片上传失败"); |
||||
|
} |
||||
|
vo.setOriginUrl(generUrl(FileType.IMAGE, fileName)); |
||||
|
// 大于30K的文件需上传缩略图
|
||||
|
if (file.getSize() > 30 * 1024 && withThumb) { |
||||
|
byte[] imageByte = ImageUtil.compressForScale(file.getBytes(), 30); |
||||
|
fileName = minioSerivce.upload(minioProps.getBucketName(), minioProps.getImagePath(), |
||||
|
file.getOriginalFilename(), imageByte, file.getContentType()); |
||||
|
if (StringUtils.isEmpty(fileName)) { |
||||
|
throw new ServiceException("图片上传失败"); |
||||
|
} |
||||
|
} |
||||
|
vo.setThumbUrl(generUrl(FileType.IMAGE, fileName)); |
||||
|
log.info("文件图片成功,url:{}", vo.getOriginUrl()); |
||||
|
return vo; |
||||
|
} catch (IOException e) { |
||||
|
log.error("上传图片失败,{}", e.getMessage(), e); |
||||
|
throw new ServiceException( "图片上传失败"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private String generUrl(FileType fileTypeEnum, String fileName) { |
||||
|
String url = minioProps.getDomain() + "/" + minioProps.getBucketName(); |
||||
|
switch (fileTypeEnum) { |
||||
|
case FILE: |
||||
|
url += "/" + minioProps.getFilePath() + "/"; |
||||
|
break; |
||||
|
case IMAGE: |
||||
|
url += "/" + minioProps.getImagePath() + "/"; |
||||
|
break; |
||||
|
case VIDEO: |
||||
|
url += "/" + minioProps.getVideoPath() + "/"; |
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
url += fileName; |
||||
|
return url; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
package org.dromara.common.minio.util; |
||||
|
|
||||
|
public final class FileUtil { |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 获取文件后缀 |
||||
|
* |
||||
|
* @param fileName 文件名 |
||||
|
* @return |
||||
|
*/ |
||||
|
public static String getFileExtension(String fileName) { |
||||
|
return fileName.substring(fileName.lastIndexOf(".") + 1); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 去除文件扩展名 |
||||
|
* |
||||
|
* @param fileName 文件名 |
||||
|
* @return |
||||
|
*/ |
||||
|
public static String excludeExtension(String fileName) { |
||||
|
return fileName.substring(0,fileName.lastIndexOf(".")); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断文件是否图片类型 |
||||
|
* |
||||
|
* @param fileName 文件名 |
||||
|
* @return boolean |
||||
|
*/ |
||||
|
public static boolean isImage(String fileName) { |
||||
|
String extension = getFileExtension(fileName); |
||||
|
String[] imageExtension = new String[]{"jpeg", "jpg", "bmp", "png", "webp", "gif"}; |
||||
|
for (String e : imageExtension) { |
||||
|
if (extension.toLowerCase().equals(e)) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
package org.dromara.common.minio.util; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import net.coobird.thumbnailator.Thumbnails; |
||||
|
|
||||
|
import java.io.ByteArrayInputStream; |
||||
|
import java.io.ByteArrayOutputStream; |
||||
|
|
||||
|
@Slf4j |
||||
|
public final class ImageUtil { |
||||
|
|
||||
|
//以下是常量,按照阿里代码开发规范,不允许代码中出现魔法值
|
||||
|
private static final Integer ZERO = 0; |
||||
|
private static final Integer ONE_ZERO_TWO_FOUR = 1024; |
||||
|
private static final Integer NINE_ZERO_ZERO = 900; |
||||
|
private static final Integer THREE_TWO_SEVEN_FIVE = 3275; |
||||
|
private static final Integer TWO_ZERO_FOUR_SEVEN = 2047; |
||||
|
private static final Double ZERO_EIGHT_FIVE = 0.85; |
||||
|
private static final Double ZERO_SIX = 0.6; |
||||
|
private static final Double ZERO_FOUR_FOUR = 0.44; |
||||
|
private static final Double ZERO_FOUR = 0.4; |
||||
|
|
||||
|
/** |
||||
|
* 根据指定大小压缩图片 |
||||
|
* |
||||
|
* @param imageBytes 源图片字节数组 |
||||
|
* @param desFileSize 指定图片大小,单位kb |
||||
|
* @return 压缩质量后的图片字节数组 |
||||
|
*/ |
||||
|
public static byte[] compressForScale(byte[] imageBytes, long desFileSize) { |
||||
|
if (imageBytes == null || imageBytes.length <= ZERO || imageBytes.length < desFileSize * ONE_ZERO_TWO_FOUR) { |
||||
|
return imageBytes; |
||||
|
} |
||||
|
long srcSize = imageBytes.length; |
||||
|
double accuracy = getAccuracy(srcSize / ONE_ZERO_TWO_FOUR); |
||||
|
try { |
||||
|
while (imageBytes.length > desFileSize * ONE_ZERO_TWO_FOUR) { |
||||
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes); |
||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length); |
||||
|
Thumbnails.of(inputStream) |
||||
|
.scale(accuracy) |
||||
|
.outputQuality(accuracy) |
||||
|
.toOutputStream(outputStream); |
||||
|
imageBytes = outputStream.toByteArray(); |
||||
|
} |
||||
|
log.info("图片原大小={}kb | 压缩后大小={}kb", |
||||
|
srcSize / ONE_ZERO_TWO_FOUR, imageBytes.length / ONE_ZERO_TWO_FOUR); |
||||
|
} catch (Exception e) { |
||||
|
log.error("【图片压缩】msg=图片压缩失败!", e); |
||||
|
} |
||||
|
return imageBytes; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 自动调节精度(经验数值) |
||||
|
* |
||||
|
* @param size 源图片大小 |
||||
|
* @return 图片压缩质量比 |
||||
|
*/ |
||||
|
private static double getAccuracy(long size) { |
||||
|
double accuracy; |
||||
|
if (size < NINE_ZERO_ZERO) { |
||||
|
accuracy = ZERO_EIGHT_FIVE; |
||||
|
} else if (size < TWO_ZERO_FOUR_SEVEN) { |
||||
|
accuracy = ZERO_SIX; |
||||
|
} else if (size < THREE_TWO_SEVEN_FIVE) { |
||||
|
accuracy = ZERO_FOUR_FOUR; |
||||
|
} else { |
||||
|
accuracy = ZERO_FOUR; |
||||
|
} |
||||
|
return accuracy; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
@ -0,0 +1,15 @@ |
|||||
|
package org.dromara.common.minio.vo; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@Schema(description = "图片上传VO") |
||||
|
public class UploadImageVO { |
||||
|
|
||||
|
@Schema(description = "原图") |
||||
|
private String originUrl; |
||||
|
|
||||
|
@Schema(description = "缩略图") |
||||
|
private String thumbUrl; |
||||
|
} |
||||
@ -1,71 +0,0 @@ |
|||||
<?xml version="1.0" encoding="UTF-8"?> |
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
|
||||
<parent> |
|
||||
<groupId>org.dromara</groupId> |
|
||||
<artifactId>ruoyi-common</artifactId> |
|
||||
<version>${revision}</version> |
|
||||
</parent> |
|
||||
<modelVersion>4.0.0</modelVersion> |
|
||||
|
|
||||
<artifactId>ruoyi-common-oss</artifactId> |
|
||||
|
|
||||
<description> |
|
||||
ruoyi-common-oss oss服务 |
|
||||
</description> |
|
||||
|
|
||||
<dependencies> |
|
||||
<dependency> |
|
||||
<groupId>org.dromara</groupId> |
|
||||
<artifactId>ruoyi-common-json</artifactId> |
|
||||
</dependency> |
|
||||
|
|
||||
<dependency> |
|
||||
<groupId>org.dromara</groupId> |
|
||||
<artifactId>ruoyi-common-redis</artifactId> |
|
||||
</dependency> |
|
||||
|
|
||||
<!-- AWS SDK for Java 2.x --> |
|
||||
<dependency> |
|
||||
<groupId>software.amazon.awssdk</groupId> |
|
||||
<artifactId>s3</artifactId> |
|
||||
<exclusions> |
|
||||
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 --> |
|
||||
<exclusion> |
|
||||
<groupId>software.amazon.awssdk</groupId> |
|
||||
<artifactId>netty-nio-client</artifactId> |
|
||||
</exclusion> |
|
||||
<!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 --> |
|
||||
<exclusion> |
|
||||
<groupId>software.amazon.awssdk</groupId> |
|
||||
<artifactId>aws-crt-client</artifactId> |
|
||||
</exclusion> |
|
||||
<!-- 将基于 Apache 的 HTTP 客户端从类路径中移除 --> |
|
||||
<exclusion> |
|
||||
<groupId>software.amazon.awssdk</groupId> |
|
||||
<artifactId>apache-client</artifactId> |
|
||||
</exclusion> |
|
||||
<!-- 将配置基于 URL 连接的 HTTP 客户端从类路径中移除 --> |
|
||||
<exclusion> |
|
||||
<groupId>software.amazon.awssdk</groupId> |
|
||||
<artifactId>url-connection-client</artifactId> |
|
||||
</exclusion> |
|
||||
</exclusions> |
|
||||
</dependency> |
|
||||
|
|
||||
<!-- 使用AWS基于 CRT 的 S3 客户端 --> |
|
||||
<dependency> |
|
||||
<groupId>software.amazon.awssdk.crt</groupId> |
|
||||
<artifactId>aws-crt</artifactId> |
|
||||
</dependency> |
|
||||
|
|
||||
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 --> |
|
||||
<dependency> |
|
||||
<groupId>software.amazon.awssdk</groupId> |
|
||||
<artifactId>s3-transfer-manager</artifactId> |
|
||||
</dependency> |
|
||||
|
|
||||
</dependencies> |
|
||||
|
|
||||
</project> |
|
||||
@ -1,40 +0,0 @@ |
|||||
package org.dromara.common.oss.constant; |
|
||||
|
|
||||
import org.dromara.common.core.constant.GlobalConstants; |
|
||||
|
|
||||
import java.util.Arrays; |
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* 对象存储常量 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
public interface OssConstant { |
|
||||
|
|
||||
/** |
|
||||
* 默认配置KEY |
|
||||
*/ |
|
||||
String DEFAULT_CONFIG_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss:default_config"; |
|
||||
|
|
||||
/** |
|
||||
* 预览列表资源开关Key |
|
||||
*/ |
|
||||
String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource"; |
|
||||
|
|
||||
/** |
|
||||
* 系统数据ids |
|
||||
*/ |
|
||||
List<Long> SYSTEM_DATA_IDS = Arrays.asList(1L, 2L, 3L, 4L); |
|
||||
|
|
||||
/** |
|
||||
* 云服务商 |
|
||||
*/ |
|
||||
String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"}; |
|
||||
|
|
||||
/** |
|
||||
* https 状态 |
|
||||
*/ |
|
||||
String IS_HTTPS = "Y"; |
|
||||
|
|
||||
} |
|
||||
@ -1,605 +0,0 @@ |
|||||
package org.dromara.common.oss.core; |
|
||||
|
|
||||
import cn.hutool.core.io.IoUtil; |
|
||||
import cn.hutool.core.util.IdUtil; |
|
||||
import org.dromara.common.core.constant.Constants; |
|
||||
import org.dromara.common.core.utils.DateUtils; |
|
||||
import org.dromara.common.core.utils.StringUtils; |
|
||||
import org.dromara.common.core.utils.file.FileUtils; |
|
||||
import org.dromara.common.oss.constant.OssConstant; |
|
||||
import org.dromara.common.oss.entity.UploadResult; |
|
||||
import org.dromara.common.oss.enumd.AccessPolicyType; |
|
||||
import org.dromara.common.oss.enumd.PolicyType; |
|
||||
import org.dromara.common.oss.exception.OssException; |
|
||||
import org.dromara.common.oss.properties.OssProperties; |
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; |
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; |
|
||||
import software.amazon.awssdk.core.ResponseInputStream; |
|
||||
import software.amazon.awssdk.core.async.AsyncResponseTransformer; |
|
||||
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody; |
|
||||
import software.amazon.awssdk.regions.Region; |
|
||||
import software.amazon.awssdk.services.s3.S3AsyncClient; |
|
||||
import software.amazon.awssdk.services.s3.S3Configuration; |
|
||||
import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration; |
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectResponse; |
|
||||
import software.amazon.awssdk.services.s3.model.NoSuchBucketException; |
|
||||
import software.amazon.awssdk.services.s3.model.S3Exception; |
|
||||
import software.amazon.awssdk.services.s3.presigner.S3Presigner; |
|
||||
import software.amazon.awssdk.transfer.s3.S3TransferManager; |
|
||||
import software.amazon.awssdk.transfer.s3.model.*; |
|
||||
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener; |
|
||||
|
|
||||
import java.io.*; |
|
||||
import java.net.URI; |
|
||||
import java.net.URL; |
|
||||
import java.nio.file.Files; |
|
||||
import java.nio.file.Path; |
|
||||
import java.time.Duration; |
|
||||
|
|
||||
/** |
|
||||
* S3 存储协议 所有兼容S3协议的云厂商均支持 |
|
||||
* 阿里云 腾讯云 七牛云 minio |
|
||||
* |
|
||||
* @author AprilWind |
|
||||
*/ |
|
||||
public class OssClient { |
|
||||
|
|
||||
/** |
|
||||
* 服务商 |
|
||||
*/ |
|
||||
private final String configKey; |
|
||||
|
|
||||
/** |
|
||||
* 配置属性 |
|
||||
*/ |
|
||||
private final OssProperties properties; |
|
||||
|
|
||||
/** |
|
||||
* Amazon S3 异步客户端 |
|
||||
*/ |
|
||||
private final S3AsyncClient client; |
|
||||
|
|
||||
/** |
|
||||
* 用于管理 S3 数据传输的高级工具 |
|
||||
*/ |
|
||||
private final S3TransferManager transferManager; |
|
||||
|
|
||||
/** |
|
||||
* AWS S3 预签名 URL 的生成器 |
|
||||
*/ |
|
||||
private final S3Presigner presigner; |
|
||||
|
|
||||
/** |
|
||||
* 构造方法 |
|
||||
* |
|
||||
* @param configKey 配置键 |
|
||||
* @param ossProperties Oss配置属性 |
|
||||
*/ |
|
||||
public OssClient(String configKey, OssProperties ossProperties) { |
|
||||
this.configKey = configKey; |
|
||||
this.properties = ossProperties; |
|
||||
try { |
|
||||
// 创建 AWS 认证信息
|
|
||||
StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( |
|
||||
AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey())); |
|
||||
|
|
||||
// MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
|
|
||||
boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE); |
|
||||
|
|
||||
// 创建AWS基于 CRT 的 S3 客户端
|
|
||||
this.client = S3AsyncClient.crtBuilder() |
|
||||
.credentialsProvider(credentialsProvider) |
|
||||
.endpointOverride(URI.create(getEndpoint())) |
|
||||
.region(of()) |
|
||||
.targetThroughputInGbps(20.0) |
|
||||
.minimumPartSizeInBytes(10 * 1025 * 1024L) |
|
||||
.checksumValidationEnabled(false) |
|
||||
.forcePathStyle(isStyle) |
|
||||
.httpConfiguration(S3CrtHttpConfiguration.builder() |
|
||||
.connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时
|
|
||||
.build()) |
|
||||
.build(); |
|
||||
|
|
||||
//AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
|
|
||||
this.transferManager = S3TransferManager.builder().s3Client(this.client).build(); |
|
||||
|
|
||||
// 创建 S3 配置对象
|
|
||||
S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false) |
|
||||
.pathStyleAccessEnabled(isStyle).build(); |
|
||||
|
|
||||
// 创建 预签名 URL 的生成器 实例,用于生成 S3 预签名 URL
|
|
||||
this.presigner = S3Presigner.builder() |
|
||||
.region(of()) |
|
||||
.credentialsProvider(credentialsProvider) |
|
||||
.endpointOverride(URI.create(getDomain())) |
|
||||
.serviceConfiguration(config) |
|
||||
.build(); |
|
||||
|
|
||||
// 创建存储桶
|
|
||||
createBucket(); |
|
||||
} catch (Exception e) { |
|
||||
if (e instanceof OssException) { |
|
||||
throw e; |
|
||||
} |
|
||||
throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 同步创建存储桶 |
|
||||
* 如果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作 |
|
||||
* |
|
||||
* @throws OssException 当创建存储桶时发生异常时抛出 |
|
||||
*/ |
|
||||
public void createBucket() { |
|
||||
String bucketName = properties.getBucketName(); |
|
||||
try { |
|
||||
// 尝试获取存储桶的信息
|
|
||||
client.headBucket( |
|
||||
x -> x.bucket(bucketName) |
|
||||
.build()) |
|
||||
.join(); |
|
||||
} catch (Exception ex) { |
|
||||
if (ex.getCause() instanceof NoSuchBucketException) { |
|
||||
try { |
|
||||
// 存储桶不存在,尝试创建存储桶
|
|
||||
client.createBucket( |
|
||||
x -> x.bucket(bucketName)) |
|
||||
.join(); |
|
||||
|
|
||||
// 设置存储桶的访问策略(Bucket Policy)
|
|
||||
client.putBucketPolicy( |
|
||||
x -> x.bucket(bucketName) |
|
||||
.policy(getPolicy(bucketName, getAccessPolicy().getPolicyType()))) |
|
||||
.join(); |
|
||||
} catch (S3Exception e) { |
|
||||
// 存储桶创建或策略设置失败
|
|
||||
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]"); |
|
||||
} |
|
||||
} else { |
|
||||
throw new OssException("判断Bucket是否存在失败,请核对配置信息:[" + ex.getMessage() + "]"); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 上传文件到 Amazon S3,并返回上传结果 |
|
||||
* |
|
||||
* @param filePath 本地文件路径 |
|
||||
* @param key 在 Amazon S3 中的对象键 |
|
||||
* @param md5Digest 本地文件的 MD5 哈希值(可选) |
|
||||
* @param contentType 文件内容类型 |
|
||||
* @return UploadResult 包含上传后的文件信息 |
|
||||
* @throws OssException 如果上传失败,抛出自定义异常 |
|
||||
*/ |
|
||||
public UploadResult upload(Path filePath, String key, String md5Digest, String contentType) { |
|
||||
try { |
|
||||
// 构建上传请求对象
|
|
||||
FileUpload fileUpload = transferManager.uploadFile( |
|
||||
x -> x.putObjectRequest( |
|
||||
y -> y.bucket(properties.getBucketName()) |
|
||||
.key(key) |
|
||||
.contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null) |
|
||||
.contentType(contentType) |
|
||||
// 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
|
|
||||
// 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
|
|
||||
//.acl(getAccessPolicy().getObjectCannedACL())
|
|
||||
.build()) |
|
||||
.addTransferListener(LoggingTransferListener.create()) |
|
||||
.source(filePath).build()); |
|
||||
|
|
||||
// 等待上传完成并获取上传结果
|
|
||||
CompletedFileUpload uploadResult = fileUpload.completionFuture().join(); |
|
||||
String eTag = uploadResult.response().eTag(); |
|
||||
|
|
||||
// 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
|
|
||||
return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build(); |
|
||||
} catch (Exception e) { |
|
||||
// 捕获异常并抛出自定义异常
|
|
||||
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); |
|
||||
} finally { |
|
||||
// 无论上传是否成功,最终都会删除临时文件
|
|
||||
FileUtils.del(filePath); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 上传 InputStream 到 Amazon S3 |
|
||||
* |
|
||||
* @param inputStream 要上传的输入流 |
|
||||
* @param key 在 Amazon S3 中的对象键 |
|
||||
* @param length 输入流的长度 |
|
||||
* @param contentType 文件内容类型 |
|
||||
* @return UploadResult 包含上传后的文件信息 |
|
||||
* @throws OssException 如果上传失败,抛出自定义异常 |
|
||||
*/ |
|
||||
public UploadResult upload(InputStream inputStream, String key, Long length, String contentType) { |
|
||||
// 如果输入流不是 ByteArrayInputStream,则将其读取为字节数组再创建 ByteArrayInputStream
|
|
||||
if (!(inputStream instanceof ByteArrayInputStream)) { |
|
||||
inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); |
|
||||
} |
|
||||
try { |
|
||||
// 创建异步请求体(length如果为空会报错)
|
|
||||
BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder() |
|
||||
.contentLength(length) |
|
||||
.subscribeTimeout(Duration.ofSeconds(30)) |
|
||||
.build(); |
|
||||
|
|
||||
// 使用 transferManager 进行上传
|
|
||||
Upload upload = transferManager.upload( |
|
||||
x -> x.requestBody(body) |
|
||||
.putObjectRequest( |
|
||||
y -> y.bucket(properties.getBucketName()) |
|
||||
.key(key) |
|
||||
.contentType(contentType) |
|
||||
// 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
|
|
||||
// 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
|
|
||||
//.acl(getAccessPolicy().getObjectCannedACL())
|
|
||||
.build()) |
|
||||
.build()); |
|
||||
|
|
||||
// 将输入流写入请求体
|
|
||||
body.writeInputStream(inputStream); |
|
||||
|
|
||||
// 等待文件上传操作完成
|
|
||||
CompletedUpload uploadResult = upload.completionFuture().join(); |
|
||||
String eTag = uploadResult.response().eTag(); |
|
||||
|
|
||||
// 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
|
|
||||
return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build(); |
|
||||
} catch (Exception e) { |
|
||||
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 下载文件从 Amazon S3 到临时目录 |
|
||||
* |
|
||||
* @param path 文件在 Amazon S3 中的对象键 |
|
||||
* @return 下载后的文件在本地的临时路径 |
|
||||
* @throws OssException 如果下载失败,抛出自定义异常 |
|
||||
*/ |
|
||||
public Path fileDownload(String path) { |
|
||||
// 构建临时文件
|
|
||||
Path tempFilePath = FileUtils.createTempFile().toPath(); |
|
||||
// 使用 S3TransferManager 下载文件
|
|
||||
FileDownload downloadFile = transferManager.downloadFile( |
|
||||
x -> x.getObjectRequest( |
|
||||
y -> y.bucket(properties.getBucketName()) |
|
||||
.key(removeBaseUrl(path)) |
|
||||
.build()) |
|
||||
.addTransferListener(LoggingTransferListener.create()) |
|
||||
.destination(tempFilePath) |
|
||||
.build()); |
|
||||
// 等待文件下载操作完成
|
|
||||
downloadFile.completionFuture().join(); |
|
||||
return tempFilePath; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 下载文件从 Amazon S3 到 输出流 |
|
||||
* |
|
||||
* @param key 文件在 Amazon S3 中的对象键 |
|
||||
* @param out 输出流 |
|
||||
* @return 输出流中写入的字节数(长度) |
|
||||
* @throws OssException 如果下载失败,抛出自定义异常 |
|
||||
*/ |
|
||||
public long download(String key, OutputStream out) { |
|
||||
try { |
|
||||
// 构建下载请求
|
|
||||
DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder() |
|
||||
// 文件对象
|
|
||||
.getObjectRequest(y -> y.bucket(properties.getBucketName()) |
|
||||
.key(key) |
|
||||
.build()) |
|
||||
.addTransferListener(LoggingTransferListener.create()) |
|
||||
// 使用订阅转换器
|
|
||||
.responseTransformer(AsyncResponseTransformer.toBlockingInputStream()) |
|
||||
.build(); |
|
||||
// 使用 S3TransferManager 下载文件
|
|
||||
Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest); |
|
||||
// 输出到流中
|
|
||||
try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
|
|
||||
return responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
|
|
||||
} |
|
||||
} catch (Exception e) { |
|
||||
throw new OssException("文件下载失败,错误信息:[" + e.getMessage() + "]"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 删除云存储服务中指定路径下文件 |
|
||||
* |
|
||||
* @param path 指定路径 |
|
||||
*/ |
|
||||
public void delete(String path) { |
|
||||
try { |
|
||||
client.deleteObject( |
|
||||
x -> x.bucket(properties.getBucketName()) |
|
||||
.key(removeBaseUrl(path)) |
|
||||
.build()); |
|
||||
} catch (Exception e) { |
|
||||
throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 获取私有URL链接 |
|
||||
* |
|
||||
* @param objectKey 对象KEY |
|
||||
* @param second 授权时间 |
|
||||
*/ |
|
||||
public String getPrivateUrl(String objectKey, Integer second) { |
|
||||
// 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
|
|
||||
URL url = presigner.presignGetObject( |
|
||||
x -> x.signatureDuration(Duration.ofSeconds(second)) |
|
||||
.getObjectRequest( |
|
||||
y -> y.bucket(properties.getBucketName()) |
|
||||
.key(objectKey) |
|
||||
.build()) |
|
||||
.build()) |
|
||||
.url(); |
|
||||
return url.toString(); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 上传 byte[] 数据到 Amazon S3,使用指定的后缀构造对象键。 |
|
||||
* |
|
||||
* @param data 要上传的 byte[] 数据 |
|
||||
* @param suffix 对象键的后缀 |
|
||||
* @return UploadResult 包含上传后的文件信息 |
|
||||
* @throws OssException 如果上传失败,抛出自定义异常 |
|
||||
*/ |
|
||||
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) { |
|
||||
return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), contentType); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 上传 InputStream 到 Amazon S3,使用指定的后缀构造对象键。 |
|
||||
* |
|
||||
* @param inputStream 要上传的输入流 |
|
||||
* @param suffix 对象键的后缀 |
|
||||
* @param length 输入流的长度 |
|
||||
* @return UploadResult 包含上传后的文件信息 |
|
||||
* @throws OssException 如果上传失败,抛出自定义异常 |
|
||||
*/ |
|
||||
public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length, String contentType) { |
|
||||
return upload(inputStream, getPath(properties.getPrefix(), suffix), length, contentType); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 上传文件到 Amazon S3,使用指定的后缀构造对象键 |
|
||||
* |
|
||||
* @param file 要上传的文件 |
|
||||
* @param suffix 对象键的后缀 |
|
||||
* @return UploadResult 包含上传后的文件信息 |
|
||||
* @throws OssException 如果上传失败,抛出自定义异常 |
|
||||
*/ |
|
||||
public UploadResult uploadSuffix(File file, String suffix) { |
|
||||
return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null, FileUtils.getMimeType(suffix)); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 获取文件输入流 |
|
||||
* |
|
||||
* @param path 完整文件路径 |
|
||||
* @return 输入流 |
|
||||
*/ |
|
||||
public InputStream getObjectContent(String path) throws IOException { |
|
||||
// 下载文件到临时目录
|
|
||||
Path tempFilePath = fileDownload(path); |
|
||||
// 创建输入流
|
|
||||
InputStream inputStream = Files.newInputStream(tempFilePath); |
|
||||
// 删除临时文件
|
|
||||
FileUtils.del(tempFilePath); |
|
||||
// 返回对象内容的输入流
|
|
||||
return inputStream; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 获取 S3 客户端的终端点 URL |
|
||||
* |
|
||||
* @return 终端点 URL |
|
||||
*/ |
|
||||
public String getEndpoint() { |
|
||||
// 根据配置文件中的是否使用 HTTPS,设置协议头部
|
|
||||
String header = getIsHttps(); |
|
||||
// 拼接协议头部和终端点,得到完整的终端点 URL
|
|
||||
return header + properties.getEndpoint(); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 获取 S3 客户端的终端点 URL(自定义域名) |
|
||||
* |
|
||||
* @return 终端点 URL |
|
||||
*/ |
|
||||
public String getDomain() { |
|
||||
// 从配置中获取域名、终端点、是否使用 HTTPS 等信息
|
|
||||
String domain = properties.getDomain(); |
|
||||
String endpoint = properties.getEndpoint(); |
|
||||
String header = getIsHttps(); |
|
||||
|
|
||||
// 如果是云服务商,直接返回域名或终端点
|
|
||||
if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { |
|
||||
return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint; |
|
||||
} |
|
||||
|
|
||||
// 如果是 MinIO,处理域名并返回
|
|
||||
if (StringUtils.isNotEmpty(domain)) { |
|
||||
return domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP) ? domain : header + domain; |
|
||||
} |
|
||||
|
|
||||
// 返回终端点
|
|
||||
return header + endpoint; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 根据传入的 region 参数返回相应的 AWS 区域 |
|
||||
* 如果 region 参数非空,使用 Region.of 方法创建并返回对应的 AWS 区域对象 |
|
||||
* 如果 region 参数为空,返回一个默认的 AWS 区域(例如,us-east-1),作为广泛支持的区域 |
|
||||
* |
|
||||
* @return 对应的 AWS 区域对象,或者默认的广泛支持的区域(us-east-1) |
|
||||
*/ |
|
||||
public Region of() { |
|
||||
//AWS 区域字符串
|
|
||||
String region = properties.getRegion(); |
|
||||
// 如果 region 参数非空,使用 Region.of 方法创建对应的 AWS 区域对象,否则返回默认区域
|
|
||||
return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 获取云存储服务的URL |
|
||||
* |
|
||||
* @return 文件路径 |
|
||||
*/ |
|
||||
public String getUrl() { |
|
||||
String domain = properties.getDomain(); |
|
||||
String endpoint = properties.getEndpoint(); |
|
||||
String header = getIsHttps(); |
|
||||
// 云服务商直接返回
|
|
||||
if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { |
|
||||
return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint); |
|
||||
} |
|
||||
// MinIO 单独处理
|
|
||||
if (StringUtils.isNotEmpty(domain)) { |
|
||||
// 如果 domain 以 "https://" 或 "http://" 开头
|
|
||||
return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ? |
|
||||
domain + StringUtils.SLASH + properties.getBucketName() : header + domain + StringUtils.SLASH + properties.getBucketName(); |
|
||||
} |
|
||||
return header + endpoint + StringUtils.SLASH + properties.getBucketName(); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 生成一个符合特定规则的、唯一的文件路径。通过使用日期、UUID、前缀和后缀等元素的组合,确保了文件路径的独一无二性 |
|
||||
* |
|
||||
* @param prefix 前缀 |
|
||||
* @param suffix 后缀 |
|
||||
* @return 文件路径 |
|
||||
*/ |
|
||||
public String getPath(String prefix, String suffix) { |
|
||||
// 生成uuid
|
|
||||
String uuid = IdUtil.fastSimpleUUID(); |
|
||||
// 生成日期路径
|
|
||||
String datePath = DateUtils.datePath(); |
|
||||
// 拼接路径
|
|
||||
String path = StringUtils.isNotEmpty(prefix) ? |
|
||||
prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid; |
|
||||
return path + suffix; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 移除路径中的基础URL部分,得到相对路径 |
|
||||
* |
|
||||
* @param path 完整的路径,包括基础URL和相对路径 |
|
||||
* @return 去除基础URL后的相对路径 |
|
||||
*/ |
|
||||
public String removeBaseUrl(String path) { |
|
||||
return path.replace(getUrl() + StringUtils.SLASH, ""); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 服务商 |
|
||||
*/ |
|
||||
public String getConfigKey() { |
|
||||
return configKey; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 获取是否使用 HTTPS 的配置,并返回相应的协议头部。 |
|
||||
* |
|
||||
* @return 协议头部,根据是否使用 HTTPS 返回 "https://" 或 "http://" |
|
||||
*/ |
|
||||
public String getIsHttps() { |
|
||||
return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 检查配置是否相同 |
|
||||
*/ |
|
||||
public boolean checkPropertiesSame(OssProperties properties) { |
|
||||
return this.properties.equals(properties); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 获取当前桶权限类型 |
|
||||
* |
|
||||
* @return 当前桶权限类型code |
|
||||
*/ |
|
||||
public AccessPolicyType getAccessPolicy() { |
|
||||
return AccessPolicyType.getByType(properties.getAccessPolicy()); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 生成 AWS S3 存储桶访问策略 |
|
||||
* |
|
||||
* @param bucketName 存储桶 |
|
||||
* @param policyType 桶策略类型 |
|
||||
* @return 符合 AWS S3 存储桶访问策略格式的字符串 |
|
||||
*/ |
|
||||
private static String getPolicy(String bucketName, PolicyType policyType) { |
|
||||
String policy = switch (policyType) { |
|
||||
case WRITE -> """ |
|
||||
{ |
|
||||
"Version": "2012-10-17", |
|
||||
"Statement": [] |
|
||||
} |
|
||||
"""; |
|
||||
case READ_WRITE -> """ |
|
||||
{ |
|
||||
"Version": "2012-10-17", |
|
||||
"Statement": [ |
|
||||
{ |
|
||||
"Effect": "Allow", |
|
||||
"Principal": "*", |
|
||||
"Action": [ |
|
||||
"s3:GetBucketLocation", |
|
||||
"s3:ListBucket", |
|
||||
"s3:ListBucketMultipartUploads" |
|
||||
], |
|
||||
"Resource": "arn:aws:s3:::bucketName" |
|
||||
}, |
|
||||
{ |
|
||||
"Effect": "Allow", |
|
||||
"Principal": "*", |
|
||||
"Action": [ |
|
||||
"s3:AbortMultipartUpload", |
|
||||
"s3:DeleteObject", |
|
||||
"s3:GetObject", |
|
||||
"s3:ListMultipartUploadParts", |
|
||||
"s3:PutObject" |
|
||||
], |
|
||||
"Resource": "arn:aws:s3:::bucketName/*" |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
"""; |
|
||||
case READ -> """ |
|
||||
{ |
|
||||
"Version": "2012-10-17", |
|
||||
"Statement": [ |
|
||||
{ |
|
||||
"Effect": "Allow", |
|
||||
"Principal": "*", |
|
||||
"Action": ["s3:GetBucketLocation"], |
|
||||
"Resource": "arn:aws:s3:::bucketName" |
|
||||
}, |
|
||||
{ |
|
||||
"Effect": "Deny", |
|
||||
"Principal": "*", |
|
||||
"Action": ["s3:ListBucket"], |
|
||||
"Resource": "arn:aws:s3:::bucketName" |
|
||||
}, |
|
||||
{ |
|
||||
"Effect": "Allow", |
|
||||
"Principal": "*", |
|
||||
"Action": "s3:GetObject", |
|
||||
"Resource": "arn:aws:s3:::bucketName/*" |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
"""; |
|
||||
}; |
|
||||
return policy.replaceAll("bucketName", bucketName); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,30 +0,0 @@ |
|||||
package org.dromara.common.oss.entity; |
|
||||
|
|
||||
import lombok.Builder; |
|
||||
import lombok.Data; |
|
||||
|
|
||||
/** |
|
||||
* 上传返回体 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Data |
|
||||
@Builder |
|
||||
public class UploadResult { |
|
||||
|
|
||||
/** |
|
||||
* 文件路径 |
|
||||
*/ |
|
||||
private String url; |
|
||||
|
|
||||
/** |
|
||||
* 文件名 |
|
||||
*/ |
|
||||
private String filename; |
|
||||
|
|
||||
/** |
|
||||
* 已上传对象的实体标记(用来校验文件) |
|
||||
*/ |
|
||||
private String eTag; |
|
||||
|
|
||||
} |
|
||||
@ -1,61 +0,0 @@ |
|||||
package org.dromara.common.oss.enumd; |
|
||||
|
|
||||
import lombok.AllArgsConstructor; |
|
||||
import lombok.Getter; |
|
||||
import software.amazon.awssdk.services.s3.model.BucketCannedACL; |
|
||||
import software.amazon.awssdk.services.s3.model.ObjectCannedACL; |
|
||||
|
|
||||
/** |
|
||||
* 桶访问策略配置 |
|
||||
* |
|
||||
* @author 陈賝 |
|
||||
*/ |
|
||||
@Getter |
|
||||
@AllArgsConstructor |
|
||||
public enum AccessPolicyType { |
|
||||
|
|
||||
/** |
|
||||
* private |
|
||||
*/ |
|
||||
PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE), |
|
||||
|
|
||||
/** |
|
||||
* public |
|
||||
*/ |
|
||||
PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE), |
|
||||
|
|
||||
/** |
|
||||
* custom |
|
||||
*/ |
|
||||
CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ); |
|
||||
|
|
||||
/** |
|
||||
* 桶 权限类型(数据库值) |
|
||||
*/ |
|
||||
private final String type; |
|
||||
|
|
||||
/** |
|
||||
* 桶 权限类型 |
|
||||
*/ |
|
||||
private final BucketCannedACL bucketCannedACL; |
|
||||
|
|
||||
/** |
|
||||
* 文件对象 权限类型 |
|
||||
*/ |
|
||||
private final ObjectCannedACL objectCannedACL; |
|
||||
|
|
||||
/** |
|
||||
* 桶策略类型 |
|
||||
*/ |
|
||||
private final PolicyType policyType; |
|
||||
|
|
||||
public static AccessPolicyType getByType(String type) { |
|
||||
for (AccessPolicyType value : values()) { |
|
||||
if (value.getType().equals(type)) { |
|
||||
return value; |
|
||||
} |
|
||||
} |
|
||||
throw new RuntimeException("'type' not found By " + type); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,35 +0,0 @@ |
|||||
package org.dromara.common.oss.enumd; |
|
||||
|
|
||||
import lombok.AllArgsConstructor; |
|
||||
import lombok.Getter; |
|
||||
|
|
||||
/** |
|
||||
* minio策略配置 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Getter |
|
||||
@AllArgsConstructor |
|
||||
public enum PolicyType { |
|
||||
|
|
||||
/** |
|
||||
* 只读 |
|
||||
*/ |
|
||||
READ("read-only"), |
|
||||
|
|
||||
/** |
|
||||
* 只写 |
|
||||
*/ |
|
||||
WRITE("write-only"), |
|
||||
|
|
||||
/** |
|
||||
* 读写 |
|
||||
*/ |
|
||||
READ_WRITE("read-write"); |
|
||||
|
|
||||
/** |
|
||||
* 类型 |
|
||||
*/ |
|
||||
private final String type; |
|
||||
|
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
package org.dromara.common.oss.exception; |
|
||||
|
|
||||
import java.io.Serial; |
|
||||
|
|
||||
/** |
|
||||
* OSS异常类 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
public class OssException extends RuntimeException { |
|
||||
|
|
||||
@Serial |
|
||||
private static final long serialVersionUID = 1L; |
|
||||
|
|
||||
public OssException(String msg) { |
|
||||
super(msg); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,73 +0,0 @@ |
|||||
package org.dromara.common.oss.factory; |
|
||||
|
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.dromara.common.core.constant.CacheNames; |
|
||||
import org.dromara.common.core.utils.StringUtils; |
|
||||
import org.dromara.common.json.utils.JsonUtils; |
|
||||
import org.dromara.common.oss.constant.OssConstant; |
|
||||
import org.dromara.common.oss.core.OssClient; |
|
||||
import org.dromara.common.oss.exception.OssException; |
|
||||
import org.dromara.common.oss.properties.OssProperties; |
|
||||
import org.dromara.common.redis.utils.CacheUtils; |
|
||||
import org.dromara.common.redis.utils.RedisUtils; |
|
||||
|
|
||||
import java.util.Map; |
|
||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||
import java.util.concurrent.locks.ReentrantLock; |
|
||||
|
|
||||
/** |
|
||||
* 文件上传Factory |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Slf4j |
|
||||
public class OssFactory { |
|
||||
|
|
||||
private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>(); |
|
||||
private static final ReentrantLock LOCK = new ReentrantLock(); |
|
||||
|
|
||||
/** |
|
||||
* 获取默认实例 |
|
||||
*/ |
|
||||
public static OssClient instance() { |
|
||||
// 获取redis 默认类型
|
|
||||
String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY); |
|
||||
if (StringUtils.isEmpty(configKey)) { |
|
||||
throw new OssException("文件存储服务类型无法找到!"); |
|
||||
} |
|
||||
return instance(configKey); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 根据类型获取实例 |
|
||||
*/ |
|
||||
public static OssClient instance(String configKey) { |
|
||||
String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey); |
|
||||
if (json == null) { |
|
||||
throw new OssException("系统异常, '" + configKey + "'配置信息不存在!"); |
|
||||
} |
|
||||
OssProperties properties = JsonUtils.parseObject(json, OssProperties.class); |
|
||||
// 使用租户标识避免多个租户相同key实例覆盖
|
|
||||
String key = configKey; |
|
||||
if (StringUtils.isNotBlank(properties.getTenantId())) { |
|
||||
key = properties.getTenantId() + ":" + configKey; |
|
||||
} |
|
||||
OssClient client = CLIENT_CACHE.get(key); |
|
||||
// 客户端不存在或配置不相同则重新构建
|
|
||||
if (client == null || !client.checkPropertiesSame(properties)) { |
|
||||
LOCK.lock(); |
|
||||
try { |
|
||||
client = CLIENT_CACHE.get(key); |
|
||||
if (client == null || !client.checkPropertiesSame(properties)) { |
|
||||
CLIENT_CACHE.put(key, new OssClient(configKey, properties)); |
|
||||
log.info("创建OSS实例 key => {}", configKey); |
|
||||
return CLIENT_CACHE.get(key); |
|
||||
} |
|
||||
} finally { |
|
||||
LOCK.unlock(); |
|
||||
} |
|
||||
} |
|
||||
return client; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,63 +0,0 @@ |
|||||
package org.dromara.common.oss.properties; |
|
||||
|
|
||||
import lombok.Data; |
|
||||
|
|
||||
/** |
|
||||
* OSS对象存储 配置属性 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Data |
|
||||
public class OssProperties { |
|
||||
|
|
||||
/** |
|
||||
* 租户id |
|
||||
*/ |
|
||||
private String tenantId; |
|
||||
|
|
||||
/** |
|
||||
* 访问站点 |
|
||||
*/ |
|
||||
private String endpoint; |
|
||||
|
|
||||
/** |
|
||||
* 自定义域名 |
|
||||
*/ |
|
||||
private String domain; |
|
||||
|
|
||||
/** |
|
||||
* 前缀 |
|
||||
*/ |
|
||||
private String prefix; |
|
||||
|
|
||||
/** |
|
||||
* ACCESS_KEY |
|
||||
*/ |
|
||||
private String accessKey; |
|
||||
|
|
||||
/** |
|
||||
* SECRET_KEY |
|
||||
*/ |
|
||||
private String secretKey; |
|
||||
|
|
||||
/** |
|
||||
* 存储空间名 |
|
||||
*/ |
|
||||
private String bucketName; |
|
||||
|
|
||||
/** |
|
||||
* 存储区域 |
|
||||
*/ |
|
||||
private String region; |
|
||||
|
|
||||
/** |
|
||||
* 是否https(Y=是,N=否) |
|
||||
*/ |
|
||||
private String isHttps; |
|
||||
|
|
||||
/** |
|
||||
* 桶权限类型(0private 1public 2custom) |
|
||||
*/ |
|
||||
private String accessPolicy; |
|
||||
|
|
||||
} |
|
||||
@ -1,29 +0,0 @@ |
|||||
package org.dromara.common.translation.core.impl; |
|
||||
|
|
||||
import org.dromara.common.core.service.OssService; |
|
||||
import org.dromara.common.translation.annotation.TranslationType; |
|
||||
import org.dromara.common.translation.constant.TransConstant; |
|
||||
import org.dromara.common.translation.core.TranslationInterface; |
|
||||
import lombok.AllArgsConstructor; |
|
||||
|
|
||||
/** |
|
||||
* OSS翻译实现 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@AllArgsConstructor |
|
||||
@TranslationType(type = TransConstant.OSS_ID_TO_URL) |
|
||||
public class OssUrlTranslationImpl implements TranslationInterface<String> { |
|
||||
|
|
||||
private final OssService ossService; |
|
||||
|
|
||||
@Override |
|
||||
public String translation(Object key, String other) { |
|
||||
if (key instanceof String ids) { |
|
||||
return ossService.selectUrlByIds(ids); |
|
||||
} else if (key instanceof Long id) { |
|
||||
return ossService.selectUrlByIds(id.toString()); |
|
||||
} |
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
@ -1,6 +1,5 @@ |
|||||
org.dromara.common.translation.config.TranslationConfig |
org.dromara.common.translation.config.TranslationConfig |
||||
org.dromara.common.translation.core.impl.DeptNameTranslationImpl |
org.dromara.common.translation.core.impl.DeptNameTranslationImpl |
||||
org.dromara.common.translation.core.impl.DictTypeTranslationImpl |
org.dromara.common.translation.core.impl.DictTypeTranslationImpl |
||||
org.dromara.common.translation.core.impl.OssUrlTranslationImpl |
|
||||
org.dromara.common.translation.core.impl.UserNameTranslationImpl |
org.dromara.common.translation.core.impl.UserNameTranslationImpl |
||||
org.dromara.common.translation.core.impl.NicknameTranslationImpl |
org.dromara.common.translation.core.impl.NicknameTranslationImpl |
||||
|
|||||
@ -1,105 +0,0 @@ |
|||||
package org.dromara.system.controller.system; |
|
||||
|
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission; |
|
||||
import org.dromara.common.core.domain.R; |
|
||||
import org.dromara.common.core.validate.AddGroup; |
|
||||
import org.dromara.common.core.validate.EditGroup; |
|
||||
import org.dromara.common.core.validate.QueryGroup; |
|
||||
import org.dromara.common.web.core.BaseController; |
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit; |
|
||||
import org.dromara.common.log.annotation.Log; |
|
||||
import org.dromara.common.log.enums.BusinessType; |
|
||||
import org.dromara.common.mybatis.core.page.PageQuery; |
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo; |
|
||||
import org.dromara.system.domain.bo.SysOssConfigBo; |
|
||||
import org.dromara.system.domain.vo.SysOssConfigVo; |
|
||||
import org.dromara.system.service.ISysOssConfigService; |
|
||||
import jakarta.validation.constraints.NotEmpty; |
|
||||
import jakarta.validation.constraints.NotNull; |
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import org.springframework.validation.annotation.Validated; |
|
||||
import org.springframework.web.bind.annotation.*; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* 对象存储配置 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
* @author 孤舟烟雨 |
|
||||
* @date 2021-08-13 |
|
||||
*/ |
|
||||
@Validated |
|
||||
@RequiredArgsConstructor |
|
||||
@RestController |
|
||||
@RequestMapping("/resource/oss/config") |
|
||||
public class SysOssConfigController extends BaseController { |
|
||||
|
|
||||
private final ISysOssConfigService ossConfigService; |
|
||||
|
|
||||
/** |
|
||||
* 查询对象存储配置列表 |
|
||||
*/ |
|
||||
@SaCheckPermission("system:ossConfig:list") |
|
||||
@GetMapping("/list") |
|
||||
public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) { |
|
||||
return ossConfigService.queryPageList(bo, pageQuery); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 获取对象存储配置详细信息 |
|
||||
* |
|
||||
* @param ossConfigId OSS配置ID |
|
||||
*/ |
|
||||
@SaCheckPermission("system:ossConfig:list") |
|
||||
@GetMapping("/{ossConfigId}") |
|
||||
public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空") |
|
||||
@PathVariable Long ossConfigId) { |
|
||||
return R.ok(ossConfigService.queryById(ossConfigId)); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 新增对象存储配置 |
|
||||
*/ |
|
||||
@SaCheckPermission("system:ossConfig:add") |
|
||||
@Log(title = "对象存储配置", businessType = BusinessType.INSERT) |
|
||||
@RepeatSubmit() |
|
||||
@PostMapping() |
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) { |
|
||||
return toAjax(ossConfigService.insertByBo(bo)); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 修改对象存储配置 |
|
||||
*/ |
|
||||
@SaCheckPermission("system:ossConfig:edit") |
|
||||
@Log(title = "对象存储配置", businessType = BusinessType.UPDATE) |
|
||||
@RepeatSubmit() |
|
||||
@PutMapping() |
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) { |
|
||||
return toAjax(ossConfigService.updateByBo(bo)); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 删除对象存储配置 |
|
||||
* |
|
||||
* @param ossConfigIds OSS配置ID串 |
|
||||
*/ |
|
||||
@SaCheckPermission("system:ossConfig:remove") |
|
||||
@Log(title = "对象存储配置", businessType = BusinessType.DELETE) |
|
||||
@DeleteMapping("/{ossConfigIds}") |
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") |
|
||||
@PathVariable Long[] ossConfigIds) { |
|
||||
return toAjax(ossConfigService.deleteWithValidByIds(List.of(ossConfigIds), true)); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 状态修改 |
|
||||
*/ |
|
||||
@SaCheckPermission("system:ossConfig:edit") |
|
||||
@Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE) |
|
||||
@PutMapping("/changeStatus") |
|
||||
public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) { |
|
||||
return toAjax(ossConfigService.updateOssConfigStatus(bo)); |
|
||||
} |
|
||||
} |
|
||||
@ -1,108 +0,0 @@ |
|||||
package org.dromara.system.controller.system; |
|
||||
|
|
||||
|
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission; |
|
||||
import cn.hutool.core.util.ObjectUtil; |
|
||||
import org.dromara.common.core.domain.R; |
|
||||
import org.dromara.common.core.validate.QueryGroup; |
|
||||
import org.dromara.common.web.core.BaseController; |
|
||||
import org.dromara.common.log.annotation.Log; |
|
||||
import org.dromara.common.log.enums.BusinessType; |
|
||||
import org.dromara.common.mybatis.core.page.PageQuery; |
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo; |
|
||||
import org.dromara.system.domain.bo.SysOssBo; |
|
||||
import org.dromara.system.domain.vo.SysOssUploadVo; |
|
||||
import org.dromara.system.domain.vo.SysOssVo; |
|
||||
import org.dromara.system.service.ISysOssService; |
|
||||
import jakarta.servlet.http.HttpServletResponse; |
|
||||
import jakarta.validation.constraints.NotEmpty; |
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import org.springframework.http.MediaType; |
|
||||
import org.springframework.validation.annotation.Validated; |
|
||||
import org.springframework.web.bind.annotation.*; |
|
||||
import org.springframework.web.multipart.MultipartFile; |
|
||||
|
|
||||
import java.io.IOException; |
|
||||
import java.util.Arrays; |
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* 文件上传 控制层 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Validated |
|
||||
@RequiredArgsConstructor |
|
||||
@RestController |
|
||||
@RequestMapping("/resource/oss") |
|
||||
public class SysOssController extends BaseController { |
|
||||
|
|
||||
private final ISysOssService ossService; |
|
||||
|
|
||||
/** |
|
||||
* 查询OSS对象存储列表 |
|
||||
*/ |
|
||||
@SaCheckPermission("system:oss:list") |
|
||||
@GetMapping("/list") |
|
||||
public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) { |
|
||||
return ossService.queryPageList(bo, pageQuery); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 查询OSS对象基于id串 |
|
||||
* |
|
||||
* @param ossIds OSS对象ID串 |
|
||||
*/ |
|
||||
@SaCheckPermission("system:oss:list") |
|
||||
@GetMapping("/listByIds/{ossIds}") |
|
||||
public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空") |
|
||||
@PathVariable Long[] ossIds) { |
|
||||
List<SysOssVo> list = ossService.listByIds(Arrays.asList(ossIds)); |
|
||||
return R.ok(list); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 上传OSS对象存储 |
|
||||
* |
|
||||
* @param file 文件 |
|
||||
*/ |
|
||||
@SaCheckPermission("system:oss:upload") |
|
||||
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT) |
|
||||
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) |
|
||||
public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) { |
|
||||
if (ObjectUtil.isNull(file)) { |
|
||||
return R.fail("上传文件不能为空"); |
|
||||
} |
|
||||
SysOssVo oss = ossService.upload(file); |
|
||||
SysOssUploadVo uploadVo = new SysOssUploadVo(); |
|
||||
uploadVo.setUrl(oss.getUrl()); |
|
||||
uploadVo.setFileName(oss.getOriginalName()); |
|
||||
uploadVo.setOssId(oss.getOssId().toString()); |
|
||||
return R.ok(uploadVo); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 下载OSS对象 |
|
||||
* |
|
||||
* @param ossId OSS对象ID |
|
||||
*/ |
|
||||
@SaCheckPermission("system:oss:download") |
|
||||
@GetMapping("/download/{ossId}") |
|
||||
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException { |
|
||||
ossService.download(ossId, response); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 删除OSS对象存储 |
|
||||
* |
|
||||
* @param ossIds OSS对象ID串 |
|
||||
*/ |
|
||||
@SaCheckPermission("system:oss:remove") |
|
||||
@Log(title = "OSS对象存储", businessType = BusinessType.DELETE) |
|
||||
@DeleteMapping("/{ossIds}") |
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") |
|
||||
@PathVariable Long[] ossIds) { |
|
||||
return toAjax(ossService.deleteWithValidByIds(List.of(ossIds), true)); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,50 +0,0 @@ |
|||||
package org.dromara.system.domain; |
|
||||
|
|
||||
import com.baomidou.mybatisplus.annotation.TableId; |
|
||||
import com.baomidou.mybatisplus.annotation.TableName; |
|
||||
import org.dromara.common.tenant.core.TenantEntity; |
|
||||
import lombok.Data; |
|
||||
import lombok.EqualsAndHashCode; |
|
||||
|
|
||||
/** |
|
||||
* OSS对象存储对象 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Data |
|
||||
@EqualsAndHashCode(callSuper = true) |
|
||||
@TableName("sys_oss") |
|
||||
public class SysOss extends TenantEntity { |
|
||||
|
|
||||
/** |
|
||||
* 对象存储主键 |
|
||||
*/ |
|
||||
@TableId(value = "oss_id") |
|
||||
private Long ossId; |
|
||||
|
|
||||
/** |
|
||||
* 文件名 |
|
||||
*/ |
|
||||
private String fileName; |
|
||||
|
|
||||
/** |
|
||||
* 原名 |
|
||||
*/ |
|
||||
private String originalName; |
|
||||
|
|
||||
/** |
|
||||
* 文件后缀名 |
|
||||
*/ |
|
||||
private String fileSuffix; |
|
||||
|
|
||||
/** |
|
||||
* URL地址 |
|
||||
*/ |
|
||||
private String url; |
|
||||
|
|
||||
/** |
|
||||
* 服务商 |
|
||||
*/ |
|
||||
private String service; |
|
||||
|
|
||||
} |
|
||||
@ -1,89 +0,0 @@ |
|||||
package org.dromara.system.domain; |
|
||||
|
|
||||
import com.baomidou.mybatisplus.annotation.TableId; |
|
||||
import com.baomidou.mybatisplus.annotation.TableName; |
|
||||
import lombok.Data; |
|
||||
import lombok.EqualsAndHashCode; |
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity; |
|
||||
|
|
||||
/** |
|
||||
* 对象存储配置对象 sys_oss_config |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Data |
|
||||
@EqualsAndHashCode(callSuper = true) |
|
||||
@TableName("sys_oss_config") |
|
||||
public class SysOssConfig extends BaseEntity { |
|
||||
|
|
||||
/** |
|
||||
* 主键 |
|
||||
*/ |
|
||||
@TableId(value = "oss_config_id") |
|
||||
private Long ossConfigId; |
|
||||
|
|
||||
/** |
|
||||
* 配置key |
|
||||
*/ |
|
||||
private String configKey; |
|
||||
|
|
||||
/** |
|
||||
* accessKey |
|
||||
*/ |
|
||||
private String accessKey; |
|
||||
|
|
||||
/** |
|
||||
* 秘钥 |
|
||||
*/ |
|
||||
private String secretKey; |
|
||||
|
|
||||
/** |
|
||||
* 桶名称 |
|
||||
*/ |
|
||||
private String bucketName; |
|
||||
|
|
||||
/** |
|
||||
* 前缀 |
|
||||
*/ |
|
||||
private String prefix; |
|
||||
|
|
||||
/** |
|
||||
* 访问站点 |
|
||||
*/ |
|
||||
private String endpoint; |
|
||||
|
|
||||
/** |
|
||||
* 自定义域名 |
|
||||
*/ |
|
||||
private String domain; |
|
||||
|
|
||||
/** |
|
||||
* 是否https(0否 1是) |
|
||||
*/ |
|
||||
private String isHttps; |
|
||||
|
|
||||
/** |
|
||||
* 域 |
|
||||
*/ |
|
||||
private String region; |
|
||||
|
|
||||
/** |
|
||||
* 是否默认(0=是,1=否) |
|
||||
*/ |
|
||||
private String status; |
|
||||
|
|
||||
/** |
|
||||
* 扩展字段 |
|
||||
*/ |
|
||||
private String ext1; |
|
||||
|
|
||||
/** |
|
||||
* 备注 |
|
||||
*/ |
|
||||
private String remark; |
|
||||
|
|
||||
/** |
|
||||
* 桶权限类型(0private 1public 2custom) |
|
||||
*/ |
|
||||
private String accessPolicy; |
|
||||
} |
|
||||
@ -1,49 +0,0 @@ |
|||||
package org.dromara.system.domain.bo; |
|
||||
|
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity; |
|
||||
import org.dromara.system.domain.SysOss; |
|
||||
import io.github.linpeilie.annotations.AutoMapper; |
|
||||
import lombok.Data; |
|
||||
import lombok.EqualsAndHashCode; |
|
||||
|
|
||||
/** |
|
||||
* OSS对象存储分页查询对象 sys_oss |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Data |
|
||||
@EqualsAndHashCode(callSuper = true) |
|
||||
@AutoMapper(target = SysOss.class, reverseConvertGenerate = false) |
|
||||
public class SysOssBo extends BaseEntity { |
|
||||
|
|
||||
/** |
|
||||
* ossId |
|
||||
*/ |
|
||||
private Long ossId; |
|
||||
|
|
||||
/** |
|
||||
* 文件名 |
|
||||
*/ |
|
||||
private String fileName; |
|
||||
|
|
||||
/** |
|
||||
* 原名 |
|
||||
*/ |
|
||||
private String originalName; |
|
||||
|
|
||||
/** |
|
||||
* 文件后缀名 |
|
||||
*/ |
|
||||
private String fileSuffix; |
|
||||
|
|
||||
/** |
|
||||
* URL地址 |
|
||||
*/ |
|
||||
private String url; |
|
||||
|
|
||||
/** |
|
||||
* 服务商 |
|
||||
*/ |
|
||||
private String service; |
|
||||
|
|
||||
} |
|
||||
@ -1,109 +0,0 @@ |
|||||
package org.dromara.system.domain.bo; |
|
||||
|
|
||||
import org.dromara.common.core.validate.AddGroup; |
|
||||
import org.dromara.common.core.validate.EditGroup; |
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity; |
|
||||
import org.dromara.system.domain.SysOssConfig; |
|
||||
import io.github.linpeilie.annotations.AutoMapper; |
|
||||
import jakarta.validation.constraints.NotBlank; |
|
||||
import jakarta.validation.constraints.NotNull; |
|
||||
import jakarta.validation.constraints.Size; |
|
||||
import lombok.Data; |
|
||||
import lombok.EqualsAndHashCode; |
|
||||
|
|
||||
/** |
|
||||
* 对象存储配置业务对象 sys_oss_config |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
* @author 孤舟烟雨 |
|
||||
* @date 2021-08-13 |
|
||||
*/ |
|
||||
|
|
||||
@Data |
|
||||
@EqualsAndHashCode(callSuper = true) |
|
||||
@AutoMapper(target = SysOssConfig.class, reverseConvertGenerate = false) |
|
||||
public class SysOssConfigBo extends BaseEntity { |
|
||||
|
|
||||
/** |
|
||||
* 主键 |
|
||||
*/ |
|
||||
@NotNull(message = "主键不能为空", groups = {EditGroup.class}) |
|
||||
private Long ossConfigId; |
|
||||
|
|
||||
/** |
|
||||
* 配置key |
|
||||
*/ |
|
||||
@NotBlank(message = "配置key不能为空", groups = {AddGroup.class, EditGroup.class}) |
|
||||
@Size(min = 2, max = 100, message = "configKey长度必须介于{min}和{max} 之间") |
|
||||
private String configKey; |
|
||||
|
|
||||
/** |
|
||||
* accessKey |
|
||||
*/ |
|
||||
@NotBlank(message = "accessKey不能为空", groups = {AddGroup.class, EditGroup.class}) |
|
||||
@Size(min = 2, max = 100, message = "accessKey长度必须介于{min}和{max} 之间") |
|
||||
private String accessKey; |
|
||||
|
|
||||
/** |
|
||||
* 秘钥 |
|
||||
*/ |
|
||||
@NotBlank(message = "secretKey不能为空", groups = {AddGroup.class, EditGroup.class}) |
|
||||
@Size(min = 2, max = 100, message = "secretKey长度必须介于{min}和{max} 之间") |
|
||||
private String secretKey; |
|
||||
|
|
||||
/** |
|
||||
* 桶名称 |
|
||||
*/ |
|
||||
@NotBlank(message = "桶名称不能为空", groups = {AddGroup.class, EditGroup.class}) |
|
||||
@Size(min = 2, max = 100, message = "bucketName长度必须介于{min}和{max}之间") |
|
||||
private String bucketName; |
|
||||
|
|
||||
/** |
|
||||
* 前缀 |
|
||||
*/ |
|
||||
private String prefix; |
|
||||
|
|
||||
/** |
|
||||
* 访问站点 |
|
||||
*/ |
|
||||
@NotBlank(message = "访问站点不能为空", groups = {AddGroup.class, EditGroup.class}) |
|
||||
@Size(min = 2, max = 100, message = "endpoint长度必须介于{min}和{max}之间") |
|
||||
private String endpoint; |
|
||||
|
|
||||
/** |
|
||||
* 自定义域名 |
|
||||
*/ |
|
||||
private String domain; |
|
||||
|
|
||||
/** |
|
||||
* 是否https(Y=是,N=否) |
|
||||
*/ |
|
||||
private String isHttps; |
|
||||
|
|
||||
/** |
|
||||
* 是否默认(0=是,1=否) |
|
||||
*/ |
|
||||
private String status; |
|
||||
|
|
||||
/** |
|
||||
* 域 |
|
||||
*/ |
|
||||
private String region; |
|
||||
|
|
||||
/** |
|
||||
* 扩展字段 |
|
||||
*/ |
|
||||
private String ext1; |
|
||||
|
|
||||
/** |
|
||||
* 备注 |
|
||||
*/ |
|
||||
private String remark; |
|
||||
|
|
||||
/** |
|
||||
* 桶权限类型(0private 1public 2custom) |
|
||||
*/ |
|
||||
@NotBlank(message = "桶权限类型不能为空", groups = {AddGroup.class, EditGroup.class}) |
|
||||
private String accessPolicy; |
|
||||
|
|
||||
} |
|
||||
@ -1,97 +0,0 @@ |
|||||
package org.dromara.system.domain.vo; |
|
||||
|
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; |
|
||||
import org.dromara.system.domain.SysOssConfig; |
|
||||
import io.github.linpeilie.annotations.AutoMapper; |
|
||||
import lombok.Data; |
|
||||
|
|
||||
import java.io.Serial; |
|
||||
import java.io.Serializable; |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* 对象存储配置视图对象 sys_oss_config |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
* @author 孤舟烟雨 |
|
||||
* @date 2021-08-13 |
|
||||
*/ |
|
||||
@Data |
|
||||
@ExcelIgnoreUnannotated |
|
||||
@AutoMapper(target = SysOssConfig.class) |
|
||||
public class SysOssConfigVo implements Serializable { |
|
||||
|
|
||||
@Serial |
|
||||
private static final long serialVersionUID = 1L; |
|
||||
|
|
||||
/** |
|
||||
* 主键 |
|
||||
*/ |
|
||||
private Long ossConfigId; |
|
||||
|
|
||||
/** |
|
||||
* 配置key |
|
||||
*/ |
|
||||
private String configKey; |
|
||||
|
|
||||
/** |
|
||||
* accessKey |
|
||||
*/ |
|
||||
private String accessKey; |
|
||||
|
|
||||
/** |
|
||||
* 秘钥 |
|
||||
*/ |
|
||||
private String secretKey; |
|
||||
|
|
||||
/** |
|
||||
* 桶名称 |
|
||||
*/ |
|
||||
private String bucketName; |
|
||||
|
|
||||
/** |
|
||||
* 前缀 |
|
||||
*/ |
|
||||
private String prefix; |
|
||||
|
|
||||
/** |
|
||||
* 访问站点 |
|
||||
*/ |
|
||||
private String endpoint; |
|
||||
|
|
||||
/** |
|
||||
* 自定义域名 |
|
||||
*/ |
|
||||
private String domain; |
|
||||
|
|
||||
/** |
|
||||
* 是否https(Y=是,N=否) |
|
||||
*/ |
|
||||
private String isHttps; |
|
||||
|
|
||||
/** |
|
||||
* 域 |
|
||||
*/ |
|
||||
private String region; |
|
||||
|
|
||||
/** |
|
||||
* 是否默认(0=是,1=否) |
|
||||
*/ |
|
||||
private String status; |
|
||||
|
|
||||
/** |
|
||||
* 扩展字段 |
|
||||
*/ |
|
||||
private String ext1; |
|
||||
|
|
||||
/** |
|
||||
* 备注 |
|
||||
*/ |
|
||||
private String remark; |
|
||||
|
|
||||
/** |
|
||||
* 桶权限类型(0private 1public 2custom) |
|
||||
*/ |
|
||||
private String accessPolicy; |
|
||||
|
|
||||
} |
|
||||
@ -1,28 +0,0 @@ |
|||||
package org.dromara.system.domain.vo; |
|
||||
|
|
||||
import lombok.Data; |
|
||||
|
|
||||
/** |
|
||||
* 上传对象信息 |
|
||||
* |
|
||||
* @author Michelle.Chung |
|
||||
*/ |
|
||||
@Data |
|
||||
public class SysOssUploadVo { |
|
||||
|
|
||||
/** |
|
||||
* URL地址 |
|
||||
*/ |
|
||||
private String url; |
|
||||
|
|
||||
/** |
|
||||
* 文件名 |
|
||||
*/ |
|
||||
private String fileName; |
|
||||
|
|
||||
/** |
|
||||
* 对象存储主键 |
|
||||
*/ |
|
||||
private String ossId; |
|
||||
|
|
||||
} |
|
||||
@ -1,72 +0,0 @@ |
|||||
package org.dromara.system.domain.vo; |
|
||||
|
|
||||
import org.dromara.common.translation.annotation.Translation; |
|
||||
import org.dromara.common.translation.constant.TransConstant; |
|
||||
import org.dromara.system.domain.SysOss; |
|
||||
import io.github.linpeilie.annotations.AutoMapper; |
|
||||
import lombok.Data; |
|
||||
|
|
||||
import java.io.Serial; |
|
||||
import java.io.Serializable; |
|
||||
import java.util.Date; |
|
||||
|
|
||||
/** |
|
||||
* OSS对象存储视图对象 sys_oss |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Data |
|
||||
@AutoMapper(target = SysOss.class) |
|
||||
public class SysOssVo implements Serializable { |
|
||||
|
|
||||
@Serial |
|
||||
private static final long serialVersionUID = 1L; |
|
||||
|
|
||||
/** |
|
||||
* 对象存储主键 |
|
||||
*/ |
|
||||
private Long ossId; |
|
||||
|
|
||||
/** |
|
||||
* 文件名 |
|
||||
*/ |
|
||||
private String fileName; |
|
||||
|
|
||||
/** |
|
||||
* 原名 |
|
||||
*/ |
|
||||
private String originalName; |
|
||||
|
|
||||
/** |
|
||||
* 文件后缀名 |
|
||||
*/ |
|
||||
private String fileSuffix; |
|
||||
|
|
||||
/** |
|
||||
* URL地址 |
|
||||
*/ |
|
||||
private String url; |
|
||||
|
|
||||
/** |
|
||||
* 创建时间 |
|
||||
*/ |
|
||||
private Date createTime; |
|
||||
|
|
||||
/** |
|
||||
* 上传人 |
|
||||
*/ |
|
||||
private Long createBy; |
|
||||
|
|
||||
/** |
|
||||
* 上传人名称 |
|
||||
*/ |
|
||||
@Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy") |
|
||||
private String createByName; |
|
||||
|
|
||||
/** |
|
||||
* 服务商 |
|
||||
*/ |
|
||||
private String service; |
|
||||
|
|
||||
|
|
||||
} |
|
||||
@ -1,16 +0,0 @@ |
|||||
package org.dromara.system.mapper; |
|
||||
|
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; |
|
||||
import org.dromara.system.domain.SysOssConfig; |
|
||||
import org.dromara.system.domain.vo.SysOssConfigVo; |
|
||||
|
|
||||
/** |
|
||||
* 对象存储配置Mapper接口 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
* @author 孤舟烟雨 |
|
||||
* @date 2021-08-13 |
|
||||
*/ |
|
||||
public interface SysOssConfigMapper extends BaseMapperPlus<SysOssConfig, SysOssConfigVo> { |
|
||||
|
|
||||
} |
|
||||
@ -1,13 +0,0 @@ |
|||||
package org.dromara.system.mapper; |
|
||||
|
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; |
|
||||
import org.dromara.system.domain.SysOss; |
|
||||
import org.dromara.system.domain.vo.SysOssVo; |
|
||||
|
|
||||
/** |
|
||||
* 文件上传 数据层 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
public interface SysOssMapper extends BaseMapperPlus<SysOss, SysOssVo> { |
|
||||
} |
|
||||
@ -1,28 +0,0 @@ |
|||||
package org.dromara.system.runner; |
|
||||
|
|
||||
import org.dromara.system.service.ISysOssConfigService; |
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.springframework.boot.ApplicationArguments; |
|
||||
import org.springframework.boot.ApplicationRunner; |
|
||||
import org.springframework.stereotype.Component; |
|
||||
|
|
||||
/** |
|
||||
* 初始化 system 模块对应业务数据 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@Slf4j |
|
||||
@RequiredArgsConstructor |
|
||||
@Component |
|
||||
public class SystemApplicationRunner implements ApplicationRunner { |
|
||||
|
|
||||
private final ISysOssConfigService ossConfigService; |
|
||||
|
|
||||
@Override |
|
||||
public void run(ApplicationArguments args) throws Exception { |
|
||||
ossConfigService.init(); |
|
||||
log.info("初始化OSS配置成功"); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,64 +0,0 @@ |
|||||
package org.dromara.system.service; |
|
||||
|
|
||||
import org.dromara.common.mybatis.core.page.PageQuery; |
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo; |
|
||||
import org.dromara.system.domain.bo.SysOssConfigBo; |
|
||||
import org.dromara.system.domain.vo.SysOssConfigVo; |
|
||||
|
|
||||
import java.util.Collection; |
|
||||
|
|
||||
/** |
|
||||
* 对象存储配置Service接口 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
* @author 孤舟烟雨 |
|
||||
* @date 2021-08-13 |
|
||||
*/ |
|
||||
public interface ISysOssConfigService { |
|
||||
|
|
||||
/** |
|
||||
* 初始化OSS配置 |
|
||||
*/ |
|
||||
void init(); |
|
||||
|
|
||||
/** |
|
||||
* 查询单个 |
|
||||
*/ |
|
||||
SysOssConfigVo queryById(Long ossConfigId); |
|
||||
|
|
||||
/** |
|
||||
* 查询列表 |
|
||||
*/ |
|
||||
TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo, PageQuery pageQuery); |
|
||||
|
|
||||
/** |
|
||||
* 根据新增业务对象插入对象存储配置 |
|
||||
* |
|
||||
* @param bo 对象存储配置新增业务对象 |
|
||||
* @return 结果 |
|
||||
*/ |
|
||||
Boolean insertByBo(SysOssConfigBo bo); |
|
||||
|
|
||||
/** |
|
||||
* 根据编辑业务对象修改对象存储配置 |
|
||||
* |
|
||||
* @param bo 对象存储配置编辑业务对象 |
|
||||
* @return 结果 |
|
||||
*/ |
|
||||
Boolean updateByBo(SysOssConfigBo bo); |
|
||||
|
|
||||
/** |
|
||||
* 校验并删除数据 |
|
||||
* |
|
||||
* @param ids 主键集合 |
|
||||
* @param isValid 是否校验,true-删除前校验,false-不校验 |
|
||||
* @return 结果 |
|
||||
*/ |
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid); |
|
||||
|
|
||||
/** |
|
||||
* 启用停用状态 |
|
||||
*/ |
|
||||
int updateOssConfigStatus(SysOssConfigBo bo); |
|
||||
|
|
||||
} |
|
||||
@ -1,80 +0,0 @@ |
|||||
package org.dromara.system.service; |
|
||||
|
|
||||
import org.dromara.common.mybatis.core.page.PageQuery; |
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo; |
|
||||
import org.dromara.system.domain.bo.SysOssBo; |
|
||||
import org.dromara.system.domain.vo.SysOssVo; |
|
||||
import jakarta.servlet.http.HttpServletResponse; |
|
||||
import org.springframework.web.multipart.MultipartFile; |
|
||||
|
|
||||
import java.io.File; |
|
||||
import java.io.IOException; |
|
||||
import java.util.Collection; |
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* 文件上传 服务层 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
public interface ISysOssService { |
|
||||
|
|
||||
/** |
|
||||
* 查询OSS对象存储列表 |
|
||||
* |
|
||||
* @param sysOss OSS对象存储分页查询对象 |
|
||||
* @param pageQuery 分页查询实体类 |
|
||||
* @return 结果 |
|
||||
*/ |
|
||||
TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery); |
|
||||
|
|
||||
/** |
|
||||
* 根据一组 ossIds 获取对应的 SysOssVo 列表 |
|
||||
* |
|
||||
* @param ossIds 一组文件在数据库中的唯一标识集合 |
|
||||
* @return 包含 SysOssVo 对象的列表 |
|
||||
*/ |
|
||||
List<SysOssVo> listByIds(Collection<Long> ossIds); |
|
||||
|
|
||||
/** |
|
||||
* 根据 ossId 从缓存或数据库中获取 SysOssVo 对象 |
|
||||
* |
|
||||
* @param ossId 文件在数据库中的唯一标识 |
|
||||
* @return SysOssVo 对象,包含文件信息 |
|
||||
*/ |
|
||||
SysOssVo getById(Long ossId); |
|
||||
|
|
||||
/** |
|
||||
* 上传 MultipartFile 到对象存储服务,并保存文件信息到数据库 |
|
||||
* |
|
||||
* @param file 要上传的 MultipartFile 对象 |
|
||||
* @return 上传成功后的 SysOssVo 对象,包含文件信息 |
|
||||
*/ |
|
||||
SysOssVo upload(MultipartFile file); |
|
||||
|
|
||||
/** |
|
||||
* 上传文件到对象存储服务,并保存文件信息到数据库 |
|
||||
* |
|
||||
* @param file 要上传的文件对象 |
|
||||
* @return 上传成功后的 SysOssVo 对象,包含文件信息 |
|
||||
*/ |
|
||||
SysOssVo upload(File file); |
|
||||
|
|
||||
/** |
|
||||
* 文件下载方法,支持一次性下载完整文件 |
|
||||
* |
|
||||
* @param ossId OSS对象ID |
|
||||
* @param response HttpServletResponse对象,用于设置响应头和向客户端发送文件内容 |
|
||||
*/ |
|
||||
void download(Long ossId, HttpServletResponse response) throws IOException; |
|
||||
|
|
||||
/** |
|
||||
* 删除OSS对象存储 |
|
||||
* |
|
||||
* @param ids OSS对象ID串 |
|
||||
* @param isValid 判断是否需要校验 |
|
||||
* @return 结果 |
|
||||
*/ |
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid); |
|
||||
|
|
||||
} |
|
||||
@ -1,176 +0,0 @@ |
|||||
package org.dromara.system.service.impl; |
|
||||
|
|
||||
import cn.hutool.core.collection.CollUtil; |
|
||||
import cn.hutool.core.util.ObjectUtil; |
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.dromara.common.core.constant.CacheNames; |
|
||||
import org.dromara.common.core.exception.ServiceException; |
|
||||
import org.dromara.common.core.utils.MapstructUtils; |
|
||||
import org.dromara.common.core.utils.StringUtils; |
|
||||
import org.dromara.common.json.utils.JsonUtils; |
|
||||
import org.dromara.common.mybatis.core.page.PageQuery; |
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo; |
|
||||
import org.dromara.common.oss.constant.OssConstant; |
|
||||
import org.dromara.common.redis.utils.CacheUtils; |
|
||||
import org.dromara.common.redis.utils.RedisUtils; |
|
||||
import org.dromara.system.domain.SysOssConfig; |
|
||||
import org.dromara.system.domain.bo.SysOssConfigBo; |
|
||||
import org.dromara.system.domain.vo.SysOssConfigVo; |
|
||||
import org.dromara.system.mapper.SysOssConfigMapper; |
|
||||
import org.dromara.system.service.ISysOssConfigService; |
|
||||
import org.springframework.stereotype.Service; |
|
||||
import org.springframework.transaction.annotation.Transactional; |
|
||||
|
|
||||
import java.util.Collection; |
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* 对象存储配置Service业务层处理 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
* @author 孤舟烟雨 |
|
||||
* @date 2021-08-13 |
|
||||
*/ |
|
||||
@Slf4j |
|
||||
@RequiredArgsConstructor |
|
||||
@Service |
|
||||
public class SysOssConfigServiceImpl implements ISysOssConfigService { |
|
||||
|
|
||||
private final SysOssConfigMapper baseMapper; |
|
||||
|
|
||||
/** |
|
||||
* 项目启动时,初始化参数到缓存,加载配置类 |
|
||||
*/ |
|
||||
@Override |
|
||||
public void init() { |
|
||||
List<SysOssConfig> list = baseMapper.selectList(); |
|
||||
// 加载OSS初始化配置
|
|
||||
for (SysOssConfig config : list) { |
|
||||
String configKey = config.getConfigKey(); |
|
||||
if ("0".equals(config.getStatus())) { |
|
||||
RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, configKey); |
|
||||
} |
|
||||
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public SysOssConfigVo queryById(Long ossConfigId) { |
|
||||
return baseMapper.selectVoById(ossConfigId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo, PageQuery pageQuery) { |
|
||||
LambdaQueryWrapper<SysOssConfig> lqw = buildQueryWrapper(bo); |
|
||||
Page<SysOssConfigVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw); |
|
||||
return TableDataInfo.build(result); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
private LambdaQueryWrapper<SysOssConfig> buildQueryWrapper(SysOssConfigBo bo) { |
|
||||
LambdaQueryWrapper<SysOssConfig> lqw = Wrappers.lambdaQuery(); |
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getConfigKey()), SysOssConfig::getConfigKey, bo.getConfigKey()); |
|
||||
lqw.like(StringUtils.isNotBlank(bo.getBucketName()), SysOssConfig::getBucketName, bo.getBucketName()); |
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysOssConfig::getStatus, bo.getStatus()); |
|
||||
lqw.orderByAsc(SysOssConfig::getOssConfigId); |
|
||||
return lqw; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Boolean insertByBo(SysOssConfigBo bo) { |
|
||||
SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class); |
|
||||
validEntityBeforeSave(config); |
|
||||
boolean flag = baseMapper.insert(config) > 0; |
|
||||
if (flag) { |
|
||||
// 从数据库查询完整的数据做缓存
|
|
||||
config = baseMapper.selectById(config.getOssConfigId()); |
|
||||
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config)); |
|
||||
} |
|
||||
return flag; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Boolean updateByBo(SysOssConfigBo bo) { |
|
||||
SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class); |
|
||||
validEntityBeforeSave(config); |
|
||||
LambdaUpdateWrapper<SysOssConfig> luw = new LambdaUpdateWrapper<>(); |
|
||||
luw.set(ObjectUtil.isNull(config.getPrefix()), SysOssConfig::getPrefix, ""); |
|
||||
luw.set(ObjectUtil.isNull(config.getRegion()), SysOssConfig::getRegion, ""); |
|
||||
luw.set(ObjectUtil.isNull(config.getExt1()), SysOssConfig::getExt1, ""); |
|
||||
luw.set(ObjectUtil.isNull(config.getRemark()), SysOssConfig::getRemark, ""); |
|
||||
luw.eq(SysOssConfig::getOssConfigId, config.getOssConfigId()); |
|
||||
boolean flag = baseMapper.update(config, luw) > 0; |
|
||||
if (flag) { |
|
||||
// 从数据库查询完整的数据做缓存
|
|
||||
config = baseMapper.selectById(config.getOssConfigId()); |
|
||||
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config)); |
|
||||
} |
|
||||
return flag; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 保存前的数据校验 |
|
||||
*/ |
|
||||
private void validEntityBeforeSave(SysOssConfig entity) { |
|
||||
if (StringUtils.isNotEmpty(entity.getConfigKey()) |
|
||||
&& !checkConfigKeyUnique(entity)) { |
|
||||
throw new ServiceException("操作配置'" + entity.getConfigKey() + "'失败, 配置key已存在!"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) { |
|
||||
if (isValid) { |
|
||||
if (CollUtil.containsAny(ids, OssConstant.SYSTEM_DATA_IDS)) { |
|
||||
throw new ServiceException("系统内置, 不可删除!"); |
|
||||
} |
|
||||
} |
|
||||
List<SysOssConfig> list = CollUtil.newArrayList(); |
|
||||
for (Long configId : ids) { |
|
||||
SysOssConfig config = baseMapper.selectById(configId); |
|
||||
list.add(config); |
|
||||
} |
|
||||
boolean flag = baseMapper.deleteByIds(ids) > 0; |
|
||||
if (flag) { |
|
||||
list.forEach(sysOssConfig -> |
|
||||
CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, sysOssConfig.getConfigKey())); |
|
||||
} |
|
||||
return flag; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 判断configKey是否唯一 |
|
||||
*/ |
|
||||
private boolean checkConfigKeyUnique(SysOssConfig sysOssConfig) { |
|
||||
long ossConfigId = ObjectUtil.isNull(sysOssConfig.getOssConfigId()) ? -1L : sysOssConfig.getOssConfigId(); |
|
||||
SysOssConfig info = baseMapper.selectOne(new LambdaQueryWrapper<SysOssConfig>() |
|
||||
.select(SysOssConfig::getOssConfigId, SysOssConfig::getConfigKey) |
|
||||
.eq(SysOssConfig::getConfigKey, sysOssConfig.getConfigKey())); |
|
||||
if (ObjectUtil.isNotNull(info) && info.getOssConfigId() != ossConfigId) { |
|
||||
return false; |
|
||||
} |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 启用禁用状态 |
|
||||
*/ |
|
||||
@Override |
|
||||
@Transactional(rollbackFor = Exception.class) |
|
||||
public int updateOssConfigStatus(SysOssConfigBo bo) { |
|
||||
SysOssConfig sysOssConfig = MapstructUtils.convert(bo, SysOssConfig.class); |
|
||||
int row = baseMapper.update(null, new LambdaUpdateWrapper<SysOssConfig>() |
|
||||
.set(SysOssConfig::getStatus, "1")); |
|
||||
row += baseMapper.updateById(sysOssConfig); |
|
||||
if (row > 0) { |
|
||||
RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, sysOssConfig.getConfigKey()); |
|
||||
} |
|
||||
return row; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,269 +0,0 @@ |
|||||
package org.dromara.system.service.impl; |
|
||||
|
|
||||
import cn.hutool.core.bean.BeanUtil; |
|
||||
import cn.hutool.core.convert.Convert; |
|
||||
import cn.hutool.core.util.ObjectUtil; |
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|
||||
import jakarta.servlet.http.HttpServletResponse; |
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import org.dromara.common.core.constant.CacheNames; |
|
||||
import org.dromara.common.core.domain.dto.OssDTO; |
|
||||
import org.dromara.common.core.exception.ServiceException; |
|
||||
import org.dromara.common.core.service.OssService; |
|
||||
import org.dromara.common.core.utils.MapstructUtils; |
|
||||
import org.dromara.common.core.utils.SpringUtils; |
|
||||
import org.dromara.common.core.utils.StreamUtils; |
|
||||
import org.dromara.common.core.utils.StringUtils; |
|
||||
import org.dromara.common.core.utils.file.FileUtils; |
|
||||
import org.dromara.common.mybatis.core.page.PageQuery; |
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo; |
|
||||
import org.dromara.common.oss.core.OssClient; |
|
||||
import org.dromara.common.oss.entity.UploadResult; |
|
||||
import org.dromara.common.oss.enumd.AccessPolicyType; |
|
||||
import org.dromara.common.oss.factory.OssFactory; |
|
||||
import org.dromara.system.domain.SysOss; |
|
||||
import org.dromara.system.domain.bo.SysOssBo; |
|
||||
import org.dromara.system.domain.vo.SysOssVo; |
|
||||
import org.dromara.system.mapper.SysOssMapper; |
|
||||
import org.dromara.system.service.ISysOssService; |
|
||||
import org.jetbrains.annotations.NotNull; |
|
||||
import org.springframework.cache.annotation.Cacheable; |
|
||||
import org.springframework.http.MediaType; |
|
||||
import org.springframework.stereotype.Service; |
|
||||
import org.springframework.web.multipart.MultipartFile; |
|
||||
|
|
||||
import java.io.File; |
|
||||
import java.io.IOException; |
|
||||
import java.util.ArrayList; |
|
||||
import java.util.Collection; |
|
||||
import java.util.List; |
|
||||
import java.util.Map; |
|
||||
|
|
||||
/** |
|
||||
* 文件上传 服务层实现 |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
*/ |
|
||||
@RequiredArgsConstructor |
|
||||
@Service |
|
||||
public class SysOssServiceImpl implements ISysOssService, OssService { |
|
||||
|
|
||||
private final SysOssMapper baseMapper; |
|
||||
|
|
||||
/** |
|
||||
* 查询OSS对象存储列表 |
|
||||
* |
|
||||
* @param bo OSS对象存储分页查询对象 |
|
||||
* @param pageQuery 分页查询实体类 |
|
||||
* @return 结果 |
|
||||
*/ |
|
||||
@Override |
|
||||
public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo, PageQuery pageQuery) { |
|
||||
LambdaQueryWrapper<SysOss> lqw = buildQueryWrapper(bo); |
|
||||
Page<SysOssVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw); |
|
||||
List<SysOssVo> filterResult = StreamUtils.toList(result.getRecords(), this::matchingUrl); |
|
||||
result.setRecords(filterResult); |
|
||||
return TableDataInfo.build(result); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 根据一组 ossIds 获取对应的 SysOssVo 列表 |
|
||||
* |
|
||||
* @param ossIds 一组文件在数据库中的唯一标识集合 |
|
||||
* @return 包含 SysOssVo 对象的列表 |
|
||||
*/ |
|
||||
@Override |
|
||||
public List<SysOssVo> listByIds(Collection<Long> ossIds) { |
|
||||
List<SysOssVo> list = new ArrayList<>(); |
|
||||
SysOssServiceImpl ossService = SpringUtils.getAopProxy(this); |
|
||||
for (Long id : ossIds) { |
|
||||
SysOssVo vo = ossService.getById(id); |
|
||||
if (ObjectUtil.isNotNull(vo)) { |
|
||||
try { |
|
||||
list.add(this.matchingUrl(vo)); |
|
||||
} catch (Exception ignored) { |
|
||||
// 如果oss异常无法连接则将数据直接返回
|
|
||||
list.add(vo); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return list; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 根据一组 ossIds 获取对应文件的 URL 列表 |
|
||||
* |
|
||||
* @param ossIds 以逗号分隔的 ossId 字符串 |
|
||||
* @return 以逗号分隔的文件 URL 字符串 |
|
||||
*/ |
|
||||
@Override |
|
||||
public String selectUrlByIds(String ossIds) { |
|
||||
List<String> list = new ArrayList<>(); |
|
||||
SysOssServiceImpl ossService = SpringUtils.getAopProxy(this); |
|
||||
for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) { |
|
||||
SysOssVo vo = ossService.getById(id); |
|
||||
if (ObjectUtil.isNotNull(vo)) { |
|
||||
try { |
|
||||
list.add(this.matchingUrl(vo).getUrl()); |
|
||||
} catch (Exception ignored) { |
|
||||
// 如果oss异常无法连接则将数据直接返回
|
|
||||
list.add(vo.getUrl()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return String.join(StringUtils.SEPARATOR, list); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public List<OssDTO> selectByIds(String ossIds) { |
|
||||
List<OssDTO> list = new ArrayList<>(); |
|
||||
for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) { |
|
||||
SysOssVo vo = SpringUtils.getAopProxy(this).getById(id); |
|
||||
if (ObjectUtil.isNotNull(vo)) { |
|
||||
try { |
|
||||
vo.setUrl(this.matchingUrl(vo).getUrl()); |
|
||||
list.add(BeanUtil.toBean(vo, OssDTO.class)); |
|
||||
} catch (Exception ignored) { |
|
||||
// 如果oss异常无法连接则将数据直接返回
|
|
||||
list.add(BeanUtil.toBean(vo, OssDTO.class)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return list; |
|
||||
} |
|
||||
|
|
||||
private LambdaQueryWrapper<SysOss> buildQueryWrapper(SysOssBo bo) { |
|
||||
Map<String, Object> params = bo.getParams(); |
|
||||
LambdaQueryWrapper<SysOss> lqw = Wrappers.lambdaQuery(); |
|
||||
lqw.like(StringUtils.isNotBlank(bo.getFileName()), SysOss::getFileName, bo.getFileName()); |
|
||||
lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), SysOss::getOriginalName, bo.getOriginalName()); |
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix()); |
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl()); |
|
||||
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, |
|
||||
SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); |
|
||||
lqw.eq(ObjectUtil.isNotNull(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy()); |
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService()); |
|
||||
lqw.orderByAsc(SysOss::getOssId); |
|
||||
return lqw; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 根据 ossId 从缓存或数据库中获取 SysOssVo 对象 |
|
||||
* |
|
||||
* @param ossId 文件在数据库中的唯一标识 |
|
||||
* @return SysOssVo 对象,包含文件信息 |
|
||||
*/ |
|
||||
@Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId") |
|
||||
@Override |
|
||||
public SysOssVo getById(Long ossId) { |
|
||||
return baseMapper.selectVoById(ossId); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* 文件下载方法,支持一次性下载完整文件 |
|
||||
* |
|
||||
* @param ossId OSS对象ID |
|
||||
* @param response HttpServletResponse对象,用于设置响应头和向客户端发送文件内容 |
|
||||
*/ |
|
||||
@Override |
|
||||
public void download(Long ossId, HttpServletResponse response) throws IOException { |
|
||||
SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId); |
|
||||
if (ObjectUtil.isNull(sysOss)) { |
|
||||
throw new ServiceException("文件数据不存在!"); |
|
||||
} |
|
||||
FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName()); |
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8"); |
|
||||
OssClient storage = OssFactory.instance(sysOss.getService()); |
|
||||
long contentLength = storage.download(sysOss.getFileName(), response.getOutputStream()); |
|
||||
response.setContentLengthLong(contentLength); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 上传 MultipartFile 到对象存储服务,并保存文件信息到数据库 |
|
||||
* |
|
||||
* @param file 要上传的 MultipartFile 对象 |
|
||||
* @return 上传成功后的 SysOssVo 对象,包含文件信息 |
|
||||
* @throws ServiceException 如果上传过程中发生异常,则抛出 ServiceException 异常 |
|
||||
*/ |
|
||||
@Override |
|
||||
public SysOssVo upload(MultipartFile file) { |
|
||||
String originalfileName = file.getOriginalFilename(); |
|
||||
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); |
|
||||
OssClient storage = OssFactory.instance(); |
|
||||
UploadResult uploadResult; |
|
||||
try { |
|
||||
uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType()); |
|
||||
} catch (IOException e) { |
|
||||
throw new ServiceException(e.getMessage()); |
|
||||
} |
|
||||
// 保存文件信息
|
|
||||
return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 上传文件到对象存储服务,并保存文件信息到数据库 |
|
||||
* |
|
||||
* @param file 要上传的文件对象 |
|
||||
* @return 上传成功后的 SysOssVo 对象,包含文件信息 |
|
||||
*/ |
|
||||
@Override |
|
||||
public SysOssVo upload(File file) { |
|
||||
String originalfileName = file.getName(); |
|
||||
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); |
|
||||
OssClient storage = OssFactory.instance(); |
|
||||
UploadResult uploadResult = storage.uploadSuffix(file, suffix); |
|
||||
// 保存文件信息
|
|
||||
return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult); |
|
||||
} |
|
||||
|
|
||||
@NotNull |
|
||||
private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult) { |
|
||||
SysOss oss = new SysOss(); |
|
||||
oss.setUrl(uploadResult.getUrl()); |
|
||||
oss.setFileSuffix(suffix); |
|
||||
oss.setFileName(uploadResult.getFilename()); |
|
||||
oss.setOriginalName(originalfileName); |
|
||||
oss.setService(configKey); |
|
||||
baseMapper.insert(oss); |
|
||||
SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class); |
|
||||
return this.matchingUrl(sysOssVo); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 删除OSS对象存储 |
|
||||
* |
|
||||
* @param ids OSS对象ID串 |
|
||||
* @param isValid 判断是否需要校验 |
|
||||
* @return 结果 |
|
||||
*/ |
|
||||
@Override |
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) { |
|
||||
if (isValid) { |
|
||||
// 做一些业务上的校验,判断是否需要校验
|
|
||||
} |
|
||||
List<SysOss> list = baseMapper.selectByIds(ids); |
|
||||
for (SysOss sysOss : list) { |
|
||||
OssClient storage = OssFactory.instance(sysOss.getService()); |
|
||||
storage.delete(sysOss.getUrl()); |
|
||||
} |
|
||||
return baseMapper.deleteByIds(ids) > 0; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 桶类型为 private 的URL 修改为临时URL时长为120s |
|
||||
* |
|
||||
* @param oss OSS对象 |
|
||||
* @return oss 匹配Url的OSS对象 |
|
||||
*/ |
|
||||
private SysOssVo matchingUrl(SysOssVo oss) { |
|
||||
OssClient storage = OssFactory.instance(oss.getService()); |
|
||||
// 仅修改桶类型为 private 的URL,临时URL时长为120s
|
|
||||
if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) { |
|
||||
oss.setUrl(storage.getPrivateUrl(oss.getFileName(), 120)); |
|
||||
} |
|
||||
return oss; |
|
||||
} |
|
||||
} |
|
||||
@ -1,7 +0,0 @@ |
|||||
<?xml version="1.0" encoding="UTF-8" ?> |
|
||||
<!DOCTYPE mapper |
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|
||||
<mapper namespace="org.dromara.system.mapper.SysOssConfigMapper"> |
|
||||
|
|
||||
</mapper> |
|
||||
@ -1,5 +0,0 @@ |
|||||
<?xml version="1.0" encoding="UTF-8" ?> |
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|
||||
<mapper namespace="org.dromara.system.mapper.SysOssMapper"> |
|
||||
|
|
||||
</mapper> |
|
||||
Loading…
Reference in new issue