update 删除源ruoyi-oss模块,使用x-file-storage重新新模块ruoyi-file,支持大文件分片上传,并开发前端文件和图片上传组件

master 2.0.0
管理员 1 year ago
parent 6ed2c7c601
commit 78b7bac43b

File diff suppressed because one or more lines are too long

@ -0,0 +1,527 @@
<template>
<div>
<div v-if="!props.disabled && props.text" style="display: inline-flex;" :class="{ doing: doing || !uploadKey }"
@click="open()">
<slot name="button"><el-button>{{ props.text }}</el-button></slot>
</div>
<input ref="fileInputRef" v-if="!props.disabled" accept="image/*" @change="fileInputChange" type="file"
style="position: absolute; top:-100vh;" :multiple="props.max != 1" />
<div class="list">
<slot :list="data">
<div v-for="(item, index) in data" :key="index" class="item" :class="{ error: item.error, abort: item.abort }"
:style="{ '--ps': item.uploading ? (item.uploading?.loaded || 0) * 100 / (item.uploading?.total || 1) + '%' : '100%' }"
@click="imgPreview(index)">
<img :src="item.url ? getUrl(props.saveMin ? (item.url + '.min.webp') : item.url) : item.preview">
<div class="btns">
<div>
<svg t="1729755606480" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="3478" width="200" height="200">
<path
d="M995.474286 446.902857a569.782857 569.782857 0 0 0-95.817143-134.034286 540.708571 540.708571 0 0 0-775.314286 0 569.782857 569.782857 0 0 0-95.817143 134.034286 143.36 143.36 0 0 0 0 130.194286 569.782857 569.782857 0 0 0 95.817143 134.034286 540.708571 540.708571 0 0 0 775.314286 0 569.782857 569.782857 0 0 0 95.817143-134.034286 143.36 143.36 0 0 0 0-130.194286zM512 676.571429a164.571429 164.571429 0 1 1 164.571429-164.571429 164.571429 164.571429 0 0 1-164.571429 164.571429z"
p-id="3479"></path>
</svg>
</div>
<div v-if="item.url" @click.stop="download(item.url)">
<svg t="1729757927480" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="9348" width="200" height="200">
<path
d="M14.224034 1009.780983v-224.022264h109.647986v74.722589h776.250943V785.758719h109.653003v224.022264H14.224034zM123.87202 350.076808l259.815653 0.145501V14.219017h259.810637v329.781845h259.805619l-385.082482 404.429175-394.349427-398.353229z m0 0"
fill="#231815" p-id="9349"></path>
</svg>
</div>
<div v-if="item.uploading && !item.abort" class="btn" @click.stop="item.uploading.abort()">
<svg t="1729756297498" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="9240" width="200" height="200">
<path
d="M507.426133 156.4672c-4.2496-0.119467-8.413867 0.119467-12.6464 0.136533V56.661333c0-27.392-30.651733-43.6224-53.316266-28.245333L82.756267 272.0256a34.133333 34.133333 0 0 0 0 56.4736l358.741333 243.592533c22.664533 15.394133 53.316267-0.836267 53.316267-28.245333v-101.0688c172.834133-2.0992 316.5696 118.545067 323.515733 274.551467 5.3248 119.671467-71.424 225.757867-185.207467 274.756266-2.065067 0.887467-4.232533 2.628267-4.0448 6.178134 0.1536 2.901333 3.106133 3.754667 5.7344 2.9696C816.930133 947.677867 951.005867 790.306133 956.074667 600.917333c6.365867-238.660267-194.491733-437.640533-448.648534-444.450133z"
fill="#040000" p-id="9241"></path>
</svg>
</div>
<div v-if="item.url && !props.disabled" @click.stop="item.remove()" class="btn">
<svg t="1729755512665" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="2491" width="200" height="200">
<path
d="M513.43007 1019.262092c-280.20375 0-507.388982-227.207745-507.388982-507.410472 0-280.224216 227.185232-507.409448 507.388982-507.409448 280.247752 0 507.391029 227.185232 507.391029 507.409448C1020.821099 792.054347 793.678846 1019.262092 513.43007 1019.262092zM746.107387 363.903034c9.540284-9.53926 9.540284-25.021883 0-34.539654l-51.822272-51.800783c-9.535167-9.558703-24.977881-9.558703-34.518165 0L512.976746 424.334381 366.184495 277.562597c-9.53619-9.558703-24.977881-9.558703-34.518165 0l-51.822272 51.800783c-9.538237 9.517771-9.538237 25.001417 0 34.539654l146.793274 146.770761-146.793274 146.790204c-9.538237 9.518794-9.538237 25.004487 0 34.540677l51.822272 51.79976c9.540284 9.538237 24.981974 9.538237 34.518165 0L512.976746 597.014232l146.790204 146.790204c9.540284 9.538237 24.982998 9.538237 34.518165 0l51.822272-51.79976c9.540284-9.53619 9.540284-25.021883 0-34.540677L599.317183 510.674818 746.107387 363.903034z"
p-id="2492"></path>
</svg>
</div>
</div>
</div>
</slot>
</div>
<el-image-viewer v-if="imgShow" @close="imgShow = false" ref="imgRef"
:url-list="data.map(a => a.url ? getUrl(a.url) : a.preview)" />
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed, getCurrentInstance } from 'vue'
import { request } from '@/utils'
import { ElMessage } from 'element-plus'
const base = "/file/upload/"
const baseUrl = import.meta.env.VITE_APP_BASE_API
const { proxy } = getCurrentInstance()
const imgShow = ref(false)
const imgPreview = async (index) => {
imgShow.value = true
await proxy.$nextTick()
proxy.$refs.imgRef.setActiveItem(index)
}
const props = defineProps({
/**
* 上传的文件
*/
modelValue: {
type: [Array, String],
required: true
},
/**
* 上传的数量,0表示无限制,1表示单文件上传
*/
max: {
type: Number,
default: 0
},
/**
* 获取上传凭证的方法
*/
getUploadKey: {
type: Function,
default: async () => {
let r = await request.get("/file/upload/getUploadKey")
return r.data
}
},
/**
* 上传路径前缀
*/
prefix: {
type: String,
default: "default"
},
/**
* 禁止上传图片预览模式
*/
disabled: {
type: Boolean,
default: false
},
/**
* 按钮文字
*/
text: {
type: String,
default: '上传图片'
},
/**
* 上传中?
*/
uploading: {
type: Boolean,
default: false
},
/**
* 允许上传的文件最大大小,0表示不限制默认0
*/
maxSize: {
type: Number,
default: 0
},
/**
* 并行上传数量默认2
*/
threadNum: {
type: Number,
default: 2
},
/** 重试次数默认3 */
retry: {
type: Number,
default: 3
},
saveSrc: {
type: Boolean,
default: false
},
saveMin: {
type: Boolean,
default: true
},
watermark: {
type: Boolean,
default: true
}
})
const emit = defineEmits(["update:modelValue", "change", "update:uploading"]);
const mv = computed({
get() {
return props.modelValue
},
set(value) {
emit("update:modelValue", value)
emit("change", value)
}
})
const uploading = computed({
get() {
return props.uploading
},
set(value) {
emit("update:uploading", value)
}
})
const data = ref([])
const uploadKey = ref("");
const getUrl = (url) => {
return baseUrl + base + 'download?' + request.params({ url, key: uploadKey.value }).toString()
}
const download = (url) => {
window.open(getUrl(props.saveSrc ? (url + '.zip') : url))
}
onMounted(async () => {
uploadKey.value = await props.getUploadKey()
if (props.max == 1) {
data.value = []
if (mv.value) {
data.value = [{ url: mv.value }]
}
} else {
data.value = []
if (mv.value) {
data.value = mv.value.map(a => ({ url: a }))
}
}
for (let one of data.value) {
one.remove = () => {
remove(one)
}
}
console.debug(proxy.$refs.imgRef)
})
const remove = (one) => {
data.value.splice(data.value.indexOf(one), 1)
if (props.max == 1) {
mv.value = data.value[0]?.url
} else {
mv.value = data.value.filter(a => 'url' in a).map(a => a.url)
}
request.post(base + "remove?r=" + Math.random(), request.params({ key: uploadKey.value, url: one.url }))
request.post(base + "remove?r=" + Math.random(), request.params({ key: uploadKey.value, url: one.url + '.min.webp' }))
request.post(base + "remove?r=" + Math.random(), request.params({ key: uploadKey.value, url: one.url + '.zip' }))
}
onUnmounted(async () => {
await request.get(base + "removeUploadKey-" + uploadKey.value)
})
window.addEventListener('beforeunload', async () => {
await request.get(base + "removeUploadKey-" + uploadKey.value)
})
/**
* 刷新上传凭证
*/
const reloadUploadKey = async () => {
request.get(base + "removeUploadKey-" + uploadKey.value)
uploadKey.value = await props.getUploadKey()
}
const fileInputRef = ref(null);
/**
* 打开文件选择对话框
*/
const open = () => {
if (doing.value) {
return
}
try {
fileInputRef.value.click();
} catch (e) { }
}
const fileInputChange = () => {
let files = fileInputRef.value.files
if (files.length > 0) {
// fileInputRef.value.value = ''
for (let file of files) {
addUploadFile(file)
}
uploadFiles()
}
}
const addUploadFile = async (file) => {
console.debug(file)
if (props.max > 1 && props.max == data.value.length) {
ElMessage.error('最多只允许上传' + props.max + '个文件')
return
}
//
if (!file.type.startsWith("image/")) {
ElMessage.error(file.name + '不是图片')
return
}
if (props.maxSize > 0) {
if (file.size > props.maxSize) {
ElMessage.error(file.name + '大小超过了' + props.maxSize.toFileSize())
return
}
}
if (props.max == 1) {
data.value.length = 0
}
data.value.push({ raw: file, preview: window.URL.createObjectURL(file) })
}
let doing = ref(false);
const uploadFiles = async () => {
if (doing.value) {
return
}
doing.value = true
uploading.value = true
await reloadUploadKey()
let index = 0
let doOne = async () => {
while (index < data.value.length) {
await uploadFile(index++)
}
}
let a = [];
for (let i = 0; i < props.threadNum; i++) {
a.push(doOne())
}
Promise.all(a).then(() => {
console.debug('end')
}).finally(() => {
data.value = data.value.filter(a => !a.error && !a.abort)
console.debug('finally')
doing.value = false
uploading.value = false
})
}
const uploadFile = async (index, retry = 0) => {
console.debug(index)
let one = data.value[index];
if (!one) {
return
}
if (one.url) {
return
}
if (retry >= props.retry) {
one.error = true
return
}
if (one.uploading || one.error || one.abort) {
return
}
retry++
one.uploading = { loaded: 0, total: one.raw.size }
one.uploading.controller = new AbortController();
one.uploading.abort = () => {
one.uploading.controller.abort()
}
one.remove = () => {
remove(one)
}
let formData = new FormData()
formData.append("prefix", props.prefix)
formData.append("key", uploadKey.value)
formData.append("saveSrc", props.saveSrc)
formData.append("saveThumbnail", props.saveMin)
formData.append("watermark", props.watermark)
formData.append("file", one.raw)
let onUploadProgress = (event) => {
one.uploading.loaded = event.loaded
}
try {
let r = await request.post(base + "image?r=" + Math.random(), formData, {
onUploadProgress,
timeout: 600000,
showLoading: false,
signal: one.uploading.controller.signal
})
console.debug(r)
if (r.code == 200) {
delete one.raw
delete one.uploading
one.url = r.data
if (props.max == 1) {
mv.value = r.data
} else {
mv.value = data.value.filter(a => 'url' in a).map(a => a.url)
}
} else {
delete one.uploading
await uploadFile(index, retry)
}
} catch (e) {
delete one.uploading
await uploadFile(index, retry)
}
}
defineExpose({ open, reloadUploadKey })
</script>
<style lang="scss" scoped>
.doing {
opacity: .5;
position: relative;
&::before {
content: '';
position: absolute;
height: 100%;
width: 100%;
background-color: #0000;
cursor: not-allowed;
}
}
.list {
position: relative;
display: flex;
flex-wrap: wrap;
.item {
width: var(--image-size, 10em);
height: var(--image-size, 10em);
position: relative;
overflow: hidden;
border-radius: .4em;
margin-top: .5em;
margin-right: .5em;
padding: 0;
border: #FFF solid 2px;
box-shadow: 0 0 .2em #0005;
cursor: pointer;
img {
object-fit: cover;
width: 100%;
height: 100%;
object-position: center;
transition: all .5s;
}
.btns {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
top: 100%;
left: 0;
transition: all .5s;
&>* {
padding: .3em;
background-color: #0006;
border-radius: .3em;
margin: .1em;
display: flex;
justify-content: center;
align-items: center;
transition: all .3s;
border: solid 1px #FFF8;
&:hover {
transform: translate(1px, 1px);
}
svg {
width: 1em;
height: 1em;
path {
fill: #FFFA;
}
}
}
}
&:hover {
img {
transform: scale(1.05);
}
.btns {
top: 0;
}
}
&.abort {
opacity: .5;
}
&.error {
border-color: var(--el-color-danger, #f56c6c);
}
&::after {
content: '';
pointer-events: none;
background-color: #00000035;
position: absolute;
top: var(--ps, 100%);
// top: 30%;
left: 0;
height: 100%;
width: 100%;
backdrop-filter: blur(1px);
}
}
}
</style>

@ -1,9 +1,5 @@
<template>
<div class="image-view">
<el-image ref="imgRef" :src="img" :preview-src-list="[img]" close-on-press-escape preview-teleported>
</el-image>
</div>
<el-image-viewer v-if="imgShow" @close="imgShow=false" ref="imgRef" :url-list="[img]" />
</template>
<script setup>
@ -13,20 +9,14 @@ const { proxy } = getCurrentInstance();
const img = ref("");
const imgShow = ref(false);
const open = async (src) => {
img.value = src;
imgShow.value = true
await proxy.$nextTick();
console.info(proxy.$refs.imgRef.$refs.container);
proxy.$refs.imgRef.$refs.container.querySelector("img").click();
}
defineExpose({open})
</script>
<style lang="scss" scoped>
.image-view {
user-select: none;
display: none;
}
</style>

@ -0,0 +1,159 @@
<template>
<div style="margin: 1rem;">
<el-tabs type="border-card">
<el-tab-pane label="图片上传组件">
<div style="padding: 1rem;">
<div>
图片上传组件说明
<ol style="font-size: .8rem; line-height: 1.8;">
<li>基础用法 &lt;w-image-uploader v-model="value" :getUploadKey="getUploadKey" max="1"
v-model:uploading="uploading" /&gt;</li>
<li>属性getUploadKey获取上传凭证的方法需要使用者提供开发模式下提供:<br />
前端示例<br />
<pre class="code">
const getUploadKey =async () => {
let r = await request.get("/file/upload/getUploadKey")
return r.data
} </pre>
后端示例<br />
<pre class="code"> @GetMapping("getUploadKey")
public R getUploadKey() {
return R.ok().setData(FileUtils.getUploadKey());
}</pre>
</li>
<li>属性v-model双向绑定上传的文件URL,max=1是类型为String,否则为Array</li>
<li>属性v-model:uploading双向绑定是否上传中,PS: 上传完成后才允许业务表单提交</li>
<li>属性max允许上传的文件数量0表示没有限制,默认0</li>
<li>属性prefix上传路径前缀默认default</li>
<li>属性disabled禁止上传图片预览模式 默认false</li>
<li>属性text按钮文字默认上传图片</li>
<li>属性maxSize允许上传的文件最大大小,0表示不限制默认0</li>
<li>属性threadNum并行上传数量默认2</li>
<li>属性retry重试次数默认3 </li>
<li>属性saveSrc是否保存图片原文件如果true则下载时下载原图zip文件否则下载大图片默认false </li>
<li>属性saveMin是否保存缩略图如果true则列表时显示缩略图否则显示大图默认true </li>
<li>属性watermark是否添加水印默认true </li>
<li>css属性--image-size图片显示大小默认 10em </li>
<li>插槽button上传按钮插槽</li>
<li>插槽default图片列表插槽作用域list不建议定义</li>
<li>事件changemodelValue发送了变化</li>
</ol>
</div>
<el-divider>单图片上传</el-divider>
<w-image-uploader v-model="imgs[0]" saveSrc max="1" v-model:uploading="imging" />
<pre>绑定值{{ imgs[0] }}</pre>
<div>{{ imging ? '上传中' : '没有上传' }}
<w-image-view ref="imgView" />
<el-button @click="proxy.$refs.imgView.open(imgs[0])"></el-button>
</div>
<el-divider>9张图片上传</el-divider>
<div style="width: 50em;"><w-image-uploader v-model="imgs[1]" saveSrc max="9" text="9张图片上传"
style="--image-size:15em;" /></div>
<pre>绑定值{{ imgs[1] }}</pre>
<el-divider>预览模式</el-divider>
<w-image-uploader style="width: 35em;" disabled :modelValue="[
'/files/default/2024/10/24/d0wt3m3j4tud.webp',
'/files/default/2024/10/24/4x53htt9v8j1.webp',
'/files/default/2024/10/24/1trnacdjxeoju.webp',
'/files/default/2024/10/24/742tvr5pq526.webp',
'/files/default/2024/10/24/1dg74ibgi0xbj.webp',
'/files/default/2024/10/24/1bgesfnixpfvt.webp',
'/files/default/2024/10/24/h1legy4o8tko.webp',
'/files/default/2024/10/24/9xe6yyoed9dj.webp'
]" />
</div>
</el-tab-pane>
<el-tab-pane label="文件上传组件">
<div style="padding: 1rem;">
<div>
文件上传组件说明
<ol style="font-size: .8rem; line-height: 1.8;">
<li>基础用法 &lt;w-file-uploader v-model="value" :getUploadKey="getUploadKey" max="1"
v-model:uploading="uploading" /&gt;</li>
<li>大小大于chunksize的大文件上传会自动分片上传</li>
<li>属性getUploadKey获取上传凭证的方法需要使用者提供开发模式下提供:<br />
前端示例<br />
<pre class="code">
const getUploadKey =async () => {
let r = await request.get("/file/upload/getUploadKey")
return r.data
} </pre>
后端示例<br />
<pre class="code"> @GetMapping("getUploadKey")
public R getUploadKey() {
return R.ok().setData(FileUtils.getUploadKey());
}</pre>
</li>
<li>属性v-model双向绑定上传的文件URL,max=1是类型为String,否则为Array</li>
<li>属性v-model:uploading双向绑定是否上传中,PS: 上传完成后才允许业务表单提交</li>
<li>属性max允许上传的文件数量0表示没有限制,默认0</li>
<li>属性prefix上传路径前缀默认default</li>
<li>属性keepFilename是否保留文件名, 默认true</li>
<li>属性disabled禁止上传文件预览模式 默认false</li>
<li>属性text按钮文字默认上传文件</li>
<li>属性accept允许上传的文件扩展名列表默认'' ,office:'.doc,.docx,.ppt,.pptx,.xls,.xlsx'</li>
<li>属性maxSize允许上传的文件最大大小,0表示不限制默认0</li>
<li>属性threadNum并行上传数量默认2</li>
<li>属性retry重试次数默认3 </li>
<li>属性chunksize分片大小默认5Mb不建议修改</li>
<li>属性border文件项是否显示边框默认true</li>
<li>插槽button上传按钮插槽</li>
<li>插槽default文件列表作用域list不建议定义</li>
<li>事件changemodelValue发送了变化</li>
</ol>
</div>
<el-divider>单文件上传</el-divider>
<w-file-uploader v-model="data[0]" max="1" v-model:uploading="uploading" />
<pre>绑定值{{ data[0] }}</pre>
<div>{{ uploading ? '上传中' : '没有上传' }}</div>
<el-divider>预览模式</el-divider>
<w-file-uploader v-model="data[0]" max="1" :border="false" disabled />
<el-divider>单word文件小于10kb上传</el-divider>
<w-file-uploader v-model="data[1]" max="1" accept=".docx,.doc" text="单word文件小于10kb上传" :max-size="10240" />
<pre>绑定值{{ data[1] }}</pre>
<el-divider>多文件上传</el-divider>
<w-file-uploader v-model="data[2]" max="2" />
<pre>绑定值{{ data[2] }}</pre>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup>
import { ref, getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
const data = ref(['/files/default/2024/10/24/2nhdirb2ujey/5mb.txt'])
const uploading = ref(false)
const imgs = ref(['/files/default/2024/10/24/f99pkzpyc0bt.webp'])
const imging = ref(false)
</script>
<style lang="scss" scoped>
.code {
border: solid 1px #0001;
margin: .5rem;
padding: .5rem;
border-radius: .3rem;
}
</style>

@ -275,21 +275,20 @@
<artifactId>ruoyi-system-cron</artifactId>
<version>${ruoyi-vue-plus.version}</version>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<artifactId>ruoyi-system-file</artifactId>
<version>${ruoyi-vue-plus.version}</version>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-websocket</artifactId>
<artifactId>ruoyi-common</artifactId>
<version>${ruoyi-vue-plus.version}</version>
</dependency>
<!-- OSS对象存储模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-oss</artifactId>
<artifactId>ruoyi-common-websocket</artifactId>
<version>${ruoyi-vue-plus.version}</version>
</dependency>
<!-- SMS短信模块 -->
@ -340,11 +339,6 @@
<artifactId>ruoyi-demo</artifactId>
<version>${ruoyi-vue-plus.version}</version>
</dependency>
<dependency>
<groupId>com.github.gotson</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.2.2</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
@ -372,9 +366,9 @@
<module>ruoyi-framework</module>
<module>ruoyi-system</module>
<module>ruoyi-system-cron</module>
<module>ruoyi-system-file</module>
<module>ruoyi-common</module>
<module>ruoyi-demo</module>
<module>ruoyi-oss</module>
<module>ruoyi-sms</module>
</modules>
<packaging>pom</packaging>

@ -70,7 +70,7 @@
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-oss</artifactId>
<artifactId>ruoyi-system-file</artifactId>
</dependency>

@ -1,56 +1,67 @@
package com.ruoyi.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.annotation.Dev;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.service.ISysOssService;
import com.ruoyi.file.FileService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;
@RequiredArgsConstructor
@RestController
@RequestMapping("/")
public class UploadController {
private final ISysOssService iSysOssService;
private final FileService fileService;
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R upload(@RequestPart("file") MultipartFile file, String pre) {
if (ObjectUtil.isNull(file)) {
throw new ServiceException("文件为空");
}
return R.ok(iSysOssService.upload(file, pre));
}
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@PostMapping(value = "/uploadImg", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R uploadImg(@RequestPart("file") MultipartFile file, String pre) {
if (ObjectUtil.isNull(file)) {
throw new ServiceException("文件为空");
}
if(!file.getContentType().startsWith("image/")){
throw new ServiceException("不是图片");
}
return R.ok(iSysOssService.uploadImgs(file, pre));
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R upload(@RequestPart("file") MultipartFile file, String pre) {
if (ObjectUtil.isNull(file)) {
throw new ServiceException("文件为空");
}
// return R.ok(iSysOssService.upload(file, pre));
return R.ok(fileService.setPrefix(pre).save(file));//TODO: fileService
}
/**
* OSS
*
* @param ossId OSSID
*/
@PostMapping("/download/{ossId}")
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
iSysOssService.download(ossId, response);
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@PostMapping(value = "/uploadImg", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R uploadImg(@RequestPart("file") MultipartFile file, String pre) {
if (ObjectUtil.isNull(file)) {
throw new ServiceException("文件为空");
}
if (!file.getContentType().startsWith("image/")) {
throw new ServiceException("不是图片");
}
// return R.ok(iSysOssService.uploadImgs(file, pre));
return R.ok(fileService.setPrefix(pre).setThumbnail().saveImage(file));//TODO: fileService
}
/**
*
*
* @param url
* @param request
* @param response
* @throws IOException
*/
@PostMapping("/download")
public ModelAndView download(String url, HttpServletRequest request, HttpServletResponse response) {
// iSysOssService.download(ossId, response);
fileService.download(url, Duration.ofMinutes(30), request, response);//TODO: fileService
return null;
}
}

@ -1,105 +0,0 @@
package com.ruoyi.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.core.validate.QueryGroup;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.bo.SysOssConfigBo;
import com.ruoyi.system.domain.vo.SysOssConfigVo;
import com.ruoyi.system.service.ISysOssConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
/**
*
*
* @author Lion Li
* @author
* @date 2021-08-13
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/oss/config")
public class SysOssConfigController extends BaseController {
private final ISysOssConfigService iSysOssConfigService;
/**
*
*/
@SaCheckPermission("system:oss:list")
@GetMapping("/list")
public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) {
return iSysOssConfigService.queryPageList(bo, pageQuery);
}
/**
*
*
* @param ossConfigId OSSID
*/
@SaCheckPermission("system:oss:query")
@GetMapping("/{ossConfigId}")
public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long ossConfigId) {
return R.ok(iSysOssConfigService.queryById(ossConfigId));
}
/**
*
*/
@SaCheckPermission("system:oss:add")
@Log(title = "对象存储配置", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.insertByBo(bo));
}
/**
*
*/
@SaCheckPermission("system:oss:edit")
@Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateByBo(bo));
}
/**
*
*
* @param ossConfigIds OSSID
*/
@SaCheckPermission("system:oss:remove")
@Log(title = "对象存储配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossConfigIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossConfigIds) {
return toAjax(iSysOssConfigService.deleteWithValidByIds(Arrays.asList(ossConfigIds), true));
}
/**
*
*/
@SaCheckPermission("system:oss:edit")
@Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));
}
}

