编程不良人, springCloud项目新知识点

1. 格式化RedisTemplate保存的对象为json格式

// An highlighted block
@Configuration
public class RedisTemplateConfig {
    @Autowired
    public RedisTemplateConfig(RedisTemplate redisTemplate) {
        //1.创建jackson序列化方式
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
                Object.class);
        //2..创建object mapper
        ObjectMapper objectMapper = new ObjectMapper();
        //3.允许访问对象中所有属性
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //4.转换json过程中保存类的信息
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //5.设置value的序列化规则和 key的序列化规则   //key String hashkey:String hashvalue: json序列化
        StringRedisSerializer stringKeySerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringKeySerializer);
        //6.jackson2JsonRedisSerializer就是JSON序列号规则,
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //6.5 设置hash类型key value 序列化方式
        redisTemplate.setHashKeySerializer(stringKeySerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        //7工厂创建redisTemplate对象之后在进行配置
        redisTemplate.afterPropertiesSet();
    }
}

2. 创建objecMapper配置类

/**
 * ObjectMapper配置类
 */
@Configuration
public class BeanConfig {

    @Bean
    public ObjectMapper getObjectMapper() {
        return new ObjectMapper();
    }

}

3. 属性赋值, 业务key

        /**
         * 已登录用户信息接口,
         * 使用业务key保存数据, 使用属性复制
         */
        @GetMapping("/admin-user")
        public AdminDTO admin(String token) {
            Admin admin = (Admin) redisTemplate.opsForValue()
                    .get(RedisPrefix.TOKEN_KEY + token);
            AdminDTO adminDTO = new AdminDTO();
            // 1.属性复制
            BeanUtils.copyProperties(admin, adminDTO);
            return adminDTO;
        }


	// 业务key
	public interface RedisPrefix {
	    /**代表用户认证token key*/
	    String TOKEN_KEY = "TOKEN:";
	
	    /**
	     * 点赞数量业务key
	     */
	    String VIDEO_LIKE_COUNT_PREFIX = "VIDEO_LIKE_COUNT:";
	
	    // 是否点赞业务key
	    String USER_LIKED = "USER_LIKED:";
	
	    /**
	     * 播放次数业务key
	     */
	    String VIDEO_PLAYED_COUNT_PREFIX = "VIDEO_PLAYED_COUNT:";
	
	    /**
	     * 用户不喜欢业务key
	     */
	    String USER_DISLIKE_PREFIX = "USER_DISLIKE:";
	
	    String SESSION = "session:";
}

4. 自定义注解, 获取redis中的token

// 自定义接口, 运行时有效, 加载方法上,
// 根据request对象,获取redis中的token注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredToken {
}




/**
 * 自定义拦截器, 拦截所有带有自定义注解的方法请求
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Autowired  // 注入自定义拦截器
    private TokenInterceptors tokenInterceptors;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 拦截所有带有自定义注解的方法请求
        registry.addInterceptor(tokenInterceptors)
                .addPathPatterns("/**");
    }
}




/**
 * 自定义拦截器,保存用户信息和token
 */
@Component
@Slf4j
public class TokenInterceptors implements HandlerInterceptor {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 判断方法上是否有自定义注解  RequiredToken
        boolean requiredToken = ((HandlerMethod) handler).getMethod().isAnnotationPresent(RequiredToken.class);

        if (requiredToken) {
            // 1 获取token信息
            String token = request.getParameter("token");
            log.info("拦截器获取的token信息为{}", token);
            // 2设置保存的业务key
            String tokenKey = "session_" + token;
            // 3 根据token获取用户信息
            String userStr = redisTemplate.opsForValue().get(tokenKey);
            User user = objectMapper.readValue(userStr, User.class);

            // 获取不到用户信息
            if (user == null) throw new RuntimeException("无效的token");

            // 4 保存到请求上下文中
            request.setAttribute("token", token);
            request.setAttribute("user", user);
        }

        return true;
    }
}

在这里插入图片描述

5. 自定义过滤器配置, 设置需要token才可以访问的接口

/**
 * 自定义过滤器配置, 设置需要token才可以访问的接口
 */
