项目之创建静态资源和设置子模块项目、开发简易上传功能(12)

简介: 项目之创建静态资源和设置子模块项目、开发简易上传功能(12)

50. 创建静态资源子模块项目


创建新的straw-resource子模块项目,用于管理用户上传的文件等静态资源。


创建出来后,在straw-resource的pom.xml中,自行将父级项目由SpringBoot改为straw项目,删除<dependencies>和<build>节点(因为没有存在的必要,在父项目中已经配置好了)。


在straw项目中的<mudules>中添加子模块项目。


在straw-resource的application.properties中显式的配置端口号,必须与straw-portal的不同:


server.port=8081


全部完成后,更新Maven,straw-portal和straw-resource这2个项目是可以同时启动的!


51. 设置straw-resource子模块项目的静态目录


在straw-resource项目的application.properties中添加配置:


spring.resources.static-locations=file:D:/IdeaProjects/straw-static-resource

1

则straw-resource项目的静态目录就是以上指定的位置,后续straw-portal项目中涉及上传操作时,上传的文件也应该存放到以上位置。


52.设置straw-resource子模块项目的静态目录


在straw-portal项目的application.properties中添加配置:


# 发布问题时,将图片上传到哪里,需要与straw-resource项目的静态资源目录保持一致
project.question.image-upload-path=D:/IdeaProjects/straw-static-resource
# 发布问题时,上传的图片通过哪个服务器提供访问,配置的端口号需要与straw-resource项目保持一致
project.question.image-host=http://localhost:8081/
# 发布问题时,允许上传的文件的最大大小
project.question.image-max-size=307200
# 发布问题时,允许上传的图片文件的类型
project.question.image-content-types=image/jpeg, image/png, image/bmp
并且,在straw-portal中调整默认限制的文件大小:
@Bean
public MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();
    factory.setMaxFileSize(DataSize.ofMegabytes(500));
    factory.setMaxRequestSize(DataSize.ofMegabytes(500));
    return factory.createMultipartConfig();
}


53. 开发简易上传功能


说明:由于上传功能不可以通过在URL上填写参数直接进行测试,为了更快的进行测试并体验上传的效果,暂且忽略不必要的代码,例如上传文件的相关检查等细节问题,当然,测试时也应该使用正确的文件和数据进行测试。当简单的上传已经完成后,再补全细节部分。

在QuestionController中开发服务器端的简易上传处理:


@Value("${project.question.image-upload-path}")
private String imageUploadPath;
@Value(("${project.question.image-host}"))
private String imageHost;
@PostMapping("/upload-image")
public R<String> uploadImage(MultipartFile imageFile) {
    File dest = new File(imageUploadPath, "1.jpg");
    try {
        imageFile.transferTo(dest);
    } catch (IOException e) {
        e.printStackTrace();
    }
    String imageUrl = imageHost + "1.jpg"; // http://localhost:8081/1.jpg
    log.debug("image url >>> {}", imageUrl);
    return R.ok(imageUrl);
}


本次需要处理的页面是“发表问题”的question/create.html,在发表问题时,使用的富文本编辑Summernote提供了名为callbacks的回调机制,其中,存在名为onImageUpload的回调属性,该属性值是函数,所以,可以自定义函数配置到这个回调属性中,则后续上传图片时,就会自动触发自定义的函数,通过自定义函数实现图片的上传,并返回上传图片的URL,生成图片插入到Summernote富文本编辑器中即可。


在question/create.html中,先将底部关于Summernote的JavaScript代码移到新创建的commons/init_summernote.js中,并调整这段代码:


$(document).ready(function () {
    $('#summernote').summernote({
        height: 300,
        tabsize: 2,
        lang: 'zh-CN',
        placeholder: '请输入问题的详细描述...',
        callbacks: {
            onImageUpload: function () {
                alert("准备上传图片!");
            }
        }
    });
});


完成后,重启项目,打开“发布问题”页面,插入图片,选择图片文件就会弹出对话框!


然后,在以上回调中,使用$.ajax()提交异步请求,在处理结果时,创建Image对象,将结果中的图片URL作为Image对象的src属性值,并将整个Image对象(就是一个<src>标签)插入到富文本编辑器中:


$(document).ready(function () {
    $('#summernote').summernote({
        height: 300,
        tabsize: 2,
        lang: 'zh-CN',
        placeholder: '请输入问题的详细描述...',
        callbacks: {
            onImageUpload: function (files) {
                // ---------------------------------------
                // 当前函数的参数名称是自定义,它表示用户选择的若干个文件
                // Summernote在调用该函数时,会把用户选择的文件作为函数的参数
                // ---------------------------------------
                if (!files || files.length < 1) {
                    alert("请选择您要上传的文件!");
                    return;
                }
                if (files.length > 1) {
                    alert("一次只允许上传1个文件!");
                    return;
                }
                let formData = new FormData();
                let file = files[0];
                formData.append("imageFile", file);
                console.log("form data >>> " + formData);
                $.ajax({
                    url: '/api/v1/questions/upload-image',
                    type: 'post',
                    data: formData,
                    contentType: false,
                    processData: false,
                    success: function(json) {
                        if (json.state == 2000) {
                            // alert(json.data);
                            let img = new Image(); // <img>
                            img.src = json.data; // <img src="xxx">
                            $('#summernote').summernote('insertNode', img);
                        } else {
                            alert(json.message);
                        }
                    }
                });
            }
        }
    });
});

54. 完善服务器端的上传功能


先创建关于文件上传的异常类型:


public class FileUploadException extends RuntimeException {
}
public class FileEmptyException extends FileUploadException {
}
public class FileSizeException extends FileUploadException {
}
public class FileTypeException extends FileUploadException {
}
public class FileIOException extends FileUploadException {
}
在GlobalExceptionHandler中处理以上异常,完整代码如下(需在R.State中添加常量):
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler
    public R handleException(Throwable e) {
        if (e instanceof ParameterValidationException) {
            return R.failure(R.State.ERR_PARAMETER_INVALIDATION, e);
        } else if (e instanceof InviteCodeException){
            return R.failure(R.State.ERR_INVITE_CODE, e);
        } else if (e instanceof ClassDisabledException) {
            return R.failure(R.State.ERR_CLASS_DISABLED, e);
        } else if (e instanceof PhoneDuplicateException) {
            return R.failure(R.State.ERR_PHONE_DUPLICATE, e);
        } else if (e instanceof InsertException) {
            return R.failure(R.State.ERR_INSERT, e);
        } else if (e instanceof FileEmptyException) {
            return R.failure(R.State.ERR_UPLOAD_EMPTY, e);
        } else if (e instanceof FileSizeException) {
            return R.failure(R.State.ERR_UPLOAD_FILE_SIZE, e);
        } else if (e instanceof FileTypeException) {
            return R.failure(R.State.ERR_UPLOAD_FILE_TYPE, e);
        } else if (e instanceof FileIOException) {
            return R.failure(R.State.ERR_UPLOAD_FILE_IO, e);
        } else if (e instanceof AccessDeniedException) {
            return R.failure(R.State.ERR_ACCESS_DENIED, e);
        } else {
            log.debug("Unknown Exception", e);
            return R.failure(R.State.ERR_UNKNOWN, e);
        }
    }
}

在处理上传请求之前,先声明2个全局属性,用于读取配置中的“文件最大大小”和“文件类型”:


@Value("${project.question.image-max-size}")
private long imageMaxSize;
@Value(("${project.question.image-content-types}"))
private List<String> imageContentTypes;


在处理上传请求的过程中:


应该创建子级文件夹,避免所有的文件都传到指定的同一个文件夹中,推荐使用“年”和“月”分别创建2级子文件夹,上传的图片应该放在“月”的文件夹中;

可以使用UUID作为文件名;

不需要判断原始扩展名,而是直接从原始文件全名中截取即可;

及时打桩,输出关键信息,例如保存文件的文件夹路径、文件名、完整路径等,便于出错时排查问题。

具体代码:

@Value("${project.question.image-upload-path}")
private String imageUploadPath;
@Value(("${project.question.image-host}"))
private String imageHost;
@Value("${project.question.image-max-size}")
private long imageMaxSize;
@Value(("${project.question.image-content-types}"))
private List<String> imageContentTypes;
@PostMapping("/upload-image")
public R<String> uploadImage(MultipartFile imageFile) {
    // 判断上传的文件是否为空
    if (imageFile.isEmpty()) {
        throw new FileEmptyException("上传图片失败!请选择有效的图片文件!");
    }
    // 判断上传的文件大小是否超标
    if (imageFile.getSize() > imageMaxSize) {
        throw new FileSizeException("上传图片失败!不允许使用超过" + (imageMaxSize / 1024) + "KB的图片文件!");
    }
    // 判断上传的文件类型是否超标
    if (!imageContentTypes.contains(imageFile.getContentType())) {
        throw new FileTypeException("上传图片失败!图片类型错误!允许上传的图片类型有:" + imageContentTypes);
    }
    // 确定本次上传时使用的文件夹
    String dir = DateTimeFormatter.ofPattern("yyyy/MM").format(LocalDateTime.now());
    File parent = new File(imageUploadPath, dir);
    if (!parent.exists()) {
        parent.mkdirs();
    }
    log.debug("dir >>> {}", parent);
    // 确定本次上传时使用的文件名
    String filename = UUID.randomUUID().toString();
    String originalFilename = imageFile.getOriginalFilename();
    String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
    String child = filename + suffix;
    // 创建最终保存时的文件对象
    File dest = new File(parent, child);
    // 执行保存
    try {
        imageFile.transferTo(dest);
    } catch (IOException e) {
        throw new FileIOException("上传图片失败!当前服务器忙,请稍后再次尝试!");
    }
    // 确定网络访问路径
    String imageUrl = imageHost + dir + "/" + child; // http://localhost:8081/1.jpg
    log.debug("image url >>> {}", imageUrl);
    // 返回
    return R.ok(imageUrl);
}
目录
相关文章
|
1月前
|
缓存 监控 前端开发
如何确保动态导入的模块被正确加载?
通过以上这些方法的综合运用,可以有效地确保动态导入的模块被正确加载,提高应用的稳定性、性能和用户体验。在实际开发过程中,要根据项目的具体情况和需求,灵活运用这些方法,并不断进行测试和优化。
35 4
|
3月前
|
Shell Linux Python
你知道创建模块都有哪些方式吗?
你知道创建模块都有哪些方式吗?
33 0
|
5月前
|
存储 JavaScript Java
若依修改,如何安装wangEditor,图片上传接口编写。建议暴露专门写一个图片存储的接口
若依修改,如何安装wangEditor,图片上传接口编写。建议暴露专门写一个图片存储的接口
若依修改,如何安装wangEditor,图片上传接口编写。建议暴露专门写一个图片存储的接口
|
5月前
|
Java
自主定义访问路径-----SpringBoot自主定义静态资源访问路径的方法
自主定义访问路径-----SpringBoot自主定义静态资源访问路径的方法
|
5月前
|
前端开发 NoSQL JavaScript
若依修改---重新部署项目注意事项,新文件初始化需要修改的地方,打包后的文件很难进行修改,如果想要不断修改项目,注意保存原项目,才可以不断修改,前端:在Vue.config.js文件中修改target
若依修改---重新部署项目注意事项,新文件初始化需要修改的地方,打包后的文件很难进行修改,如果想要不断修改项目,注意保存原项目,才可以不断修改,前端:在Vue.config.js文件中修改target
|
5月前
|
前端开发 JavaScript Linux
若依修改之后,无法访问前端项目如何解决,只能访问后端的接口,我的接口8083,端不显示咋解决?在vue.config.js文件中的映射路径要跟后端匹配,到软件商店里找到Ngnix配置代理,设80不用加
若依修改之后,无法访问前端项目如何解决,只能访问后端的接口,我的接口8083,端不显示咋解决?在vue.config.js文件中的映射路径要跟后端匹配,到软件商店里找到Ngnix配置代理,设80不用加
|
7月前
|
JavaScript 前端开发 算法
< 封装公共导出模块:配合element实现提示 >
在 Vue + elementUi 开发中,我们偶尔会遇到需要导出的列表,或者指定位置的导出内容。在一个项目里面是十分常见的,但是由于导出代码有稍微有点长,不方便维护!基于项目开发需求,封装了一个公用的导出模块,模块入口提供了 四个参数,分别是:导出接口地址导出参数对象导出文件名称导出时选择的服务地址(需要配合config文件实现选择功能)。且基于信息安全问题,实现信息提示,当提示点击遵守规则才允许下载文件!
|
7月前
|
JavaScript 前端开发
若依 自定义实现导入功能
若依 自定义实现导入功能
204 1
|
7月前
|
JSON JavaScript 小程序
uniapp的配置文件、入口文件、主组件、页面管理部分
uniapp的配置文件、入口文件、主组件、页面管理部分
|
缓存 JavaScript 前端开发
Vue 新增不参与打包的接口地址配置文件
Vue 新增不参与打包的接口地址配置文件
218 0