@ -1,117 +0,0 @@
package com.ruoyi.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.validate.QueryGroup;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.oss.core.OssClient;
import com.ruoyi.oss.factory.OssFactory;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.bo.SysOssBo;
import com.ruoyi.system.domain.vo.SysOssVo;
import com.ruoyi.system.service.ISysOssService;
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 javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/oss")
public class SysOssController extends BaseController {
private final ISysOssService iSysOssService;
/**
* OSS
*/
@SaCheckPermission("system:oss:list")
@GetMapping("/list")
public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) {
return iSysOssService.queryPageList(bo, pageQuery);
}
/**
* OSSid
*
* @param ossIds OSSID
*/
@SaCheckPermission("system:oss:list")
@GetMapping("/listByIds/{ossIds}")
public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) {
List<SysOssVo> list = iSysOssService.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<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
if (ObjectUtil.isNull(file)) {
throw new ServiceException("上传文件不能为空");
}
SysOssVo oss = iSysOssService.upload(file);
Map<String, String> map = new HashMap<>(2);
map.put("url", oss.getUrl());
map.put("fileName", oss.getOriginalName());
map.put("ossId", oss.getOssId().toString());
return R.ok(map);
}
/**
* OSS
*
* @param ossId OSSID
*/
@SaCheckPermission("system:oss:download")
@GetMapping("/download/{ossId}")
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
iSysOssService.download(ossId,response);
}
/**
* OSS
*
* @param ossIds OSSID
*/
@SaCheckPermission("system:oss:remove")
@Log(title = "OSS对象存储", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) {
return toAjax(iSysOssService.deleteWithValidByIds(Arrays.asList(ossIds), true));
}
}

@ -5,7 +5,6 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.entity.SysUser;
@ -13,10 +12,7 @@ import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.vo.SysOssVo;
import com.ruoyi.system.service.FileService;
import com.ruoyi.system.service.ISysOssService;
import com.ruoyi.file.FileService;
import com.ruoyi.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
@ -40,122 +36,125 @@ import java.util.Map;
@RequestMapping("/system/user/profile")
public class SysProfileController extends BaseController {
private final ISysUserService userService;
private final ISysOssService iSysOssService;
/**
*
*/
@GetMapping
public R<Map<String, Object>> profile() {
SysUser user = userService.selectUserById(getUserId());
Map<String, Object> ajax = new HashMap<>();
ajax.put("user", user);
ajax.put("roleGroup", userService.selectUserRoleGroup(user.getUserName()));
ajax.put("postGroup", userService.selectUserPostGroup(user.getUserName()));
return R.ok(ajax);
private final ISysUserService userService;
private final FileService fileService;
/**
*
*/
@GetMapping
public R<Map<String, Object>> profile() {
SysUser user = userService.selectUserById(getUserId());
Map<String, Object> ajax = new HashMap<>();
ajax.put("user", user);
ajax.put("roleGroup", userService.selectUserRoleGroup(user.getUserName()));
ajax.put("postGroup", userService.selectUserPostGroup(user.getUserName()));
return R.ok(ajax);
}
/**
*
*/
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> updateProfile(@RequestBody SysUser user) {
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("手机已存在");
}
/**
*
*/
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> updateProfile(@RequestBody SysUser user) {
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("手机已存在");
}
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("邮箱已存在");
}
user.setUserId(getUserId());
user.setUserName(null);
user.setPassword(null);
user.setAvatar(null);
user.setDeptId(null);
if (userService.updateUserProfile(user) > 0) {
return R.ok();
}
return R.fail("修改个人异常");
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("邮箱已存在");
}
user.setUserId(getUserId());
user.setUserName(null);
user.setPassword(null);
user.setAvatar(null);
user.setDeptId(null);
if (userService.updateUserProfile(user) > 0) {
return R.ok();
}
@Log(title = "绑定用户名", businessType = BusinessType.UPDATE)
@RepeatSubmit
@PutMapping("bindUserName")
public R<Void> bindUserName(String userName) {
SysUser u = new SysUser();
u.setUserId(getUserId());
u.setUserName(userName);
String oldUsername = userService.selectUserById(u.getUserId()).getUserName();
if(!(StrUtil.isBlank(oldUsername) || oldUsername.startsWith("_"))) {
return R.fail("只能绑定一次");
}
if(!userService.checkUserNameUnique(u)){
return R.fail("账户已存在");
}
if (userService.updateUserProfile(u) > 0) {
return R.ok();
}
return R.fail("绑定帐号异常");
return R.fail("修改个人异常");
}
@Log(title = "绑定用户名", businessType = BusinessType.UPDATE)
@RepeatSubmit
@PutMapping("bindUserName")
public R<Void> bindUserName(String userName) {
SysUser u = new SysUser();
u.setUserId(getUserId());
u.setUserName(userName);
String oldUsername = userService.selectUserById(u.getUserId()).getUserName();
if (!(StrUtil.isBlank(oldUsername) || oldUsername.startsWith("_"))) {
return R.fail("只能绑定一次");
}
if (!userService.checkUserNameUnique(u)) {
return R.fail("账户已存在");
}
/**
*
*
* @param newPassword
* @param oldPassword
*/
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping("/updatePwd")
public R<Void> updatePwd(String oldPassword, String newPassword) {
SysUser user = userService.selectUserById(LoginHelper.getUserId());
String userName = user.getUserName();
String password = user.getPassword();
if (StrUtil.isNotBlank(password) && !BCrypt.checkpw(oldPassword, password)) {
return R.fail("旧密码错误");
}
if (BCrypt.checkpw(newPassword, password)) {
return R.fail("新旧密码相同");
}
if (userService.resetUserPwd(userName, BCrypt.hashpw(newPassword)) > 0) {
return R.ok();
}
return R.fail("修改密码异常");
if (userService.updateUserProfile(u) > 0) {
return R.ok();
}
return R.fail("绑定帐号异常");
}
/**
*
*
* @param newPassword
* @param oldPassword
*/
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
@PutMapping("/updatePwd")
public R<Void> updatePwd(String oldPassword, String newPassword) {
SysUser user = userService.selectUserById(LoginHelper.getUserId());
String userName = user.getUserName();
String password = user.getPassword();
if (StrUtil.isNotBlank(password) && !BCrypt.checkpw(oldPassword, password)) {
return R.fail("旧密码错误");
}
if (BCrypt.checkpw(newPassword, password)) {
return R.fail("新旧密码相同");
}
/**
*
*
* @param avatarfile
*/
@Log(title = "用户头像", businessType = BusinessType.UPDATE)
@PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Map<String, Object>> avatar(@RequestPart("avatarfile") MultipartFile avatarfile) throws IOException {
Map<String, Object> ajax = new HashMap<>();
if (!avatarfile.isEmpty()) {
String extension = FileUtil.extName(avatarfile.getOriginalFilename());
if(StrUtil.isBlank(extension)){//NOTE: 修复h5上传问题
extension="png";
}
if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
}
SysOssVo oss = iSysOssService.uploadImgs(avatarfile,"avatar",400,400,null);
String avatar = oss.getUrl();
if (userService.updateUserAvatar(getUsername(), avatar)) {
ajax.put("imgUrl", avatar);
return R.ok(ajax);
}
}
return R.fail("上传图片异常,请联系管理员");
if (userService.resetUserPwd(userName, BCrypt.hashpw(newPassword)) > 0) {
return R.ok();
}
return R.fail("修改密码异常");
}
/**
*
*
* @param avatarfile
*/
@Log(title = "用户头像", businessType = BusinessType.UPDATE)
@PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Map<String, Object>> avatar(@RequestPart("avatarfile") MultipartFile avatarfile) throws IOException {
Map<String, Object> ajax = new HashMap<>();
if (!avatarfile.isEmpty()) {
String extension = FileUtil.extName(avatarfile.getOriginalFilename());
if (StrUtil.isBlank(extension)) {//NOTE: 修复h5上传问题
extension = "png";
}
if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
}
// SysOssVo oss = iSysOssService.uploadImgs(avatarfile,"avatar",400,400,null);
// String avatar = oss.getUrl();
String avatar = fileService.setSize(400, 400).saveImage(avatarfile);//TODO: fileService
if (userService.updateUserAvatar(getUsername(), avatar)) {
ajax.put("imgUrl", avatar);
return R.ok(ajax);
}
}
return R.fail("上传图片异常,请联系管理员");
}
}

