diff --git a/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java b/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java index 1aae87f3de..3466cadd6e 100644 --- a/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java +++ b/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java @@ -48,6 +48,7 @@ public class ShiroServiceImpl implements ShiroService { filterChainDefinitionMap.put("/plugin/theme/themes", ANON); filterChainDefinitionMap.put("/plugin/theme/items/**", ANON); filterChainDefinitionMap.put("/plugin/view/types", ANON); + filterChainDefinitionMap.put("/static-resource/**", ANON); // 验证链接 filterChainDefinitionMap.put("/api/link/validate**", ANON); diff --git a/backend/src/main/java/io/dataease/commons/utils/FileUtils.java b/backend/src/main/java/io/dataease/commons/utils/FileUtils.java new file mode 100644 index 0000000000..64760511b0 --- /dev/null +++ b/backend/src/main/java/io/dataease/commons/utils/FileUtils.java @@ -0,0 +1,26 @@ +package io.dataease.commons.utils; + +import org.springframework.lang.NonNull; +import org.springframework.util.Assert; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Author: wangjiahao + * Date: 2022/4/24 + * Description: + */ +public class FileUtils { + + public static void createIfAbsent(@NonNull Path path) throws IOException { + Assert.notNull(path, "Path must not be null"); + + if (Files.notExists(path)) { + // Create directories + Files.createDirectories(path); + LogUtil.debug("Created directory: [{}]", path); + } + } +} diff --git a/backend/src/main/java/io/dataease/commons/utils/StaticResourceUtils.java b/backend/src/main/java/io/dataease/commons/utils/StaticResourceUtils.java new file mode 100644 index 0000000000..cd644a98a0 --- /dev/null +++ b/backend/src/main/java/io/dataease/commons/utils/StaticResourceUtils.java @@ -0,0 +1,54 @@ +package io.dataease.commons.utils; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.lang.NonNull; +import org.springframework.util.Assert; + +/** + * Author: wangjiahao + * Date: 2022/4/24 + * Description: + */ +public class StaticResourceUtils { + public static final String URL_SEPARATOR = "/"; + + private static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + public static String ensureBoth(@NonNull String string, @NonNull String bothfix) { + return ensureBoth(string, bothfix, bothfix); + } + + public static String ensureBoth(@NonNull String string, @NonNull String prefix, + @NonNull String suffix) { + return ensureSuffix(ensurePrefix(string, prefix), suffix); + } + + /** + * Ensures the string contain prefix. + * + * @param string string must not be blank + * @param prefix prefix must not be blank + * @return string contain prefix specified + */ + public static String ensurePrefix(@NonNull String string, @NonNull String prefix) { + Assert.hasText(string, "String must not be blank"); + Assert.hasText(prefix, "Prefix must not be blank"); + + return prefix + StringUtils.removeStart(string, prefix); + } + + + /** + * Ensures the string contain suffix. + * + * @param string string must not be blank + * @param suffix suffix must not be blank + * @return string contain suffix specified + */ + public static String ensureSuffix(@NonNull String string, @NonNull String suffix) { + Assert.hasText(string, "String must not be blank"); + Assert.hasText(suffix, "Suffix must not be blank"); + + return StringUtils.removeEnd(string, suffix) + suffix; + } +} diff --git a/backend/src/main/java/io/dataease/config/DeMvcConfig.java b/backend/src/main/java/io/dataease/config/DeMvcConfig.java new file mode 100644 index 0000000000..04f73c5217 --- /dev/null +++ b/backend/src/main/java/io/dataease/config/DeMvcConfig.java @@ -0,0 +1,52 @@ +package io.dataease.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.io.File; +import java.util.concurrent.TimeUnit; + +import static io.dataease.commons.utils.StaticResourceUtils.ensureBoth; +import static io.dataease.commons.utils.StaticResourceUtils.ensureSuffix; + +/** + * Author: wangjiahao + * Date: 2022/4/24 + * Description: + */ +@Configuration +public class DeMvcConfig implements WebMvcConfigurer { + private static final String FILE_PROTOCOL = "file://"; + public static final String FILE_SEPARATOR = File.separator; + public static final String USER_HOME = "/opt/dataease/data"; + + private static String WORK_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "static-resource" + FILE_SEPARATOR; + + + /** + * Upload prefix. + */ + private final static String UPLOAD_URL_PREFIX = "static-resource"; + + + /** + * url separator. + */ + public static final String URL_SEPARATOR = "/"; + + /** + * Configuring static resource path + * + * @param registry registry + */ + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + String workDir = FILE_PROTOCOL + ensureSuffix(WORK_DIR, FILE_SEPARATOR); + String uploadUrlPattern = ensureBoth(UPLOAD_URL_PREFIX, URL_SEPARATOR) + "**"; + registry.addResourceHandler(uploadUrlPattern) + .addResourceLocations(workDir); + + } +} diff --git a/backend/src/main/java/io/dataease/config/properties/StaticResourceProperties.java b/backend/src/main/java/io/dataease/config/properties/StaticResourceProperties.java new file mode 100644 index 0000000000..9e49adeb24 --- /dev/null +++ b/backend/src/main/java/io/dataease/config/properties/StaticResourceProperties.java @@ -0,0 +1,18 @@ +package io.dataease.config.properties; + +import lombok.Data; + +/** + * Author: wangjiahao + * Date: 2022/4/24 + * Description: + */ +@Data +public class StaticResourceProperties { + + /** + * Upload prefix. + */ + private String uploadUrlPrefix = "static-resource"; + +} diff --git a/backend/src/main/java/io/dataease/controller/staticResource/StaticResourceController.java b/backend/src/main/java/io/dataease/controller/staticResource/StaticResourceController.java new file mode 100644 index 0000000000..8f4297289f --- /dev/null +++ b/backend/src/main/java/io/dataease/controller/staticResource/StaticResourceController.java @@ -0,0 +1,28 @@ +package io.dataease.controller.staticResource; + +import io.dataease.service.staticResource.StaticResourceService; +import io.swagger.annotations.ApiOperation; +import org.pentaho.ui.xul.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; + +/** + * Author: wangjiahao + * Date: 2022/4/24 + * Description: + */ +@RestController +@RequestMapping("/static/resource") +public class StaticResourceController { + + @Resource + StaticResourceService staticResourceService; + + @PostMapping("upload/{fileId}") + @ApiOperation("Uploads static file") + public void upload(@PathVariable("fileId") String fileId, @RequestPart("file") MultipartFile file) { + staticResourceService.upload(fileId,file); + } +} diff --git a/backend/src/main/java/io/dataease/service/staticResource/StaticResourceService.java b/backend/src/main/java/io/dataease/service/staticResource/StaticResourceService.java new file mode 100644 index 0000000000..61cc19bacd --- /dev/null +++ b/backend/src/main/java/io/dataease/service/staticResource/StaticResourceService.java @@ -0,0 +1,48 @@ +package io.dataease.service.staticResource; + +import io.dataease.commons.utils.FileUtils; +import io.dataease.commons.utils.LogUtil; +import io.dataease.exception.DataEaseException; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Author: wangjiahao + * Date: 2022/4/24 + * Description: + */ +@Service +public class StaticResourceService { + + private final Path staticDir = Paths.get("/opt/dataease/data/static-resource/"); + + public void upload(String fileId,MultipartFile file) { + // check if the path is valid (not outside staticDir) + Assert.notNull(file, "Multipart file must not be null"); + try { + String originName = file.getOriginalFilename(); + String newFileName = fileId+originName.substring(originName.lastIndexOf("."),originName.length()); + Path uploadPath = Paths.get(staticDir.toString(), newFileName); + // create dir is absent + FileUtils.createIfAbsent(Paths.get(staticDir.toString())); + Files.createFile(uploadPath); + file.transferTo(uploadPath); + } catch (IOException e) { + LogUtil.error("文件上传失败",e); + DataEaseException.throwException("文件上传失败"); + } catch (Exception e){ + DataEaseException.throwException(e); + } + } +} diff --git a/frontend/src/api/staticResource/staticResource.js b/frontend/src/api/staticResource/staticResource.js new file mode 100644 index 0000000000..de424b0680 --- /dev/null +++ b/frontend/src/api/staticResource/staticResource.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' +import { uuid } from 'vue-uuid' +import store from '@/store' + +export function uploadFile(fileId, file) { + const param = new FormData() + param.append('file', file.file) + return request({ + url: '/static/resource/upload/' + fileId, + method: 'post', + headers: { 'Content-Type': 'multipart/form-data' }, + data: param, + loading: false + }) +} + +export function uploadFileResult(file, callback) { + const fileId = uuid.v1() + const fileName = file.file.name + const newFileName = fileId + fileName.substr(fileName.lastIndexOf('.'), fileName.length) + const fileUrl = store.state.staticResourcePath + newFileName + uploadFile(fileId, file).then(() => { + callback(fileUrl) + }) +} + diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 9aaef94971..1946f180c5 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -109,7 +109,9 @@ const data = { // 仪表板视图明细 panelViewDetailsInfo: {}, // 当前tab页内组件 - curActiveTabInner: null + curActiveTabInner: null, + // static resource local path + staticResourcePath: '/static-resource/' }, mutations: { ...animation.mutations, diff --git a/frontend/src/views/background/index.vue b/frontend/src/views/background/index.vue index 3a5ca9c74e..cdec3808a9 100644 --- a/frontend/src/views/background/index.vue +++ b/frontend/src/views/background/index.vue @@ -64,7 +64,6 @@ :on-remove="handleRemove" :http-request="upload" :file-list="fileList" - :on-change="onChange" > @@ -108,9 +107,9 @@ import { queryBackground } from '@/api/background/background' import BackgroundItem from '@/views/background/BackgroundItem' import { mapState } from 'vuex' -import eventBus from '@/components/canvas/utils/eventBus' import { deepCopy } from '@/components/canvas/utils/utils' import { COLOR_PANEL } from '@/views/chart/chart/chart' +import { uploadFileResult } from '@/api/staticResource/staticResource' export default { name: 'Background', @@ -179,7 +178,7 @@ export default { }, handleRemove(file, fileList) { this.uploadDisabled = false - this.panel.imageUrl = null + this.curComponent.commonBackground.outerImage = null this.fileList = [] this.commitStyle() }, @@ -187,16 +186,11 @@ export default { this.dialogImageUrl = file.url this.dialogVisible = true }, - onChange(file, fileList) { - var _this = this - _this.uploadDisabled = true - const reader = new FileReader() - reader.onload = function() { - _this.curComponent.commonBackground.outerImage = reader.result - } - reader.readAsDataURL(file.raw) - }, upload(file) { + const _this = this + uploadFileResult(file, (fileUrl) => { + _this.curComponent.commonBackground.outerImage = fileUrl + }) // console.log('this is upload') } diff --git a/frontend/src/views/panel/SubjectSetting/PanelStyle/BackgroundSelector.vue b/frontend/src/views/panel/SubjectSetting/PanelStyle/BackgroundSelector.vue index 85cbcd70b3..a1ad601846 100644 --- a/frontend/src/views/panel/SubjectSetting/PanelStyle/BackgroundSelector.vue +++ b/frontend/src/views/panel/SubjectSetting/PanelStyle/BackgroundSelector.vue @@ -25,12 +25,11 @@ accept=".jpeg,.jpg,.png,.gif" class="avatar-uploader" list-type="picture-card" + :http-request="upload" :class="{disabled:uploadDisabled}" :on-preview="handlePictureCardPreview" :on-remove="handleRemove" - :http-request="upload" :file-list="fileList" - :on-change="onChange" > @@ -51,6 +50,7 @@ import { mapState } from 'vuex' import { deepCopy } from '@/components/canvas/utils/utils' import { COLOR_PANEL } from '@/views/chart/chart/chart' +import { uploadFileResult } from '@/api/staticResource/staticResource' export default { name: 'BackgroundSelector', @@ -98,19 +98,13 @@ export default { this.dialogImageUrl = file.url this.dialogVisible = true }, - onChange(file, fileList) { - var _this = this - _this.uploadDisabled = true - const reader = new FileReader() - reader.onload = function() { - _this.panel.imageUrl = reader.result - this.commitStyle() - } - this.$store.state.styleChangeTimes++ - reader.readAsDataURL(file.raw) - }, upload(file) { - // console.log('this is upload') + const _this = this + uploadFileResult(file, (fileUrl) => { + _this.$store.state.styleChangeTimes++ + _this.panel.imageUrl = fileUrl + _this.commitStyle() + }) } } }