• 如何在数据库只保存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 {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    需要定义一个 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);
            }
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    上述类就是方法一的核心思想, 代码标有备注信息. 完整的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;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    定义枚举类

    @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    核心类, 就是上述注解中引用的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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    上述类就是方法一的核心思想, 代码标有备注信息. 完整的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吧.
  • 相关阅读:
    Java编写xml文件时,文件中特殊字符如何解决?
    详解 C++ 实现K-means算法
    创建多个 conda 环境和 jupyter 切换使用环境
    python编程:SQLite 管理图片数据库
    视频技术笔记-色差分量
    9、MyBatis缓存
    多轨音频编辑软件Multitrack Editor mac中文版主要功能
    【GAMES101】作业7 Path Tracing 关于Renderer::Render()中相机射线方向dir的疑惑&解答
    form表单与模板引擎
    基于SpringBoot+vue的文件管理系统
  • 原文地址:https://blog.csdn.net/weixin_45089791/article/details/128026184