@ -3,12 +3,43 @@ ruoyi:
# 是否是开发模式
dev: true
# 本地文件存储配置
upload:
# 资源访问前缀
pre: /upload
# 物理保存地址
save-path: /.data/upload
# 文件存储配置
file:
max-width: 1500
max-height: 1500
th-width: 200
th-height: 200
watermark: classpath:/watermark.png
default-platform: minio #默认使用的存储平台
local-plus:
- platform: local # 存储平台标识
enable-storage: true #启用存储
enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高)
path-patterns: /upload/** # 访问路径
storage-path: /upload/ # 存储路径
domain: "/upload/" # 访问域名例如“http://127.0.0.1:8030/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名
base-path: "" # 基础路径
minio:
- platform: minio
enable-storage: true # 启用存储
access-key: ${ruoyi.name}
secret-key: ${ruoyi.name}1415926
end-point: http://192.168.3.222:9000
bucket-name: files
domain: "/files/" # 访问域名,注意“/”结尾例如http://minio.abc.com/abc/
base-path: "" # 基础路径
aliyun-oss:
- platform: aliyun # 存储平台标识
enable-storage: true # 启用存储
access-key: LTAI5tKkFMwc4SuDF8LpgRQ3
secret-key: 74S18FfuyxTd85iYKifsVXjY5DhVAB
end-point: https://oss-cn-shenzhen.aliyuncs.com
bucket-name: base-2024
domain: "https://base-2024.oss-cn-shenzhen.aliyuncs.com/" # 访问域名,注意“/”结尾例如https://abc.oss-cn-shanghai.aliyuncs.com/
base-path: "" # 基础路径
logging:
level:

@ -3,12 +3,43 @@ ruoyi:
# 是否是开发模式
dev: true
# 本地文件存储配置
upload:
# 资源访问前缀
pre: /upload
# 物理保存地址
save-path: /.data/upload
# 文件存储配置
file:
max-width: 1500
max-height: 1500
th-width: 200
th-height: 200
watermark: classpath:/watermark.png
default-platform: minio #默认使用的存储平台
local-plus:
- platform: local # 存储平台标识
enable-storage: true #启用存储
enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高)
path-patterns: /upload/** # 访问路径
storage-path: /upload/ # 存储路径
domain: "/upload/" # 访问域名例如“http://127.0.0.1:8030/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名
base-path: "" # 基础路径
minio:
- platform: minio
enable-storage: true # 启用存储
access-key: ${ruoyi.name}
secret-key: ${ruoyi.name}1415926
end-point: http://192.168.3.222:9000
bucket-name: files
domain: "/files/" # 访问域名,注意“/”结尾例如http://minio.abc.com/abc/
base-path: "" # 基础路径
aliyun-oss:
- platform: aliyun # 存储平台标识
enable-storage: true # 启用存储
access-key: LTAI5tKkFMwc4SuDF8LpgRQ3
secret-key: 74S18FfuyxTd85iYKifsVXjY5DhVAB
end-point: https://oss-cn-shenzhen.aliyuncs.com
bucket-name: base-2024
domain: "https://base-2024.oss-cn-shenzhen.aliyuncs.com/" # 访问域名,注意“/”结尾例如https://abc.oss-cn-shanghai.aliyuncs.com/
base-path: "" # 基础路径
logging:
level:

@ -3,12 +3,42 @@ ruoyi:
# 是否是开发模式
dev: false
# 本地文件存储配置
upload:
# 资源访问前缀
pre: /upload
# 物理保存地址
save-path: /server/upload
# 文件存储配置
file:
max-width: 1500
max-height: 1500
th-width: 200
th-height: 200
watermark: classpath:/watermark.png
default-platform: minio #默认使用的存储平台
local-plus:
- platform: local # 存储平台标识
enable-storage: true #启用存储
enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高)
path-patterns: /upload/** # 访问路径
storage-path: /server/upload/ # 存储路径
domain: "/upload/" # 访问域名例如“http://127.0.0.1:8030/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名
base-path: "" # 基础路径
minio:
- platform: minio
enable-storage: true # 启用存储
access-key: ${ruoyi.name}
secret-key: ${ruoyi.name}1415926
end-point: http://minio:9000
bucket-name: files
domain: "/files/" # 访问域名,注意“/”结尾例如http://minio.abc.com/abc/
base-path: "" # 基础路径
aliyun-oss:
- platform: aliyun # 存储平台标识
enable-storage: true # 启用存储
access-key: XXXXXXXXXXXXXXXXXXXXXX
secret-key: XXXXXXXXXXXXXXXXXXXXXXXXXXXX
end-point: https://oss-cn-shenzhen.aliyuncs.com
bucket-name: base2024
domain: "https://base2024.oss-cn-shenzhen.aliyuncs.com/" # 访问域名,注意“/”结尾例如https://abc.oss-cn-shanghai.aliyuncs.com/
base-path: "" # 基础路径
--- # 临时文件存储位置 避免临时文件被系统清理报错

@ -14,12 +14,6 @@ ruoyi:
addressEnabled: true
# 缓存懒加载
cacheLazy: false
# 本地文件存储配置
upload:
# 资源访问前缀
pre: /upload
# 物理保存地址
save-path: /upload
# 小程序的用户默认设置
default-user:
# 所在单位
@ -34,6 +28,9 @@ ruoyi:
# 岗位组
post-ids:
- 4
-
--- # 验证码配置

@ -1,9 +1,12 @@
package com.ruoyi.test;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.util.URLUtil;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.net.URL;
public class PasswordTest {
@ -16,4 +19,10 @@ public class PasswordTest {
public void abc() {
new File("E:\\upload\\2023\\7\\12\\75s0.jpg").delete();
}
@Test
public void urlEncode() {
System.out.println(URLUtil.encode("/files/default/2024/10/24/553vijcx66we/长沙销售部.txt"));
}
}

@ -0,0 +1,201 @@
package com.ruoyi.test;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.HexUtil;
import com.ruoyi.TestSuper;
import com.ruoyi.file.FileService;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.tika.TikaFactory;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.time.Duration;
import java.time.LocalDate;
@NoArgsConstructor
public class XFileStorageTest extends TestSuper {
@Autowired
private FileStorageService fileStorageService;//注入实列
@Autowired
private FileService fileService;
@Autowired
private TikaFactory tikaFactory;
public InputStream getInstream() {
return this.getClass().getResourceAsStream("/test.png");
}
@Test
@DisplayName("分片上传")
@Disabled
@SneakyThrows
public void testMultipartUpload() {
File file = new File("D:\\test.mp4");
String uploadId = fileService.setPlatform("aliyun").setUri("/test.mp4").multipartUploadInit();
try (
InputStream in = new FileInputStream(file);
) {
byte[] bs = new byte[5 * 1024 * 1024];//每一片5MB
int len = 0;
int partNumber = 0;
try {
while ((len = in.read(bs)) > 0) {
partNumber++;
ByteArrayInputStream bin = new ByteArrayInputStream(bs, 0, len);
fileService.multipartUpload(uploadId, partNumber, bin);
out("上传分片成功:" + partNumber + " len:" + len);
}
out("分片上传成功:" + fileService.multipartUploadComplete(uploadId));
} catch (Exception e) {
out("分片上传失败", e);
fileService.multipartUploadAbort(uploadId);
}
}
}
@Test
@DisplayName("分片上传")
@Disabled
@SneakyThrows
public void testMultipartUpload1() {
File file = new File("D:\\test.mp4");//文件大概6M
// File file = new File("D:\\VMware-images\\CentOS-7-x86_64-Minimal-2009.iso");;//900M+
FileInfo fileInfo = fileStorageService.initiateMultipartUpload().setPlatform("aliyun").setPath("default/").setSaveFilename(file.getName()).init();
try (
InputStream in = new FileInputStream(file);
) {
byte[] bs = new byte[5 * 1024 * 1024];//每一片5MB
int len = 0;
int partNumber = 1;
try {
while ((len = in.read(bs)) > 0) {
partNumber++;
ByteArrayInputStream bin = new ByteArrayInputStream(bs, 0, len);
fileStorageService.uploadPart(fileInfo, partNumber, bin).upload();
out("上传分片成功:" + partNumber + " len:" + len);
}
out("分片上传成功:" + fileStorageService.completeMultipartUpload(fileInfo).complete().getUrl());
} catch (Exception e) {
out("分片上传失败", e);
fileStorageService.abortMultipartUpload(fileInfo).abort();
}
}
}
@Test
@Disabled
@DisplayName("读取配置信息")
public void testConfig() throws Exception {
out(fileStorageService.getProperties());
}
@Test
@Disabled
@DisplayName("保存文件")
@SneakyThrows
public void saveTest() {
// out("保存随机文件名:" + fileService.setFilename("test.png").save(getInstream()));
// out("保存文件名:" + fileService.setKeepFilename().setFilename("test.png").save(getInstream()));
// out("指定保存规则:" + fileService.setFilename("test.png").setRule("/{yyyy}/{MM}/{dd}/{id}.{ext}").save(getInstream()));
out("指定保存路径:" + fileService.setUri("/aa/bb/cc/a.png").save(getInstream()));
out("aliyun指定保存路径:" + fileService.setPlatform("aliyun").setUri("/aa/bb/cc/a.png").save(getInstream()));
out("local指定保存路径:" + fileService.setPlatform("local").setUri("/aa/bb/cc/a.png").save(getInstream()));
// out("指定路径前缀保存随机文件名:" + fileService.setPrefix("test").setFilename("test.png").save(getInstream()));
// out("指定平台保存随机文件名:" + fileService.setPlatform("local").setFilename("test.png").save(getInstream()));
}
@Test
@Disabled
@DisplayName("保存图片")
@SneakyThrows
public void saveImage() {
// out("保存图片:" + fileService.saveImage(getInstream()));
// out("保存图片+缩略图:" + fileService.setThumbnail().saveImage(getInstream()));
// out("保存图片+调整大小+缩略图:" + fileService.setSize(500,500).setThumbnail().saveImage(getInstream()));
// out("保存图片+水印+调整大小+缩略图:" + fileService.setPlatform("aliyun").setWatermark().setSize(700, 700).setThumbnail().saveImage(getInstream()));
out("保存图片+水印+调整大小+缩略图+源文件:" + fileService.setPlatform("local").setFilename("test.png").setSaveSrc().setWatermark().setSize(700, 700).setThumbnail().saveImage(getInstream()));
}
private String uri = "/files/default/aa/bb/cc/a.png";
private String aliyun = "https://base-2024.oss-cn-shenzhen.aliyuncs.com/default/aa/bb/cc/a.png";
@Test
@Disabled
@DisplayName("删除文件")
@SneakyThrows
public void deleteTest() {
// fileService.delete(uri);
// fileService.setPlatform("local").delete("/upload/default/aa/bb/cc/a.png");
fileService.setPlatform("aliyun").delete(aliyun);
}
@Test
@Disabled
@DisplayName("下载文件")
@SneakyThrows
public void downloadTest() {
// FileInfo fileInfo = new FileInfo();
// fileInfo.setPath("default/aa/bb/cc/");
// fileInfo.setFilename("a.png");
// fileInfo.setPlatform(fileStorageService.getFileStorage("aliyun").getPlatform());
// out(HexUtil.encodeHex(fileStorageService.download(fileInfo).bytes()));
// out(HexUtil.encodeHex(fileService.download(uri).bytes()));
out(HexUtil.encodeHex(fileService.setPlatform("aliyun").download(aliyun).bytes()));
}
@Test
@Disabled
@DisplayName("生成预签名URL")
@SneakyThrows
public void generatePresignedUrlTest() {
out(fileService.generatePresignedUrl(uri, Duration.ofMinutes(30)));
out(fileService.setPlatform("aliyun").generatePresignedUrl(aliyun, Duration.ofMinutes(30)));
try {
out(fileService.setPlatform("local").generatePresignedUrl(uri, Duration.ofMinutes(30)));
} catch (Exception e) {
out("不支持预签名", e);
}
}
@Test
@Disabled
@DisplayName("类型检测")
@SneakyThrows
public void contentTypeTest() {
System.out.println(tikaFactory.getTika().detect(getInstream()));
}
@Test
@Disabled
@DisplayName("转换格式")
@SneakyThrows
public void testWebp() {
BufferedImage image = ImageIO.read(getInstream());
ImageIO.write(image, "webp", new File("/test.webp"));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

@ -1,6 +1,5 @@
package com.ruoyi.common.config;
import com.baomidou.mybatisplus.annotation.TableField;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.enums.UserType;
import lombok.Data;
@ -16,97 +15,68 @@ import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "ruoyi")
@ConfigurationProperties(prefix = "ruoyi", ignoreInvalidFields = true)
public class RuoYiConfig {
/**
*
*/
private Boolean dev = false;
/**
*
*/
private String name;
/**
*
*/
private String version;
/**
*
*/
private String copyrightYear;
/**
*
*/
private boolean demoEnabled;
/**
*
*/
private boolean cacheLazy;
/**
*
*/
@Getter
private static boolean addressEnabled;
public void setAddressEnabled(boolean addressEnabled) {
RuoYiConfig.addressEnabled = addressEnabled;
}
private DefaultUser defaultUser = new DefaultUser();
@Data
public static class DefaultUser {
private Long deptId = 100L;
private String userType = UserType.APP_USER.getUserType();
/**
*
* 0 1
*/
private Boolean dev=false;
private String status = UserStatus.OK.getCode();
/**
*
*
*/
private String name;
private Long[] roleIds = {2L};
/**
*
*
*/
private String version;
private Long[] postIds = {4L};
/**
*
*/
private String copyrightYear;
/**
*
*/
private boolean demoEnabled;
/**
*
*/
private boolean cacheLazy;
/**
*
*/
@Getter
private static boolean addressEnabled;
public void setAddressEnabled(boolean addressEnabled) {
RuoYiConfig.addressEnabled = addressEnabled;
}
private DefaultUser defaultUser = new DefaultUser();
private Tencentcloud tencentcloud = new Tencentcloud();
@Data
public static class DefaultUser {
private Long deptId = 100L;
private String userType = UserType.APP_USER.getUserType();
/**
* 0 1
*/
private String status = UserStatus.OK.getCode();
/**
*
*/
private Long[] roleIds = {2L};
/**
*
*/
private Long[] postIds = {4L};
}
public Upload upload = new Upload();
/**
*
*/
@Data
public static class Upload {
/**
*
*/
public String pre = "/upload";
/**
*
*/
public String savePath="/upload";
}
}
@Data
public static class Tencentcloud {
private String secretId = "AKIDoeWFoKdhaLuFLD1sX2LRItFMI2f7NRRh";
private String secretKey = "RkspdHuOflngNgnhXRL4Zpq096pLhrmQ";
private String ocrEndpoint = "ocr.ap-guangzhou.tencentcloudapi.com";
private String ocrRegion = "ap-guangzhou";
}
}

@ -1,25 +0,0 @@
package com.ruoyi.common.translation.impl;
import com.ruoyi.common.annotation.TranslationType;
import com.ruoyi.common.constant.TransConstant;
import com.ruoyi.common.core.service.OssService;
import com.ruoyi.common.translation.TranslationInterface;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
/**
* OSS
*
* @author Lion Li
*/
@Component
@AllArgsConstructor
@TranslationType(type = TransConstant.OSS_ID_TO_URL)
public class OssUrlTranslationImpl implements TranslationInterface<String> {
private final OssService ossService;
public String translation(Object key, String other) {
return ossService.selectUrlByIds(key.toString());
}
}

