在使用QQ、微信等社交媒体时,彰显个人个性的用户头像是软件不可或缺的功能。本篇记录微服务架构下软件开发过程中的用户头像上传更新的功能点开发与迭代记录。
一、SpringBoot的文件本地上传与下载
如图在文件服务(file-service)controller包下创建FileController。
将地址映射为"file"。
@RestController
@RequestMapping("file")
public class FileController
使用POST方法uploadFace创建更新头像方法,与前端对应使用@RequestParam("file")指定file文件,传入userId为后续保存本地图片命名,涉及IO操作方法后抛出IOException。
@PostMapping("uploadFace")
public GraceJSONResult uploadFace(@RequestParam("file")MultipartFile file, String userId, HttpServletRequest request) throws IOException
接下来对收到图片进行命名处理:
//获得文件名
String filename = file.getOriginalFilename();
//确保取到文件名中最后一个"."后的文件扩展名,避免文件类型出错
String suffix = filename.substring(filename.lastIndexOf("."));
//用userId作为文件的新命名
String newFileName = userId +suffix;
收到图片后对图片保存路径进行处理,使用File.separator作为路径分隔符,避免不同系统引起的冲突,提升代码高可用。
//使用System.getProperty("user.home")获取用户主目录(如 C:\Users\你的用户名)
String rootPath = System.getProperty("user.home") + File.separator;
String filePath = rootPath + File.separator + "face" + File.separator + newFileName;
保存处理完的文件到本地,判断不存在文件路径时先创建:
File newFile = new File(filePath);
if(!newFile.getParentFile().exists()){
newFile.getParentFile().mkdirs();
}
file.transferTo(newFile);
最后返回ok:
return GraceJSONResult.ok();
先在网关中对/file/**开放通过后,可以通过postman进行测试:
在header中设置id和token并在Body中form-data选择一张本地图片。
得到图片,
可以发现图片名是null,这是因为在postman模拟前端传递参数时,没用同时传入用户id,导致空命名null。
经过以上功能开发可以发现是存在于本地路径,不具备web能力在浏览器中展示。
二、创建配置文件默认静态路径
在file-service服务模块下,与Application启动类同一包下创建配置类StaticResourceConfig
@Configuration
public class StaticResourceConfig extends WebMvcConfigurationSupport {
/**
* 添加静态资源映射路径
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//替换路径的分隔符模式
String userHome = System.getProperty("user.home").replace("\\", "/");
String resourceLocation = "file:/" + userHome + "/";
//设置映射
registry.addResourceHandler("/static/**").addResourceLocations(resourceLocation);
super.addResourceHandlers(registry);
}
}
设置映射后,所有static的网络路径都会指向本地文件存储路径。
直接在浏览器访问http://127.0.0.1:55/static/face/null.jpg
可通过postman再次上传新头像刷新浏览器验证。
关于上传文件大小限制,可在application.yml中Spring:下添加如下内容
servlet:
multipart:
max-file-size: 500KB //文件上传最大值
max-request-size: 500KB //请求文件最大值
三、构建分布式文件管理系统
分布式的文件管理将文件从客户端app中剥离开来,单独作为一个系统将文件以对象的形式存储起来,并且文件可以是视频、图像、语音等等。
通过MinIO来实现分布式文件传输管理,安装部署本地后,可通过http://127.0.0.1:9000访问网页端可视化界面,可先对MinIO的操作内容先做熟悉。
pom.xml中引入MinIO相关依赖
<!-- minio相关依赖 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
修改yml配置文件:
minio:
endpoint: http://127.0.0.1:9000 #服务器地址
fileHost: http://127.0.0.1:9000 #文件服务地址
bucketName: wechat #桶名称
accessKey: #账号
secretKey: #密码
与以下MinIOConfig类形成静态配置:
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class MinIOConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.fileHost}")
private String fileHost;
@Value("${minio.bucketName}")
private String bucketName;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Bean
public MinIOUtils creatMinioClient() {
return new MinIOUtils(endpoint, fileHost, bucketName, accessKey, secretKey);
}
}
再将MinIO相关方法封装在MinIOUtils类中,就可以在控制器类迭代上传头像的方法了。
@Resource
private MinIOConfig minIOConfig;
@Resource
private UserInfoMicroServiceFeign userInfoMicroServiceFeign;
@PostMapping("uploadFace")
public GraceJSONResult uploadFace(@RequestParam("file")MultipartFile file, String userId, HttpServletRequest request) throws Exception {
//判断userId不为空
if(StringUtils.isBlank(userId)){
GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);
}
String filename = file.getOriginalFilename();
if(StringUtils.isBlank(filename)){
GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);
}
filename = "face/" + userId + "/" + filename;
MinIOUtils.uploadFile(minIOConfig.getBucketName(),filename,file.getInputStream());
String faceUrl = minIOConfig.getFileHost()+"/"+minIOConfig.getBucketName()+"/"+filename;
//微服务远程调用更新头像到数据库OpenFeign
GraceJSONResult jsonResult = userInfoMicroServiceFeign.updateFace(userId,faceUrl);
Object data = jsonResult.getData();
String json = JsonUtils.objectToJson(data);
UsersVO usersVO = JsonUtils.jsonToPojo(json, UsersVO.class);
return GraceJSONResult.ok(usersVO);
}
这里我还引入了OpenFeign来通过微服务之间的远程调用来实现更新头像后前端的实时更新,而不用退出重新登陆后才得以显示更新后的头像。
另外,MinIO的路径分隔符与Windows的不同(Windows 下使用 \
,而 MinIO 要求使用 /
)。所以这里没有使用File.separator。通过postman发送更新头像请求,查看MinIO存储内容:
图片成功在wechat的bucket中,按照face / userId的路径成功存储。