这个作业属于哪个课程 | 2302软件工程 |
---|---|
这个作业要求在哪里 | 软件工程实践总结&个人技术博客 |
这个作业的目标 | 软件工程实践总结 |
其他参考文献 | 《构建之法》 |
一、技术概述
目前微信小程序登录通过前端的wx.login方法进行登录,初次登录需要上传头像与昵称。后端需要将前端上传的图片重命名并保存到服务器的文件夹中,并暴露图片访问方式,供前端访问图片。
二、技术详述
- 1.前端上传头像图片,即将前端生成的临时图片URL地址传递给后端,后端根据文件上传接口接受文件,并编写文件保存方法保存图片。(代码中filePath多余,直接返回fileName,此时前端只收到了fileName,还不是一个可访问的图片;后续在前端调用login方法时,给后端传递数据,将fileName带给后端,后端处理成一个完整的URL返回给前端。)
//uploadPath存的是形如"D:\\pictures"这样的路径,当然在部署时需要改成服务器的文件夹路径
@Value("${tomato.upload-path}")
public String uploadPath;
//imgBaseUrl存的是"http://10.133.48.239:8081/img/",公网ip。
@Value("${tomato.img-baseurl}")
public String imgBaseUrl;
@Autowired
UserService userService;
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(@RequestParam("files") MultipartFile files) {
String fileName = null;
String filePath;
if (files == null) {
throw new BaseException("未上传任何文件");
}
System.out.println("文件保存地址:" + uploadPath);
//for (MultipartFile file : files) {
//调用函数,返回文件存储路径
fileName = renameFile(files, uploadPath);
uploadToLocalServer(uploadPath+fileName, files, uploadPath);
filePath=fileName;
//}
System.out.println("返回结果:"+filePath);
return Result.success(filePath);
}
- 2.renameFile方法的作用是将图片重命名,生成该图片唯一的UUID。
public static String renameFile(MultipartFile file, String uploadPath) {
//获取 原始文件名
String originalFileName = file.getOriginalFilename();
//System.out.println("原始文件名:" + originalFileName);
//判断文件名是否有值 没有则抛出异常中断程序执行
if (originalFileName == null) {
throw new BaseException("未上传任何文件");
}
//使用UUID通用唯一识别码 + 后缀名的形式
//设置唯一文件路径 防止文件名重复 出现覆盖的情况
String fileName = UUID.randomUUID() + originalFileName.substring(originalFileName.lastIndexOf("."));
//System.out.println("唯一文件名:" + fileName);
// 指定文件保存的路径
String filePath = uploadPath + File.separator + fileName;
System.out.println("文件成功重命名并获得路径:" + filePath);
return fileName;
}
- 3.uploadToLocalServer方法是将图片保存到本地文件夹,后续部署阶段只需要修改配置文件使其保存在服务器文件夹。
public static void uploadToLocalServer(String filePath, MultipartFile file, String uploadPath) {
//根据上传路径创建文件夹File对象
File saveAddress = new File(uploadPath);
if (!saveAddress.exists()) {
saveAddress.mkdirs();// 如果文件夹不存在 创建保存文件对应的文件夹
}
// 将上传的文件保存到指定路径
try {
file.transferTo(new File(filePath));
System.out.println("成功");
} catch (IOException e) {
throw new BaseException("文件保存失败!");
}
}
- 4.在WebMvcConfiguration配置类里添加资源映射;配置该资源映射使前端可以通过"http://10.133.48.239:8081/img/abcdefg.png"这样的路径访问服务器文件夹里的图片。
/**
* 设置静态资源映射
*
* @param registry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
// 使用file:前缀指定文件系统路径
registry.addResourceHandler("/img/**")
.addResourceLocations("file:" + filePath );
}
- 5.小程序登录接口,后端点击登录访问该接口后,将数据存储到数据库,并封装返回。
@Autowired
private JwtProperties jwtProperties;
@Autowired
UserService userService;
@PostMapping("/login")
@ApiOperation("微信登录")
public Result login(@RequestBody UserLoginDTO userLoginDTO) {
User user=userService.wechatLogin(userLoginDTO);
//生成JWT令牌
Map<String,Object> claims=new HashMap<>();
claims.put("userId",user.getId());
String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);
//封装成UserLoginVO对象返回
//TODO 测试
UserLoginVO userLoginVO = BeanUtil.copyProperties(user, UserLoginVO.class);
userLoginVO.setToken(token);
log.info(userLoginVO.toString());
return Result.success(userLoginVO);
}
- 6.userService里的wechatLogin方法,主要思路是拿着appid和secret和前端给的code去访问微信接口,获取用户openId等信息。(详情查看微信小程序官方文档)
//微信服务接口地址
public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
@Autowired
private WeChatProperties weChatProperties;
@Value("${tomato.upload-path}")
public String uploadPath;
@Override
public User wechatLogin(UserLoginDTO userLoginDTO) {
//请求微信官方,获取包含openId等信息的字符串
String s = getOpenId(userLoginDTO);
//转成JSON对象,提取字段
JSONObject jsonObject = JSON.parseObject(s);
String openId = jsonObject.getString("openid");
//如果openId不存在,抛出异常
if (openId == null) {
throw new BaseException("微信登录openId不存在!");
}
User user = lambdaQuery().eq(User::getOpenId, openId).one();
getOrSetUser(user,userLoginDTO,openId);
log.info("code="+userLoginDTO.getCode());
//返回成功登录信息
User theUser = lambdaQuery().eq(User::getOpenId, openId).one();
return theUser;
}
- 7.getOpenId方法,获得微信官方接口返回的JSON字符串,解析得到openId。
private String getOpenId(UserLoginDTO userLoginDTO) {
Map<String, String> map = new HashMap<>();
map.put("appid", weChatProperties.getAppid());
map.put("secret", weChatProperties.getSecret());
map.put("js_code", userLoginDTO.getCode());
map.put("grant_type", "authorization_code");
String s = HttpClientUtil.doGet(WX_LOGIN, map);//返回封装的字符串
return s;
}
- 8.getOrSetUser方法是对数据库进行操作,如果是新用户,就写入数据库,如果是老用户重新登录并上传了新头像和昵称,就需要更新记录。(未解决旧头像的删除问题)
- 数据库里存储的头像只有"uuid.png",不包括URL前缀。
//返回用户信息或注册新用户
public void getOrSetUser(User user,UserLoginDTO userLoginDTO,String openId) {
//如果是新用户,自动注册
if (user == null) {
user = new User();
user.setOpenId(openId);
//裁剪图片url,获取最后的UUID部分。
String[] parts = userLoginDTO.getAvatar().split("/");
String lastPart = parts[parts.length-1];
user.setAvatar(JSON.toJSONString(lastPart));
user.setUsername(userLoginDTO.getUserName());
//这里的是默认初始值,按照需求修改
user.setGender(1L);
user.setMajor("无");
user.setPassword("123456");
user.setStatus(0L);
user.setPhone("无");
save(user);
}
//老用户更新头像,忘记写更新昵称了。删除旧图片未实现。
else {
user.setUsername(userLoginDTO.getUserName());
//裁剪图片url,获取最后的UUID部分。
String[] parts = userLoginDTO.getAvatar().split("/");
String lastPart = parts[parts.length-1];
user.setAvatar(JSON.toJSONString(lastPart));
updateById(user);
}
}
- 关于代码中的jwtProperties和HttpClientUtil,都是一些比较通用的东西,需要自主学习理解(理解后cv大法)。
三、技术使用中遇到的问题和解决过程
- 1.由于在数据库里存储的图片是JSON格式,后续取出数据需要对数据进行处理,包括转成字符串、拼接前缀使其成为可访问路径等。
//Pictures前拼接URL,参数为article的pictures--JSON
public List<String> transPictures(String p) {
List<String> pictures= JSON.parseArray(p,String.class);
for (int i = 0; i < pictures.size(); i++) {
pictures.set(i,imgBaseURL+pictures.get(i));
}
return pictures;
}
//avatar拼接URL,参数为user的avatar字符串--JSON
public String transAvatar(String a) {
String avatar=JSON.parseObject(a,String.class);
avatar=imgBaseURL+avatar;
return avatar;
}
- 2.由于upload方法只返回fileName而不是filePath,所以是无法访问的,之所以不在upload时就写入数据库是因为upload是一个通用接口,在文章模块也有图片需要上传,无法确定需要将图片保存在user表还是article表,所以只返回fileName;如果前端需要图片在上传时立刻回显,可以考虑在前端图片加上前缀。
- 而微信登录并不需要图片立刻回显,其会生成一个临时的图片url,不需要使用upload传递的fileName。
- 3.使用JSON格式存储多张图片时,每张图片前后都会有双引号"",需要后端代码中手动裁剪。
四、总结
- 微信小程序登录只需要参照官方文档,从前端接受code等参数,访问微信接口,取得登录数据。
- 而登录过程中涉及的头像上传,需要程序员自己实现,接收图片下载,回传,需要考虑数据库如何存储图片,如果在数据库里存储url前缀,在数据迁移时会带来问题。