From 0541172f7adb42ee2fb7159810c3d3ed1f901795 Mon Sep 17 00:00:00 2001 From: lijianlin Date: Fri, 21 Nov 2025 14:45:32 +0800 Subject: [PATCH] =?UTF-8?q?11-21=20=E6=AF=8F=E6=97=A5=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=9A=84=E4=BA=A7=E5=93=81=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/demo/DemoApplication.java | 2 +- .../com/example/demo/Util/ProductRemoteClient.java | 89 ++++++++++++++++++++++ .../demo/controller/coin/GeneralController.java | 6 ++ .../com/example/demo/domain/DTO/ProductDTO.java | 29 +++++++ .../example/demo/mapper/coin/GeneralMapper.java | 11 +++ .../example/demo/service/coin/GeneralService.java | 2 + .../demo/serviceImpl/coin/GeneralServiceImpl.java | 48 ++++++++++++ .../resources/jindouMapper/BeanConsumeMapper.xml | 3 + src/main/resources/mapper/GeneralMapper.xml | 33 ++++++++ 9 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/demo/Util/ProductRemoteClient.java create mode 100644 src/main/java/com/example/demo/domain/DTO/ProductDTO.java diff --git a/src/main/java/com/example/demo/DemoApplication.java b/src/main/java/com/example/demo/DemoApplication.java index 54a0a53..8fc0e36 100644 --- a/src/main/java/com/example/demo/DemoApplication.java +++ b/src/main/java/com/example/demo/DemoApplication.java @@ -19,7 +19,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; public class DemoApplication { public static void main(String[] args) { - System.setProperty("https.protocols", "TLSv1"); + System.setProperty("https.protocols", "TLSv1,TLSv1.2,TLSv1.3"); SpringApplication.run(DemoApplication.class, args); } diff --git a/src/main/java/com/example/demo/Util/ProductRemoteClient.java b/src/main/java/com/example/demo/Util/ProductRemoteClient.java new file mode 100644 index 0000000..9f2824d --- /dev/null +++ b/src/main/java/com/example/demo/Util/ProductRemoteClient.java @@ -0,0 +1,89 @@ +package com.example.demo.Util; + +import com.example.demo.domain.DTO.ProductDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +@Slf4j +@Component +@RequiredArgsConstructor +public class ProductRemoteClient { + + private final RestTemplate restTemplate; + + /** + * 获取远端全部商品(全量) + */ + public List fetchAll() { + // 1. 去掉 url 尾部空格 + String url = "https://api.homilychart.com/live_mall/api/product/all"; + + // 2. 构造日志 + log.info("[ProductRemote] 开始拉取远端全量商品"); + + try { + // 3. 指定返回类型,避免 Map Raw Type 警告 + ResponseEntity resp = + restTemplate.getForEntity(url, Map.class); + + // 4. 状态码校验 + if (resp.getStatusCode() != HttpStatus.OK || resp.getBody() == null) { + throw new RuntimeException("远端返回异常,状态码:" + resp.getStatusCode()); + } + + // 5. 安全取值 + Object dataObj = resp.getBody().get("data"); + if (!(dataObj instanceof List)) { + throw new RuntimeException("远端 data 字段不是数组"); + } + + List> data = (List>) dataObj; + + // 6. 空数组直接返回,避免 NPE + if (data.isEmpty()) { + log.warn("[ProductRemote] 远端返回空数组"); + return Collections.emptyList(); + } + + // 7. 映射 + 空指针保护 + long now = System.currentTimeMillis(); + return data.stream() + .filter(Objects::nonNull) // 过滤 null 元素 + .map(m -> { + ProductDTO dto = new ProductDTO(); + dto.setId((Integer) m.get("id")); + dto.setName((String) m.get("name")); + dto.setCover((String) m.get("cover")); + Object priceObj = m.get("price"); + dto.setPrice(priceObj != null ? new BigDecimal(priceObj.toString()) : BigDecimal.ZERO); + dto.setUpdatedAt(now); + return dto; + }) + .collect(Collectors.toList()); + + } catch (ResourceAccessException e) { + // 网络/超时异常 + log.error("拉取商品失败,真实原因:", e); + throw new RuntimeException("拉取商品失败:" + e.getMessage(), e); + } catch (RestClientException e) { + // 4xx/5xx/解析异常 + log.error("[ProductRemote] 远端异常:{}", e.getMessage()); + throw new RuntimeException("拉取商品失败:远端返回异常", e); + } catch (Exception e) { + // 兜底 + log.error("[ProductRemote] 未知异常", e); + throw new RuntimeException("拉取商品失败:未知错误", e); + }} +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/controller/coin/GeneralController.java b/src/main/java/com/example/demo/controller/coin/GeneralController.java index c173ec4..f1cecef 100644 --- a/src/main/java/com/example/demo/controller/coin/GeneralController.java +++ b/src/main/java/com/example/demo/controller/coin/GeneralController.java @@ -86,4 +86,10 @@ public class GeneralController { { return Result.success(generalService.getRate()); } + + //手动同步link商品表 + @PostMapping("/syncLinkProducts") + public void syncLinkProducts(){ + generalService.fullSyncProducts(); + } } diff --git a/src/main/java/com/example/demo/domain/DTO/ProductDTO.java b/src/main/java/com/example/demo/domain/DTO/ProductDTO.java new file mode 100644 index 0000000..032962e --- /dev/null +++ b/src/main/java/com/example/demo/domain/DTO/ProductDTO.java @@ -0,0 +1,29 @@ +package com.example.demo.domain.DTO; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.poi.hpsf.Decimal; + +import java.math.BigDecimal; + +/** + * @program: gold-java + * @ClassName ProductDTO + * @description: + * @author: Ethan + * @create: 2025−11-21 10:54 + * @Version 1.0 + **/ + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ProductDTO { + private Integer id; //id + private String name; //商品名 + private String cover; + private BigDecimal price; //价格 + private Long updatedAt; + private Long syncTime; //更新 +} diff --git a/src/main/java/com/example/demo/mapper/coin/GeneralMapper.java b/src/main/java/com/example/demo/mapper/coin/GeneralMapper.java index 88501e1..8b76038 100644 --- a/src/main/java/com/example/demo/mapper/coin/GeneralMapper.java +++ b/src/main/java/com/example/demo/mapper/coin/GeneralMapper.java @@ -1,9 +1,13 @@ package com.example.demo.mapper.coin; +import com.example.demo.domain.DTO.ProductDTO; import com.example.demo.domain.entity.Rate; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import java.util.Collection; import java.util.List; +import java.util.Set; /** * @program: GOLD @@ -31,4 +35,11 @@ public interface GeneralMapper { List getAllRoleMarket(); List getRate(); + //查找最新更新时间 + Long maxUpdatedAt(); + // 批量插入 + void replaceBatch(@Param("list") List list, + @Param("syncTime") long syncTime); + //删除已删掉的产品 + void deleteNotIn(@Param("ids") Collection ids); } diff --git a/src/main/java/com/example/demo/service/coin/GeneralService.java b/src/main/java/com/example/demo/service/coin/GeneralService.java index e7a5c04..a2673bb 100644 --- a/src/main/java/com/example/demo/service/coin/GeneralService.java +++ b/src/main/java/com/example/demo/service/coin/GeneralService.java @@ -43,4 +43,6 @@ public interface GeneralService { List getAllRoleMarket(); //获取汇率 List getRate(); + + void fullSyncProducts(); } diff --git a/src/main/java/com/example/demo/serviceImpl/coin/GeneralServiceImpl.java b/src/main/java/com/example/demo/serviceImpl/coin/GeneralServiceImpl.java index c59e6d3..0db75e1 100644 --- a/src/main/java/com/example/demo/serviceImpl/coin/GeneralServiceImpl.java +++ b/src/main/java/com/example/demo/serviceImpl/coin/GeneralServiceImpl.java @@ -1,17 +1,22 @@ package com.example.demo.serviceImpl.coin; +import com.example.demo.Util.ProductRemoteClient; +import com.example.demo.domain.DTO.ProductDTO; import com.example.demo.domain.entity.Rate; import com.example.demo.mapper.coin.GeneralMapper; import com.example.demo.service.coin.GeneralService; +import org.apache.commons.lang3.time.StopWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; @@ -28,8 +33,15 @@ import java.util.stream.Collectors; public class GeneralServiceImpl implements GeneralService { private static final Logger log = LoggerFactory.getLogger(GeneralServiceImpl.class); + private final ProductRemoteClient remoteClient; + @Autowired private GeneralMapper generalMapper; + + public GeneralServiceImpl(ProductRemoteClient remoteClient) { + this.remoteClient = remoteClient; + } + @Override public List getMarket() { List list = generalMapper.getMarket(); @@ -220,4 +232,40 @@ public class GeneralServiceImpl implements GeneralService { List list = generalMapper.getRate(); return list; } + + @Override + @Scheduled(cron = "0 0 2 * * *") // 02:00 + public void fullSyncProducts() { + StopWatch sw = StopWatch.createStarted(); + log.info("【全量商品同步】开始"); + + try { + // 1. 拉取远端全量 + List remoteList = remoteClient.fetchAll(); + if (remoteList.isEmpty()) { + log.warn("远端无数据,跳过"); + return; + } + Map remoteMap = remoteList.stream() + .collect(Collectors.toMap(ProductDTO::getId, Function.identity())); + + // 2. 覆盖本地(含新增 & 修改) + long syncTime = System.currentTimeMillis(); + generalMapper.replaceBatch(remoteList, syncTime); + + // 3. 删除本地已下架的品 + Set remoteIds = remoteMap.keySet(); + generalMapper.deleteNotIn(remoteIds); + + // 4. 统计 + int total = remoteList.size(); + log.info("【全量商品同步】完成,远端 {} 条,本地覆盖 {} 条,耗时 {} ms", + total, total, sw.getTime()); + + } catch (Exception e) { + log.error("【全量商品同步】失败", e); + // 可钉钉/邮件告警 + } + + } } diff --git a/src/main/resources/jindouMapper/BeanConsumeMapper.xml b/src/main/resources/jindouMapper/BeanConsumeMapper.xml index 1fbafa8..be802d3 100644 --- a/src/main/resources/jindouMapper/BeanConsumeMapper.xml +++ b/src/main/resources/jindouMapper/BeanConsumeMapper.xml @@ -333,6 +333,9 @@ diff --git a/src/main/resources/mapper/GeneralMapper.xml b/src/main/resources/mapper/GeneralMapper.xml index 2a33c57..8b076e7 100644 --- a/src/main/resources/mapper/GeneralMapper.xml +++ b/src/main/resources/mapper/GeneralMapper.xml @@ -1,6 +1,34 @@ + + + INSERT INTO product_dict + (id, name, cover, price, updated_at, sync_time) + VALUES + + (#{p.id}, + #{p.name}, + #{p.cover}, + #{p.price}, + #{p.updatedAt}, + #{syncTime}) + + ON DUPLICATE KEY UPDATE + name = VALUES(name), + cover = VALUES(cover), + price = VALUES(price), + updated_at = VALUES(updated_at), + sync_time = VALUES(sync_time) + + + + DELETE FROM product_dict + WHERE id NOT IN + + #{id} + + select id,rate_name from rate + + \ No newline at end of file