@Component
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {


    @Autowired
    private StringRedisTemplate redisTemplate;

    public TokenGatewayFilterFactory() {
        super(Config.class);
    }

    /**
     * @param config 就是本类中Config的对象
     * @return
     */
    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            /**
             * 在网关中配置 -Token就可以使用了
             * @param exchange 交换机,包装的request,response
             * @param chain     放行功能
             * @return
             */
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

                // 当requiredToken为True的时候才认证
                if (config.requiredToken) {

                    if (exchange.getRequest().getQueryParams().get(0) == null) throw new RuntimeException("非法异常!");

                    // 获取token信息
                    String token = exchange.getRequest().getQueryParams().get("token").get(0);

                    // 判断token是否存在,不存在直接抛出异常
                    if (!redisTemplate.hasKey(RedisPreFix.TOKEN_KEY + token)) throw new RuntimeException("不合法的令牌!");
                }

                return chain.filter(exchange);
            }
        };
    }

    /**
     * 用来配置将使用filter时指定值赋值给Config中哪个属性, 多个参数时按顺序返回
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("requiredToken", "name");
    }

    /**
     * 内部类,指定配置中的token需要的参数
     */
    public static class Config {
        // 默认为false
        private boolean requiredToken;

        public boolean isRequiredToken() {
            return requiredToken;
        }

        public void setRequiredToken(boolean requiredToken) {
            this.requiredToken = requiredToken;
        }
    }
}
// 全局路由配置
server:
  port: 9999
spring:
  application:
    name: API-GATEWAY
  cloud:
    nacos:
      server-addr: 192.168.216.128:8848
    gateway:
      routes: #断言 用来配置路由规则 id uri path

        #admins router
        - id: admins_router
          uri: lb://API-ADMINS #负载均衡写法
          predicates:
            #采用这种路径断言办法,请求进入网关断言,但是不能已这种方式发送给controller,因为没有此路径的请求
            - Path=/admin/demos,/admin/tokens,/admin/admin-user,/admin/tokens/**
          filters:
            - StripPrefix=1 #去掉请求前缀的filter =写int类型 1代表去掉1级前缀 2代表去掉两级

        #categary router
        - id: categary_router
          uri: lb://API-CATEGARYS
          predicates:
            - Path=/categary/**
          filters:
            - StripPrefix=1
            - Token=true
        #users router
        - id: users_router
          uri: lb://API-USERS
          predicates:
            - Path=/admin/users/**
          filters:
            - StripPrefix=1
            - Token=true
        #videos router
        - id: videos_router
          uri: lb://API-VIDEOS
          predicates:
            - Path=/admin/videos/**
          filters:
            - StripPrefix=1
            - Token=true

      #跨域配置
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods: "*"
            allowedHeaders: "*"

  #配置redis用来做token验证的
  redis:
    port: 6379
    host: 192.168.216.128
    database: 0
    password: 123456

6.全局异常处理

/**
 * 全局异常配置, 实现ErrorWebExceptionHandler接口
 */
@Configuration
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {

    @Override //参数1: request response   ex:出现异常时异常对象
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        Map<String, String> result = new HashMap<>();

        //1.获取响应对象
        ServerHttpResponse response = exchange.getResponse();

        //2.response是否结束  多个异常处理时候
        if (response.isCommitted()) {
            return Mono.error(ex);
        }

        //2.设置响应头类型
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

        //3.设置响应状态吗
        if (ex instanceof IllegalTokenException) {
            response.setStatusCode(HttpStatus.FORBIDDEN);
        } else {
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        //4.设置响应内容
        return response
                .writeWith(Mono.fromSupplier(() -> {
                    DataBufferFactory bufferFactory = response.bufferFactory();
                    result.put("msg", ex.getMessage());
                    ObjectMapper objectMapper = new ObjectMapper();
                    try {
                        return bufferFactory.wrap(objectMapper.writeValueAsBytes(result));
                    } catch (JsonProcessingException e) {
                        e.printStackTrace();
                        return null;
                    }
                }));
    }
}

7. 用户上传接口, 使用MQ异步上传

    /**
     * 用户文件上传
     *
     * @param file        上传文件
     * @param video       视频对象需要的参数
     * @param category_id 分类id
     * @param request     token验证获取用户信息
     * @return
     */
    @PostMapping("/user/videos")
    @RequiredToken
    public Video publishVideos(MultipartFile file, Video video,
                               Integer category_id, HttpServletRequest request) throws IOException {
        // 1.获取文件原始名称 getOriginalFilename()
        String originalFilename = file.getOriginalFilename();

        // 2 获取文件名称后缀, .mp4, 没有点 添加上
        String ext = FilenameUtils.getExtension(originalFilename);

        // 3 生成uuid
        String uuidFileName = UUID.randomUUID().toString().replace("-", "");

        // 4 生成uuid文件名名称  如 20220714.MP4
        String newFileName = uuidFileName + "" + ext;

        // 5 上传阿里云oss, 获取路径
        String url = OSSUtils.upload(file.getInputStream(), "videos", newFileName);
        log.info("上传成功返回的路径为:{}", url);

        // 使用阿里云截取某一帧作为封面
        String cover = url
                + "?x-oss-process=video/snapshot,t_30000,f_jpg,w_0,h_0,m_fast,ar_auto";
        log.info("阿里云oss根据ur1截取视频封面: {}", cover);

        // 获取用户信息
        User user = (User) request.getAttribute("user");
        video.setUid(user.getId());

        // 6 设置视频信息
        video.setLink(url);
        video.setCover(cover);
        video.setCategoryId(category_id);

        // 7 调用video微服务保存数据库信息
        Video videoResult = videosClient.publish(video);
        log.info("保存成功之后返回的视频信息:{}", videoResult);
        return videoResult;
    }



