持久层
1、sql语句的规划
上传文件的操作其实是:先将用户上传的文件保存到服务器端的某个位置,然后将保存文件的路径记录在数据库中。当后续需要使用该文件时,从数据库中读出文件的路径,即可实现在线访问该文件。
在持久层处理数据库中的数据时,只需要关心如何记录头像文件的路径,并不需要考虑上传时保存文件的过程。所以,需要执行的SQL语句大致是:
update t_user set avatar=?, modified_user=?, modified_time=? where uid=?
2、设计接口与抽象方法
在UserMapper接口中添加updateAvatarByUid()抽象方法。
/**
* 根据用户uid值来修改用户头像
* @param uid
* @param avatar
* @param modifiedUser
* @param modifiedTime
* @return
*/
Integer updateAvatarByUid(@Param("uid") Integer uid,
@Param("avatar") String avatar,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
3、接口的映射
<update id="updateAvatarByUid">
update store.t_user set avatar=#{avatar}, modified_user=#{modified_user},
modified_time=#{modified_time}
where uid=#{uid}
</update>
3、测试
@Test
public void updateAvatarByUid() {
Integer uid = 8;
String avatar = "/upload/avatar.png";
String modifiedUser = "超级管理员";
Date modifiedTime = new Date();
Integer rows = usermapper.updateAvatarByUid(uid, avatar, modifiedUser, modifiedTime);
System.out.println("rows=" + rows);
}
业务层
1、规划异常
1、在修改头像值前先检查用户数据状态,可能抛UserNotFoundException异常;
2、由于最终执行的是修改操作还可能抛UpdateException异常。
2、 接口与抽象方法
在IUserService中添加changeAvatar(Integer uid, String username, String avatar)抽象方法。
/**
* 修改用户的头像
* @param uid 用户uid
* @param username 用户头像
* @param avatar 用户名称
*/
void changeAvatar(Integer uid, String username, String avatar);
3、实现抽象方法
1.在UserServiceImpl类中实现changeAvatar(Integer uid, String username, String avatar)方法。
@Override
public void changeAvatar(Integer uid, String username, String avatar) {
//查询当前用户数据是否存在
User result=usermapper.findByUid(uid);
if (result==null || result.getIsDelete()==1){
throw new UserNotFoundException("用户没有找到");
}
Integer rows = usermapper.updateAvatarByUid(uid, avatar, username, new Date());
if (rows != 1) {
throw new UpdateException("更新用户头像时出现未知错误");
}
}
4、在UserServiceTests类中进行单元测试。
@Test
public void changeavatar(){
userService.changeAvatar(8, "小明", "/upload/test.png");
}
控制层
1、处理异常
1.在处理上传文件的过程中,用户可能会选择错误的文件上传,此时就应该抛出对应的异常并进行处理。所以需要创建文件上传相关异常的基类,
即在com.cy.store.controller.ex包下创建FileUploadException类,并继承自RuntimeException类。
2.在处理上传的文件过程中,经分析可能会产生以下异常。这些异常类都需要继承自FileUploadException类。
// 上传的文件为空
cn.tedu.store.controller.ex.FileEmptyException
// 上传的文件大小超出了限制值
cn.tedu.store.controller.ex.FileSizeException
// 上传的文件类型超出了限制
cn.tedu.store.controller.ex.FileTypeException
// 上传的文件状态异常
cn.tedu.store.controller.ex.FileStateException
// 上传文件时读写异常
cn.tedu.store.controller.ex.FileUploadIOException
2、处理异常
在BaseController的handleException()的@ExceptionHandler注解中添加FileUploadException.class异常的处理;最后在方法中处理这些异常。
else if (e instanceof FileEmptyException) {
result.setState(6000);
} else if (e instanceof FileSizeException) {
result.setState(6001);
} else if (e instanceof FileTypeException) {
result.setState(6002);
} else if (e instanceof FileStateException) {
result.setState(6003);
} else if (e instanceof FileUploadIOException) {
result.setState(6004);
}
3、设计请求
请求路径:/users/change_avatar
请求参数:MultipartFile file, HttpSession session
请求类型:POST
响应结果:JsonResult
4、处理请求
在UserController类中添加处理请求的changeAvatar(@RequestParam(“file”) MultipartFile file, HttpSession session)方法。
/** 头像文件大小的上限值(10MB) */
public static final int AVATAR_MAX_SIZE = 10 * 1024 * 1024;
/** 允许上传的头像的文件类型 */
public static final List<String> AVATAR_TYPES = new ArrayList<String>();
/** 初始化允许上传的头像的文件类型 */
static {
AVATAR_TYPES.add("image/jpeg");
AVATAR_TYPES.add("image/png");
AVATAR_TYPES.add("image/bmp");
AVATAR_TYPES.add("image/gif");
}
@RequestMapping("change_avatar")
/**
* MultipartFile是springmvc的接口,这个接口为我们包装了获取文件类型的数据(任何类型的file文件),
* springboot整合了mvc,所以只需要在处理请求的方法参数列表上声明一个参数类型为MultipartFile的参数
* 然后springboot自动将传递给服务器的文件数据赋值给这个参数
*
* @RequestParam:表示请求的参数,将请求的参数注入请求处理方法的某个参数上,
* 如果名称不一致则可以使用 @RequestParam注解进行标记和映射
*/
public JsonResult<String> changeAvatar(HttpSession session,
@RequestParam("file") MultipartFile file){
// 判断上传的文件是否为空
if (file.isEmpty()) {
throw new FileEmptyException("上传的头像文件不允许为空");
}
// 判断上传的文件大小是否超出限制值
if (file.getSize() > AVATAR_MAX_SIZE) { // getSize():返回文件的大小,以字节为单位
throw new FileSizeException("不允许上传超过" + (AVATAR_MAX_SIZE / 1024) + "KB的头像文件");
}
// 判断上传的文件类型是否是我们规定的后缀类型
String contentType = file.getContentType();
// public boolean list.contains(Object o):当前列表若包含某元素,返回结果为true;若不包含该元素,返回结果为false。
if (!AVATAR_TYPES.contains(contentType)) {
throw new FileTypeException("不支持使用该类型的文件作为头像,允许的文件类型:\n" + AVATAR_TYPES);
}
// 获取当前项目的绝对磁盘路径
String parent = session.getServletContext().getRealPath("upload");
// 保存头像文件的文件夹
File dir = new File(parent);
if (!dir.exists()) {
dir.mkdirs();//创建当前目录
}
// 获取到这个文件的文件名,UUID来生成一个新的字符串
String suffix = "";
String originalFilename = file.getOriginalFilename();
int beginIndex = originalFilename.lastIndexOf(".");
if (beginIndex > 0) {
suffix = originalFilename.substring(beginIndex);
}
String filename = UUID.randomUUID().toString().toUpperCase() + suffix;
// 创建文件对象,表示保存的头像文件
File dest = new File(dir, filename);//空文件
// 将参数file的数据写入到这个空文件中
try {
file.transferTo(dest);//将file文件中的数据写入dest
} catch (FileStateException e) {
// 抛出异常
throw new FileStateException("文件状态异常,可能文件已被移动或删除");
} catch (IOException e) {
// 抛出异常
throw new FileUploadIOException("上传文件时读写错误,请稍后重尝试");
}
// 头像路径
String avatar = "/upload/" + filename;
// 从Session中获取uid和username
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
// 将头像写入到数据库中
userService.changeAvatar(uid, username, avatar);
// 返回成功头像路径
return new JsonResult<String>(ok, avatar);
}
前端页面
在upload.html页面中配置用户上传头像的form表单。
如果直接使用表单进行文件的上传,,需要给表单显示的添加一个属性enctype=“multipart/form-data”,不会将目标文件的数据结构做修改在上传,不同字符串
<form action="/users/change_avatar" method="post"
enctype="multipart/form-data"
class="form-horizontal" role="form">
完成后启动项目,打开浏览器先登录,再访问http://localhost:8080/web/upload.html进行测试。
部分优化修改
在选择图片时报错:1M
原因:SpringBoot中默认MultipartResolver的最大文件大小值为1M。如果上传的文件的大小超过1M,会抛FileSizeLimitExceededException异常。
解决:手动修改默认MultipartResolver的最大文件大小,
- 直接在配置文件中进行配置
spring.http.multipart.max-file-size=10MB
spring.http.multipart.max-request-size=15MB
- 采用java代码的形式来设置的上传大小的限制
直接在启动类中添加getMultipartConfigElement()方法,并且在启动类之前添加@Configuration注解。
@Bean
public MultipartConfigElement getMultipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();//配置的工厂类对象
// 设置文件最大10M,DataUnit提供5中类型B,KB,MB,GB,TB
factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));
// 设置总上传数据总大小10M
factory.setMaxRequestSize(DataSize.of(10, DataUnit.MEGABYTES));
//通过工厂类来创建MultipartConfigElement对象
return factory.createMultipartConfig();
}
用户-上传头像-前端页面BUG解决
1、 上传后显示头像
- 头像上传成功后,显示上传的头像。在upload.html页面中,是使用img标签来显示头像图片的。首先确定img标签是否添加有id="img-avatar"属性,便于后续访问该标签;而img标签是通过src属性来决定显示哪张图片的,所以修改src该属性的值即可设置需要显示的图片。修改表单添加id="form-change-avatar"属性。修改input标签,添加id="btn-change-avatar"和type="button"属性。
- serialixe():可以将表单数据自动拼接成key=value的结构进行提交给服务器,一把你叫是普通控件类型中的数据(text/password/radio/checkbox)等等
- FormData类:将表单数据保持原有的结构进行数据的条件。
文件类型的数据可以使用formData对象进行存储
new FormData($("#form-change-avatar")[0]
ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式进行提交数据。关闭这两个默认的功能
- processData:处理数据。默认情况下,processData的值是true,其代表以对象的形式上传的数据都会被转换为字符串的形式上传。而当上传文件的时候,则不需要把其转换为字符串,因此要改成false。
- contentType:发送数据的格式。其代表的是前端发送数据的格式,默认值application/x-www-form-urlencoded。代表的是ajax的 data是以字符串的形式传递,使用这种传数据的格式,无法传输复杂的数据,比如多维数组、文件等。把contentType设置为false就会改掉之前默认的数据格式,在上传文件时就不会报错。
<script type="text/javascript">
$("#btn-change-avatar").click(function() {
$.ajax({
url: "/users/change_avatar",
type: "POST",
data: new FormData($("#form-change-avatar")[0]),
dataType: "JSON",
processData: false, // processData处理数据
contentType: false, // contentType发送数据的格式
success: function(json) {
if (json.state == 200) {
alert("修改成功!");
//attr(属性,属性值):给某个属性设置某个值
$("#img-avatar").attr("src", json.data);
} else {
alert("修改失败!" + json.message);
}
},
error: function(xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
});
});
</script>
2、登录后显示头像
1.用户名、用户Id、用户头像等数据,属于常用数据,在客户端的许多页面都可能需要使用,如果每次都向服务器提交请求获取这些数据,是非常不合适的。可以在用户登录成功后,将这些数据存储在客户端本地,后续在客户端中需要显示这些数据时,直接从本地获取即可,无需再向服务器请求这些数据。在客户端本地存取数据时,可以使用Cookie技术。
2.设计思路:当用户登录成功后,将服务器返回的头像路径存储到本地的Cookie中,在打开“上传头像”页面时,从本地的Cookie中读取头像路径并显示即可。在登录login.html页面中,当登录成功后,将用户头像路径保存到Cookie中。
设置cookie的值:
- 导入cookie.js文件
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
- 调用cookie方法
$.cookie(key, value, time);//单位:天
在upload.html页面通过ready()自动读取cookie中的数据
$(document).ready(function () {
let avatar= $.cookie("avatar")
$("#img-avatar").attr("src", avatar);
});
添加头文件
在upload.html页面中,默认并没有引用jqueyr.cookie.js文件,因此无法识别$.cookie()函数;所以需要在upload.html页面head标签内添加jqueyr.cookie.js文件。
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
显示最新头像
以上代码表示“每次打开页面时,读取Cookie中的头像并显示”,如果此时重新上传用户头像,而Cookie中所保存的头像还是之前上传的头像路径值,无法显示最新的用户头像。所以当用户重新上传头像后,还应把新头像的路径更新到Cookie中。
在upload.html页面中,用户头像修改成功后,并将新的用户头像路径保存到Cookie中。