赞
踩
最近在做springboot +vue 前后端分离项目,之前一直是打war包部署到tomcat ,最近想打jar包部署,毕竟springboot 的优势是可以打jar包部署(因为jar包里内置了tomcat ,为以后的微服务及分布式部署打下基础),但是打jar 包部署会涉及到读取文件,之前用项目路径的方式就不起作用了,都要改为流的方式读取,之前我有一篇文章专门介绍了在jar里读取流的三种方式(classLoader、Thread、classpathResource)。今天我主要介绍头像上传,废话不多说,直接上代码:
1.项目路径
application-dev.yml里图片配置路径
prop:
upload_folder_headImg: D:/upload_files/headImage/
2.配置一下通过前端vue访问图片路径权限:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${prop.upload_folder_headImg}")
private String UPLOAD_FOLDER_IMG_HEAD;
@Autowired
private RefererInterceptor refererInterceptor;
@Bean
public RefererInterceptor getRefererInterceptor(){
return new RefererInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(refererInterceptor).addPathPatterns("/**")
.excludePathPatterns("/sys/user/login","/sys/user/logout","/sys/user/tologin","/sys/user/unauth");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")// 3允许任何方法(post、get等)
.allowedOrigins("*")// 1允许任何域名使用
.allowedHeaders("*")// 2允许任何头
.allowCredentials(true)
.maxAge(3600L);// 4.解决跨域请求两次,预检请求的有效期,单位为秒
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**");
// 映射图片访问路径
registry.addResourceHandler("/img/**").addResourceLocations("file:" + UPLOAD_FOLDER_IMG_HEAD);
}
}
3.图片上传FileUploadController
package com.dechnic.omsdc.server.common.controller;
import com.dechnic.omsdc.server.admin.controller.BaseController;
import com.dechnic.omsdc.server.admin.dto.JsonResult;
import com.dechnic.omsdc.server.common.utils.SpringUtil;
import com.dechnic.omsdc.server.common.utils.UUIDUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
@RestController
@RequestMapping("/upload")
@Slf4j
public class FileUploadController extends BaseController {
@Value("${prop.upload_folder_headImg}")
private String uploadFolderHeadImg;
@Value("${spring.servlet.multipart.max-file-size}")
private String maxFileSize;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
@PostMapping("/headImg")
public JsonResult uploadHeadImg(
@RequestParam(name = "file", required = false) MultipartFile file,
HttpServletRequest request) throws IOException {
JsonResult ret = null;
// 对file 进行清洁处理
if (Objects.isNull(file) || file.isEmpty()) {
log.info("上传头像为空,请重新上传");
return JsonResult.error("上传头像为空,请重新上传");
}
String oldName = file.getOriginalFilename();
String suffix = oldName.substring(oldName.lastIndexOf(".")+1);
if (!"jpg,jpeg,gif,png".toUpperCase().contains(suffix.toUpperCase())){
log.info("请选择jpg,jpeg,gif,png格式的图片");
return JsonResult.error("请选择jpg,jpeg,gif,png格式的图片");
}
long fileSize = file.getSize();
DecimalFormat df = new DecimalFormat("#.00");
double mVal = (double) fileSize / (1024 * 1024);
if (mVal>Double.parseDouble(maxFileSize.substring(0,maxFileSize.trim().length()-2))){
log.info("上传头像超出大小限制:"+maxFileSize);
return JsonResult.error("上传头像超出大小限制:"+maxFileSize);
}
String format = sdf.format(new Date());
Path directory = Paths.get(uploadFolderHeadImg + format);
log.info("directory=========:"+directory);
InputStream inputStream = file.getInputStream();
if (!Files.exists(directory)){
Files.createDirectories(directory);
}
String newName = UUIDUtils.getUUID() +"."+suffix;
log.info("newFileName:======"+newName);
Files.copy(inputStream,directory.resolve(newName));
ret = JsonResult.success("头像上传成功");
String saveName = format+"/"+newName;
ret.put("avatar",saveName);
return ret;
}
4.前端Vue代码:
BaseSetting.vue
<template>
<div class="account-settings-info-view">
<a-row :gutter="16">
<a-col :md="24" :lg="16">
<a-form layout="vertical">
<a-form-item
label="昵称"
>
<a-input placeholder="给自己起个名字" maxLength="20" v-model="params.nick" />
<a-input type="hidden" v-model="params.avatar"/>
</a-form-item>
<!-- <a-form-item
label="Bio"
>
<a-textarea rows="4" placeholder="You are not alone."/>
</a-form-item>
<a-form-item
label="电子邮件"
:required="false"
>
<a-input placeholder="exp@admin.com"/>
</a-form-item>
<a-form-item
label="加密方式"
:required="false"
>
<a-select defaultValue="aes-256-cfb">
<a-select-option value="aes-256-cfb">aes-256-cfb</a-select-option>
<a-select-option value="aes-128-cfb">aes-128-cfb</a-select-option>
<a-select-option value="chacha20">chacha20</a-select-option>
</a-select>
</a-form-item>
<a-form-item
label="连接密码"
:required="false"
>
<a-input placeholder="h3gSbecd"/>
</a-form-item>
<a-form-item
label="登陆密码"
:required="false"
>
<a-input placeholder="密码"/>
</a-form-item>-->
<a-form-item>
<!--<a-button type="primary">提交</a-button>-->
<a-button style="margin-left: 8px" @click="saveForm">保存</a-button>
</a-form-item>
</a-form>
</a-col>
<a-col :md="24" :lg="8" :style="{ minHeight: '180px' }">
<a-upload
name="avatar"
listType="picture-card"
class="avatar-uploader"
:showUploadList="false"
:beforeUpload="beforeUpload"
:customRequest="function(){}"
@change="handleChange"
>
<div class="ant-upload-preview" >
<img class="default-img" v-if="imageUrl" :src="getAvatar()" alt="avatar" />
<div v-else class="mask">
<a-icon type="plus" />
</div>
<!--<div v-else>
<a-icon :type="loading ? 'loading' : 'plus'" />
<div class="ant-upload-text">点击上传</div>
</div>-->
</div>
</a-upload>
<!--<div class="ant-upload-preview" @click="$refs.modal.edit(1)" >
<a-icon type="cloud-upload-o" class="upload-icon"/>
<div class="mask">
<a-icon type="plus" />
</div>
<img :src="option.img"/>
</div>-->
</a-col>
</a-row>
<!--modal-->
<avatar-modal ref="AvatarModal" @ok="handleCropperSuccess">
</avatar-modal>
</div>
</template>
<script>
import AvatarModal from './AvatarModal'
import { mapActions, mapGetters } from 'vuex'
import { updateNick } from '@/api/user'
import { file2Base64 } from '@/utils/util'
import { uploadHeadImg } from '@api/upload'
import { baseUrl } from '@/config/env'
export default {
components: {
AvatarModal
},
props: {
// 图片格式
imgFormat: {
type: Array,
default: function () {
return ['image/jpeg', 'image/png', 'image/jpg']
}
},
// 图片大小
imgSize: {
type: Number,
default: 2
},
// 图片裁切配置
options: {
type: Object,
default: function () {
return {
autoCropWidth: 180,
autoCropHeight: 180
}
}
},
// 回显图片路径
value: {
type: String,
default: ''
}
},
data () {
return {
loading: false,
imageUrl: '',
params: {
nick: '',
avatar: ''
}
}
},
watch: {
value: {
handler (val) {
this.imageUrl = val || ''
},
immediate: true
}
},
methods: {
...mapGetters(['nickname', 'avatar']),
...mapActions(['refreshUserNickNameAndAvatar']),
// 从本地选择文件
handleChange (info) {
const { options } = this
file2Base64(info.file.originFileObj, (imageUrl) => {
const target = Object.assign({}, options, {
img: imageUrl
})
this.$refs['AvatarModal'].edit(target)
})
},
// 上传之前 格式与大小校验
beforeUpload (file) {
const { imgFormat, imgSize } = this
const isFormat = imgFormat.includes(file.type)
if (!isFormat) {
this.$message.error('图片格式不支持!')
}
const isLt2M = file.size / 1024 / 1024 <= imgSize
if (!isLt2M) {
this.$message.error('图片大小限制在' + imgSize + 'MB内!')
}
return isFormat && isLt2M
},
// 裁剪成功后的File对象
handleCropperSuccess (data) {
console.log('File:', data)
// 进行图片上传动作
// 模拟后端请求 2000 毫秒延迟
const that = this
that.loading = true
const formData = new FormData()
formData.set('file', data)
uploadHeadImg(formData).then(res => {
if (!res.code) {
// that.$message.success('上传成功')
// 将返回的数据回显
// that.imageUrl = res.imgUrl
this.params.avatar = res.avatar
this.imageUrl = (that.avatar == null || that.avatar === '') ? require('@/assets/user.png') : baseUrl + '/img/' + that.avatar
this.saveForm()
// that.$emit('ok')
} else {
that.$message.error('上传失败:' + res.msg)
}
})
/* new Promise((resolve) => {
setTimeout(() => resolve(), 2000)
}).then((res) => {
that.$message.success('上传成功')
// 将返回的数据回显
that.imageUrl = res.img
that.$emit('ok')
}).catch(() => {
// Do something
}).finally(() => {
that.loading = false
}) */
},
getAvatar () {
return (this.avatar() == null || this.avatar() === '') ? require('@/assets/user.png') : baseUrl + '/img/' + this.avatar()
},
saveForm () {
const { refreshUserNickNameAndAvatar } = this
updateNick(this.params).then(res => {
if (!res.code) {
refreshUserNickNameAndAvatar(this.params)
this.$message.success('修改成功!')
}
})
}
},
computed: {
},
created () {
this.params.nick = this.nickname()
this.imageUrl = (this.avatar() == null || this.avatar() === '') ? require('@/assets/user.png') : baseUrl + '/img/' + this.avatar()
// this.imageUrl = 'http://localhost:8080/omsdc/img/2020/03/03/b3b87ff51dc8460cb9c47d8f3b14edca.jpg'
}
}
</script>
<style lang="less" scoped>
.avatar-upload-wrapper {
height: 200px;
width: 100%;
}
.ant-upload-preview {
position: relative;
margin: 0 auto;
width: 100%;
max-width: 180px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
.upload-icon {
position: absolute;
top: 0;
right: 10px;
font-size: 1.4rem;
padding: 0.5rem;
background: rgba(222, 221, 221, 0.7);
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.mask {
opacity: 0;
position: absolute;
background: rgba(0,0,0,0.4);
cursor: pointer;
transition: opacity 0.4s;
&:hover {
opacity: 1;
}
i {
font-size: 2rem;
position: absolute;
top: 50%;
left: 50%;
margin-left: -1rem;
margin-top: -1rem;
color: #d6d6d6;
}
}
img, .mask {
width: 100%;
max-width: 180px;
height: 100%;
border-radius: 50%;
overflow: hidden;
}
}
</style>
AvatarModal.vue
<template>
<a-modal
title="修改头像"
:visible="visible"
:maskClosable="false"
:confirmLoading="confirmLoading"
:width="800"
@cancel="cancelHandel">
<a-row>
<a-col :xs="24" :md="12" :style="{height: '350px'}">
<vue-cropper
ref="cropper"
:img="options.img"
:info="true"
:autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:fixedBox="options.fixedBox"
@realTime="realTime"
>
</vue-cropper>
</a-col>
<a-col :xs="24" :md="12" :style="{height: '350px'}">
<div class="avatar-upload-preview">
<img :src="previews.url" :style="previews.img"/>
</div>
</a-col>
</a-row>
<template slot="footer">
<a-button key="back" @click="cancelHandel">取消</a-button>
<a-button key="submit" type="primary" :loading="confirmLoading" @click="okHandel">保存</a-button>
</template>
</a-modal>
</template>
<script>
import { VueCropper } from 'vue-cropper'
import { dataURLtoFile, blobToBase64 } from '@/utils/util'
export default {
components: {
VueCropper
},
data () {
return {
visible: false,
id: null,
confirmLoading: false,
options: {
img: '/avatar2.jpg',
autoCrop: true,
autoCropWidth: 200,
autoCropHeight: 200,
fixedBox: true
},
previews: {}
}
},
methods: {
edit (record) {
const { options } = this
this.visible = true
this.options = Object.assign({}, options, record)
},
close () {
this.options = {
img: '/avatar2.jpg',
autoCrop: true,
autoCropWidth: 200,
autoCropHeight: 200,
fixedBox: true
}
this.visible = false
},
cancelHandel () {
this.close()
},
okHandel () {
const that = this
that.confirmLoading = true
// 获取截图的base64 数据
this.$refs.cropper.getCropData((data) => {
// 转换为File对象
const file = dataURLtoFile(data, 'cropperHeader.jpg')
// 将裁剪侯的图片对象返回给父组件
that.$emit('ok', file)
that.cancelHandel()
})
/* const vm = this
vm.confirmLoading = true
setTimeout(() => {
vm.confirmLoading = false
vm.close()
vm.$message.success('上传头像成功')
}, 2000) */
},
// 下载输入框里的图片
downloadNewImg () {
// 获取截图的blob数据
this.$refs.cropper.getCropBlob((data) => {
blobToBase64(data).then(res => {
// Utils.downLoadImage(res, '测试的哟')
})
})
},
// 移动框的事件
realTime (data) {
this.previews = data
}
}
}
</script>
<style lang="less" scoped>
.avatar-upload-preview {
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 180px;
height: 180px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
</style>
5.补充后台方法读取图片的实现方式:
/**
* 读取图片
*
* @param response
* @param name
* @throws Exception
*/
@GetMapping("/getImg/{name}")
public void getImage(HttpServletResponse response, String name) throws Exception {
response.setContentType("image/jpeg;charset=utf-8");
response.setHeader("Content-Disposition", "inline; filename=girls.png");
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(Files.readAllBytes(Paths.get(UPLOAD_PATH).resolve(name)));
outputStream.flush();
outputStream.close();
}
至此,jar方式的图片上传功能完成,喜欢我的朋友请给我一下关注,我会努力给大家奉献好的作品
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。