|
|
@@ -0,0 +1,197 @@
|
|
|
+package com.xuekairui.user.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.http.HttpRequest;
|
|
|
+import cn.hutool.http.HttpResponse;
|
|
|
+import cn.hutool.json.JSONObject;
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.xuekairui.user.entity.BusinessLicense;
|
|
|
+import com.xuekairui.user.entity.User;
|
|
|
+import com.xuekairui.user.service.ShopResourceSyncService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 店铺资源同步服务 HTTP 实现(基于 Hutool HttpUtil,与项目现有风格一致)
|
|
|
+ * <p>
|
|
|
+ * 对接第三方"智价云药店版"系统接口:POST /api/sync_shop_resource/report_user
|
|
|
+ * 参数全部以 query 形式传递,返回 JSON。
|
|
|
+ * <p>
|
|
|
+ * 容错策略:外部调用失败时返回 null,不抛异常阻塞主流程。
|
|
|
+ *
|
|
|
+ * @author ProPrice Team
|
|
|
+ * @since 2026-07-10
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class ShopResourceSyncServiceImpl implements ShopResourceSyncService {
|
|
|
+
|
|
|
+ @Value("${external-system.shop-resource.base-url:}")
|
|
|
+ private String baseUrl;
|
|
|
+
|
|
|
+ @Value("${external-system.shop-resource.app-id:wx246605ec671bf08d}")
|
|
|
+ private String appId;
|
|
|
+
|
|
|
+ @Value("${external-system.shop-resource.connect-timeout:5000}")
|
|
|
+ private int connectTimeout;
|
|
|
+
|
|
|
+ @Value("${external-system.shop-resource.read-timeout:10000}")
|
|
|
+ private int readTimeout;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SyncResult syncShopResource(User user, BusinessLicense license) {
|
|
|
+ if (isNotConfigured()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (license == null) {
|
|
|
+ log.warn("同步店铺资源跳过:入驻信息为空");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ Map<String, String> params = buildParams(user, license);
|
|
|
+ String url = buildUrl(params);
|
|
|
+ log.info("同步店铺资源到第三方: phone={}, storeName={}",
|
|
|
+ user != null ? user.getPhone() : null, license.getStoreName());
|
|
|
+
|
|
|
+ String response = doPost(url);
|
|
|
+ if (response == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return parseResponse(response);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("同步店铺资源到第三方失败: licenseId={}, error={}",
|
|
|
+ license.getId(), e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造请求参数(query 形式)
|
|
|
+ */
|
|
|
+ private Map<String, String> buildParams(User user, BusinessLicense license) {
|
|
|
+ Map<String, String> params = new LinkedHashMap<>();
|
|
|
+ if (user != null && user.getPhone() != null) {
|
|
|
+ params.put("phone", user.getPhone());
|
|
|
+ }
|
|
|
+ params.put("contact_province", nullToEmpty(license.getProvince()));
|
|
|
+ params.put("contact_city", nullToEmpty(license.getCity()));
|
|
|
+ params.put("contact_area", nullToEmpty(license.getDistrict()));
|
|
|
+ params.put("contact_addr", nullToEmpty(license.getStoreAddress()));
|
|
|
+ params.put("contact_name", nullToEmpty(license.getContactPerson()));
|
|
|
+ params.put("contact_shop", nullToEmpty(license.getStoreName()));
|
|
|
+ params.put("shop_type", mapTerminalType(license.getTerminalType()));
|
|
|
+ params.put("contact_phone", nullToEmpty(license.getContactPhone()));
|
|
|
+ params.put("business_license_image", nullToEmpty(license.getLicenseImageUrl()));
|
|
|
+ params.put("drug_business_license_image", nullToEmpty(license.getDrugLicenseUrl()));
|
|
|
+ if (license.getMedicalDeviceClass2Url() != null) {
|
|
|
+ params.put("two_medical_device_registration", license.getMedicalDeviceClass2Url());
|
|
|
+ }
|
|
|
+ if (license.getMedicalDeviceClass3Url() != null) {
|
|
|
+ params.put("three_medical_device_business_license", license.getMedicalDeviceClass3Url());
|
|
|
+ }
|
|
|
+ params.put("app_id", appId);
|
|
|
+ params.put("status", mapReviewStatus(license.getReviewStatus()));
|
|
|
+ return params;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 审核状态映射:我方枚举 → 第三方数字编码
|
|
|
+ * PENDING-审核中→0, APPROVED-审核成功→1, REJECTED-审核失败→2, 其他→0
|
|
|
+ */
|
|
|
+ private String mapReviewStatus(String reviewStatus) {
|
|
|
+ if (reviewStatus == null) {
|
|
|
+ return "0";
|
|
|
+ }
|
|
|
+ return switch (reviewStatus) {
|
|
|
+ case "APPROVED" -> "1";
|
|
|
+ case "REJECTED" -> "2";
|
|
|
+ default -> "0";
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 终端类型映射:我方枚举 → 第三方数字编码
|
|
|
+ * SINGLE-单店→1, CHAIN-连锁→2, CLINIC/COMMUNITY_HEALTH-诊所社康等→3
|
|
|
+ */
|
|
|
+ private String mapTerminalType(String terminalType) {
|
|
|
+ if (terminalType == null) {
|
|
|
+ return "3";
|
|
|
+ }
|
|
|
+ return switch (terminalType) {
|
|
|
+ case "SINGLE" -> "1";
|
|
|
+ case "CHAIN" -> "2";
|
|
|
+ default -> "3";
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造带 query 参数的完整 URL
|
|
|
+ */
|
|
|
+ private String buildUrl(Map<String, String> params) {
|
|
|
+ StringBuilder sb = new StringBuilder(baseUrl);
|
|
|
+ sb.append("/api/sync_shop_resource/report_user");
|
|
|
+ sb.append('?');
|
|
|
+ boolean first = true;
|
|
|
+ for (Map.Entry<String, String> e : params.entrySet()) {
|
|
|
+ if (!first) {
|
|
|
+ sb.append('&');
|
|
|
+ }
|
|
|
+ first = false;
|
|
|
+ sb.append(URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8));
|
|
|
+ sb.append('=');
|
|
|
+ sb.append(URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8));
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String doPost(String url) {
|
|
|
+ try (HttpResponse response = HttpRequest.post(url)
|
|
|
+ .timeout(connectTimeout + readTimeout)
|
|
|
+ .execute()) {
|
|
|
+ if (response.isOk()) {
|
|
|
+ return response.body();
|
|
|
+ }
|
|
|
+ log.warn("第三方同步店铺资源 POST {} 返回: {} body={}",
|
|
|
+ url, response.getStatus(), response.body());
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private SyncResult parseResponse(String body) {
|
|
|
+ JSONObject json = JSONUtil.parseObj(body);
|
|
|
+ String code = json.getStr("code");
|
|
|
+ if (!"success".equalsIgnoreCase(code)) {
|
|
|
+ log.warn("第三方同步店铺资源返回失败: code={}, msg={}", code, json.getStr("msg"));
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ JSONObject data = json.getJSONObject("data");
|
|
|
+ if (data == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return SyncResult.builder()
|
|
|
+ .userId(data.getStr("user_id"))
|
|
|
+ .accessToken(data.getStr("access_token"))
|
|
|
+ .refreshToken(data.getStr("refresh_token"))
|
|
|
+ .expiresIn(data.getLong("expires_in"))
|
|
|
+ .newUser(data.getBool("new_user"))
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isNotConfigured() {
|
|
|
+ if (baseUrl == null || baseUrl.isBlank()) {
|
|
|
+ log.debug("第三方店铺资源 base-url 未配置,跳过同步");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String nullToEmpty(String s) {
|
|
|
+ return s != null ? s : "";
|
|
|
+ }
|
|
|
+}
|