该系列博客主要记录笔者的开发过程,参考B站系列视频:【SpringBoot项目实战完整版】SpringBoot+MyBatis+MySQL电脑商城项目实战_哔哩哔哩_bilibili
所用的一些版本信息:
IDEA开发、JDK1.8版本以上、maven3.61版本以上,springboot,DataGrip管理数据库
上传头像的页面如下:
1.上传头像-持久层
1.1规划SQL语句
将对应文件保存在操作系统,文件路径记录在t_user表的avatar字段下。需要依据更新用户avatar字段的SQL语句。
1.2 设计接口和抽象方法
store\src\main\java\com\cy\store\mapper\UserMapper.java
/**
* @Param("uid1") uid2 主要是做名称装换, 后面那个uid2的值注入到sql语句中的uid1中
* 根据用户id修改头像
* @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);
1.3 接口的映射
store\src\main\resources\mapper\UserMapper.xml
<update id="updateAvatarByUid">
UPDATE t_user SET
avatar = #{avator},
modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
where uid=${uid}
</update>
1.4 单元测试
store\src\test\java\com\cy\store\mapper\UserMapperTests.java
@Test
public void updateAvtarByUid(){
Integer rows = userMapper.updateAvatarByUid(29, "/upload/avatar.png", "管理员", new Date());
}
2.上传头像-业务层
2.1 规划异常
1.各种情况导致用户数据不存在。(异常已有)
2.未知异常产生。(异常已有)
2.2 设计接口和抽象方法
store\src\main\java\com\cy\store\service\IUserService.java
/**
* 修改用户的头像
* @param uid 用户id
* @param avatar 头像
* @param username 用户姓名
*/
void changeAvatar(Integer uid, String avatar, String username);
store\src\main\java\com\cy\store\service\impl\UserServiceImpl.java
@Override
public void changeAvatar(Integer uid, String avatar, String username) {
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("更新数据时产生未知异常");
}
}
2.3 单元测试
store\src\test\java\com\cy\store\service\UserServiceTests.java
@Test
public void changeAvatar() {
userService.changeAvatar(29, "/123/123.png", "guanli");
}
3.上传头像-控制层
3.0 规划异常
由于上传头像是传文件,需要规划产生的异常。
首先创建文件基类异常:
FileUploadException 父类
子类:
FileEmptyException 空文件异常
FileSizeException 文件大小超出限制异常
FileTypeException 文件类型异常
FileUploadIOException 文件读写异常
FileStateException 文件状态异常
代码略
3.1 处理异常
在基类BaseController中进行统一处理:
store\src\main\java\com\cy\store\controller\BaseController.java
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);
}
并在handleException这个方法上添加新的异常的参数
3.2 设计请求
url:/users/change_avator
请求类型:POST
参数:HTTPSession, MultipartFile file
响应:JsonResult<String>
3.3 处理请求
该部分需要处理文件,所以代码较长
store\src\main\java\com\cy\store\controller\UserController.java下编写如下代码:
/** 上传头像最大值 */
public static final int AVATAR_MAX_SIZE = 10 * 1024 * 1024;
/** 上传限制文件类型 */
public static final List<String> AVATAR_TYPE = new ArrayList<>();
static {
AVATAR_TYPE.add("image/jpeg");
AVATAR_TYPE.add("image/png");
AVATAR_TYPE.add("image/bmp");
AVATAR_TYPE.add("image/gif");
}
/**
* MultipartFile SpringMVC提供的接口,包装了获取文件类型的数据(任何类型的file),使用时申明参数,Springboot自动将数据赋值给参数
* @RequestParam("file1") file2 将前端表单(请求参数)file1,注入到请求方法参数上file2
*/
@RequestMapping("change_avatar")
public JsonResult<String> changeAvatar(HttpSession session,
@RequestParam("file") MultipartFile file){
System.out.println("进入修改接口");
//判断文件是否合规
if(file.isEmpty()){
throw new FileEmptyException("文件为空");
}
if(file.getSize()>AVATAR_MAX_SIZE){
throw new FileSizeException("文件超出大小限制");
}
String contentType = file.getContentType();
if(!AVATAR_TYPE.contains(contentType)){
throw new FileTypeException("文件类型不支持");
}
//上传的文件目录结构.../upload/file.png
String parent = session.getServletContext().getRealPath("upload");
//upload 可能找不到
//File指向路径 File不存在则创建目录
File dir = new File(parent);
if(!dir.exists()){
dir.mkdirs();
}
//获取文件名称,用UUID用具类生成新的字符串作为文件名
String originalFilename = file.getOriginalFilename();
System.out.println("originalFilename ="+originalFilename);
int index = originalFilename.lastIndexOf(".");
String suffix = originalFilename.substring(index);
String filename = UUID.randomUUID().toString().toUpperCase() + suffix;
//先在目录下创建一个空文件,再拷贝写入空文件
File dest = new File(dir, filename);
try {
file.transferTo(dest); //将file文件数据写入dest文件中,两个文件后缀一致
} catch (FileStateException e){
throw new FileStateException("文件状态异常");
} catch (IOException e) {
throw new FileUploadIOException("文件读写异常");
}
//修改数据库
String avatar = "/upload/" + filename;
userService.changeAvatar(getuidFromSession(session), avatar, getusernameFromSession(session));
//返回头像路径给前端,后面用作展示
return new JsonResult<String>(OK, avatar);
}
4.上传头像-前端页面
这里直接使用表单进行文件类型上传(方便测试,后面需要用ajax进行上传),需要给表单添加属性 enctype="multipart/form-data",不会将目标文件的数据结构做修改上传。
这里添加标签如下:
action = "/user/change_avatar"
method="post"
enctype="multipart/form-data"
5.解决Bug
5.1 手动修改上传头像的默认大小
SpringMVC默认1MB的文件可以上传,需要手动修改。
1)在配置文件中进行配置
2通过Java代码来设置文件的上传限制。
在主类(store\src\main\java\com\cy\store\StoreApplication.java)中进行配置,可以定义一个方法,使用@Bean,@configuration修饰。
package com.cy.store;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
import javax.servlet.MultipartConfigElement;
@SpringBootApplication
//mapperScan注解指定当前项目的mapper接口路径的位置,在项目启动时会自动加载所有的接口文件
@MapperScan("com.cy.store.mapper")
public class StoreApplication {
public static void main(String[] args) {
SpringApplication.run(StoreApplication.class, args);
}
@Bean
public MultipartConfigElement getMultipartConfigElement(){
//创建一个配置的工厂类对象
MultipartConfigFactory factory = new MultipartConfigFactory();
//设置需要创建的对象的相关信息
factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));
factory.setMaxRequestSize(DataSize.of(15, DataUnit.MEGABYTES));
//工厂类创建MultipartConfigElement对象
return factory.createMultipartConfig();
}
}
5.2 前端页面显示头像
在页面中通过ajax请求提交文件,后端返回json串后,解析出data数据设置到img头像标签的src属性上:
在store\src\main\resources\static\web\upload.html 中添加如下代码(在此之前要将前面的表单提交代码删除掉)
<script type="text/javascript">
$("#btn-change-avatar").click(function(){
$.ajax({
url:"/users/change_avatar",
type: "POST",
data: new FormData($('#form-change_avatar')[0]),
processData: false,//关闭处理数据(否则会按照串的形式处理数据)
contentType: false,// 关闭提交数据的形式(否则会按照串的形式提交数据)
dataType:"JSON",
success:function (json){
console.log(json)
if(json.state == 200){
alert("头像上传成功");
// 将服务器端返回头像设置到img标签的src属性上
$("#img-avatar").attr("src", json.data);
}else{
alert("头像上传失败" + json.message);
}
},
error: function(xhr){
alert("头像上传时产生未知异常" + xhr.message)
}
});
});
</script>
5.3 登录后直接显示头像
更新头像成功后,将头像路径保存在cookie中,每次检测到用户打开上传头像页面,在这个页面中通过ready()方法自动检测读取头像并设置到src属性上。
1.设置cookie的值
在login.html中设置cookie
2.在upload.html中引入cookie
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
3.使用ready()读头像
upload.html中添加代码如下:
<script type="text/javascript">
$(document).ready(function(){
let avatar = $.cookie("avatar");
$("#img-avatar").attr("src", avatat);
})
$("#btn-change-avatar").click(function(){
$.ajax({
url:"/users/change_avatar",
type: "POST",
data: new FormData($('#form-change_avatar')[0]),
processData: false,//关闭处理数据(否则会按照串的形式处理数据)
contentType: false,// 关闭提交数据的形式(否则会按照串的形式提交数据)
dataType:"JSON",
success:function (json){
console.log(json)
if(json.state == 200){
alert("头像上传成功");
// 将服务器端返回头像设置到img标签的src属性上
$("#img-avatar").attr("src", json.data);
$.cookie("avatar", json.data.avatar, {expires:7}) //存活时间的单位为天
}else{
alert("头像上传失败" + json.message);
}
},
error: function(xhr){
alert("头像上传时产生未知异常" + xhr.message)
}
});
});
</script>
这样就成功实现了,但由于浏览器存在缓存,所以代码正确也可能导致功能不正确,需要清浏览器缓存重试。