@ -1,6 +1,8 @@
package com.ruoyi.common.utils.file;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import com.ruoyi.common.utils.redis.RedisUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@ -8,6 +10,7 @@ import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
/**
*
@ -17,36 +20,74 @@ import java.nio.charset.StandardCharsets;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FileUtils extends FileUtil {
/**
*
*
* @param response
* @param realFileName
*/
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException {
String percentEncodedFileName = percentEncode(realFileName);
StringBuilder contentDispositionValue = new StringBuilder();
contentDispositionValue.append("attachment; filename=")
.append(percentEncodedFileName)
.append(";")
.append("filename*=")
.append("utf-8''")
.append(percentEncodedFileName);
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
response.setHeader("Content-disposition", contentDispositionValue.toString());
response.setHeader("download-filename", percentEncodedFileName);
}
/**
*
*
* @param s
* @return
*/
public static String percentEncode(String s) throws UnsupportedEncodingException {
String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
return encode.replaceAll("\\+", "%20");
}
/**
*
*
* @param response
* @param realFileName
*/
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException {
String percentEncodedFileName = percentEncode(realFileName);
StringBuilder contentDispositionValue = new StringBuilder();
contentDispositionValue.append("attachment; filename=")
.append(percentEncodedFileName)
.append(";")
.append("filename*=")
.append("utf-8''")
.append(percentEncodedFileName);
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
response.setHeader("Content-disposition", contentDispositionValue.toString());
response.setHeader("download-filename", percentEncodedFileName);
}
/**
*
*
* @param s
* @return
*/
public static String percentEncode(String s) throws UnsupportedEncodingException {
String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
return encode.replaceAll("\\+", "%20");
}
public static final String UPLOAD_KEY_PREFIX = "UPLOAD:KEY:";
/**
*
* @param duration
* @return
*/
public static String getUploadKey(Duration duration) {
String key = UUID.fastUUID().toString(true);
RedisUtils.setCacheObject(UPLOAD_KEY_PREFIX + key, true, duration);
return key;
}
/**
* ,30
* @return
*/
public static String getUploadKey() {
return getUploadKey(Duration.ofMinutes(60));
}
/**
*
* @param key
* @return
*/
public static boolean exitisUploadKey(String key){
return RedisUtils.isExistsObject(UPLOAD_KEY_PREFIX + key);
}
/**
*
* @param key
*/
public static void removeUploadKey(String key){
RedisUtils.deleteObject(UPLOAD_KEY_PREFIX + key);
}
}

@ -1,33 +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>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>4.6.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-oss</artifactId>
<description>
OSS对象存储模块
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</dependency>
</dependencies>
</project>

@ -1,38 +0,0 @@
package com.ruoyi.oss.constant;
import java.util.Arrays;
import java.util.List;
/**
*
*
* @author Lion Li
*/
public interface OssConstant {
/**
* KEY
*/
String DEFAULT_CONFIG_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,268 +0,0 @@
package com.ruoyi.oss.core;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.HttpMethod;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.AccessPolicyType;
import com.ruoyi.oss.enumd.PolicyType;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import lombok.Getter;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
/**
* S3 S3
* minio
*
* @author Lion Li
*/
public class OssClient {
private final String configKey;
@Getter
private final OssProperties properties;
private final AmazonS3 client;
public OssClient(String configKey, OssProperties ossProperties) {
this.configKey = configKey;
this.properties = ossProperties;
try {
AwsClientBuilder.EndpointConfiguration endpointConfig =
new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());
AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
ClientConfiguration clientConfig = new ClientConfiguration();
if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
clientConfig.setProtocol(Protocol.HTTPS);
} else {
clientConfig.setProtocol(Protocol.HTTP);
}
AmazonS3ClientBuilder build = AmazonS3Client.builder()
.withEndpointConfiguration(endpointConfig)
.withClientConfiguration(clientConfig)
.withCredentials(credentialsProvider)
.disableChunkedEncoding();
if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
// minio 使用https限制使用域名访问 需要此配置 站点填域名
build.enablePathStyleAccess();
}
this.client = build.build();
createBucket();
} catch (Exception e) {
if (e instanceof OssException) {
throw e;
}
throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
}
}
public void createBucket() {
try {
String bucketName = properties.getBucketName();
if (client.doesBucketExistV2(bucketName)) {
return;
}
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
AccessPolicyType accessPolicy = getAccessPolicy();
createBucketRequest.setCannedAcl(accessPolicy.getAcl());
client.createBucket(createBucketRequest);
client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
} catch (Exception e) {
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
}
}
public UploadResult upload(byte[] data, String path, String contentType) {
return upload(new ByteArrayInputStream(data), path, contentType);
}
public UploadResult upload(InputStream inputStream, String path, String contentType) {
if (!(inputStream instanceof ByteArrayInputStream)) {
inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
}
try {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(contentType);
metadata.setContentLength(inputStream.available());
PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata);
// 设置上传对象的 Acl 为公共读
putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
client.putObject(putObjectRequest);
} catch (Exception e) {
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
}
return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
}
public void delete(String path) {
path = path.replace(getUrl() + "/", "");
try {
client.deleteObject(properties.getBucketName(), path);
} catch (Exception e) {
throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
}
}
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
}
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
}
/**
*
*
* @param path
*/
public ObjectMetadata getObjectMetadata(String path) {
path = path.replace(getUrl() + "/", "");
S3Object object = client.getObject(properties.getBucketName(), path);
return object.getObjectMetadata();
}
public InputStream getObjectContent(String path) {
path = path.replace(getUrl() + "/", "");
S3Object object = client.getObject(properties.getBucketName(), path);
return object.getObjectContent();
}
public String getUrl() {
String domain = properties.getDomain();
String endpoint = properties.getEndpoint();
String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
// 云服务商直接返回
if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
if (StringUtils.isNotBlank(domain)) {
if(domain.contains("{root}")){
return domain.replace("{root}", "");
}else if(domain.contains("//")){
return domain;
}else{
return header + domain;
}
}
return header + properties.getBucketName() + "." + endpoint;
}
// minio 单独处理
if (StringUtils.isNotBlank(domain)) {
if(domain.equalsIgnoreCase("{root}")){
return domain.replace("{root}", "")+ "/" + properties.getBucketName();
}else if(domain.contains("//")){
return domain + "/" + properties.getBucketName();
}else{
return header + domain + "/" + properties.getBucketName();
}
}
return header + endpoint + "/" + properties.getBucketName();
}
public String getPath(String prefix, String suffix) {
// 生成uuid
String uuid = IdUtil.fastSimpleUUID();
// 文件路径
String path = DateUtils.datePath() + "/" + uuid;
if (StringUtils.isNotBlank(prefix)) {
path = prefix + "/" + path;
}
return path + suffix;
}
public String getConfigKey() {
return configKey;
}
/**
* URL
*
* @param objectKey KEY
* @param second
*/
public String getPrivateUrl(String objectKey, Integer second) {
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
.withMethod(HttpMethod.GET)
.withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
return url.toString();
}
/**
*
*/
public boolean checkPropertiesSame(OssProperties properties) {
return this.properties.equals(properties);
}
/**
*
*
* @return code
*/
public AccessPolicyType getAccessPolicy() {
return AccessPolicyType.getByType(properties.getAccessPolicy());
}
private static String getPolicy(String bucketName, PolicyType policyType) {
StringBuilder builder = new StringBuilder();
builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
if (policyType == PolicyType.WRITE) {
builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n");
} else if (policyType == PolicyType.READ_WRITE) {
builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n");
} else {
builder.append("\"s3:GetBucketLocation\"\n");
}
builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("\"\n},\n");
if (policyType == PolicyType.READ) {
builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("\"\n},\n");
}
builder.append("{\n\"Action\": ");
switch (policyType) {
case WRITE:
builder.append("[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
break;
case READ_WRITE:
builder.append("[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
break;
default:
builder.append("\"s3:GetObject\",\n");
break;
}
builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
return builder.toString();
}
}

@ -1,24 +0,0 @@
package com.ruoyi.oss.entity;
import lombok.Builder;
import lombok.Data;
/**
*
*
* @author Lion Li
*/
@Data
@Builder
public class UploadResult {
/**
*
*/
private String url;
/**
*
*/
private String filename;
}

@ -1,55 +0,0 @@
package com.ruoyi.oss.enumd;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 访
*
* @author
*/
@Getter
@AllArgsConstructor
public enum AccessPolicyType {
/**
* private
*/
PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE),
/**
* public
*/
PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ),
/**
* custom
*/
CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ);
/**
*
*/
private final String type;
/**
*
*/
private final CannedAccessControlList acl;
/**
*
*/
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 com.ruoyi.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,16 +0,0 @@
package com.ruoyi.oss.exception;
/**
* OSS
*
* @author Lion Li
*/
public class OssException extends RuntimeException {
private static final long serialVersionUID = 1L;
public OssException(String msg) {
super(msg);
}
}

@ -1,63 +0,0 @@
package com.ruoyi.oss.factory;
import com.ruoyi.common.constant.CacheNames;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.CacheUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.core.OssClient;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Factory
*
* @author Lion Li
*/
@Slf4j
public class OssFactory {
private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
/**
*
*/
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);
OssClient client = CLIENT_CACHE.get(configKey);
if (client == null) {
CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
log.info("创建OSS实例 key => {}", configKey);
return CLIENT_CACHE.get(configKey);
}
// 配置不相同则重新构建
if (!client.checkPropertiesSame(properties)) {
CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
log.info("重载OSS实例 key => {}", configKey);
return CLIENT_CACHE.get(configKey);
}
return client;
}
}

@ -1,58 +0,0 @@
package com.ruoyi.oss.properties;
import lombok.Data;
/**
* OSS
*
* @author Lion Li
*/
@Data
public class OssProperties {
/**
* 访
*/
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;
/**
* httpsY=,N=
*/
private String isHttps;
/**
* (0private 1public 2custom)
*/
private String accessPolicy;
}

@ -31,7 +31,7 @@
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
<artifactId>ruoyi-common</artifactId>
</dependency>
</dependencies>

@ -0,0 +1 @@
# 系统文件模块

@ -0,0 +1,115 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-vue-plus</artifactId>
<version>4.6.0</version>
</parent>
<artifactId>ruoyi-system-file</artifactId>
<description>
系统文件模块
</description>
<dependencies>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
<dependency>
<groupId>com.github.gotson</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.2.2</version>
</dependency>
<dependency>
<groupId>org.dromara.x-file-storage</groupId>
<artifactId>x-file-storage-spring</artifactId>
<version>2.2.1</version>
</dependency>
<!-- 华为云OBS -->
<dependency>
<groupId>com.huaweicloud</groupId>
<artifactId>esdk-obs-java</artifactId>
<version>3.22.12</version>
</dependency>
<!-- 华为云OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
<!-- 七牛云Kodo -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.12.1</version>
</dependency>
<!-- 腾讯云COS -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.137</version>
</dependency>
<!-- minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.2</version>
</dependency>
<!-- Amazon S3 -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.429</version>
</dependency>
<!-- 如果出现 okhttp 版本问题,可以尝试引入以下版本,没问题则无需引入-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- FTP SFTP -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.9.0</version>
</dependency>
<!--糊涂工具类扩展-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
</dependency>
<!-- Apache 的对象池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<!-- WebDAV -->
<dependency>
<groupId>com.github.lookfirst</groupId>
<artifactId>sardine</artifactId>
<version>5.10</version>
</dependency>
</dependencies>
</project>

@ -0,0 +1,30 @@
package com.ruoyi.file;
import cn.dev33.satoken.annotation.SaIgnore;
import com.ruoyi.common.annotation.Dev;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Duration;
@Controller
@RequiredArgsConstructor
@RequestMapping("/file/test/")
public class FileDownloadTestController {
private final FileService fileService;
@GetMapping("/download")
@SaIgnore
@Dev
public ModelAndView downloadFile(String url,String p, HttpServletRequest request, HttpServletResponse response) {
fileService.setPlatform(p).download(url, Duration.ofHours(1), request, response);
return null;
}
}