VideoClient层代码, 转发到消费者代码

    /** controller
     * 添加一条视频
     * @param video
     * @return
     */
    @PostMapping("publish")
    public Video publish(@RequestBody Video video) {
        log.info("接收到的video对象为:{}", video);
        return videoService.insert(video);
    }



Service层, mq对象
@Override
    public Video insert(Video video) {
        // 更新数据
        video.setCreatedAt(new Date());
        video.setUpdatedAt(new Date());
        video.setLikes(0L);
        // video.setCategory(categoryClient.findById(video.getCategoryId()));
        // 保存到数据库
        videoMapper.insert(video);

        // 将video对象转为videoVo对象,
        VideoVo videoVO = this.getVideoVO(video);

        // 使用mq异步处理,提升系统响应, 将视频信息写入es索引库
        rabbitTemplate.convertAndSend("videos", "", 
                JSONUtils.writeJSON(videoVO));
        return video;
    }




服务消费者代码, 在search微服务里面, 同时保存到el中, 方便搜索
/**
 * 服务消费者
 */
@Component
@Slf4j
public class VideoConsumer {

    @Autowired
    @Qualifier("elasticsearchClient")
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private ObjectMapper objectMapper;



    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,   // 绑定队列
            exchange = @Exchange(name = "videos", type = "fanout") // 绑定交换机, 默认交换机
    ))
    public void receive(String message) throws IOException {
        log.info("消费者MQ接收到的video信息为:{}", message);
        // 1 将mq中的 json转为对象
        VideoVo videoVo = objectMapper.readValue(message, VideoVo.class);

        // 2 创建ES中索引请求对象,  参数1 操作索引, 参数2: 索引类型, 参数3: 文当id
        IndexRequest indexRequest = new IndexRequest("video", "video", videoVo.getId().toString());

        // 3 设置ES文档的内容
        indexRequest.source(message, XContentType.JSON);

        // 4 执行索引操作
        IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        log.info(" video录入ES中的信息为:{}", indexResponse);
    }


}

7. 分布式搜索Elasticsearch

/**
 * ES配置类, 不用配置文件中配置
 */
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {

    @Bean
    @Override
    public RestHighLevelClient elasticsearchClient() {
        ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo("localhost:9200") // 端口号
                .build();
        return RestClients.create(clientConfiguration).rest();
    }
}




@Service
public class VideoServiceSearchImpl implements VideoSearchService {

    @Qualifier("elasticsearchClient")
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 根据ES分页搜索
     *
     * @param q
     * @param page
     * @param rows
     * @return
     */
    @Override
    public Map<String, Object> videos(String q, Integer page, Integer rows) {
        HashMap<String, Object> map = new HashMap<>();

        // 1. 计算分页
        int start = (page - 1) * rows;
        // 2.创建索引对象
        SearchRequest request = new SearchRequest();

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder
                .from(start).size(rows)     // 分页查询
                .query(QueryBuilders.termQuery("title", q));  // 根据name字段分词搜索
        // 3 设置搜索索引, 搜索类型
        request.indices("video").types("video").source(searchSourceBuilder);

        SearchResponse searchResponse = null;

        //执行搜索
        try {
            searchResponse = restHighLevelClient.search(request, RequestOptions.DEFAULT);

            // 获取条数
            Long totalHits = searchResponse.getHits().getTotalHits().value;

            // 创建VO集合
            ArrayList<VideoVo> videoVos = new ArrayList<>();

            if (totalHits > 0) {
                map.put("total_count", totalHits);

                // 获取数组对象
                SearchHit[] hits = searchResponse.getHits().getHits();

                // 遍历结果,保存到集合中
                for (SearchHit hit : hits) {
                    String videoVOJSON = hit.getSourceAsString();
                    VideoVo videoVo = objectMapper.readValue(videoVOJSON, VideoVo.class);
                    videoVo.setId(Integer.parseInt(hit.getId()));
                    videoVos.add(videoVo);
                }

            }

            map.put("items", videoVos);
            return map;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return map;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值