|
|
@@ -0,0 +1,233 @@
|
|
|
+package com.webflux.launchadmin.mysql.task;
|
|
|
+
|
|
|
+import com.fasterxml.jackson.core.JsonParser;
|
|
|
+import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
+import com.fasterxml.jackson.databind.DeserializationContext;
|
|
|
+import com.fasterxml.jackson.databind.JsonDeserializer;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.fasterxml.jackson.databind.SerializationFeature;
|
|
|
+import com.fasterxml.jackson.databind.module.SimpleModule;
|
|
|
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
|
|
+import com.plumelog.core.util.LogExceptionStackTrace;
|
|
|
+import com.webflux.launchadmin.global.BaseException;
|
|
|
+import com.webflux.launchadmin.mysql.entity.listening.ListeningAudio;
|
|
|
+import com.webflux.launchadmin.mysql.entity.listening.ListeningPlanNew;
|
|
|
+import com.weblux.launchredis.utils.LinteningAudioRedisKey;
|
|
|
+import jakarta.annotation.Resource;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
|
|
|
+import org.jetbrains.annotations.NotNull;
|
|
|
+import org.springframework.data.domain.Sort;
|
|
|
+import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
|
|
+import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
|
|
+import org.springframework.data.redis.core.ScanOptions;
|
|
|
+import org.springframework.data.relational.core.query.Criteria;
|
|
|
+import org.springframework.data.relational.core.query.Query;
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import reactor.core.publisher.Flux;
|
|
|
+import reactor.core.publisher.Mono;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.time.ZonedDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Component
|
|
|
+@Slf4j
|
|
|
+public class SyncListeningAudioToRedis {
|
|
|
+ @Resource
|
|
|
+ private R2dbcEntityTemplate template;
|
|
|
+ @Resource
|
|
|
+ private ReactiveRedisTemplate<String,String> reactiveRedisTemplate;
|
|
|
+
|
|
|
+
|
|
|
+ private static final String COLUMN="new_audio_id,audio_name," +
|
|
|
+ "audio_image_url,audio_url,popup_node,idx_num";
|
|
|
+ private static final String[] COLUMNARRAY=COLUMN.split(",");
|
|
|
+ /**
|
|
|
+ *同步裂变计划
|
|
|
+ */
|
|
|
+ @Scheduled(cron="0 */30 * * * ?")
|
|
|
+ @SchedulerLock(name = "launch:SyncListeningAudioToRedis:syncLinteningAudioPlan", lockAtLeastFor = "PT5S", lockAtMostFor = "PT15M")
|
|
|
+ public void syncLinteningAudioPlan(){
|
|
|
+ Mono<List<ListeningAudio>> listMono = template.select(Query.query(Criteria.empty())
|
|
|
+ .sort(Sort.by(Sort.Order.asc("idx_num")))
|
|
|
+ .columns(COLUMNARRAY)
|
|
|
+ , ListeningAudio.class).collectList();
|
|
|
+ listMono.flatMap(list -> {
|
|
|
+ var collect = list.stream().map(this::getSetValue).collect(Collectors.toList());
|
|
|
+ Mono<List<String>> listMono1 = reactiveRedisTemplate
|
|
|
+ .opsForSet()
|
|
|
+ .members(LinteningAudioRedisKey.PLAN_NEW_INFO_SET).collectList()
|
|
|
+ .switchIfEmpty(Mono.just(List.of()));
|
|
|
+ return Mono.zip(Mono.just(collect),listMono1,Mono.just(list)).flatMap(tupleN->{
|
|
|
+ var mids = tupleN.getT1();
|
|
|
+ var rids = tupleN.getT2();
|
|
|
+ var planNews = tupleN.getT3();
|
|
|
+ // +
|
|
|
+ var addIds = mids.stream().filter(mid -> !rids.contains(mid)).collect(Collectors.toList());
|
|
|
+ if (!addIds.isEmpty()) {
|
|
|
+ List<ListeningAudio> addPlanNew = planNews.stream().filter(f -> addIds.contains(getSetValue(f))).collect(Collectors.toList());
|
|
|
+ if (!addPlanNew.isEmpty()) {
|
|
|
+ Flux.fromIterable(addPlanNew).flatMap(this::setRedisValue).subscribe();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // -
|
|
|
+ var removeIds = rids.stream().filter(rid -> !mids.contains(rid)).collect(Collectors.toList());
|
|
|
+ if (!removeIds.isEmpty()) {
|
|
|
+ Flux.fromIterable(removeIds).flatMap(this::getDelete).subscribe();
|
|
|
+ }
|
|
|
+ return Mono.empty();
|
|
|
+ });
|
|
|
+ }).subscribe();
|
|
|
+ }
|
|
|
+
|
|
|
+ @NotNull
|
|
|
+ public Mono<Void> setRedisValue(ListeningAudio planNew) {
|
|
|
+ String stringBuilder = getSetValue(planNew);
|
|
|
+ String json= getPlanNewToString(planNew);
|
|
|
+ reactiveRedisTemplate.opsForHash().put(LinteningAudioRedisKey.lintening_Audio_INFO_HASH , stringBuilder, json).subscribe();
|
|
|
+ reactiveRedisTemplate.opsForSet().add(LinteningAudioRedisKey.PLAN_NEW_INFO_SET,stringBuilder ).subscribe();
|
|
|
+ return Mono.empty();
|
|
|
+ }
|
|
|
+ public Mono<Void> deleteAll() {
|
|
|
+ deleteByPattern("sync_image_*").subscribe();
|
|
|
+ deleteByPattern("sync_lintening_*").subscribe();
|
|
|
+ deleteByPattern("sync_plan_new_*").subscribe();
|
|
|
+ // reactiveRedisTemplate.delete("sync_lintening_*").subscribe();
|
|
|
+ return Mono.empty();
|
|
|
+ }
|
|
|
+ public Mono<Long> deleteByPattern(String pattern) {
|
|
|
+ return reactiveRedisTemplate
|
|
|
+ .scan(ScanOptions.scanOptions().match(pattern).build())
|
|
|
+ .collectList()
|
|
|
+ .flatMap(keys -> {
|
|
|
+ if (keys.isEmpty()) {
|
|
|
+ return Mono.just(0L);
|
|
|
+ }
|
|
|
+ return reactiveRedisTemplate.delete(Flux.fromIterable(keys));
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @NotNull
|
|
|
+ private String getSetValue(ListeningAudio planNew) {
|
|
|
+ return planNew.idxNum() +
|
|
|
+ "-" +
|
|
|
+ planNew.newAudioId();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getPlanNewToString(ListeningAudio planNew) {
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ objectMapper.registerModule(new JavaTimeModule());
|
|
|
+ objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
|
|
+ try {
|
|
|
+ // 将 record 转换为 JSON 字符串
|
|
|
+ return objectMapper.writeValueAsString(planNew);
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ throw new BaseException("PlanNew 将 record 转换为 JSON 字符串 异常:::"+e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public Mono<Void> deleteRedis(ListeningAudio planNew) {
|
|
|
+ String setValue = getSetValue(planNew);
|
|
|
+ reactiveRedisTemplate.opsForSet().remove(LinteningAudioRedisKey.PLAN_NEW_INFO_SET, setValue).subscribe();
|
|
|
+ reactiveRedisTemplate.opsForHash().remove(LinteningAudioRedisKey.lintening_Audio_INFO_HASH, setValue).subscribe();
|
|
|
+ return Mono.empty();
|
|
|
+ }
|
|
|
+ @NotNull
|
|
|
+ public Mono<Void> getDelete(String key) {
|
|
|
+ reactiveRedisTemplate.opsForSet().remove(LinteningAudioRedisKey.PLAN_NEW_INFO_SET, key).subscribe();
|
|
|
+ reactiveRedisTemplate.opsForHash().remove(LinteningAudioRedisKey.lintening_Audio_INFO_HASH, key).subscribe();
|
|
|
+ return Mono.empty();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过pageShow从数据库获取 并缓存redis
|
|
|
+ * @param pageShow
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public Mono<List<ListeningAudio>> byCodeAndMysqlGetPlanNew(Integer pageShow){
|
|
|
+ Mono<List<ListeningAudio>> idx_num = template.select(Query.query(Criteria.empty())
|
|
|
+ .sort(Sort.by(Sort.Order.asc("idx_num")))
|
|
|
+ .columns(COLUMNARRAY)
|
|
|
+ .limit(pageShow), ListeningAudio.class).collectList();
|
|
|
+ return idx_num.flatMap(planNew -> {
|
|
|
+ Flux.fromIterable(planNew).flatMap(this::setRedisValue).subscribe();
|
|
|
+ return Mono.just(planNew);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过redis获取缓存 空或失败 从mysql中取
|
|
|
+ * @param
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public Mono<List<ListeningAudio>> byRedisGetLinteningAudioIfErByMysql(Integer size){
|
|
|
+ return reactiveRedisTemplate
|
|
|
+ .opsForSet()
|
|
|
+ .members(LinteningAudioRedisKey.PLAN_NEW_INFO_SET).collectList()
|
|
|
+ .switchIfEmpty(Mono.just(List.of()))
|
|
|
+ .onErrorResume(throwable -> {
|
|
|
+ log.error("接口异常 {} e = {}","获取redis ListeningAudio 失败 暂时从Mysql获取内容" , LogExceptionStackTrace.erroStackTrace(throwable));
|
|
|
+ return Mono.just(List.of());
|
|
|
+ })
|
|
|
+ .flatMap(s -> {
|
|
|
+ if (Objects.nonNull(s) && !s.isEmpty()) {
|
|
|
+ var collect = s.stream()
|
|
|
+ .filter(f->Objects.nonNull(f)&&f.contains("-"))
|
|
|
+ .sorted(Comparator.comparing((f)-> Integer.valueOf(f.split("-")[0])))
|
|
|
+ .limit(size)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ return Flux.fromIterable(collect).flatMap(f -> reactiveRedisTemplate
|
|
|
+ .opsForHash()
|
|
|
+ .get(LinteningAudioRedisKey.lintening_Audio_INFO_HASH , f))
|
|
|
+ .flatMap(str -> {
|
|
|
+ if (Objects.nonNull(str) && !"".equals(str)) {
|
|
|
+ return Mono.just(strParsePlanNew(String.valueOf(str)));
|
|
|
+ }
|
|
|
+ return Mono.empty();
|
|
|
+ }).collectList();
|
|
|
+ }else {
|
|
|
+ return byCodeAndMysqlGetPlanNew(size);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * str 转PlanNew
|
|
|
+ * @param str
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public ListeningAudio strParsePlanNew(String str) {
|
|
|
+ // 创建 ObjectMapper 实例并配置
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ objectMapper.registerModule(new JavaTimeModule());
|
|
|
+ SimpleModule module = new SimpleModule();
|
|
|
+ module.addDeserializer(ZonedDateTime.class, new CustomZonedDateTimeDeserializer());
|
|
|
+ objectMapper.registerModule(module);
|
|
|
+ try {
|
|
|
+ // 将 JSON 字符串转换为 record
|
|
|
+ return objectMapper.readValue(str, ListeningAudio.class);
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ throw new BaseException(" 将 JSON字符串 转换为 ListeningAudio 异常:::"+e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 自定义反序列化器
|
|
|
+ public static class CustomZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> {
|
|
|
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+ @Override
|
|
|
+ public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt)
|
|
|
+ throws IOException, JsonProcessingException {
|
|
|
+ String date = p.getText();
|
|
|
+ LocalDateTime localDateTime = LocalDateTime.parse(date, FORMATTER);
|
|
|
+ // 使用系统默认时区,可以根据需要修改
|
|
|
+ return ZonedDateTime.of(localDateTime, ZoneId.systemDefault());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|