有这样一个需求, 图片上传到oss 上, 返回文件名和公网访问地址, 但是要求数据库中只存储文件名称.
有两个目的:
- 数据库只存储文件名称, 方便后期oss 上数据迁移到其他对象存储上, 迁移保证文件名不变, 后期就只需要更改获取公网地址的地方.
- 在查询数据时再获取文件的访问地址, 并设置链接的有效期, 可以提高文件的安全性, 也可以防止oss 流量被盗刷.
实现的思路有两种方式
都是基于java语言, 和springboot框架实现.
都需要使用自定义注解.
利弊:
方法一, 使用到了反射, 去层层扫描对象中的所有基本类型属性, 并且需要记录当前字段所属的对象, 否则在重写的时候,就不知道该属性的对象了. 方法二使用了jack字段序列化时,进行修改, 本身就避免了我们自己写反射扫描的问题, 而且方法二的效率比方法一快.
下面介绍两种方法的实现方式.
自定义注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OssConverter {
}
需要定义一个 ResponseBodyAdvice的实现类
@Slf4j
@RestControllerAdvice(basePackages = "com.wdhcr") // 指定controller的路径, 可以范围大一点
public class ResponseBodyConfig implements ResponseBodyAdvice {@Autowiredprivate OssComponent ossComponent;@Autowiredprivate ThreadPoolTaskExecutor threadPoolTaskExecutor;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {long start = System.currentTimeMillis();if (body instanceof R) {R result = (R) body;Object data = result.getData();Map fields = new HashMap<>();// 使用反射工具类 获取到指定对象中所有的属性,包含嵌套对象的属性ReflectUtil.getAllFields(data,fields);ArrayList> futures = new ArrayList<>();for (Field field : fields.keySet()) {// 遍历字段是否包含指定注解, OssConverter为自定义注解.OssConverter annotation = field.getAnnotation(OssConverter.class);if (annotation != null) {CompletableFuture future = CompletableFuture.runAsync(() -> setImageUrlWithOssUrl(field, fields),threadPoolTaskExecutor);futures.add(future);}}CompletableFuture[] completableFutures = new CompletableFuture[futures.size()];try {// 想法是太多的网络请求, 可能影响返回数据效率, 所以使用了多线程方式.CompletableFuture.allOf(futures.toArray(completableFutures)).get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}long end = System.currentTimeMillis();System.err.println("总计用时:" + ((end - start)));return body;}private void setImageUrlWithOssUrl(Field field, Map fields) {Object value = null;try {field.setAccessible(true);value = field.get(fields.get(field));if (value != null) {// 这个是oss 组件, 用来根据文件名获取访问地址的, 可见源码.String url = ossComponent.getUrl(value.toString());field.set(fields.get(field), url);}} catch (Exception e) {log.error("更新返回体中oss地址的字段异常,值为:{}", value);}}}
上述类就是方法一的核心思想, 代码标有备注信息. 完整的demo,可查看文章末尾.
测试类(com.wdhcr.osspolicy.bean.User)和测试方法(com.wdhcr.osspolicy.controller.GetOssUrlBodyDemoController)可查看demo.
测试接口: http://localhost:8080/user
自定义注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = UrlConverterJsonSerializer.class)
public @interface OssConverterUrl {// 地址转换的类型, 默认为ossUrlConverterType type() default UrlConverterType.OSS;}
定义枚举类
@Getter
public enum UrlConverterType {OSS(url -> {// 枚举的参数是一个lambda表达式// url是入参OssComponent ossComponent = SpringUtils.getBean(OssComponent.class);return ossComponent.getUrl(url);});// 使用了java8的新特性,函数式接口private final Function deserialize;UrlConverterType(Function deserialize) {this.deserialize = deserialize;}
}
核心类, 就是上述注解中引用的UrlConverterJsonSerializer.class
public class UrlConverterJsonSerializer extends JsonSerializer implements ContextualSerializer {private UrlConverterType urlConverterType;@Overridepublic void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {if (Objects.nonNull(urlConverterType)) {// 核心代码就是 json生成器写string时, 将原始属性值传入当前属性上的注解中的枚举, 获取到函数式接口并执行. (.apply(s)方法就会调用到枚举参数的lambda表达式了)jsonGenerator.writeString(urlConverterType.getDeserialize().apply(s));}else {jsonGenerator.writeString(s);}}@Overridepublic JsonSerializer> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {OssConverterUrl converterUrl = beanProperty.getAnnotation(OssConverterUrl.class);if (Objects.nonNull(converterUrl) && Objects.equals(String.class, beanProperty.getType().getRawClass())) {// 判断这个bean属性上OssConverterUrl自定义注解不为空, 并且该属性是string类型.this.urlConverterType = converterUrl.type();return this;}return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}
}
上述类就是方法一的核心思想, 代码标有备注信息. 完整的demo,可查看文章末尾.
测试类(com.wdhcr.osspolicy.bean.UserJson)和测试方法(com.wdhcr.osspolicy.controller.GetOssUrlBodyDemoController)可查看demo.
测试接口: http://localhost:8080/getUserJson
https://gitee.com/jack_whh/oss-policy-springboot
大家觉得还不错的话, 给个star吧.
上一篇: 租房协议书合同
下一篇:形容花草树木的两字词语