@ -0,0 +1,644 @@
package com.ruoyi.file;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.Img;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.ruoyi.common.utils.IdUtils;
import lombok.Data;
import lombok.SneakyThrows;
import org.apache.tika.Tika;
import org.dromara.x.file.storage.core.Downloader;
import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
public interface FileService {
/**
*
*/
String RULE_KEEP_FILENAME = "/{yyyy}/{MM}/{dd}/{id36}/{filename}.{ext}";
/**
*
*/
String RULE_RANDOM_FILENAME = "/{yyyy}/{MM}/{dd}/{id36}.{ext}";
/**
*
*/
String DEFAULT_PREFIX = "default";
/**
*
*/
String DEFAULT_IMAGE_FILENAME = "a.webp";
/**
*
*/
String THUMBNAIL_EXT = ".min.webp";
/**
*
*/
String SRC_EXT = ".zip";
/**
* webp
*/
String IMAGE_WEBP = "webp";
ThreadLocal<Param> paramThreadLocal = ThreadLocal.withInitial(() -> new Param());
/**
*
*
* @return
*/
String multipartUploadInit();
/**
*
*
* @param uploadId
* @param partNumber
* @param inputStream
*/
void multipartUpload(String uploadId, Integer partNumber, InputStream inputStream);
/**
*
*
* @param uploadId
* @return
*/
String multipartUploadComplete(String uploadId);
/**
*
*
* @param uploadId
*/
void multipartUploadAbort(String uploadId);
List<Integer> multipartUploadListParts(String uploadId);
/**
*
*
* @param url
*/
void delete(String url);
/**
*
*
* @param url
* @return
*/
Downloader download(String url);
/**
*
* 1.
* 2.
* 3.
*
* @param url url
* @param exp
* @param request
* @param response
*/
void download(String url, Duration exp, HttpServletRequest request, HttpServletResponse response);
/**
* URL
*
* @param url URL
* @param exp
* @return
*/
String generatePresignedUrl(String url, Duration exp);
/**
*
*
*
* @param platform
* @return
*/
default FileService setPlatform(String platform) {
paramThreadLocal.get().setPlatform(platform);
return this;
}
/**
*
*
*
* @param prefix
* @return
*/
default FileService setPrefix(String prefix) {
if (StrUtil.isBlank(prefix)) {
throw new RuntimeException("路径前缀不能为空");
}
if (!prefix.matches("^[\\w\\-]+$")) {
throw new RuntimeException("路径前缀规则错误");
}
paramThreadLocal.get().setPrefix(prefix);
return this;
}
/**
*
*
* inputstream
*
* @param filename
* @return
*/
default FileService setFilename(String filename) {
paramThreadLocal.get().setFilename(filename);
return this;
}
/**
*
* 使RULE_KEEP_FILENAME
* 使RULE_RANDOM_FILENAME
*
* @return
*/
default FileService setKeepFilename() {
return setKeepFilename(true);
}
/**
*
* 使RULE_KEEP_FILENAME
* 使RULE_RANDOM_FILENAME
*
* @return
*/
default FileService setKeepFilename(Boolean keepFilename) {
paramThreadLocal.get().setKeepFilename(keepFilename);
return this;
}
/**
*
* setKeepFilenamesetFilename,setRule
*
* @param uri
* @return
*/
default FileService setUri(String uri) {
paramThreadLocal.get().setUri(uri);
return this;
}
/**
*
* 使rule
*
* @param rule
* @return
*/
default FileService setRule(String rule) {
paramThreadLocal.get().setRule(rule);
return this;
}
/**
* saveImage
*
*
* @param maxWidth
* @param maxHeight
* @return
*/
default FileService setSize(int maxWidth, int maxHeight) {
paramThreadLocal.get().setMaxWidth(maxWidth);
paramThreadLocal.get().setMaxHeight(maxHeight);
return this;
}
/**
* saveImage
* ()
*
* @return
*/
FileService setSize();
/**
* saveImage
*
*
* @param watermark
* @return
*/
default FileService setWatermark(BufferedImage watermark) {
paramThreadLocal.get().setWatermark(watermark);
return this;
}
/**
*
* @param watermark
* @return
*/
default FileService setWatermark(Boolean watermark) {
if(watermark){
this.setWatermark();
}else {
paramThreadLocal.get().setWatermark(null);
}
return this;
}
/**
* saveImage
*
*
* @return
*/
FileService setWatermark();
/**
* saveImage
*
*
* @return
*/
default FileService setThumbnail() {
return setThumbnail(true);
}
/**
* saveImage
*
*
* @return
*/
FileService setThumbnail(boolean thumbnail);
default FileService setSaveSrc(){
return setSaveSrc(true);
}
default FileService setSaveSrc(boolean saveSrc){
paramThreadLocal.get().setSaveSrc(saveSrc);
return this;
}
/**
* saveImage
* ,
*
* @param width
* @param height
* @return
*/
default FileService setThumbnail(int width, int height) {
paramThreadLocal.get().setThWidth(width);
paramThreadLocal.get().setThHeight(height);
return this;
}
/**
*
*
* @param in
* @return
*/
String save(InputStream in);
/**
*
*
* @param file
* @return
*/
@SneakyThrows
default String save(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new RuntimeException("文件不能为空");
}
return setFilename(file.getOriginalFilename()).save(file.getInputStream());
}
/**
*
*
* @param in
* @return
*/
String saveImage(InputStream in);
/**
*
*
* @param file
* @return
*/
@SneakyThrows
default String saveImage(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new RuntimeException("文件不能为空");
}
return setFilename(file.getOriginalFilename()).saveImage(file.getInputStream());
}
/**
*
*
* @return
*/
Tika getTika();
/**
*
*
* @param in
* @return
*/
@SneakyThrows
default String getContentType(InputStream in) {
return getTika().detect(in);
}
/**
*
*
* @param in
* @return
*/
@SneakyThrows
default boolean isImage(InputStream in) {
return getContentType(in).startsWith("image/");
}
default InputStream formatImage(MultipartFile file, int maxWidth, int maxHeight,
BufferedImage watermark) {
try {
if (file == null || file.isEmpty()) {
throw new RuntimeException("上传图片不能为空");
}
if (!file.getContentType().startsWith("image/")) {
throw new RuntimeException("上传的文件不是图片");
}
return formatImage(file.getInputStream(), maxWidth, maxHeight, watermark);
} catch (IOException e) {
throw new RuntimeException("处理图片错误", e);
}
}
default InputStream formatImage(Image img, int maxWidth, int maxHeight, BufferedImage watermark) {
if (img == null) {
throw new RuntimeException("图片不能为空");
}
return formatImage(Img.from(img), maxWidth, maxHeight, watermark);
}
default InputStream formatImage(InputStream in, int maxWidth, int maxHeight, BufferedImage watermark) {
return formatImage(Img.from(in), maxWidth, maxHeight, watermark);
}
default InputStream formatImage(Img img, int maxWidth, int maxHeight, BufferedImage watermark) {
try {
int w = img.getImg().getWidth(null);
int h = img.getImg().getHeight(null);
if (maxWidth > 0 && maxHeight > 0) {
if (w > maxWidth || h > maxHeight) {
int outWidth = 0;
int outHeight = 0;
outHeight = maxWidth * h / w;
if (outHeight > maxHeight) {
outHeight = maxHeight;
outWidth = outHeight * w / h;
} else {
outWidth = maxWidth;
}
img = img.scale(outWidth, outHeight);
w = outWidth;
h = outHeight;
}
}
if (watermark != null) {
int ww = watermark.getWidth(null);
int wh = watermark.getHeight(null);
if (w > ww && h > wh) {
img = img.pressImage(watermark, 0, 0, 1f);
}
}
ByteArrayOutputStream pout = new ByteArrayOutputStream();
// img.setTargetImageType(IMAGE_WEBP).write(pout);
ImageIO.write(ImgUtil.toBufferedImage(img.getImg()), IMAGE_WEBP, pout);
ByteArrayInputStream pin = new ByteArrayInputStream(pout.toByteArray());
pout.close();
pout = null;
return pin;
} catch (Exception e) {
throw new RuntimeException("处理图片错误", e);
}
}
/**
* URI
*
* @param filename
* @param prefix
* @return
*/
default String generateURIKeepFilename(String prefix, String filename) {
return generateURI(prefix, RULE_KEEP_FILENAME, filename);
}
/**
* URI
*
* @param filename
* @return
*/
default String generateURIKeepFilename(String filename) {
return generateURI(DEFAULT_PREFIX, RULE_KEEP_FILENAME, filename);
}
/**
* URI
*
* @param filename
* @param prefix
* @return
*/
default String generateURIRandomFilename(String prefix, String filename) {
return generateURI(prefix, RULE_RANDOM_FILENAME, filename);
}
/**
* URI
*
* @param filename
* @return
*/
default String generateURIRandomFilename(String filename) {
return generateURI(DEFAULT_PREFIX, RULE_RANDOM_FILENAME, filename);
}
/**
* <pre>
* - URI
* -
* - {yyyy}/{MM}/{dd}/{HH}/{mm}/{ss}
* - {UUID} 32
* - {i} id
* - {id} intid,+
* - {id16} intid16,+
* - {id36} intid36,+
* - {filename}
* - {ext}
* </pre>
*
* @param prefix
* @param rule
* @param filename
* @return
*/
default String generateURI(String prefix, String rule, String filename) {
if (StrUtil.isBlank(prefix)) {
prefix = DEFAULT_PREFIX;
}
LocalDateTime now = LocalDateTime.now();
if (rule.contains("{yyyy}")) {
rule = rule.replace("{yyyy}", "" + now.getYear());
}
if (rule.contains("{MM}")) {
rule = rule.replace("{MM}", String.format("%02d", now.getMonthValue()));
}
if (rule.contains("{dd}")) {
rule = rule.replace("{dd}", String.format("%02d", now.getDayOfMonth()));
}
if (rule.contains("{HH}")) {
rule = rule.replace("{HH}", String.format("%02d", now.getHour()));
}
if (rule.contains("{mm}")) {
rule = rule.replace("{mm}", String.format("%02d", now.getMinute()));
}
if (rule.contains("{ss}")) {
rule = rule.replace("{ss}", String.format("%02d", now.getSecond()));
}
if (rule.contains("{UUID}")) {
rule = rule.replace("{UUID}", UUID.fastUUID().toString(true));
}
if (rule.contains("{i}")) {
rule = rule.replace("{i}", IdUtils.nextId(Id.groupName).toString());
}
if (rule.contains("{id}")) {
rule = rule.replace("{id}", Long.toString(id.nextId()));
}
if (rule.contains("{id16}")) {
rule = rule.replace("{id16}", Long.toString(id.nextId(), 16));
}
if (rule.contains("{id36}")) {
rule = rule.replace("{id36}", Long.toString(id.nextId(), 36));
}
if (rule.contains("{filename}")) {
String temp = null;
if (filename.contains(".")) {
temp = filename.substring(0, filename.lastIndexOf("."));
} else {
temp = filename;
}
rule = rule.replace("{filename}", temp);
}
if (rule.contains("{ext}")) {
String temp = null;
if (filename.contains(".")) {
temp = filename.substring(filename.lastIndexOf(".") + 1);
} else {
temp = "";
}
rule = rule.replace("{ext}", temp.toLowerCase());
}
return prefix + rule;
}
Id id = new Id();
static class Id {
private static final String groupName = "file:id";
private SymmetricCrypto crypto;
private String today = DateUtil.today();
public synchronized Long nextId() {
if (crypto == null || !today.equals(DateUtil.today())) {
today = DateUtil.today();
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), today.getBytes()).getEncoded();
crypto = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
}
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.putInt(IdUtils.nextDayId(groupName).intValue());
return Math.abs(ByteBuffer.wrap(crypto.encrypt(buffer.array())).getLong());
}
}
@Data
public static class Param {
private String platform;
private String prefix = FileService.DEFAULT_PREFIX;
private String filename;
private boolean keepFilename = false;
private boolean saveSrc = false;
private String rule;
private String uri;
private int maxWidth = 0;
private int maxHeight = 0;
private BufferedImage watermark;
private int thWidth = 0;
private int thHeight = 0;
}
}

@ -0,0 +1,184 @@
package com.ruoyi.file;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.annotation.Dev;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.file.FileUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.time.Duration;
@RestController
@RequiredArgsConstructor
@RequestMapping("/file/upload/")
@SaIgnore
public class FileUploadApi {
public final FileService fileService;
/**
*
*
*
* @return
*/
@GetMapping("getUploadKey")
@Dev
public R getUploadKey() {
return R.ok().setData(FileUtils.getUploadKey());
}
/**
*
*
* @param key
*/
@GetMapping("removeUploadKey-{key}")
public void removeUploadKey(@PathVariable String key) {
FileUtils.removeUploadKey(key);
}
@GetMapping("exitisUploadKey-{key}")
public Boolean exitisUploadKey(@PathVariable String key) {
return FileUtils.exitisUploadKey(key);
}
/**
*
*
* @param file
* @param prefix
* @param key
* @param keepFilename
* @return
*/
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R upload(@RequestPart("file") MultipartFile file, String prefix, String key, @RequestParam(defaultValue = "false") Boolean keepFilename) {
if (ObjectUtil.isNull(file)) {
throw new ServiceException("文件必填");
}
if (!FileUtils.exitisUploadKey(key)) {
throw new ServiceException("上传凭证无效");
}
return R.ok().setData(fileService.setPrefix(prefix).setKeepFilename(keepFilename).save(file));
}
@PostMapping("multipartUploadInit")
public R multipartUploadInit(String filename, String prefix, String key, @RequestParam(defaultValue = "false") Boolean keepFilename) {
if (StrUtil.isBlank(filename)) {
throw new ServiceException("文件名必填");
}
if (!FileUtils.exitisUploadKey(key)) {
throw new ServiceException("上传凭证无效");
}
return R.ok().setData(fileService.setPrefix(prefix).setFilename(filename).setKeepFilename(keepFilename).multipartUploadInit());
}
@PostMapping(value = "multipartUpload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@SneakyThrows
public R multipartUpload(@RequestPart("file") MultipartFile file, String uploadId, Integer partNumber) {
if (StrUtil.isBlank(uploadId)) {
throw new ServiceException("上传标识必填");
}
fileService.multipartUpload(uploadId, partNumber, file.getInputStream());
return R.ok();
}
@GetMapping("multipartUploadListParts")
public R multipartUploadListParts(String uploadId) {
if (StrUtil.isBlank(uploadId)) {
throw new ServiceException("上传标识必填");
}
return R.ok().setData(fileService.multipartUploadListParts(uploadId));
}
@PostMapping("multipartUploadComplete")
public R multipartUploadComplete(String uploadId) {
if (StrUtil.isBlank(uploadId)) {
throw new ServiceException("上传标识必填");
}
return R.ok().setData(fileService.multipartUploadComplete(uploadId));
}
@PostMapping("multipartUploadAbort")
public R multipartUploadAbort(String uploadId) {
if (StrUtil.isBlank(uploadId)) {
throw new ServiceException("上传标识必填");
}
try {
fileService.multipartUploadAbort(uploadId);
} catch (Exception e) {
}
return R.ok();
}
/**
*
*
* @param file
* @param prefix
* @param key
* @param saveSrc
* @param saveThumbnail
* @return
*/
@PostMapping(value = "image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R uploadImage(@RequestPart("file") MultipartFile file, String prefix, String key,
@RequestParam(defaultValue = "false") Boolean saveSrc,
@RequestParam(defaultValue = "true") Boolean watermark,
@RequestParam(defaultValue = "true") Boolean saveThumbnail
) {
if (ObjectUtil.isNull(file)) {
throw new ServiceException("文件必填");
}
if (!FileUtils.exitisUploadKey(key)) {
throw new ServiceException("上传凭证无效");
}
return R.ok().setData(fileService.setPrefix(prefix).setWatermark(watermark).setSaveSrc(saveSrc).setThumbnail(saveThumbnail).saveImage(file));
}
@GetMapping("/download")
@SneakyThrows
public ModelAndView downloadFile(String url, String key, HttpServletRequest request, HttpServletResponse response) {
if (StrUtil.isBlank(url)) {
response.sendError(404, "URL不能为空");
return null;
}
if (!FileUtils.exitisUploadKey(key)) {
response.sendError(404, "上传凭证无效");
return null;
}
fileService.download(url, Duration.ofHours(1), request, response);
return null;
}
@PostMapping("/remove")
public R remove(String url, String key){
if (StrUtil.isBlank(url)) {
throw new ServiceException("URL不能为空");
}
if (!FileUtils.exitisUploadKey(key)) {
throw new ServiceException("上传凭证无效");
}
fileService.delete(url);
return R.ok();
}
}

@ -0,0 +1,18 @@
package com.ruoyi.file.config;
import org.dromara.x.file.storage.spring.EnableFileStorage;
import org.dromara.x.file.storage.spring.FileStorageAutoConfiguration;
import org.dromara.x.file.storage.spring.SpringFileStorageProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
//@EnableFileStorage
@Import({FileStorageAutoConfiguration.class})
public class FileConfig {
}

@ -0,0 +1,45 @@
package com.ruoyi.file.config;
import cn.hutool.core.img.ImgUtil;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.awt.image.BufferedImage;
import java.io.File;
@Configuration
@ConfigurationProperties(prefix = "ruoyi.file")
@Data
@Slf4j
public class SpringFileStorageProperties extends org.dromara.x.file.storage.spring.SpringFileStorageProperties {
private Integer maxWidth;
private Integer maxHeight;
private File watermark;
private Integer thWidth;
private Integer thHeight;
@Getter
@Setter(AccessLevel.PRIVATE)
private BufferedImage watermarkImage;
@PostConstruct
public void init() {
try {
if (watermark != null && watermark.isFile()) {
watermarkImage = ImgUtil.read(watermark);
log.warn("水印图片加载成功:" + watermark.toString());
}
} catch (Exception e) {
log.warn("初始化水印失败", e);
}
}
}

@ -0,0 +1,370 @@
package com.ruoyi.file.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.core.util.ZipUtil;
import com.ruoyi.common.utils.HttpDownloadUtil;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.file.FileService;
import com.ruoyi.file.config.SpringFileStorageProperties;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.tika.Tika;
import org.dromara.x.file.storage.core.Downloader;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.constant.Constant;
import org.dromara.x.file.storage.core.platform.FileStorage;
import org.dromara.x.file.storage.core.platform.LocalPlusFileStorage;
import org.dromara.x.file.storage.core.platform.UpyunUssFileStorage;
import org.dromara.x.file.storage.core.presigned.GeneratePresignedUrlResult;
import org.dromara.x.file.storage.core.tika.TikaFactory;
import org.dromara.x.file.storage.core.upload.FilePartInfo;
import org.dromara.x.file.storage.core.upload.MultipartUploadSupportInfo;
import org.dromara.x.file.storage.core.upload.UploadPretreatment;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.*;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Slf4j
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {
private final FileStorageService service;
private final SpringFileStorageProperties properties;
private final TikaFactory tikaFactory;
public static final String CACHE_KEY_PREFIX = "file:multipart:";
@Override
public String multipartUploadInit() {
try {
UploadPretreatment p = new UploadPretreatment();
Param param = paramThreadLocal.get();
initUploadPretreatment(p, param);
FileStorage fileStorage = service.getFileStorage(p.getPlatform());
MultipartUploadSupportInfo supportInfo = fileStorage.isSupportMultipartUpload();
if (!(supportInfo.getIsSupport() && supportInfo.getIsSupportAbort() && supportInfo.getIsSupportListParts())) {
throw new RuntimeException("不支持分片上传");
}
boolean isUpyunUss = fileStorage instanceof UpyunUssFileStorage;
FileInfo fileInfo = service.initiateMultipartUpload()
.setPlatform(p.getPlatform())
.setPath(p.getPath())
.setSaveFilename(p.getSaveFilename())
.putMetadata(isUpyunUss, "X-Upyun-Multi-Part-Size", String.valueOf(5 * 1024 * 1024))// 设置 Metadata不需要可以不写
.init();
String uploadId = UUID.fastUUID().toString(true);
RedisUtils.setCacheObject(CACHE_KEY_PREFIX + uploadId, fileInfo, Duration.ofMinutes(60));
return uploadId;
} finally {
paramThreadLocal.remove();
}
}
@Override
public void multipartUpload(String uploadId, Integer partNumber, InputStream inputStream) {
FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
if (fileInfo == null) {
throw new RuntimeException("未初始化分片上传");
}
service.uploadPart(fileInfo, partNumber, inputStream).upload();
}
@Override
public String multipartUploadComplete(String uploadId) {
FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
if (fileInfo == null) {
throw new RuntimeException("未初始化分片上传");
}
service.completeMultipartUpload(fileInfo).complete();
RedisUtils.deleteObject(CACHE_KEY_PREFIX + uploadId);
return fileInfo.getUrl();
}
@Override
public void multipartUploadAbort(String uploadId) {
FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
if (fileInfo == null) {
throw new RuntimeException("未初始化分片上传");
}
service.abortMultipartUpload(fileInfo).abort();
RedisUtils.deleteObject(CACHE_KEY_PREFIX + uploadId);
}
@Override
public List<Integer> multipartUploadListParts(String uploadId) {
FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
return service.listParts(fileInfo).listParts().getList().stream().map(FilePartInfo::getPartNumber).collect(Collectors.toList());
}
@Override
public void delete(String url) {
try {
FileInfo fileInfo = getFileInfoByURL(url);
service.delete(fileInfo);
} finally {
paramThreadLocal.remove();
}
}
@Override
public Downloader download(String url) {
try {
FileInfo fileInfo = getFileInfoByURL(url);
return service.download(fileInfo);
} finally {
paramThreadLocal.remove();
}
}
@SneakyThrows
public void download(String url, Duration exp, HttpServletRequest request, HttpServletResponse response) {
try {
FileInfo fileInfo = getFileInfoByURL(url);
FileStorage fileStorage = service.getFileStorage(fileInfo.getPlatform());
if (fileStorage.isSupportPresignedUrl()) {
url = generatePresignedUrlInner(url, null, fileInfo, fileStorage);
log.debug("跳转:"+url);
response.reset();
response.setStatus(HttpServletResponse.SC_FOUND);
response.setHeader("Location", url);
response.getWriter().print("");
response.flushBuffer();
} else {
if (fileStorage instanceof LocalPlusFileStorage) {
LocalPlusFileStorage fs = (LocalPlusFileStorage) fileStorage;
File file = new File(new File(fs.getStoragePath(), fileInfo.getPath()), fileInfo.getFilename());
log.debug("下载本地文件:" + file.getAbsolutePath());
if (!file.isFile()) {
throw new FileNotFoundException("文件不存在");
}
HttpDownloadUtil.download(request, response, file);
} else {
response.reset();
FileUtils.setAttachmentResponseHeader(response, fileInfo.getFilename());
service.download(fileInfo).outputStream(response.getOutputStream());
response.flushBuffer();
}
}
} catch (Exception e) {
log.warn("下载文件失败", e);
response.sendError(404, "下载文件失败");
} finally {
paramThreadLocal.remove();
}
}
@Override
@SneakyThrows
public String generatePresignedUrl(String url, Duration exp) {
try {
FileInfo fileInfo = getFileInfoByURL(url);
FileStorage fileStorage = service.getFileStorage(fileInfo.getPlatform());
return generatePresignedUrlInner(url, exp, fileInfo, fileStorage);
} finally {
paramThreadLocal.remove();
}
}
@SneakyThrows
private String generatePresignedUrlInner(String url, Duration exp, FileInfo fileInfo, FileStorage fileStorage) {
if (!fileStorage.isSupportPresignedUrl()) {
throw new RuntimeException("不支持预签名URL");
}
GeneratePresignedUrlResult downloadResult = service.generatePresignedUrl()
.setPlatform(fileInfo.getPlatform()) // 存储平台,不传使用默认的
.setPath(fileInfo.getPath()) // 文件路径
.setFilename(fileInfo.getFilename()) // 文件名,也可以换成缩略图的文件名
.setMethod(Constant.GeneratePresignedUrl.Method.GET) // 签名方法
.setExpiration(exp == null ? new Date(System.currentTimeMillis() + 600000) : new Date(System.currentTimeMillis() + exp.toMillis())) // 过期时间 10 分钟
.putResponseHeaders(
Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=" + (fileInfo.getFilename().endsWith("." + FileService.IMAGE_WEBP + FileService.SRC_EXT) ? "原图.zip" : fileInfo.getFilename()))
.generatePresignedUrl();
log.debug("生成访问预签名 URL 结果:{}", downloadResult);
String q = downloadResult.getUrl().substring(downloadResult.getUrl().indexOf("?"));
return URLUtil.encode(url) + q;
}
@Override
public FileService setSize() {
return setSize(properties.getMaxWidth(), properties.getMaxHeight());
}
@Override
public FileService setWatermark() {
return setWatermark(properties.getWatermarkImage());
}
@Override
public FileService setThumbnail(boolean thumbnail) {
if (thumbnail) {
return setThumbnail(properties.getThWidth(), properties.getThHeight());
} else {
return setThumbnail(0, 0);
}
}
@Override
@SneakyThrows
public String save(InputStream in) {
try {
UploadPretreatment p = service.of(in);
Param param = paramThreadLocal.get();
initUploadPretreatment(p, param);
return p.upload().getUrl();
} finally {
paramThreadLocal.remove();
}
}
@Override
@SneakyThrows
public String saveImage(InputStream in) {
try {
byte[] imageBytes = IoUtil.readBytes(in, true);
Image image = ImgUtil.read(new ByteArrayInputStream(imageBytes));
Param param = paramThreadLocal.get();
param.setKeepFilename(false);
String srcFilename = param.getFilename();
param.setFilename(FileService.DEFAULT_IMAGE_FILENAME);
UploadPretreatment p = service.of(formatImage(image, param.getMaxWidth(), param.getMaxHeight(), param.getWatermark()));
initUploadPretreatment(p, param);
String url = p.upload().getUrl();
if (param.getThHeight() > 0 && param.getThWidth() > 0) {
UploadPretreatment p1 = service.of(formatImage(image, param.getThWidth(), param.getThHeight(), null));
String platform = param.getPlatform();
if (StrUtil.isNotBlank(platform)) {
p1 = p1.setPlatform(platform);
}
p1.setPath(p.getPath());
p1.setSaveFilename(p.getSaveFilename() + FileService.THUMBNAIL_EXT);
p1.upload();
}
if (param.isSaveSrc() && StrUtil.isNotBlank(srcFilename)) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ZipUtil.zip(out, new String[]{srcFilename}, new InputStream[]{new ByteArrayInputStream(imageBytes)});
UploadPretreatment p1 = service.of(new ByteArrayInputStream(out.toByteArray()));
String platform = param.getPlatform();
if (StrUtil.isNotBlank(platform)) {
p1 = p1.setPlatform(platform);
}
p1.setPath(p.getPath());
p1.setSaveFilename(p.getSaveFilename() + FileService.SRC_EXT);
p1.upload();
}
return url;
} finally {
paramThreadLocal.remove();
}
}
@Override
public Tika getTika() {
return tikaFactory.getTika();
}
private FileInfo getFileInfoByURL(String url) {
if (StrUtil.isBlank(url)) {
throw new RuntimeException("URL不能为空");
}
FileInfo fileInfo = new FileInfo();
String platform = paramThreadLocal.get().getPlatform();
FileStorage fileStorage = null;
if (StrUtil.isBlank(platform)) {
fileStorage = service.getFileStorage();
} else {
fileStorage = service.getFileStorage(platform);
}
fileInfo.setPlatform(fileStorage.getPlatform());
String domain = null;
try {
domain = (String) BeanUtil.getProperty(fileStorage, "domain");
} catch (Exception e) {
}
if (StrUtil.isBlank(domain)) {
int index = url.indexOf("://");
if (index > -1) {
url = url.substring(url.indexOf("/", index + 4) + 1);
} else if (url.startsWith("//")) {
url = url.substring(url.indexOf("/", 3) + 1);
} else if (url.startsWith("/")) {
url = url.substring(1);
}
index = url.indexOf("/");
url = url.substring(index + 1);
index = url.lastIndexOf("/");
fileInfo.setPath(url.substring(0, index + 1));
fileInfo.setFilename(url.substring(index + 1));
} else {
url = url.substring(domain.length());
int index = url.lastIndexOf("/");
fileInfo.setPath(url.substring(0, index + 1));
fileInfo.setFilename(url.substring(index + 1));
}
return fileInfo;
}
private void initUploadPretreatment(UploadPretreatment p, Param param) {
String platform = param.getPlatform();
if (StrUtil.isNotBlank(platform)) {
p = p.setPlatform(platform);
} else {
p = p.setPlatform(service.getFileStorage().getPlatform());
}
String uri = param.getUri();
if (StrUtil.isBlank(uri)) {
String rule = param.getRule();
if (StrUtil.isBlank(rule)) {
if (param.isKeepFilename()) {
rule = RULE_KEEP_FILENAME;
} else {
rule = RULE_RANDOM_FILENAME;
}
}
uri = generateURI(param.getPrefix(), rule, param.getFilename());
} else {
uri = param.getPrefix() + uri;
}
int index = uri.lastIndexOf("/") + 1;
String path = uri.substring(0, index);
String filename = uri.substring(index);
log.debug("path={},filename={}", path, filename);
p.setPath(path).setSaveFilename(filename);
}
}

@ -0,0 +1,31 @@
package com.ruoyi.file.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.aspect.CompleteMultipartUploadAspectChain;
import org.dromara.x.file.storage.core.aspect.FileStorageAspect;
import org.dromara.x.file.storage.core.aspect.ListPartsAspectChain;
import org.dromara.x.file.storage.core.platform.AliyunOssFileStorage;
import org.dromara.x.file.storage.core.platform.FileStorage;
import org.dromara.x.file.storage.core.recorder.FileRecorder;
import org.dromara.x.file.storage.core.tika.ContentTypeDetect;
import org.dromara.x.file.storage.core.upload.CompleteMultipartUploadPretreatment;
import org.dromara.x.file.storage.core.upload.FilePartInfoList;
import org.dromara.x.file.storage.core.upload.ListPartsPretreatment;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RequiredArgsConstructor
public class FixFileStorageAspect implements FileStorageAspect {
@Override
public FilePartInfoList listParts(ListPartsAspectChain chain, ListPartsPretreatment pre, FileStorage fileStorage) {
if(fileStorage instanceof AliyunOssFileStorage) {
pre.setPartNumberMarker(null);
}
return chain.next(pre,fileStorage);
}
}

@ -0,0 +1,4 @@
/**
*
*/
package com.ruoyi.file;

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@ -23,11 +23,7 @@
<artifactId>ruoyi-common</artifactId>
</dependency>
<!-- OSS功能模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-oss</artifactId>
</dependency>
<!-- SMS功能模块 -->
<dependency>
@ -35,10 +31,6 @@
<artifactId>ruoyi-sms</artifactId>
</dependency>
<dependency>
<groupId>com.github.gotson</groupId>
<artifactId>webp-imageio</artifactId>
</dependency>
</dependencies>

@ -1,75 +0,0 @@
package com.ruoyi.system.config;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.URLUtil;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.HttpDownloadUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
/**
*
* :SpringBoot
* :Servlet
*/
@Configuration
@RequiredArgsConstructor
@Slf4j
public class DownloadFileConfig {
private final RuoYiConfig config;
@Bean
public WebMvcConfigurer DownloadFileWebMvcConfigurer() {
return new WebMvcConfigurer(){
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//加入外部静态资源html文件夹
String path = new File(new File(config.upload.savePath).getAbsolutePath()).toURI().toString();
registry.addResourceHandler(config.upload.pre+"/**").addResourceLocations(path);
log.info("添加静态资源目: {}/** ={}",config.upload.pre,path);
}
};
}
public ServletRegistrationBean DownloadFileServletRegistrationBean() {
ServletRegistrationBean bean = new ServletRegistrationBean();
bean.setServlet(new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri = req.getRequestURI().substring(config.upload.pre.length());
uri = URLUtil.decode(uri);
File down = new File(config.upload.savePath, uri);
if(down.isFile()) {
try {
HttpDownloadUtil.download(req, resp,down,
FileNameUtil.getName(uri));
resp.getOutputStream().close();
} catch (Exception e) {
resp.reset();
resp.sendError(404);
}
}else {
resp.sendError(404);
}
}
});
bean.addUrlMappings(config.upload.pre+"/*");
return bean;
}
}

