如何在数据库只保存oss上的文件名, 当查询数据时根据字段的文件名, 获取oss的公网访问地址,并对字段内容重写

如何在数据库只保存oss上的文件名, 当查询数据时根据字段的文件名, 获取oss的公网访问地址,并对字段内容重写.

有这样一个需求, 图片上传到oss 上, 返回文件名和公网访问地址, 但是要求数据库中只存储文件名称.

有两个目的:

  1. 数据库只存储文件名称, 方便后期oss 上数据迁移到其他对象存储上, 迁移保证文件名不变, 后期就只需要更改获取公网地址的地方.
  2. 在查询数据时再获取文件的访问地址, 并设置链接的有效期, 可以提高文件的安全性, 也可以防止oss 流量被盗刷.

实现的思路有两种方式

  • 都是基于java语言, 和springboot框架实现.

  • 都需要使用自定义注解.

  1. 在返回时, 使用反射的方式, 递归的扫描返回对象的每一个字段属性上是否添加该注解, 符合条件了, 获取到链接替换掉原本的值.
  2. 同样是在返回时, 只是在返回体序列化上进行处理.在jack序列化每一个字段属性的时候,扫描是否存在该注解,后续操作与方法一相同.
  • 利弊:

    方法一, 使用到了反射, 去层层扫描对象中的所有基本类型属性, 并且需要记录当前字段所属的对象, 否则在重写的时候,就不知道该属性的对象了. 方法二使用了jack字段序列化时,进行修改, 本身就避免了我们自己写反射扫描的问题, 而且方法二的效率比方法一快.

下面介绍两种方法的实现方式.

方法一:

自定义注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OssConverter {
}

需要定义一个 ResponseBodyAdvice的实现类

@Slf4j
@RestControllerAdvice(basePackages = "com.wdhcr") // 指定controller的路径, 可以范围大一点
public class ResponseBodyConfig implements ResponseBodyAdvice {

    @Autowired
    private OssComponent ossComponent;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;


    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public 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<Field,Object> fields = new HashMap<>();
            // 使用反射工具类 获取到指定对象中所有的属性,包含嵌套对象的属性
            ReflectUtil.getAllFields(data,fields);
            ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
            for (Field field : fields.keySet()) {
              	// 遍历字段是否包含指定注解, OssConverter为自定义注解.
                OssConverter annotation = field.getAnnotation(OssConverter.class);
                if (annotation != null) {
                    CompletableFuture<Void> 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<Field, Object> 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 {
  	// 地址转换的类型, 默认为oss
    UrlConverterType 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<String, String> deserialize;

    UrlConverterType(Function<String, String> deserialize) {
        this.deserialize = deserialize;
    }
}

核心类, 就是上述注解中引用的UrlConverterJsonSerializer.class

public class UrlConverterJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private UrlConverterType urlConverterType;

    @Override
    public 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);
        }
    }

    @Override
    public 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吧.

你可能感兴趣的:(oss,springboot,文件存储服务,数据库,java,spring,boot)