图片上传
????前端工程中,文件上传操作需要FormData对象的支持;而后端解析则是需要MultipartFile类的支持。
FormData
????FormData对象用以将数据编译成键值对,主要用于发送表单数据,但是也可用来发送带键数据(keyed data),而独立于表单使用。而如果enctype属性设置为multipart/form-data,就可以通过form表单的submit()方法来上传数据了。
FornData添加键值对数据
????FormData提供了append()方法,可以用来添加将要上传的数据,这些数据可以是普通文本数据,也可以是二进制流数据(例如:Blob类型)。
ar formData = new FormData();
formData.append("username", "Groucho");
formData.append("accountnum", 123456);
formData.append("userfile", fileInputElement.files[0]);
var content = '<a id="a"><b id="b">hey!</b></a>';
var blob = new Blob([content], { type: "text/xml"});
formData.append("webmasterfile", blob);
var request = new XMLHttpRequest();
request.open("POST", "http://foo.com/submitform.php");
request.send(formData);
Blob数据类型
????Blob对象表示一个不可变、原始数据的类文件对象,而且Blob是File文件类型的“父级接口”,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。 ????Blob类型的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
form表单的enctype属性
????利用form表单可以提交用户基本信息,此时:enctype采用默认值:application/x-www-form-urlencoded。 ????当form表单包含type=file的input文件上传控件时,enctype的值就必须要指定为multipart/form-data了,虽然原则上来讲,它仍然具有表单提交基本字符串数据的功能。 ????在使用FormData上传文件时,需要在表单中添加一个文件类型input。例如:
<form enctype="multipart/form-data" method="post" name="fileinfo">
<label>Your email address:</label>
<input type="email" autocomplete="on" autofocus name="userid" placeholder="email" required size="32" maxlength="64" /><br />
<label>Custom file label:</label>
<input type="text" name="filelabel" size="12" maxlength="32" /><br />
<label>File to stash:</label>
<input type="file" name="file" required />
<input type="submit" value="Stash the file!" />
</form>
<div></div>
form表单
????如下HTML代码片段,enctype属性被指定为multipart/form-data,可用于上传文件。
<form enctype="multipart/form-data" id="student_award_form">
<div class="form-row">
<div class="form-group col-md-6">
<label for="awardlunit">颁奖单位</label>
<input type="text" class="form-control" id="awardlunit"
name="unit" placeholder="例如:单位A,单位B" disabled>
<small id="emailHelp" class="form-text text-muted"> 若有多个单位名称-请以
<span class="text-primary">逗号 ','</span> 分隔</small>
</div>
<div class="form-group col-md-6">
<label for="evidence">实证文件</label>
<input type="file" class="form-control" id="evidence"
accept="application/pdf" name="evidence" placeholder="" disabled>
<small id="emailHelp"
class="form-text text-muted">若有多个文件,请合并后上传-<span
class="text-primary">[文件类型:*.pdf]</span></small>
</div>
</div>
<div class="row">
<div class="col">
<button type="button"
class="btn btn-success btn-lg btn-block" id="btn_student_award_uploadlater"
οnclick="uploadLater('student_award')">稍后上报
</button>
<small
id="emailHelp" class="form-text text-muted"><span
class="text-info">选择稍后上报,将为您记录本次填写的上报信息,之后仍可继续上报</span></small>
</div>
<div class="col">
<button type="button"
class="btn btn-success btn-lg btn-block" id="btn_student_award_upload" οnclick="uploadRightNow('student_award')">立即上报
</button>
<small
id="emailHelp"
class="form-text text-muted">
<input
class="form-check-input" type="checkbox" id="gridCheck_student_award">上报前请确认:
<span
class="text-danger">当前账户与实际上报人一致,否则会导致信息错乱!</span>
</small>
</div>
</div>
</form>
Ajax+FormData文件上传
/**
* 上传文件与参数信息
* @param url 请求链接
* @param idSelector id选择器
* @param paramsValue 参数值信息
* */
function uploadFile(url,idSelector,paramsValue) {
//照片-img-id=basic_userPhoto
var filePath=$(idSelector)[0].value;//获取文件路径
var fileObj=$(idSelector)[0].files[0];//获取文件对象
if (filePath){
//获取文件后缀名
var postfix=filePath.substring(filePath.lastIndexOf(".")+1)
if(postfix&&fileObj&&(postfix=="pdf")){
//使用FileReader解析
var pdfFile = $(idSelector)[0].files[0];
//提交文件路径到服务器端程序
var formData = new FormData();
console.log(paramsValue);
formData.append("evidencfile",fileObj);
formData.append("params",JSON.stringify(paramsValue));
$.ajax({
url: url,
method: "POST",
async: true,
data: formData,
processData:false,
contentType:false,
dataType: "json",
success: function (result, status, xhr) {
// console.log("响应信息")
console.log(result)
if(result){
alert(result.message)
//禁用稍后上传菜单
$("#btn_student_award_uploadlater").attr("disabled",true);
$("#btn_student_award_upload").attr("disabled",true);
$("#btn_teacTest_award_uploadlater").attr("disabled",true);
$("#btn_teacTest_award_upload").attr("disabled",true);
}
}
})
}else{
alert("仅支持pdf文件类型!")
}
}
}
基于Servlet的后端解析
????基于Servlet的传统开发模式,可以引入第三方jar包,来支持文件上传之后的解析功能开发。 ????上传文件解析代码截取如下,
private Result uploadUserPhoto(HttpServletRequest request) {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("login_user");
Result result = null;
boolean multipartContent = ServletFileUpload.isMultipartContent(request);
System.out.println("isMultipartContent=" + multipartContent);
if (multipartContent) {
ServletFileUpload upload = new ServletFileUpload();
try {
FileItemIterator itemIterator = upload.getItemIterator(request);
if (itemIterator.hasNext()) {
FileItemStream next = itemIterator.next();
String fieldName = next.getFieldName();
InputStream inputStream = next.openStream();
System.out.println("inputStream=" + inputStream);
System.out.println("next.isFormField()=" + next.isFormField());
long rows = userDetailService.uploadPhoto(user.getUsername(), inputStream);
System.out.println("rows=" + rows);
if (rows > 0) {
result = Result.ok("设置成功!");
} else {
result = Result.error("设置失败!");
}
}else {
result = Result.error("设置失败!");
}
} catch (FileUploadException | IOException e) {
e.printStackTrace();
result = Result.error("设置失败!");
}
} else {
result = Result.error("设置失败!");
}
return result;
}
Axios+Element UI
????通过Element UI提供的el-upload组件,结合Axios实现文件上传。通常需要:
引入el-upload组件
<el-upload
class="avatar-uploader"
action="https://jsonplaceholder.typicode.com/posts/"
multiple
:show-file-list="false"
:on-preview="handlePictureCardPreview"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img
v-if="imageUrl"
:src="imageUrl"
style="width: 200px; height: 250px"
class="avatar"
/>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
el-upload事件处理与Axios文件上传
handleAvatarSuccess(res, file, fileList) {
this.imageUrl = URL.createObjectURL(file.raw)
let imgFile = file.raw
console.log(imgFile)
if (imgFile) {
let formData = new FormData()
let email = localStorage.getItem('email')
formData.append('photo', imgFile)
formData.append('email', email)
console.log(formData)
this.$axios({
url: 'http://localhost:8010/stasys_v3/userDetail/POST/update/photo',
method: 'POST',
data: formData,
})
.then((res) => {
console.log(res)
if (res.data && res.data == 1) {
this.$message({
message: '照片更新成功!',
type: 'success',
})
}
})
.catch((err) => {
console.log(err)
this.$message.error('照片更新失败!')
})
} else {
this.$message.error('照片更新失败!')
}
},
handlePictureCardPreview(file) {
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!')
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
let fd = new FormData()
let email = localStorage.getItem('email')
fd.append('photo', file)
fd.append('email', email)
return isJPG && isLt2M
},
SSM后端接口编写
????SSM框架整合操作就不写了。下面是Mapper、Service、Controller层的代码。其中要被操作的字段是以Blob类型存储在数据库中的。
Mapper接口与XML文件
<update id="updatePhoto" >
UPDATE tb_user_detail SET photo = #{photo}
WHERE userid = #{email}
</update>
public abstract Integer updatePhoto(@Param(value = "email")String email,
@Param(value = "photo")byte[] photo);
Service接口与实现子类
public interface UserDetailService {
public abstract Integer updatePhoto(String email,byte[] photo);
}
public interface UserDetailServiceImpl implements UserDetailService {
@Autowired
private UserDetailMapper mapper;
@Override
public Integer updatePhoto(String email, byte[] photo) {
return mapper.updatePhoto(email,photo);
}
Controller层与结果响应
@Controller(value = "userDetailController")
@RequestMapping(value = "/userDetail")
public class UserDetailController {
@Autowired
@Qualifier(value = "userDetailServiceImpl")
private UserDetailService userDetailService;
@RequestMapping(value = "/POST/update/photo")
@ResponseBody
public Integer updateUserPhoto(@RequestParam("photo")MultipartFile photo,
@RequestParam("email")String email){
byte[] bytes = null;
InputStream inputStream = null;
try {
inputStream = photo.getInputStream();
int available = inputStream.available();
bytes= new byte[available];
int read = inputStream.read(bytes);
inputStream.close();
if (userDetailService.selectOne(email)==null){
UserDetail userDetail=new UserDetail();
userDetail.setUserid(email);
userDetailService.insertOne(userDetail);
}
Integer rows= userDetailService.updatePhoto(email, bytes);
return rows;
} catch (IOException e) {
e.printStackTrace();
return 0;
}
}
}
图片获取
????SSM+Vue+Axios+Element UI开发时,文件下载操作:可以将从数据库中读取Blob字段值,编写Mapper接口与XML配置文件,将其自动转换为byte[]字节数组,将其直接返回给前端;最终在前端利用Axios进行解析。
基于Servlet的图片请求
????传统Servlet开发模式中,可以将从数据库中读取到的byte[]数组,转换为InputStream输入流,通过二进制输入流,配合HttpServletResponse对象的OutputStream对象,将其写出。然后在前端修改img的src属性为后端接口名称,即可实现图片的回显操作。
Servlet代码编写
????部分代码截取如下,
private void responseUserPhoto(HttpServletRequest req,HttpServletResponse resp) throws IOException {
HttpSession session = req.getSession();
User user = (User) session.getAttribute("login_user");
InputStream inputStream = userDetailService.downloadPhoto(user.getUsername());
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
if (inputStream==null){
PrintWriter writer = resp.getWriter();
writer.write(JSON.toJSONString(Result.error("获取头像失败!")));
writer.flush();
writer.close();
return;
}
resp.setCharacterEncoding("UTF-8");
resp.setContentType("image/jpeg;charset=UTF-8");
OutputStream outputStream = resp.getOutputStream();
byte[] buff=new byte[1024];
int len = -1;
while ((len=inputStream.read(buff))!=-1){
outputStream.write(buff,0,len);
}
outputStream.flush();
outputStream.close();
if (inputStream!=null)
inputStream.close();
}
前端代码编写
????部分代码截取如下,
假设后端接口为:http://localhost:8010/stasys_v2/get/userphoto.do
<img
class="rounded-circle mb-3 mt-4" src="http://localhost:8010/stasys_v2/get/userphoto.do"
width="160" height="160"
id="basic_userPhoto">
基于Axios的图片请求
SSM后端代码编写
????Mapper接口与XML中SQL语句编写、Service与子类实现没什么特殊的,只给出Controller接口。
Controller层接口与byte[]类型结果响应
@Controller(value = "userDetailController")
@RequestMapping(value = "/userDetail")
public class UserDetailController {
@RequestMapping("/GET/userdetailphoto")
@ResponseBody
public byte[] getPhoto(@RequestParam(value = "email") String email) {
UserDetail userDetail = userDetailService.selectJoin(email);
byte[] photo = userDetail.getPhoto();
return photo;
}
}
Axios前端请求与数据解析
axios({
url: 'http://localhost:8010/stasys_v3/userDetail/GET/userdetailphoto',
method: 'GET',
params: { email: "参数值" },
responseType: 'arraybuffer',
})
.then((response) => {
console.log(response)
this.imageUrl =
'data:image/png;base64,' +
btoa(
new Uint8Array(response.data).reduce(
(data, byte) => data + String.fromCharCode(byte),
''
)
)
})
.catch((error) => {
console.log(error)
})
????至此,图片上传和获取就基本完成了。
|