@ -1,50 +0,0 @@
package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* OSS
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_oss")
public class SysOss extends BaseEntity {
/**
*
*/
@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 com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 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;
/**
* https0 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,46 +0,0 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* OSS sys_oss
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
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,107 +0,0 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* sys_oss_config
*
* @author Lion Li
* @author
* @date 2021-08-13
*/
@Data
@EqualsAndHashCode(callSuper = true)
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;
/**
* httpsY=,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,90 +0,0 @@
package com.ruoyi.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import lombok.Data;
/**
* sys_oss_config
*
* @author Lion Li
* @author
* @date 2021-08-13
*/
@Data
@ExcelIgnoreUnannotated
public class SysOssConfigVo {
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;
/**
* httpsY=,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,58 +0,0 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
import java.util.Date;
/**
* OSS sys_oss
*
* @author Lion Li
*/
@Data
public class SysOssVo {
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 String createBy;
/**
*
*/
private String service;
}

@ -1,16 +0,0 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.SysOssConfig;
import com.ruoyi.system.domain.vo.SysOssConfigVo;
/**
* Mapper
*
* @author Lion Li
* @author
* @date 2021-08-13
*/
public interface SysOssConfigMapper extends BaseMapperPlus<SysOssConfigMapper, SysOssConfig, SysOssConfigVo> {
}

@ -1,13 +0,0 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.vo.SysOssVo;
/**
*
*
* @author Lion Li
*/
public interface SysOssMapper extends BaseMapperPlus<SysOssMapper, SysOss, SysOssVo> {
}

@ -3,7 +3,6 @@ package com.ruoyi.system.runner;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysDictTypeService;
import com.ruoyi.system.service.ISysOssConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
@ -23,11 +22,10 @@ public class SystemApplicationRunner implements ApplicationRunner {
private final RuoYiConfig ruoyiConfig;
private final ISysConfigService configService;
private final ISysDictTypeService dictTypeService;
private final ISysOssConfigService ossConfigService;
@Override
public void run(ApplicationArguments args) throws Exception {
ossConfigService.init();
log.info("初始化OSS配置成功");
if (ruoyiConfig.isCacheLazy()) {
return;

@ -1,121 +0,0 @@
package com.ruoyi.system.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.Img;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.Calendar;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.ruoyi.common.utils.IdUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.springframework.web.multipart.MultipartFile;
/**
* <pre>
* -
* Author : J.L.Zhou
* E-Mail : 2233875735@qq.com
* Tel : 151 1104 7708
* Date : 2021-6-1 11:48:16
* Version : 1.0
* Copyright 2021 jlzhou.top Inc. All rights reserved.
* Warning: this content is only for internal circulation of the company.
* It is forbidden to divulge it or use it for other commercial purposes.
* </pre>
*/
public interface FileService {
/**
* - ,
*
* @param in
* @param filename -
* @param pre -
* @return - URL
* @throws 400-499
*/
default String save(InputStream in, String filename, String pre) {
return save(in, filename, pre, true);
}
/**
*
* @param in
* @param filename -
* @param pre -
* @param randomName -
* @return
* @throws 400-499
*/
default String save(InputStream in, String filename, String pre, boolean randomName) {
if (StrUtil.isNotBlank(pre) && !pre.matches("^[\\w\\/]+$")) {
throw new RuntimeException("存放路径前缀只能有单词字符组成");
}
try {
if (in == null || in.available() == 0) {
throw new RuntimeException("读取上传文件长度错误");
}
} catch (IOException e) {
throw new RuntimeException("读取上传文件长度错误", e);
}
if (StrUtil.isEmpty(filename)) {
throw new RuntimeException("文件名为空");
}
filename = filename.replace(" ", "");
String name = (StrUtil.isNotBlank(pre)?(pre+"/"):"")
+ generateURI("{yyyy}/{MM}/{dd}/{id36}" + (randomName ? ".{ext}" : "/{filename}.{ext}"), filename);
return save(in, name);
}
String save(InputStream in, String filename);
/**
* -
*
* @param file URL
*/
void delete(String file);
/**
*
* @param file
* @return
*/
File getFile(String file);
/**
* <pre>
* - URI
* -
* - {yyyy}/{MM}/{dd}/{HH}/{mm}/{ss}
* - {UUID} 32
* - {id} intid,+
* - {id16} intid16,+
* - {id36} intid36,+
* - {filename}
* - {ext}
* </pre>
*
* @param rule
* @param filename
* @return
*/
default String generateURI(String rule, String filename) {
return SpringUtils.getBean(ISysOssService.class).generateURI(rule, filename);
}
}

@ -1,65 +0,0 @@
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.bo.SysOssConfigBo;
import com.ruoyi.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,396 +0,0 @@
package com.ruoyi.system.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.Img;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.helper.DataPermissionHelper;
import com.ruoyi.common.utils.IdUtils;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.bo.SysOssBo;
import com.ruoyi.system.domain.vo.SysOssVo;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;
/**
*
*
* @author Lion Li
*/
public interface ISysOssService {
/**
*
*/
public static enum Service {
minio, qiniu, aliyun, qcloud, image, upload;
}
String IMAGE_WEBP = "webp";
/**
*
*/
String PRE_DEFAULT = "default";
/**
*
*
* @param handle
*/
void setService(Service service, Runnable handle);
public <T> T setService(Service service, Supplier<T> handle);
/**
*
*
* @param handle
*/
void ignore(Runnable handle);
/**
*
*
* @param handle
*/
public <T> T ignore(Supplier<T> handle);
TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery);
List<SysOssVo> listByIds(Collection<Long> ossIds);
SysOssVo getById(Long ossId);
default SysOssVo upload(MultipartFile file) {
return upload(file, PRE_DEFAULT);
}
/**
* @param file
* @param pre -
* @return
*/
default SysOssVo upload(MultipartFile file, String pre) {
if (file == null || file.isEmpty()) {
throw new RuntimeException("文件不能为空");
}
try {
return save(file.getInputStream(), file.getOriginalFilename(), file.getContentType(), pre);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* , rule:{yyyy}/{MM}/{dd}/{id36}.{ext}
*
* @param in
* @param filename
* @param contentType
* @param pre
* @return
*/
default SysOssVo save(InputStream in, String filename, String contentType, String pre) {
return save(in, filename, contentType, pre, "{yyyy}/{MM}/{dd}/{id36}.{ext}");
}
/**
* ,rule:{yyyy}/{MM}/{dd}/{id36}/{filename}.{ext}
*
* @param in
* @param filename
* @param contentType
* @param pre
* @return
*/
default SysOssVo saveFilename(InputStream in, String filename, String contentType, String pre) {
return save(in, filename, contentType, pre, "{yyyy}/{MM}/{dd}/{id36}/{filename}.{ext}");
}
/**
* rule
* <pre>
* - URI
* -
* - {yyyy}/{MM}/{dd}/{HH}/{mm}/{ss}
* - {UUID} 32
* - {i} id
* - {id} intid,+
* - {id16} intid16,+
* - {id36} intid36,+
* - {filename}
* - {ext}
* </pre>
*
* @param in
* @param filename
* @param contentType
* @param pre
* @param rule
* @return
*/
SysOssVo save(InputStream in, String filename, String contentType, String pre, String rule);
SysOssVo uploadImgs(MultipartFile file, String pre);
default SysOssVo uploadImgs(MultipartFile file, String pre, int maxWidth, int maxHeight, BufferedImage watermark) {
if (file == null || file.isEmpty()) {
throw new RuntimeException("图片不能为空");
}
try {
return uploadImgs(file.getInputStream(), pre, maxWidth, maxHeight, watermark);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* @param in
* @param pre -
* @param maxWidth - 1
* @param maxHeight - 1
* @param watermark - null
* @return
*/
SysOssVo uploadImgs(InputStream in, String pre, int maxWidth, int maxHeight,
BufferedImage watermark);
void download(Long ossId, HttpServletResponse response) throws IOException;
void download(String url, Service service, HttpServletResponse response) throws IOException;
InputStream download(Long ossId) throws IOException;
InputStream download(SysOssVo sysOss) throws IOException;
InputStream download(String url, Service service) throws IOException;
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
default InputStream formatImage(MultipartFile file, int maxWidth, int maxHeight,
BufferedImage watermark) {
try {
if (file == null || file.isEmpty()) {
throw new RuntimeException("上传图片不能为空");
}
if (!file.getContentType().startsWith("image/")) {
throw new RuntimeException("上传的文件不是图片");
}
return formatImage(file.getInputStream(), maxWidth, maxHeight, watermark);
} catch (IOException e) {
throw new RuntimeException("处理图片错误", e);
}
}
default InputStream formatImage(Image img, int maxWidth, int maxHeight, BufferedImage watermark) {
if (img == null) {
throw new RuntimeException("图片不能为空");
}
return formatImage(Img.from(img), maxWidth, maxHeight, watermark);
}
default InputStream formatImage(InputStream in, int maxWidth, int maxHeight, BufferedImage watermark) {
return formatImage(Img.from(in), maxWidth, maxHeight, watermark);
}
default InputStream formatImage(Img img, int maxWidth, int maxHeight, BufferedImage watermark) {
try {
int w = img.getImg().getWidth(null);
int h = img.getImg().getHeight(null);
if (maxWidth > 0 && maxHeight > 0) {
if (w > maxWidth || h > maxHeight) {
int outWidth = 0;
int outHeight = 0;
outHeight = maxWidth * h / w;
if (outHeight > maxHeight) {
outHeight = maxHeight;
outWidth = outHeight * w / h;
} else {
outWidth = maxWidth;
}
img = img.scale(outWidth, outHeight);
w = outWidth;
h = outHeight;
}
}
if (watermark != null) {
int ww = watermark.getWidth(null);
int wh = watermark.getHeight(null);
if (w > ww && h > wh) {
img = img.pressImage(watermark, 0, 0, 1f);
}
}
ByteArrayOutputStream pout = new ByteArrayOutputStream();
img.setTargetImageType(IMAGE_WEBP).write(pout);
ByteArrayInputStream pin = new ByteArrayInputStream(pout.toByteArray());
pout.close();
pout = null;
return pin;
} catch (Exception e) {
throw new RuntimeException("处理图片错误", e);
}
}
/**
* <pre>
* - URI
* -
* - {yyyy}/{MM}/{dd}/{HH}/{mm}/{ss}
* - {UUID} 32
* - {i} id
* - {id} intid,+
* - {id16} intid16,+
* - {id36} intid36,+
* - {filename}
* - {ext}
* </pre>
*
* @param rule
* @param filename
* @return
*/
default String generateURI(String rule, String filename) {
Calendar c = Calendar.getInstance();
if (rule.contains("{yyyy}")) {
rule = rule.replace("{yyyy}", "" + c.get(Calendar.YEAR));
}
if (rule.contains("{MM}")) {
rule = rule.replace("{MM}", String.format("%02d", c.get(Calendar.MONTH) + 1));
}
if (rule.contains("{dd}")) {
rule = rule.replace("{dd}", String.format("%02d", c.get(Calendar.DATE)));
}
if (rule.contains("{HH}")) {
rule = rule.replace("{HH}", String.format("%02d", c.get(Calendar.HOUR_OF_DAY)));
}
if (rule.contains("{mm}")) {
rule = rule.replace("{mm}", String.format("%02d", c.get(Calendar.MINUTE)));
}
if (rule.contains("{ss}")) {
rule = rule.replace("{ss}", String.format("%02d", c.get(Calendar.SECOND)));
}
if (rule.contains("{UUID}")) {
rule = rule.replace("{UUID}", UUID.fastUUID().toString(true));
}
if (rule.contains("{i}")) {
rule = rule.replace("{i}", IdUtils.nextId(Id.groupName).toString());
}
if (rule.contains("{id}")) {
rule = rule.replace("{id}", Long.toString(id.nextId()));
}
if (rule.contains("{id16}")) {
rule = rule.replace("{id16}", Long.toString(id.nextId(), 16));
}
if (rule.contains("{id36}")) {
rule = rule.replace("{id36}", Long.toString(id.nextId(), 36));
}
if (rule.contains("{filename}")) {
String temp = null;
if (filename.contains(".")) {
temp = filename.substring(0, filename.lastIndexOf("."));
} else {
temp = filename;
}
rule = rule.replace("{filename}", temp);
}
if (rule.contains("{ext}")) {
String temp = null;
if (filename.contains(".")) {
temp = filename.substring(filename.lastIndexOf(".") + 1);
} else {
temp = "";
}
rule = rule.replace("{ext}", temp.toLowerCase());
}
return rule;
}
Id id = new Id();
static class Id {
private static final String groupName = "file:id";
private SymmetricCrypto crypto;
private String today = DateUtil.today();
public synchronized Long nextId() {
if (crypto == null || !today.equals(DateUtil.today())) {
today = DateUtil.today();
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), today.getBytes()).getEncoded();
crypto = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
}
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.putInt(IdUtils.nextDayId(groupName).intValue());
return Math.abs(ByteBuffer.wrap(crypto.encrypt(buffer.array())).getLong());
}
}
// public static void main(String[] args) {
// byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(),DateUtil.today().getBytes()).getEncoded();
// System.out.println(HexUtil.encodeHexStr(key));
//// byte[] key = HexUtil.decodeHex("a359f3fe88445c192f20d573c80af163");
// SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
//
//
// ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
//
// System.out.println(buffer.array().length);
// System.out.println(aes.encrypt(buffer.array()).length);
// buffer.clear();
// buffer.putInt(1);
// System.out.println(ByteBuffer.wrap(aes.encrypt(buffer.array())).getLong());
// buffer.clear();
// buffer.putInt(2);
// System.out.println(ByteBuffer.wrap(aes.encrypt(buffer.array())).getLong());
// buffer.clear();
// buffer.putInt(3);
// System.out.println(ByteBuffer.wrap(aes.encrypt(buffer.array())).getLong());
// }
SysOssVo url(SysOssVo oss, int second);
default SysOssVo url(SysOssVo oss) {
return url(oss, 120);
}
String url(Service service,String url,int second);
default String url(Service service,String url) {
return url(service,url, 120);
}
}

@ -8,7 +8,6 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
@ -16,11 +15,9 @@ import com.ruoyi.common.core.domain.event.LogininforEvent;
import com.ruoyi.common.core.domain.dto.RoleDTO;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.XcxLoginUser;
import com.ruoyi.common.enums.DeviceType;
import com.ruoyi.common.enums.LoginType;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.enums.UserType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
@ -63,8 +60,6 @@ public class SysLoginService {
private final IdentifierGenerator id;
private final ISysOssService ossService;
private final RuoYiConfig config;
@ -139,7 +134,7 @@ public class SysLoginService {
}
String url = null;
try {
url = ossService.uploadImgs(avatar,"avatar",400,400,null).getUrl();
// url = ossService.uploadImgs(avatar,"avatar",400,400,null).getUrl();
}catch (Exception e){
log.debug("保存头像失败",e);
// throw new RuntimeException("保存头像失败",e);

@ -1,85 +0,0 @@
package com.ruoyi.system.service.impl;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.system.service.FileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
/**
* <pre>
* -
* Author : J.L.Zhou
* E-Mail : 2233875735@qq.com
* Tel : 151 1104 7708
* Date : 2021-09-16 14:17
* Version : 1.0
* Copyright 2021 jlzhou.top Inc. All rights reserved.
* Warning: this content is only for internal circulation of the company.
* It is forbidden to divulge it or use it for other commercial purposes.
* </pre>
**/
@Service("upload")
@RequiredArgsConstructor
@Slf4j
public class FileServiceImpl implements FileService {
public final RuoYiConfig config;
@Override
public void delete(String filename) {
if (StrUtil.isEmpty(filename)) {
throw new RuntimeException("文件名不能为空");
}
try {
if (filename.startsWith(config.upload.pre)) {
filename = filename.substring(config.upload.pre.length());
}
if (filename.indexOf("?") > -1) {
filename = filename.substring(0, filename.indexOf("?"));
}
File file = new File(config.upload.savePath, filename);
log.debug("file:"+file.exists());
file.delete();
log.info("删除:{}",file.getAbsolutePath());
} catch (Exception e) {
throw new RuntimeException("删除错误", e);
}
}
@Override
public File getFile(String filename) {
if (StrUtil.isEmpty(filename)) {
throw new RuntimeException("文件名不能为空");
}
if (filename.startsWith(config.upload.pre)) {
filename = filename.substring(config.upload.pre.length());
}
if (filename.indexOf("?") > -1) {
filename = filename.substring(0, filename.indexOf("?"));
}
return new File(config.upload.savePath, filename);
}
@Override
public String save(InputStream in, String filename) {
File file = new File(config.upload.savePath, filename);
file.getParentFile().mkdirs();
try(
FileOutputStream out = new FileOutputStream(file);
) {
IoUtil.copy(in, out);
return config.upload.pre + "/" + filename;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

@ -1,170 +0,0 @@
package com.ruoyi.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
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 com.ruoyi.common.constant.CacheNames;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.CacheUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.system.domain.SysOssConfig;
import com.ruoyi.system.domain.bo.SysOssConfigBo;
import com.ruoyi.system.domain.vo.SysOssConfigVo;
import com.ruoyi.system.mapper.SysOssConfigMapper;
import com.ruoyi.system.service.ISysOssConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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());
return lqw;
}
@Override
public Boolean insertByBo(SysOssConfigBo bo) {
SysOssConfig config = BeanUtil.toBean(bo, SysOssConfig.class);
validEntityBeforeSave(config);
boolean flag = baseMapper.insert(config) > 0;
if (flag) {
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
}
return flag;
}
@Override
public Boolean updateByBo(SysOssConfigBo bo) {
SysOssConfig config = BeanUtil.toBean(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) {
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.deleteBatchIds(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 = BeanUtil.toBean(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,399 +0,0 @@
package com.ruoyi.system.service.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.constant.CacheNames;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.service.OssService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.BeanCopyUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.oss.core.OssClient;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.AccessPolicyType;
import com.ruoyi.oss.factory.OssFactory;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.bo.SysOssBo;
import com.ruoyi.system.domain.vo.SysOssVo;
import com.ruoyi.system.mapper.SysOssMapper;
import com.ruoyi.system.service.FileService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysOssService;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
*
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class SysOssServiceImpl implements ISysOssService, OssService {
private static final String UPLOAD = "UPLOAD";
private final ISysConfigService configService;
private final FileService fileService;
private final SysOssMapper baseMapper;
private static ThreadLocal<Boolean> IGNORE_THREAD_LOCAL = new ThreadLocal<>();
private static ThreadLocal<Service> SERVICE_THREAD_LOCAL = new ThreadLocal<>();
@Override
public void setService(Service service, Runnable handle) {
SERVICE_THREAD_LOCAL.set(service);
try {
handle.run();
} finally {
SERVICE_THREAD_LOCAL.remove();
}
}
@Override
public <T> T setService(Service service, Supplier<T> handle) {
SERVICE_THREAD_LOCAL.set(service);
try {
return handle.get();
} finally {
SERVICE_THREAD_LOCAL.remove();
}
}
@Override
public void ignore(Runnable handle) {
IGNORE_THREAD_LOCAL.set(true);
try {
handle.run();
} finally {
IGNORE_THREAD_LOCAL.remove();
}
}
@Override
public <T> T ignore(Supplier<T> handle) {
IGNORE_THREAD_LOCAL.set(true);
try {
return handle.get();
} finally {
IGNORE_THREAD_LOCAL.remove();
}
}
@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 = result.getRecords().stream().map(this::url).collect(Collectors.toList());
result.setRecords(filterResult);
return TableDataInfo.build(result);
}
@Override
public List<SysOssVo> listByIds(Collection<Long> ossIds) {
List<SysOssVo> list = new ArrayList<>();
for (Long id : ossIds) {
SysOssVo vo = SpringUtils.getBean(ISysOssService.class).getById(id);
if (ObjectUtil.isNotNull(vo)) {
list.add(this.url(vo));
}
}
return list;
}
@Override
public String selectUrlByIds(String ossIds) {
List<String> list = new ArrayList<>();
for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
SysOssVo vo = SpringUtils.getBean(ISysOssService.class).getById(id);
if (ObjectUtil.isNotNull(vo)) {
list.add(this.url(vo).getUrl());
}
}
return String.join(StringUtils.SEPARATOR, 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(StringUtils.isNotBlank(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy());
lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService());
return lqw;
}
@Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
@Override
public SysOssVo getById(Long ossId) {
return baseMapper.selectVoById(ossId);
}
@Override
public void download(Long ossId, HttpServletResponse response) throws IOException {
SysOssVo sysOss = SpringUtils.getBean(ISysOssService.class).getById(ossId);
if (ObjectUtil.isNull(sysOss)) {
throw new ServiceException("文件数据不存在!");
}
FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
if (UPLOAD.equals(sysOss.getService())) {
try (InputStream inputStream = new FileInputStream(fileService.getFile(sysOss.getUrl()))) {
int available = inputStream.available();
IoUtil.copy(inputStream, response.getOutputStream());
response.setContentLength(available);
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
} else {
OssClient storage = OssFactory.instance(sysOss.getService());
try (InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) {
int available = inputStream.available();
IoUtil.copy(inputStream, response.getOutputStream());
response.setContentLength(available);
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
}
}
public void download(String url, Service service, HttpServletResponse response) throws IOException {
FileUtils.setAttachmentResponseHeader(response, FileNameUtil.getName(url));
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
if (UPLOAD.equalsIgnoreCase(service.name())) {
try (InputStream inputStream = new FileInputStream(fileService.getFile(url))) {
int available = inputStream.available();
IoUtil.copy(inputStream, response.getOutputStream());
response.setContentLength(available);
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
} else {
OssClient storage = OssFactory.instance(service.name());
try (InputStream inputStream = storage.getObjectContent(url)) {
int available = inputStream.available();
IoUtil.copy(inputStream, response.getOutputStream());
response.setContentLength(available);
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
}
}
@Override
public InputStream download(Long ossId) throws IOException {
SysOssVo sysOss = SpringUtils.getBean(ISysOssService.class).getById(ossId);
return download(sysOss);
}
@Override
public InputStream download(SysOssVo sysOss) throws IOException {
if (ObjectUtil.isNull(sysOss)) {
throw new ServiceException("文件数据不存在!");
}
if (UPLOAD.equals(sysOss.getService())) {
return new FileInputStream(fileService.getFile(sysOss.getUrl()));
} else {
OssClient storage = OssFactory.instance(sysOss.getService());
return storage.getObjectContent(sysOss.getUrl());
}
}
@Override
public InputStream download(String url, Service service) throws IOException {
if (UPLOAD.equalsIgnoreCase(service.name())) {
return new FileInputStream(fileService.getFile(url));
} else {
OssClient storage = OssFactory.instance(service.name());
return storage.getObjectContent(url);
}
}
@Override
public SysOssVo save(InputStream in, String originalFileName, String contentType, String pre, String rule) {
String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length());
SysOss oss = new SysOss();
oss.setOriginalName(originalFileName);
oss.setFileSuffix(suffix);
if (configService.selectOssEnabled()) {
OssClient storage = SERVICE_THREAD_LOCAL.get() == null ? OssFactory.instance() : OssFactory.instance(SERVICE_THREAD_LOCAL.get().name());
UploadResult uploadResult;
try {
String path = "";
if (StrUtil.isNotBlank(storage.getProperties().getPrefix())) {
path += storage.getProperties().getPrefix() + "/";
}
if (StrUtil.isBlank(pre)) {
pre = PRE_DEFAULT;
}
path += pre + "/" + generateURI(rule, originalFileName);
uploadResult = storage.upload(in, path, contentType);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
// 保存文件信息
oss.setUrl(uploadResult.getUrl());
oss.setFileName(uploadResult.getFilename());
oss.setService(storage.getConfigKey());
} else {
try {
String url = fileService.save(in, originalFileName, pre);
oss.setUrl(url);
oss.setFileName(url);
oss.setService(UPLOAD);
} catch (Exception e) {
throw new RuntimeException("保存文件失败", e);
}
}
if (IGNORE_THREAD_LOCAL.get() == null) {
baseMapper.insert(oss);
}
SysOssVo sysOssVo = new SysOssVo();
BeanCopyUtils.copy(oss, sysOssVo);
return sysOssVo;
}
public SysOssVo uploadImgs(MultipartFile file, String pre) {
return uploadImgs(file, pre, configService.selectImageMaxWidth(), configService.selectImageMaxHeight(), configService.getWatermark());
}
@Override
public SysOssVo uploadImgs(InputStream inputStream, String pre, int maxWidth, int maxHeight, BufferedImage watermark) {
String originalFileName = "temp.webp";
String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length());
SysOss oss = new SysOss();
oss.setFileSuffix(suffix);
oss.setOriginalName(originalFileName);
if (configService.selectOssEnabled()) {
OssClient storage = SERVICE_THREAD_LOCAL.get() == null ? OssFactory.instance() : OssFactory.instance(SERVICE_THREAD_LOCAL.get().name());
UploadResult uploadResult;
try (
InputStream in = formatImage(inputStream, maxWidth, maxWidth, watermark)
) {
String path = "";
if (StrUtil.isNotBlank(storage.getProperties().getPrefix())) {
path += storage.getProperties().getPrefix() + "/";
}
if (StrUtil.isNotBlank(pre)) {
path += pre + "/";
}
path += generateURI("{yyyy}/{MM}/{dd}/{id36}.webp", "a.webp");
uploadResult = storage.upload(in, path, "image/webp");
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
// 保存文件信息
oss.setUrl(uploadResult.getUrl());
oss.setFileName(uploadResult.getFilename());
oss.setService(storage.getConfigKey());
} else {
try (
InputStream in = formatImage(inputStream, maxWidth, maxWidth, watermark)
) {
String url = fileService.save(in, originalFileName + ".webp", pre);
oss.setUrl(url);
oss.setFileName(url);
oss.setService(UPLOAD);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
if (IGNORE_THREAD_LOCAL.get() == null) {
baseMapper.insert(oss);
}
SysOssVo sysOssVo = new SysOssVo();
BeanCopyUtils.copy(oss, sysOssVo);
return sysOssVo;
}
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 做一些业务上的校验,判断是否需要校验
}
List<SysOss> list = baseMapper.selectBatchIds(ids);
for (SysOss sysOss : list) {
if (UPLOAD.equals(sysOss.getService())) {
fileService.delete(sysOss.getUrl());
} else {
OssClient storage = OssFactory.instance(sysOss.getService());
storage.delete(sysOss.getUrl());
}
}
return baseMapper.deleteBatchIds(ids) > 0;
}
@Override
public SysOssVo url(SysOssVo oss, int second) {
if (UPLOAD.equals(oss.getService())) {
return oss;
} else {
oss.setUrl(query(oss.getService(), oss.getUrl(), second));
return oss;
}
}
private String query(String service, String url, int second) {
OssClient storage = OssFactory.instance(service);
// 仅修改桶类型为 private 的URL临时URL时长为120s
if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
String old = url;
if (url.indexOf("://") > -1) {
url = url.substring(url.indexOf("/", url.indexOf("//") + 2));
}
if (url.startsWith("/")) {
url = url.substring(1);
}
url = url.substring(url.indexOf("/") + 1);
String queryString = storage.getPrivateUrl(url, second);
queryString = queryString.substring(queryString.indexOf("?"));
return old + queryString;
}
return url;
}
@Override
public String url(Service service, String url, int second) {
if (Service.upload.equals(service)) {
return url;
} else {
return query(service.name(), url, second);
}
}
}

@ -1,27 +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="com.ruoyi.system.mapper.SysOssConfigMapper">
<resultMap type="com.ruoyi.system.domain.SysOssConfig" id="SysOssConfigResult">
<result property="ossConfigId" column="oss_config_id"/>
<result property="configKey" column="config_key"/>
<result property="accessKey" column="access_key"/>
<result property="secretKey" column="secret_key"/>
<result property="bucketName" column="bucket_name"/>
<result property="prefix" column="prefix"/>
<result property="endpoint" column="endpoint"/>
<result property="isHttps" column="is_https"/>
<result property="region" column="region"/>
<result property="status" column="status"/>
<result property="ext1" column="ext1"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
</mapper>

@ -1,18 +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="com.ruoyi.system.mapper.SysOssMapper">
<resultMap type="com.ruoyi.system.domain.SysOss" id="SysOssResult">
<result property="ossId" column="oss_id"/>
<result property="fileName" column="file_name"/>
<result property="fileSuffix" column="file_suffix"/>
<result property="url" column="url"/>
<result property="createTime" column="create_time"/>
<result property="createBy" column="create_by"/>
<result property="updateTime" column="update_time"/>
<result property="updateBy" column="update_by"/>
<result property="service" column="service"/>
</resultMap>
</mapper>

@ -11,7 +11,7 @@
Target Server Version : 100617
File Encoding : 65001
Date: 11/10/2024 17:51:13
Date: 24/10/2024 17:46:10
*/
SET NAMES utf8mb4;
@ -209,6 +209,14 @@ CREATE TABLE `sys_logininfor` (
-- Records of sys_logininfor
-- ----------------------------
INSERT INTO `sys_logininfor` VALUES (20241011000000001, 'admin', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-10-11 17:28:10');
INSERT INTO `sys_logininfor` VALUES (20241012000000001, 'admin', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-10-12 17:19:10');
INSERT INTO `sys_logininfor` VALUES (20241014000000001, 'admin', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-10-14 09:04:16');
INSERT INTO `sys_logininfor` VALUES (20241022000000001, 'admin', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-10-22 10:58:47');
INSERT INTO `sys_logininfor` VALUES (20241022000000002, 'admin', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-10-22 14:04:30');
INSERT INTO `sys_logininfor` VALUES (20241023000000001, 'admin', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-10-23 09:03:22');
INSERT INTO `sys_logininfor` VALUES (20241023000000002, 'admin', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-10-23 15:11:46');
INSERT INTO `sys_logininfor` VALUES (20241024000000001, 'admin', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-10-24 08:49:02');
INSERT INTO `sys_logininfor` VALUES (20241024000000002, 'admin', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-10-24 14:27:40');
-- ----------------------------
-- Table structure for sys_menu
@ -347,6 +355,7 @@ INSERT INTO `sys_menu` VALUES (2001, '定时任务查询', 2000, 1, '#', '', NUL
INSERT INTO `sys_menu` VALUES (2002, '定时任务新增', 2000, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'sys:cron:add', '#', 'admin', '2024-08-08 10:02:52', '', NULL, '');
INSERT INTO `sys_menu` VALUES (2003, '定时任务修改', 2000, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'sys:cron:update', '#', 'admin', '2024-08-08 10:02:52', '', NULL, '');
INSERT INTO `sys_menu` VALUES (2004, '定时任务删除', 2000, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'sys:cron:remove', '#', 'admin', '2024-08-08 10:02:52', '', NULL, '');
INSERT INTO `sys_menu` VALUES (20241022000000001, '文件图片上传', 5, 99, 'file', 'demo/file/index', NULL, 1, 1, 'C', '0', '0', 'demo:file:index', 'upload', 'admin', '2024-10-22 11:01:10', 'admin', '2024-10-22 11:01:10', '');
-- ----------------------------
-- Table structure for sys_notice
@ -426,63 +435,8 @@ INSERT INTO `sys_oper_log` VALUES (20241011000000016, '测试单表', 1, 'com.ru
INSERT INTO `sys_oper_log` VALUES (20241011000000017, '测试单表', 1, 'com.ruoyi.demo.controller.TestDemoController.add()', 'POST', 1, 'admin', '', '/demo/demo', '127.0.0.1', '内网IP', '{\"createBy\":\"admin\",\"createTime\":\"2023-08-16 10:35:31\",\"id\":1,\"deptId\":102,\"userId\":4,\"orderNum\":1,\"testKey\":\"测试数据权限\",\"value\":\"测试\"}', '', 1, '\r\n### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry \'1\' for key \'PRIMARY\'\r\n### The error may exist in com/ruoyi/demo/mapper/TestDemoMapper.java (best guess)\r\n### The error may involve com.ruoyi.demo.mapper.TestDemoMapper.insert-Inline\r\n### The error occurred while setting parameters\r\n### SQL: INSERT INTO test_demo ( id, dept_id, user_id, create_user_id, login_ip, update_user_id, order_num, test_key, value, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )\r\n### Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry \'1\' for key \'PRIMARY\'\n; Duplicate entry \'1\' for key \'PRIMARY\'; nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry \'1\' for key \'PRIMARY\'', '2024-10-11 17:49:26');
INSERT INTO `sys_oper_log` VALUES (20241011000000018, '测试单表', 1, 'com.ruoyi.demo.controller.TestDemoController.add()', 'POST', 1, 'admin', '', '/demo/demo', '127.0.0.1', '内网IP', '{\"createBy\":\"admin\",\"createTime\":\"2023-08-16 10:35:31\",\"id\":1,\"deptId\":102,\"userId\":4,\"orderNum\":1,\"testKey\":\"测试数据权限\",\"value\":\"测试\"}', '', 1, '\r\n### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry \'1\' for key \'PRIMARY\'\r\n### The error may exist in com/ruoyi/demo/mapper/TestDemoMapper.java (best guess)\r\n### The error may involve com.ruoyi.demo.mapper.TestDemoMapper.insert-Inline\r\n### The error occurred while setting parameters\r\n### SQL: INSERT INTO test_demo ( id, dept_id, user_id, create_user_id, login_ip, update_user_id, order_num, test_key, value, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )\r\n### Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry \'1\' for key \'PRIMARY\'\n; Duplicate entry \'1\' for key \'PRIMARY\'; nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry \'1\' for key \'PRIMARY\'', '2024-10-11 17:49:28');
INSERT INTO `sys_oper_log` VALUES (20241011000000019, '测试单表', 2, 'com.ruoyi.demo.controller.TestDemoController.edit()', 'PUT', 1, 'admin', '', '/demo/demo', '127.0.0.1', '内网IP', '{\"createBy\":\"admin\",\"createTime\":\"2023-08-16 10:35:31\",\"id\":1,\"deptId\":102,\"userId\":4,\"orderNum\":1,\"testKey\":\"测试数据权限\",\"value\":\"测试\"}', '{\"code\":200,\"msg\":\"操作成功\"}', 0, '', '2024-10-11 17:50:05');
-- ----------------------------
-- Table structure for sys_oss
-- ----------------------------
DROP TABLE IF EXISTS `sys_oss`;
CREATE TABLE `sys_oss` (
`oss_id` bigint(20) NOT NULL COMMENT '对象存储主键',
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件名',
`original_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '原名',
`file_suffix` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件后缀名',
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'URL地址',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '上传人',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新人',
`service` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'minio' COMMENT '服务商',
PRIMARY KEY (`oss_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'OSS对象存储表' ROW_FORMAT = Compact;
-- ----------------------------
-- Records of sys_oss
-- ----------------------------
-- ----------------------------
-- Table structure for sys_oss_config
-- ----------------------------
DROP TABLE IF EXISTS `sys_oss_config`;
CREATE TABLE `sys_oss_config` (
`oss_config_id` bigint(20) NOT NULL COMMENT '主建',
`config_key` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '配置key',
`access_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'accessKey',
`secret_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '秘钥',
`bucket_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '桶名称',
`prefix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '前缀',
`endpoint` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '访问站点',
`domain` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '自定义域名',
`is_https` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否httpsY=是,N=否)',
`region` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '',
`access_policy` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '桶权限类型(0=private 1=public 2=custom)',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '状态0=正常,1=停用)',
`ext1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '扩展字段',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`oss_config_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '对象存储配置表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_oss_config
-- ----------------------------
INSERT INTO `sys_oss_config` VALUES (1, 'minio', 'base2024', 'base20241415926', 'files', '', '192.168.3.222:9000', '{root}', 'N', '', '1', '0', '', 'admin', '2023-04-28 11:22:31', 'admin', '2024-08-08 09:50:44', '正式环境中访问站点修改为minio:9000');
INSERT INTO `sys_oss_config` VALUES (2, 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '', 'N', '', '1', '1', '', 'admin', '2023-08-16 10:35:08', 'admin', '2023-08-16 10:35:08', NULL);
INSERT INTO `sys_oss_config` VALUES (3, 'aliyun', 'LTAI5tQMkJBHbYoDcBBrc1Kv', '25MWcjkWRlqTD0pSJrthqXe05CpjWS', 'test-data-resources', 'xxx', 'oss-cn-shenzhen.aliyuncs.com', '', 'Y', '', '1', '1', '', 'admin', '2023-08-16 10:35:08', 'admin', '2024-05-17 08:29:25', '');
INSERT INTO `sys_oss_config` VALUES (4, 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1250000000', '', 'cos.ap-beijing.myqcloud.com', '', 'N', 'ap-beijing', '1', '1', '', 'admin', '2023-08-16 10:35:08', 'admin', '2023-08-16 10:35:08', NULL);
INSERT INTO `sys_oss_config` VALUES (5, 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 'admin', '2023-08-16 10:35:08', 'admin', '2023-08-16 10:35:08', NULL);
INSERT INTO `sys_oper_log` VALUES (20241014000000020, '定时任务', 1, 'com.ruoyi.cron.api.CronTaskApi.add()', 'POST', 1, 'admin', '', '/system/cron/', '127.0.0.1', '内网IP', '{\"id\":202410140000001,\"taskId\":\"5553036b32681350546531d871d5edc9\",\"groupId\":0,\"enabled\":true,\"createTime\":\"2024-10-14 09:04\",\"paramELs\":[],\"userId\":1}', '', 0, '', '2024-10-14 09:04:34');
INSERT INTO `sys_oper_log` VALUES (20241022000000021, '菜单管理', 1, 'com.ruoyi.web.controller.system.SysMenuController.add()', 'POST', 1, 'admin', '', '/system/menu', '127.0.0.1', '内网IP', '{\"createBy\":\"admin\",\"createTime\":\"2024-10-22 11:01:10\",\"updateBy\":\"admin\",\"updateTime\":\"2024-10-22 11:01:10\",\"parentId\":5,\"children\":[],\"menuId\":\"20241022000000001\",\"menuName\":\"文件图片上传\",\"orderNum\":99,\"path\":\"file\",\"component\":\"demo/file/index\",\"isFrame\":\"1\",\"isCache\":\"1\",\"menuType\":\"C\",\"visible\":\"0\",\"status\":\"0\",\"perms\":\"demo:file:index\",\"icon\":\"upload\"}', '{\"code\":200,\"msg\":\"操作成功\"}', 0, '', '2024-10-22 11:01:10');
-- ----------------------------
-- Table structure for sys_post
@ -677,7 +631,7 @@ CREATE TABLE `sys_user` (
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 100, NULL, 'admin', '超级管理员', 'sys_user', 'admin@evolvecloud.cn', '13888888888', '1', '', '$2a$10$.ja7BDq5b8jxd6snbRvz8eAmg0loaDb05LR6SpR2F42huJb7GaOD6', '0', '0', '127.0.0.1', '2024-10-11 17:28:10', 'admin', '2024-01-03 10:35:07', 'admin', '2024-10-11 17:28:10', '管理员');
INSERT INTO `sys_user` VALUES (1, 100, NULL, 'admin', '超级管理员', 'sys_user', 'admin@evolvecloud.cn', '13888888888', '1', '', '$2a$10$.ja7BDq5b8jxd6snbRvz8eAmg0loaDb05LR6SpR2F42huJb7GaOD6', '0', '0', '127.0.0.1', '2024-10-24 14:27:40', 'admin', '2024-01-03 10:35:07', 'admin', '2024-10-24 14:27:40', '管理员');
-- ----------------------------
-- Table structure for sys_user_post

@ -33,14 +33,6 @@ http {
}
upstream monitor-admin {
server 127.0.0.1:9090;
}
upstream xxljob-admin {
server 127.0.0.1:9100;
}
server {
listen 80;
server_name localhost;
@ -68,10 +60,7 @@ http {
# return 200 '{"msg":"演示模式,不允许操作","code":500}';
# }
# 限制外网访问内网 actuator 相关路径
location ~ ^(/[^/]*)?/actuator(/.*)?$ {
return 403;
}
location / {
root /usr/share/nginx/html;

Loading…
Cancel
Save