本篇主要讲解的是后端多个模块中的common部分,也就是大众通用部分。
以后每篇讲解会说明一下开发中遇到的坑以及使用的技术,同时会贴上部分的代码,以便了解。
common部分主要承载了记录用户的登录信息,文件上传,论坛功能。其重要技术有阿里云oss上传代码、feign组件的使用,以及springcloud各服务之间的文件上传功能。其中各服务之间文件传递是难点以及坑最多的部分,这部分放在最后详细说明一下。
如果在阅读中发现任何问题,欢迎联系我,帮助我改正!谢谢各位阅读的大佬!
记录用户的登录信息
这里使用了一个aop切面技术,在后台打印日志记录用户登录,这个只是学习aop的时候做的一部分,实际记录是存储在控制台中。aop具体的代码为:
1.配置注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author wangyb
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLog {
String value() default "";
}
2.实例化注解
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
* @author wangyb
*/
@Aspect
@Component
@Slf4j
public class UserLogCut {
@Pointcut("@annotation(com.bysj.wyb.common.annotation.UserLog)")
public void UserLog(){
}
@Before("UserLog()")
public void BeforeLog(JoinPoint point){
log.info("----------开始记录用户登录信息---------");
log.info("用户名信息:" + Arrays.toString(point.getArgs()));
log.info("登录时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
@Around("UserLog()")
public void methodAround(ProceedingJoinPoint point){
log.info("----------用户登录成功!---------");
try {
Object o=point.proceed();
}catch (Throwable t){
log.error(t.getMessage(),t);
}
}
@After("UserLog()")
public void returnLog(){
log.info("-----------记录用户登录信息结束------------");
}
}
3.使用注解
使用aop切面技术给方法添加注解时要注意,业务逻辑不能放在注解中,一旦注解失效业务逻辑就会失效,造成bug。aop切面应该是进行一些辅助性操作,例如日志记录等
/**
* 登录信息登记
* @param uid
* @param request
*/
@UserLog
@RequestMapping(value = "/logCounter")
public void logCounter(@RequestParam String uid, HttpServletRequest request){
sysService.logCount(uid);
}
论坛功能
论坛部分没有突出的地方,就是普通的CRUD(虽然其他的部分也是CRUD哈哈哈),就不在这里做详细的展开了。
文件上传
第一部分是阿里云oss文件上传的java SDK,这个在阿里云的官方文档中就能找到教程,这里具体讲一下我怎么做的。
首先是引入依赖:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.4.2</version>
</dependency>
引入依赖以后便可以使用java代码对oss服务器进行操作,其他的操作这里就不展示了,只展示项目中使用到的文件上传部分:
public Result uplodToOSS(MultipartFile multipartFile,String uploadCatalogAndName){
HandleResult hr=new HandleResult();
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "http://oss-cn-chengdu.aliyuncs.com";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId = "123";
String accessKeySecret = "456";
String bucketName = "bysj";
URL url = null;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 获取文件名
String fileName = multipartFile.getOriginalFilename();
// 获取文件后缀
String prefix = fileName.substring(fileName.lastIndexOf("."));
// 若需要防止生成的临时文件重复,可以在文件名后添加随机码
try {
File file = File.createTempFile(fileName, prefix);
multipartFile.transferTo(file);
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, uploadCatalogAndName, file);
ossClient.putObject(putObjectRequest);
Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
url=ossClient.generatePresignedUrl(bucketName,uploadCatalogAndName,expiration);
System.out.println(url.toString());
} catch (Exception e) {
e.printStackTrace();
}finally {
if(url!=null){
ossClient.shutdown();
return hr.outResultWithData("0","上传成功",url.toString());
}else{
ossClient.shutdown();
return hr.outResultWithoutData("1","上传失败");
}
}
}
我们分段看一下上面的代码
第一部分是配置基础属性,也就是你的oss地址,你的bucket各项属性,例如授权id,授权秘钥,bucketname等等,同时对文件名进行了处理,然后开启上传链接
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "http://oss-cn-chengdu.aliyuncs.com";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId = "123";
String accessKeySecret = "456";
String bucketName = "bysj";
URL url = null;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 获取文件名
String fileName = multipartFile.getOriginalFilename();
// 获取文件后缀
String prefix = fileName.substring(fileName.lastIndexOf("."));
随后开始上传文件,这里没有什么技术含量,按照官方文档撰写代码即可
上传完文件后,就要获取到文件的读取地址,以便对文件的调用或者查看
try {
File file = File.createTempFile(fileName, prefix);
multipartFile.transferTo(file);
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, uploadCatalogAndName, file);
ossClient.putObject(putObjectRequest);
Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
//获取文件可读取的url地址
url=ossClient.generatePresignedUrl(bucketName,uploadCatalogAndName,expiration);
System.out.println(url.toString());
} catch (Exception e) {
e.printStackTrace();
}finally {
if(url!=null){
ossClient.shutdown();
return hr.outResultWithData("0","上传成功",url.toString());
}else{
ossClient.shutdown();
return hr.outResultWithoutData("1","上传失败");
}
}
这里值得要注意的是url的过期时间,按照官方文档以及查阅的资料,过期时间是按照毫秒换算,但是不论怎么设置最后获取的url只有三分钟时间,三分钟后即为过期,所以这个小bug对于图像文件来说不是很友好,下载文件也不友好,只能做个演示用,如果有需要的同学可以研究一下这个bug
同时在获取时间时,官方文档给出的是
new Date().getTime()
但是不知道是IDEA还是阿里巴巴代码规范检测会给你的代码标红,让你改成
System.currentTimeMillis()
这里建议使用第二种,别问,问就是不知道。
因为是公用的文件上传,所以各服务之间使用的feign组件进行服务调用,common对外提供接口。
这里的接口使用注意点有几个
1.对外提供接口时,需要指定为POST方法,或者直接使用@PostMapping注解
2.接口参数中的文件,必须添加注解@RequestPart
/**
* 上传文件接口
* @param file
* @param uploadCatalogAndName
* @return
*/
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public Result uploadToOss(@RequestPart("file") MultipartFile file, @RequestParam String uploadCatalogAndName){
return sysService.uplodToOSS(file,uploadCatalogAndName);
}
3.添加依赖,以解决FeignClient使用服务名无法调用其他服务只能指定url的问题,尚不清楚这个依赖会在服务者或者消费者那个里面其效果,保险起见二者都谈加了这个依赖
<dependency>
<groupId>com.netflix.archaius</groupId>
<artifactId>archaius-core</artifactId>
<version>0.7.6</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
4.消费者调用接口写成mapper的形式,但不添加mapper注解,添加@FeignClient注解:
/**
* @author wangyb
*/
@FeignClient(name = "pd-common",configuration = MultipartSupportConfig.class)
public interface CommonFeign {
@RequestMapping(value = "/system/logCounter")
void logCounter(@RequestParam String uid);
@RequestMapping(value = "/system/upload",method = RequestMethod.POST,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result upload(@RequestPart("file") MultipartFile file, @RequestParam String uploadCatalogAndName);
}
5.添加服务之间可以传递文件的配置文件,配置注入方式查看上面的代码:
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
@Configuration
public class MultipartSupportConfig {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Bean
@Primary
@Scope("prototype")
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
这里调用使用了发现注册中心的服务名进行feign调用服务者,还有另一种方式为绝对url的方式,但是这样违背了feign的初衷,和访问url没有什么区别,可扩展性也不高,所以遇到无法调用服务名的情况要想办法解决
如果在使用feign调用上出现什么问题,要多看几篇大佬的文章,解决方式挺多的,总有一款适合你
common部分中大家可能看到有个类是HandleResult,这个是自己写的结果返回处理,分为带数据返回和不带数据返回,类如下:
/**
* @author wangyb
*/
public class HandleResult {
public Result outResultWithData(String status, String message, Object data){
Result re=new Result();
if("1".equals(status)){
re.setStatus("1");
re.setMessage(message);
re.setData(data);
}
else {
re.setStatus("0");
re.setMessage(message);
re.setData(data);
}
return re;
}
public Result outResultWithoutData(String status, String message){
Result re=new Result();
if("1".equals(status)){
re.setStatus("1");
re.setMessage(message);
}
else {
re.setStatus("0");
re.setMessage(message);
}
return re;
}
}
配套使用的是result对象:
/**
* @author admin
*/
public class Result {
private String status;
private String message;
private Object data;
@Override
public String toString() {
return "Result{" +
"status='" + status + '\'' +
", message='" + message + '\'' +
", data=" + data +
'}';
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
同时还有一些其他的算法,会在后面做介绍。