一、背景
- 后台系统一般会有用户个人信息的模块(见下图),为了增强用户的体验度,系统会开放自定义头像的功能,让用户可以上传自定义图片替代默认的系统头像。本文将通过Vue+SpringBoot来具体实现。
二、vue-image-crop-upload组件
三、编写前端上传头像功能
- 了解了vue-image-crop-upload组件的功能后,编写自定义头像前台上传功能就相当容易了,我们只需要把这个组件嵌入到个人信息页面中,并编写相应事件即可。
<template>
<div class="app-container">
...
<div slot="header" class="clearfix">
<span>个人信息</span>
</div>
<div>
<div style="text-align: center">
<div class="el-upload">
<myUpload
v-model="showDialog"
:headers="headers"
:url="baseApi+'/api/user/updateAvatar'"
@crop-upload-success="cropUploadSuccess"
/>
<img
:src="
user.avatarUrl
? baseApi + '/file/' + user.avatarUrl
: Avatar
"
title="点击上传头像"
class="avatar"
@click="toggleShow"
>
...
</div>
</div>
</div>
..
</div>
</template>
<script>
import myUpload from 'vue-image-crop-upload'
import { mapGetters } from 'vuex'
import { getToken } from '@/utils/auth'
import store from '@/store'
import Avatar from '@/assets/images/avatar.png'
export default {
name: 'Center',
components: { myUpload },
data() {
return {
showDialog: false,
Avatar: Avatar,
headers: {
'Authorization': getToken()
},
...
}
},
computed: {
...mapGetters([
'user',
'baseApi'
])
},
methods: {
toggleShow() {
this.showDialog = !this.showDialog
},
cropUploadSuccess(jsonData, field) {
store.dispatch('getInfo').then(() => { })
},
...
}
}
</script>
...
- 页面效果如下:
四、编写后台上传用户头像接口
- 有了前端页面,就需要后台实现对应的接口,想一想,该接口需要有哪些功能呢?
- 需要将前端组件上传过来的头像文件保存到服务器上;
- 需要将保存在服务器上的用户头像路径存放到用户信息表中。
4.1 实现前端组件上传过来的头像文件保存到服务器上
- 我们需要建立一个上传文件信息表,及建立上传文件的操作服务类来实现文件的保存。
- 上传文件信息表
@ApiModel(value = "用户信息")
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@TableName("upload_file")
public class UploadFile implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String realName;
private String fileName;
private String primaryName;
private String extension;
private String path;
private String type;
private Long size;
private String uploader;
private Timestamp createTime;
@Override
public String toString() {
return "UploadFile{" +
"fileName='" + fileName + '\'' +
", uploader='" + uploader + '\'' +
", createTime=" + createTime +
'}';
}
}
- 上传文件DAO
@Mapper
public interface UploadFileMapper extends BaseMapper<UploadFile> {
}
- 上传文件工具具体实现类
public interface UploadFileTool {
UploadFile upload(String uploader, String realName, MultipartFile multipartFile);
}
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class UploadFileToolImpl implements UploadFileTool {
private final UploadFileMapper uploadFileMapper;
@Value("${uploadFile.path}")
private String path;
@Value("${uploadFile.maxSize}")
private long maxSize;
@Override
@Transactional(rollbackFor = Exception.class)
public UploadFile upload(String uploader, String realName, MultipartFile multipartFile) {
if (multipartFile.getSize() > maxSize * Constant.MB) {
throw new RuntimeException("超出文件上传大小限制" + maxSize + "MB");
}
String primaryName = FileUtil.mainName(multipartFile.getOriginalFilename());
String extension = FileUtil.extName(multipartFile.getOriginalFilename());
String type = getFileType(extension);
LocalDateTime date = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMddhhmmssS");
String nowStr = "-" + date.format(format);
String fileName = primaryName + nowStr + "." + extension;
try {
String filePath = path + type + File.separator + fileName;
File dest = new File(filePath).getCanonicalFile();
if (!dest.getParentFile().exists()) {
if (ObjectUtil.isNull(dest.getParentFile().mkdirs())) {
throw new RuntimeException("上传文件失败:建立目录错误");
}
}
multipartFile.transferTo(dest);
if (ObjectUtil.isNull(dest)) {
throw new RuntimeException("上传文件失败");
}
UploadFile uploadFile = new UploadFile(null, realName, fileName, primaryName,
extension, dest.getPath(), type, multipartFile.getSize(),
uploader, Timestamp.valueOf(LocalDateTime.now()));
if (uploadFileMapper.insert(uploadFile) > 0) {
return uploadFile;
}
throw new RuntimeException("上传文件失败");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
private static String getFileType(String extension) {
String document = "txt doc pdf ppt pps xlsx xls docx csv";
String music = "mp3 wav wma mpa ram ra aac aif m4a";
String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
if (image.contains(extension)) {
return "image";
} else if (document.contains(extension)) {
return "document";
} else if (music.contains(extension)) {
return "music";
} else if (video.contains(extension)) {
return "video";
} else {
return "other";
}
}
}
4.2 将保存在服务器上的用户头像路径存放到用户信息表中
- 在用户信息服务中增加保存头像信息的功能
public interface SysUserService {
....
Map<String,String> updateAvatar(MultipartFile file);
}
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class SysUserServiceImpl implements SysUserService {
private final SysUserMapper sysUserMapper;
private final UploadFileTool uploadFileTool;
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, String> updateAvatar(MultipartFile file) {
SysUser sysUser = findByUserName(getCurrentLoginUserName());
UploadFile uploadFile = uploadFileTool.upload(sysUser.getUserName(), file.getOriginalFilename(), file);
sysUser.setAvatarUrl(uploadFile.getType() + File.separator + uploadFile.getFileName());
update(sysUser);
return new HashMap<String, String>(1) {{
put("avatar", uploadFile.getFileName());
}};
}
@Override
@Transactional(rollbackFor = Exception.class)
public SysUser update(SysUser user) {
if (sysUserMapper.updateById(user) > 0) {
return user;
}
throw new RuntimeException("更新用户信息失败");
}
@Override
public SysUser findByUserName(String userName) {
return sysUserMapper.selectOne(new QueryWrapper<SysUser>().lambda().eq(SysUser::getUserName, userName));
}
private String getCurrentLoginUserName() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new RuntimeException("登录状态已过期");
}
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return (userDetails.getUsername());
}
throw new RuntimeException("找不到当前登录的信息");
}
}
4.3 编写用户更新头像信息API
- 最后只需要在Controller层增加用户更新头像信息API接口供前端调用即可
@ApiOperation("修改用户头像")
@PostMapping(value = "/updateAvatar")
public ResponseEntity<Object> updateAvatar(@RequestParam MultipartFile avatar) {
return ResponseEntity.ok(sysUserService.updateAvatar(avatar));
}
五、前后端联调
六、源码