style(components): format CSS styles in Vue components
- Remove extra spaces in CSS property declarations - Consolidate multi-line CSS rules into single lines - Maintain consistent formatting across component styles - Improve readability by removing unnecessary line breaks - Ensure uniform styling structure in scoped CSS blocks
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package com.tashow.erp.common;
|
||||
|
||||
/**
|
||||
* 1688业务常量
|
||||
*/
|
||||
public class Alibaba1688Constants {
|
||||
public static final String APP_ID = "32517";
|
||||
public static final String INTERFACE_NAME = "imageOfferSearchService";
|
||||
public static final String APP_NAME = "ios";
|
||||
public static final String SEARCH_SCENE = "image";
|
||||
public static final String SEO_SCENE = "seoSearch";
|
||||
public static final int PAGE_SIZE = 40;
|
||||
public static final String JSV_VERSION = "2.6.1";
|
||||
public static final String API_VERSION = "2.0";
|
||||
public static final String DATA_TYPE = "json";
|
||||
public static final int TIMEOUT_MS = 10000;
|
||||
public static final String API_BASE = "https://h5api.m.1688.com/h5";
|
||||
public static final String API_METHOD = "mtop.relationrecommend.WirelessRecommend.recommend";
|
||||
|
||||
private Alibaba1688Constants() {}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.tashow.erp.common;
|
||||
|
||||
/**
|
||||
* 亚马逊业务常量
|
||||
*/
|
||||
public class AmazonConstants {
|
||||
public static final String REGION_JP = "JP";
|
||||
public static final String REGION_US = "US";
|
||||
public static final String DOMAIN_JP = "https://www.amazon.co.jp";
|
||||
public static final String DOMAIN_US = "https://www.amazon.com";
|
||||
public static final String URL_PRODUCT_PATH = "/dp/";
|
||||
public static final String SESSION_PREFIX = "SINGLE_";
|
||||
public static final String DATA_TYPE = "AMAZON";
|
||||
public static final int RETRY_TIMES = 3;
|
||||
public static final int SLEEP_TIME_BASE = 2000;
|
||||
public static final int SLEEP_TIME_RANDOM = 2000;
|
||||
public static final int TIMEOUT_MS = 20000;
|
||||
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36";
|
||||
public static final String HEADER_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8";
|
||||
public static final String HEADER_ACCEPT_ENCODING = "gzip, deflate, br";
|
||||
public static final String HEADER_ACCEPT_LANGUAGE_US = "zh-CN,zh;q=0.9,en;q=0.8";
|
||||
public static final String HEADER_ACCEPT_LANGUAGE_JP = "ja,en;q=0.9,zh-CN;q=0.8";
|
||||
public static final String HEADER_CACHE_CONTROL = "max-age=0";
|
||||
public static final String COOKIE_I18N_PREFS_US = "USD";
|
||||
public static final String COOKIE_I18N_PREFS_JP = "JPY";
|
||||
public static final String COOKIE_LC_US = "en_US";
|
||||
public static final String COOKIE_LC_JP = "zh_CN";
|
||||
public static final String COOKIE_SESSION_ID_US = "134-6097934-2082600";
|
||||
public static final String COOKIE_SESSION_ID_JP = "358-1261309-0483141";
|
||||
public static final String COOKIE_SESSION_ID_TIME = "2082787201l";
|
||||
public static final String COOKIE_UBID_US = "132-7547587-3056927";
|
||||
public static final String COOKIE_UBID_JP = "357-8224002-9668932";
|
||||
public static final String COOKIE_SKIN = "noskin";
|
||||
|
||||
private AmazonConstants() {}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.tashow.erp.common;
|
||||
|
||||
/**
|
||||
* 斑马业务常量
|
||||
*/
|
||||
public class BanmaConstants {
|
||||
public static final String API_BASE = "https://banma365.cn";
|
||||
public static final String API_ORDER_LIST = API_BASE + "/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&_t=%d";
|
||||
public static final String API_ORDER_LIST_WITH_TIME = API_BASE + "/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&orderedAtStart=%s&orderedAtEnd=%s&_t=%d";
|
||||
public static final String API_TRACKING = API_BASE + "/zebraExpressHub/web/tracking/getByExpressNumber/%s";
|
||||
public static final String API_SHOP_LIST = API_BASE + "/api/shop/list?_t=%d";
|
||||
public static final int CONNECT_TIMEOUT_SECONDS = 5;
|
||||
public static final int READ_TIMEOUT_SECONDS = 10;
|
||||
public static final String DATA_TYPE = "BANMA";
|
||||
public static final String DATA_TYPE_CACHE = "BANMA_CACHE";
|
||||
public static final String TRACKING_PREFIX_ORDER = "ORDER_";
|
||||
public static final String TRACKING_PREFIX_PRODUCT = "PRODUCT_";
|
||||
public static final String TRACKING_PREFIX_UNKNOWN = "UNKNOWN_";
|
||||
public static final String SESSION_PREFIX = "SESSION_";
|
||||
public static final int CACHE_HOURS = 1;
|
||||
|
||||
private BanmaConstants() {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.tashow.erp.common;
|
||||
|
||||
/**
|
||||
* 缓存相关常量
|
||||
*/
|
||||
public class CacheConstants {
|
||||
public static final int DATA_RETENTION_HOURS = 1;
|
||||
public static final int TRADEMARK_CACHE_DAYS = 1;
|
||||
public static final int SESSION_LIMIT = 1;
|
||||
public static final int RAKUTEN_CACHE_HOURS = 1;
|
||||
|
||||
private CacheConstants() {}
|
||||
}
|
||||
@@ -1,169 +1,16 @@
|
||||
package com.tashow.erp.common;
|
||||
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 通用常量信息
|
||||
*
|
||||
* @author ruoyi
|
||||
* 通用常量(保留兼容性)
|
||||
* 新代码请使用具体业务常量类:AmazonConstants、RakutenConstants、HttpConstants等
|
||||
*/
|
||||
public class Constants
|
||||
{
|
||||
/**
|
||||
* UTF-8 字符集
|
||||
*/
|
||||
public static final String UTF8 = "UTF-8";
|
||||
@Deprecated
|
||||
public class Constants {
|
||||
public static final String HTTP = HttpConstants.HTTP;
|
||||
public static final String HTTPS = HttpConstants.HTTPS;
|
||||
public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
|
||||
|
||||
/**
|
||||
* GBK 字符集
|
||||
*/
|
||||
public static final String GBK = "GBK";
|
||||
|
||||
/**
|
||||
* 系统语言
|
||||
*/
|
||||
public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
|
||||
|
||||
/**
|
||||
* www主域
|
||||
*/
|
||||
public static final String WWW = "www.";
|
||||
|
||||
/**
|
||||
* http请求
|
||||
*/
|
||||
public static final String HTTP = "http://";
|
||||
|
||||
/**
|
||||
* https请求
|
||||
*/
|
||||
public static final String HTTPS = "https://";
|
||||
|
||||
/**
|
||||
* 通用成功标识
|
||||
*/
|
||||
public static final String SUCCESS = "0";
|
||||
|
||||
/**
|
||||
* 通用失败标识
|
||||
*/
|
||||
public static final String FAIL = "1";
|
||||
|
||||
/**
|
||||
* 登录成功
|
||||
*/
|
||||
public static final String LOGIN_SUCCESS = "Success";
|
||||
|
||||
/**
|
||||
* 注销
|
||||
*/
|
||||
public static final String LOGOUT = "Logout";
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public static final String REGISTER = "Register";
|
||||
|
||||
/**
|
||||
* 登录失败
|
||||
*/
|
||||
public static final String LOGIN_FAIL = "Error";
|
||||
|
||||
/**
|
||||
* 所有权限标识
|
||||
*/
|
||||
public static final String ALL_PERMISSION = "*:*:*";
|
||||
|
||||
/**
|
||||
* 管理员角色权限标识
|
||||
*/
|
||||
public static final String SUPER_ADMIN = "admin";
|
||||
|
||||
/**
|
||||
* 角色权限分隔符
|
||||
*/
|
||||
public static final String ROLE_DELIMETER = ",";
|
||||
|
||||
/**
|
||||
* 权限标识分隔符
|
||||
*/
|
||||
public static final String PERMISSION_DELIMETER = ",";
|
||||
|
||||
/**
|
||||
* 验证码有效期(分钟)
|
||||
*/
|
||||
public static final Integer CAPTCHA_EXPIRATION = 2;
|
||||
|
||||
/**
|
||||
* 令牌
|
||||
*/
|
||||
public static final String TOKEN = "token";
|
||||
|
||||
/**
|
||||
* 令牌前缀
|
||||
*/
|
||||
public static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
/**
|
||||
* 令牌前缀
|
||||
*/
|
||||
public static final String LOGIN_USER_KEY = "login_user_key";
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
public static final String JWT_USERID = "userid";
|
||||
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
public static final String JWT_AVATAR = "avatar";
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
public static final String JWT_CREATED = "created";
|
||||
|
||||
/**
|
||||
* 用户权限
|
||||
*/
|
||||
public static final String JWT_AUTHORITIES = "authorities";
|
||||
|
||||
/**
|
||||
* 资源映射路径 前缀
|
||||
*/
|
||||
public static final String RESOURCE_PREFIX = "/profile";
|
||||
|
||||
/**
|
||||
* RMI 远程方法调用
|
||||
*/
|
||||
public static final String LOOKUP_RMI = "rmi:";
|
||||
|
||||
/**
|
||||
* LDAP 远程方法调用
|
||||
*/
|
||||
public static final String LOOKUP_LDAP = "ldap:";
|
||||
|
||||
/**
|
||||
* LDAPS 远程方法调用
|
||||
*/
|
||||
public static final String LOOKUP_LDAPS = "ldaps:";
|
||||
|
||||
/**
|
||||
* 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
|
||||
*/
|
||||
public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
|
||||
|
||||
/**
|
||||
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
|
||||
*/
|
||||
public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" };
|
||||
|
||||
/**
|
||||
* 定时任务违规的字符
|
||||
*/
|
||||
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
|
||||
"org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" };
|
||||
private Constants() {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.tashow.erp.common;
|
||||
|
||||
/**
|
||||
* 方舟精选业务常量
|
||||
*/
|
||||
public class FangzhouConstants {
|
||||
public static final String API_URL = "https://api.fangzhoujingxuan.com/Task";
|
||||
public static final String API_SECRET = "e10adc3949ba59abbe56e057f20f883e";
|
||||
public static final int TOKEN_EXPIRED_CODE = -1006;
|
||||
public static final String WEBSITE_CODE = "1";
|
||||
public static final int SUCCESS_CODE = 1;
|
||||
|
||||
private FangzhouConstants() {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.tashow.erp.common;
|
||||
|
||||
/**
|
||||
* HTTP协议常量
|
||||
*/
|
||||
public class HttpConstants {
|
||||
public static final String HTTP = "http://";
|
||||
public static final String HTTPS = "https://";
|
||||
public static final String CHARSET_UTF8 = "UTF-8";
|
||||
public static final String CHARSET_GBK = "GBK";
|
||||
|
||||
private HttpConstants() {}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tashow.erp.common;
|
||||
|
||||
/**
|
||||
* 乐天业务常量
|
||||
*/
|
||||
public class RakutenConstants {
|
||||
public static final String DATA_TYPE = "RAKUTEN";
|
||||
public static final String DOMAIN = "https://item.rakuten.co.jp";
|
||||
public static final int RETRY_TIMES = 3;
|
||||
public static final int SLEEP_TIME_BASE = 2000;
|
||||
public static final int SLEEP_TIME_RANDOM = 2000;
|
||||
public static final int TIMEOUT_MS = 20000;
|
||||
public static final String DATA_TYPE_CACHE = "RAKUTEN_CACHE";
|
||||
|
||||
private RakutenConstants() {}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
package com.tashow.erp.controller;
|
||||
|
||||
import com.tashow.erp.common.RakutenConstants;
|
||||
import com.tashow.erp.model.RakutenProduct;
|
||||
import com.tashow.erp.model.SearchResult;
|
||||
import com.tashow.erp.service.Alibaba1688Service;
|
||||
@@ -14,6 +16,7 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -81,7 +84,7 @@ public class RakutenController {
|
||||
}
|
||||
int cachedCount = allProducts.size() - newProducts.size();
|
||||
if (cachedCount > 0) {
|
||||
dataReportUtil.reportDataCollection("RAKUTEN_CACHE", cachedCount, "0");
|
||||
dataReportUtil.reportDataCollection(RakutenConstants.DATA_TYPE_CACHE, cachedCount, "0");
|
||||
}
|
||||
return JsonData.buildSuccess(Map.of(
|
||||
"products", allProducts,
|
||||
|
||||
@@ -39,6 +39,10 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
||||
private final RestTemplate noSslRestTemplate = createNoSslRestTemplate();
|
||||
@Autowired
|
||||
private ErrorReporter errorReporter;
|
||||
|
||||
/**
|
||||
* 创建忽略SSL证书的RestTemplate
|
||||
*/
|
||||
private RestTemplate createNoSslRestTemplate() {
|
||||
try {
|
||||
TrustManager[] trustManagers = new TrustManager[] {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.tashow.erp.service.impl;
|
||||
|
||||
import com.tashow.erp.common.AmazonConstants;
|
||||
import com.tashow.erp.common.CacheConstants;
|
||||
import com.tashow.erp.entity.AmazonProductEntity;
|
||||
import com.tashow.erp.repository.AmazonProductRepository;
|
||||
import com.tashow.erp.service.AmazonScrapingService;
|
||||
import com.tashow.erp.utils.DataReportUtil;
|
||||
import com.tashow.erp.utils.ErrorReporter;
|
||||
import com.tashow.erp.utils.RakutenProxyUtil;
|
||||
import com.tashow.erp.utils.UrlBuilder;
|
||||
import com.tashow.erp.utils.ValidationUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import us.codecraft.webmagic.Page;
|
||||
@@ -12,13 +17,13 @@ import us.codecraft.webmagic.Site;
|
||||
import us.codecraft.webmagic.Spider;
|
||||
import us.codecraft.webmagic.processor.PageProcessor;
|
||||
import us.codecraft.webmagic.selector.Html;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
/**
|
||||
* 亚马逊数据采集服务实现类
|
||||
*
|
||||
* @author ruoyi
|
||||
* 亚马逊数据采集服务实现
|
||||
* 负责批量采集亚马逊商品信息并缓存
|
||||
*/
|
||||
@Service
|
||||
public class AmazonScrapingServiceImpl implements AmazonScrapingService, PageProcessor {
|
||||
@@ -42,7 +47,7 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
||||
String url = page.getUrl().toString();
|
||||
// 提取ASIN
|
||||
String asin = html.xpath("//input[@id='ASIN']/@value").toString();
|
||||
if (isEmpty(asin)) {
|
||||
if (ValidationUtils.isEmpty(asin)) {
|
||||
String[] parts = url.split("/dp/");
|
||||
if (parts.length > 1) asin = parts[1].split("/")[0].split("\\?")[0];
|
||||
}
|
||||
@@ -50,33 +55,33 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
||||
String priceSymbol = html.xpath("//span[@class='a-price-symbol']/text()").toString();
|
||||
String priceWhole = html.xpath("//span[@class='a-price-whole']/text()").toString();
|
||||
String price = null;
|
||||
if (!isEmpty(priceSymbol) && !isEmpty(priceWhole)) {
|
||||
if (ValidationUtils.isNotEmpty(priceSymbol) && ValidationUtils.isNotEmpty(priceWhole)) {
|
||||
price = priceSymbol + priceWhole;
|
||||
}
|
||||
if (isEmpty(price)) {
|
||||
if (ValidationUtils.isEmpty(price)) {
|
||||
price = html.xpath("//span[@class='a-price-range']/text()").toString();
|
||||
}
|
||||
// 提取卖家
|
||||
String seller = html.xpath("//a[@id='sellerProfileTriggerId']/text()").toString();
|
||||
if (isEmpty(seller)) {
|
||||
if (ValidationUtils.isEmpty(seller)) {
|
||||
seller = html.xpath("//span[@class='a-size-small offer-display-feature-text-message']/text()").toString();
|
||||
}
|
||||
// 关键数据为空时重试
|
||||
if (isEmpty(price) && isEmpty(seller)) {
|
||||
if (ValidationUtils.isEmpty(price) && ValidationUtils.isEmpty(seller)) {
|
||||
throw new RuntimeException("Retry this page");
|
||||
}
|
||||
|
||||
if (isEmpty(price)) errorReporter.reportDataEmpty("amazon", asin, price);
|
||||
if (isEmpty(seller)) errorReporter.reportDataEmpty("amazon", asin, seller);
|
||||
if (ValidationUtils.isEmpty(price)) errorReporter.reportDataEmpty(AmazonConstants.DATA_TYPE.toLowerCase(), asin, price);
|
||||
if (ValidationUtils.isEmpty(seller)) errorReporter.reportDataEmpty(AmazonConstants.DATA_TYPE.toLowerCase(), asin, seller);
|
||||
|
||||
AmazonProductEntity entity = new AmazonProductEntity();
|
||||
entity.setAsin(asin == null ? "" : asin);
|
||||
|
||||
entity.setPrice(price);
|
||||
entity.setSeller(seller);
|
||||
resultCache.put(asin, entity);
|
||||
page.putField("entity", entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取WebMagic站点配置
|
||||
*/
|
||||
@@ -86,31 +91,30 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取产品信息
|
||||
* 批量获取亚马逊商品信息
|
||||
* 优先从缓存读取,缓存未命中时实时采集
|
||||
*/
|
||||
@Override
|
||||
public List<AmazonProductEntity> batchGetProductInfo(List<String> asinList, String batchId, String region) {
|
||||
String sessionId = (batchId != null) ? batchId : "SINGLE_" + UUID.randomUUID();
|
||||
LocalDateTime batchTime = LocalDateTime.now();
|
||||
|
||||
String sessionId = (batchId != null) ? batchId : AmazonConstants.SESSION_PREFIX + UUID.randomUUID();
|
||||
resultCache.clear();
|
||||
|
||||
// 第一步:清理1小时前的所有旧数据
|
||||
amazonProductRepository.deleteAllDataBefore(LocalDateTime.now().minusHours(1));
|
||||
// 清理过期缓存数据
|
||||
amazonProductRepository.deleteAllDataBefore(LocalDateTime.now().minusHours(CacheConstants.DATA_RETENTION_HOURS));
|
||||
|
||||
// 优化:批次内复用代理检测和工具实例
|
||||
// 批次内复用代理检测和下载器实例
|
||||
RakutenProxyUtil proxyUtil = new RakutenProxyUtil();
|
||||
String sampleUrl = buildAmazonUrl(region, "B00000000");
|
||||
String sampleUrl = UrlBuilder.buildAmazonUrl(region, "B00000000");
|
||||
var proxyDownloader = proxyUtil.createProxyDownloader(proxyUtil.detectSystemProxy(sampleUrl));
|
||||
|
||||
// 第二步:处理每个ASIN
|
||||
// 处理每个ASIN
|
||||
Map<String, AmazonProductEntity> allProducts = new HashMap<>();
|
||||
for (String asin : asinList.stream().distinct().toList()) {
|
||||
if (asin == null || asin.trim().isEmpty()) continue;
|
||||
String cleanAsin = asin.replaceAll("[^a-zA-Z0-9]", "");
|
||||
|
||||
Optional<AmazonProductEntity> cached = amazonProductRepository.findByAsinAndRegion(cleanAsin, region);
|
||||
if (cached.isPresent() && !isEmpty(cached.get().getPrice()) && !isEmpty(cached.get().getSeller())) {
|
||||
if (cached.isPresent() && ValidationUtils.isNotEmpty(cached.get().getPrice()) && ValidationUtils.isNotEmpty(cached.get().getSeller())) {
|
||||
AmazonProductEntity cachedEntity = cached.get();
|
||||
AmazonProductEntity entity = new AmazonProductEntity();
|
||||
entity.setAsin(cachedEntity.getAsin());
|
||||
@@ -122,7 +126,7 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
||||
amazonProductRepository.save(entity);
|
||||
allProducts.put(cleanAsin, entity);
|
||||
} else {
|
||||
String url = buildAmazonUrl(region, cleanAsin);
|
||||
String url = UrlBuilder.buildAmazonUrl(region, cleanAsin);
|
||||
this.site = configureSiteForRegion(region);
|
||||
synchronized (spiderLock) {
|
||||
activeSpider = Spider.create(this).addUrl(url).setDownloader(proxyDownloader).thread(1);
|
||||
@@ -135,51 +139,38 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
||||
entity.setSessionId(sessionId);
|
||||
entity.setUpdatedAt(LocalDateTime.now());
|
||||
amazonProductRepository.save(entity);
|
||||
dataReportUtil.reportDataCollection("AMAZON", 1, "0");
|
||||
dataReportUtil.reportDataCollection(AmazonConstants.DATA_TYPE, 1, "0");
|
||||
allProducts.put(cleanAsin, entity);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(allProducts.values());
|
||||
}
|
||||
|
||||
private boolean isEmpty(String str) {
|
||||
return str == null || str.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据地区构建Amazon URL
|
||||
*/
|
||||
private String buildAmazonUrl(String region, String asin) {
|
||||
if ("US".equals(region)) {
|
||||
return "https://www.amazon.com/dp/" + asin;
|
||||
}
|
||||
return "https://www.amazon.co.jp/dp/" + asin;
|
||||
}
|
||||
/**
|
||||
* 根据地区配置Site
|
||||
* 根据地区配置WebMagic站点
|
||||
*/
|
||||
private Site configureSiteForRegion(String region) {
|
||||
boolean isUS = "US".equals(region);
|
||||
boolean isUS = AmazonConstants.REGION_US.equals(region);
|
||||
return Site.me()
|
||||
.setRetryTimes(3)
|
||||
.setSleepTime(2000 + random.nextInt(2000))
|
||||
.setTimeOut(20000)
|
||||
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
|
||||
.addHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8")
|
||||
.addHeader("accept-encoding", "gzip, deflate, br")
|
||||
.addHeader("accept-language", isUS ? "zh-CN,zh;q=0.9,en;q=0.8" : "ja,en;q=0.9,zh-CN;q=0.8")
|
||||
.addHeader("cache-control", "max-age=0")
|
||||
.addHeader("referer", isUS ? "https://www.amazon.com/" : "https://www.amazon.co.jp/")
|
||||
.setRetryTimes(AmazonConstants.RETRY_TIMES)
|
||||
.setSleepTime(AmazonConstants.SLEEP_TIME_BASE + random.nextInt(AmazonConstants.SLEEP_TIME_RANDOM))
|
||||
.setTimeOut(AmazonConstants.TIMEOUT_MS)
|
||||
.setUserAgent(AmazonConstants.USER_AGENT)
|
||||
.addHeader("accept", AmazonConstants.HEADER_ACCEPT)
|
||||
.addHeader("accept-encoding", AmazonConstants.HEADER_ACCEPT_ENCODING)
|
||||
.addHeader("accept-language", isUS ? AmazonConstants.HEADER_ACCEPT_LANGUAGE_US : AmazonConstants.HEADER_ACCEPT_LANGUAGE_JP)
|
||||
.addHeader("cache-control", AmazonConstants.HEADER_CACHE_CONTROL)
|
||||
.addHeader("referer", isUS ? AmazonConstants.DOMAIN_US + "/" : AmazonConstants.DOMAIN_JP + "/")
|
||||
.addHeader("sec-fetch-site", "none")
|
||||
.addHeader("sec-fetch-mode", "navigate")
|
||||
.addHeader("sec-fetch-user", "?1")
|
||||
.addHeader("sec-fetch-dest", "document")
|
||||
.addCookie("i18n-prefs", isUS ? "USD" : "JPY")
|
||||
.addCookie(isUS ? "lc-main" : "lc-acbjp", isUS ? "en_US" : "zh_CN")
|
||||
.addCookie("session-id", isUS ? "134-6097934-2082600" : "358-1261309-0483141")
|
||||
.addCookie("session-id-time", "2082787201l")
|
||||
.addCookie(isUS ? "ubid-main" : "ubid-acbjp", isUS ? "132-7547587-3056927" : "357-8224002-9668932")
|
||||
.addCookie("skin", "noskin");
|
||||
.addCookie("i18n-prefs", isUS ? AmazonConstants.COOKIE_I18N_PREFS_US : AmazonConstants.COOKIE_I18N_PREFS_JP)
|
||||
.addCookie(isUS ? "lc-main" : "lc-acbjp", isUS ? AmazonConstants.COOKIE_LC_US : AmazonConstants.COOKIE_LC_JP)
|
||||
.addCookie("session-id", isUS ? AmazonConstants.COOKIE_SESSION_ID_US : AmazonConstants.COOKIE_SESSION_ID_JP)
|
||||
.addCookie("session-id-time", AmazonConstants.COOKIE_SESSION_ID_TIME)
|
||||
.addCookie(isUS ? "ubid-main" : "ubid-acbjp", isUS ? AmazonConstants.COOKIE_UBID_US : AmazonConstants.COOKIE_UBID_JP)
|
||||
.addCookie("skin", AmazonConstants.COOKIE_SKIN);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,10 @@ import org.springframework.stereotype.Service;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 认证服务实现
|
||||
* 负责客户端信息管理和错误上报
|
||||
*/
|
||||
@Service
|
||||
public class AuthServiceImpl {
|
||||
|
||||
@@ -22,6 +26,9 @@ public class AuthServiceImpl {
|
||||
@Getter
|
||||
private String clientId = DeviceUtils.generateDeviceId();
|
||||
|
||||
/**
|
||||
* 获取客户端信息
|
||||
*/
|
||||
public Map<String, Object> getClientInfo() {
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
info.put("clientId", clientId);
|
||||
@@ -30,6 +37,9 @@ public class AuthServiceImpl {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报错误信息
|
||||
*/
|
||||
public void reportError(String errorType, String errorMessage, Exception e) {
|
||||
try {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.tashow.erp.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tashow.erp.common.BanmaConstants;
|
||||
import com.tashow.erp.entity.BanmaOrderEntity;
|
||||
import com.tashow.erp.repository.BanmaOrderRepository;
|
||||
import com.tashow.erp.service.BanmaOrderService;
|
||||
@@ -15,6 +17,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
@@ -25,13 +28,15 @@ import java.util.stream.Collectors;
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
|
||||
/**
|
||||
* 斑马订单服务实现
|
||||
* 负责订单采集、物流查询和数据管理
|
||||
*/
|
||||
@Service
|
||||
public class BanmaOrderServiceImpl implements BanmaOrderService {
|
||||
private static final Logger logger = LoggerUtil.getLogger(BanmaOrderServiceImpl.class);
|
||||
private static final String API_URL = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&_t=%d";
|
||||
private static final String API_URL_WITH_TIME = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&orderedAtStart=%s&orderedAtEnd=%s&_t=%d";
|
||||
private static final String TRACKING_URL = "https://banma365.cn/zebraExpressHub/web/tracking/getByExpressNumber/%s";
|
||||
|
||||
|
||||
@Value("${api.server.base-url}")
|
||||
private String ruoyiAdminBase;
|
||||
private RestTemplate restTemplate;
|
||||
@@ -46,25 +51,30 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
||||
private String currentBatchSessionId = null;
|
||||
// 物流信息缓存,避免重复查询
|
||||
private final Map<String, String> trackingInfoCache = new ConcurrentHashMap<>();
|
||||
|
||||
public BanmaOrderServiceImpl(BanmaOrderRepository banmaOrderRepository, CacheService cacheService, DataReportUtil dataReportUtil, ErrorReporter errorReporter) {
|
||||
this.banmaOrderRepository = banmaOrderRepository;
|
||||
this.cacheService = cacheService;
|
||||
this.dataReportUtil = dataReportUtil;
|
||||
this.errorReporter = errorReporter;
|
||||
RestTemplateBuilder builder = new RestTemplateBuilder();
|
||||
builder.connectTimeout(Duration.ofSeconds(5));
|
||||
builder.readTimeout(Duration.ofSeconds(10));
|
||||
builder.connectTimeout(Duration.ofSeconds(BanmaConstants.CONNECT_TIMEOUT_SECONDS));
|
||||
builder.readTimeout(Duration.ofSeconds(BanmaConstants.READ_TIMEOUT_SECONDS));
|
||||
restTemplate = builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从服务器获取认证Token
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void fetchTokenFromServer(Long accountId) {
|
||||
Map<String, Object> resp = restTemplate.getForObject(ruoyiAdminBase + "/tool/banma/accounts", Map.class);
|
||||
List<Map<String, Object>> list = (List<Map<String, Object>>) resp.get("data");
|
||||
if (list == null || list.isEmpty()) return;
|
||||
Map<String, Object> account = accountId != null
|
||||
? list.stream().filter(m -> accountId.equals(((Number) m.get("id")).longValue())).findFirst().orElse(null)
|
||||
: list.stream().filter(m -> ((Number) m.getOrDefault("isDefault", 0)).intValue() == 1).findFirst().orElse(list.get(0));
|
||||
|
||||
? list.stream().filter(m -> accountId.equals(((Number) m.get("id")).longValue())).findFirst().orElse(null)
|
||||
: list.stream().filter(m -> ((Number) m.getOrDefault("isDefault", 0)).intValue() == 1).findFirst().orElse(list.get(0));
|
||||
|
||||
if (account != null) {
|
||||
String token = (String) account.get("token");
|
||||
currentAuthToken = token != null && token.startsWith("Bearer ") ? token : "Bearer " + token;
|
||||
@@ -82,7 +92,7 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
||||
if (currentAuthToken != null) headers.set("Authorization", currentAuthToken);
|
||||
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
|
||||
|
||||
String url = "https://banma365.cn/api/shop/list?_t=" + System.currentTimeMillis();
|
||||
String url = String.format(BanmaConstants.API_SHOP_LIST, System.currentTimeMillis());
|
||||
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class);
|
||||
|
||||
return response.getBody() != null ? response.getBody() : new HashMap<>();
|
||||
@@ -105,16 +115,16 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
||||
String shopIdsParam = "";
|
||||
if (shopIds != null && !shopIds.isEmpty()) {
|
||||
List<String> validShopIds = shopIds.stream()
|
||||
.filter(id -> id != null && !id.trim().isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
.filter(id -> id != null && !id.trim().isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
if (!validShopIds.isEmpty()) {
|
||||
shopIdsParam = "shopIds[]=" + String.join("&shopIds[]=", validShopIds) + "&";
|
||||
}
|
||||
}
|
||||
|
||||
String url = (StringUtils.isEmpty(startDate) || StringUtils.isEmpty(endDate))
|
||||
? String.format(API_URL, shopIdsParam, page, pageSize, System.currentTimeMillis())
|
||||
: String.format(API_URL_WITH_TIME, shopIdsParam, page, pageSize, startDate, endDate, System.currentTimeMillis());
|
||||
|
||||
String url = (StringUtils.isEmpty(startDate) || StringUtils.isEmpty(endDate))
|
||||
? String.format(BanmaConstants.API_ORDER_LIST, shopIdsParam, page, pageSize, System.currentTimeMillis())
|
||||
: String.format(BanmaConstants.API_ORDER_LIST_WITH_TIME, shopIdsParam, page, pageSize, startDate, endDate, System.currentTimeMillis());
|
||||
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class);
|
||||
if (response.getBody() == null || !Integer.valueOf(0).equals(response.getBody().get("code"))) {
|
||||
Map<String, Object> errorResult = new HashMap<>();
|
||||
@@ -127,14 +137,14 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
||||
int total = ((Number) dataMap.getOrDefault("total", 0)).intValue();
|
||||
|
||||
List orders = Optional.ofNullable(dataMap.get("list"))
|
||||
.map(list -> (List<Map<String, Object>>) list)
|
||||
.orElse(Collections.emptyList())
|
||||
.stream()
|
||||
.map(this::processOrderData)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
.map(list -> (List<Map<String, Object>>) list)
|
||||
.orElse(Collections.emptyList())
|
||||
.stream()
|
||||
.map(this::processOrderData)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!orders.isEmpty()) dataReportUtil.reportDataCollection("BANMA", orders.size(), "0");
|
||||
if (!orders.isEmpty()) dataReportUtil.reportDataCollection(BanmaConstants.DATA_TYPE, orders.size(), "0");
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("orders", orders);
|
||||
@@ -152,89 +162,92 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
||||
*/
|
||||
private Map processOrderData(Map<String, Object> order) {
|
||||
String trackingNumber = (String) order.get("internationalTrackingNumber");
|
||||
|
||||
// 检查缓存
|
||||
if (StringUtils.isNotEmpty(trackingNumber)) {
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusHours(1);
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusHours(BanmaConstants.CACHE_HOURS);
|
||||
if (banmaOrderRepository.existsByTrackingNumberAndCreatedAtAfter(trackingNumber, cutoffTime)) {
|
||||
return banmaOrderRepository.findLatestByTrackingNumber(trackingNumber)
|
||||
.map(entity -> {
|
||||
if (currentBatchSessionId != null && !currentBatchSessionId.equals(entity.getSessionId())) {
|
||||
entity.setSessionId(currentBatchSessionId);
|
||||
entity.setCreatedAt(LocalDateTime.now());
|
||||
entity.setUpdatedAt(LocalDateTime.now());
|
||||
banmaOrderRepository.save(entity);
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(entity.getOrderData(), Map.class);
|
||||
} catch (Exception e) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
})
|
||||
.orElse(null);
|
||||
.map(entity -> {
|
||||
if (currentBatchSessionId != null && !currentBatchSessionId.equals(entity.getSessionId())) {
|
||||
entity.setSessionId(currentBatchSessionId);
|
||||
entity.setUpdatedAt(LocalDateTime.now());
|
||||
banmaOrderRepository.save(entity);
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(entity.getOrderData(), Map.class);
|
||||
} catch (Exception e) {
|
||||
logger.warn("解析缓存订单数据失败: {}", trackingNumber);
|
||||
return new HashMap<>();
|
||||
}
|
||||
})
|
||||
.orElse(null);
|
||||
} else {
|
||||
banmaOrderRepository.findByTrackingNumber(trackingNumber)
|
||||
.ifPresent(banmaOrderRepository::delete);
|
||||
banmaOrderRepository.findByTrackingNumber(trackingNumber).ifPresent(banmaOrderRepository::delete);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建新订单数据
|
||||
// 构建新订单
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("internationalTrackingNumber", trackingNumber);
|
||||
result.put("internationalShippingFee", order.get("internationalShippingFee"));
|
||||
result.put("trackInfo", trackingInfoCache.computeIfAbsent(trackingNumber, this::fetchTrackingInfo));
|
||||
|
||||
Optional.ofNullable(order.get("subOrders"))
|
||||
.map(sub -> (List<Map<String, Object>>) sub)
|
||||
.filter(list -> !list.isEmpty())
|
||||
.map(list -> list.get(0))
|
||||
.ifPresent(subOrder -> extractSubOrderFields(result, subOrder));
|
||||
.map(sub -> (List<Map<String, Object>>) sub)
|
||||
.filter(list -> !list.isEmpty())
|
||||
.map(list -> list.get(0))
|
||||
.ifPresent(subOrder -> extractSubOrderFields(result, subOrder));
|
||||
|
||||
BanmaOrderEntity entity = new BanmaOrderEntity();
|
||||
String entityTrackingNumber = (String) result.get("internationalTrackingNumber");
|
||||
String shopOrderNumber = (String) result.get("shopOrderNumber");
|
||||
String productTitle = (String) result.get("productTitle");
|
||||
String orderId = String.valueOf(result.get("id"));
|
||||
|
||||
// 检查并上报空数据
|
||||
if (StringUtils.isEmpty(entityTrackingNumber)) errorReporter.reportDataEmpty("banma", String.valueOf(result.get("id")), entityTrackingNumber);
|
||||
if (StringUtils.isEmpty(shopOrderNumber)) errorReporter.reportDataEmpty("banma", String.valueOf(result.get("id")), shopOrderNumber);
|
||||
if (StringUtils.isEmpty(productTitle)) errorReporter.reportDataEmpty("banma", String.valueOf(result.get("id")), productTitle);
|
||||
// 上报空数据
|
||||
if (StringUtils.isEmpty(entityTrackingNumber))
|
||||
errorReporter.reportDataEmpty(BanmaConstants.DATA_TYPE.toLowerCase(), orderId, entityTrackingNumber);
|
||||
if (StringUtils.isEmpty(shopOrderNumber))
|
||||
errorReporter.reportDataEmpty(BanmaConstants.DATA_TYPE.toLowerCase(), orderId, shopOrderNumber);
|
||||
if (StringUtils.isEmpty(productTitle))
|
||||
errorReporter.reportDataEmpty(BanmaConstants.DATA_TYPE.toLowerCase(), orderId, productTitle);
|
||||
|
||||
// 生成跟踪号
|
||||
if (StringUtils.isEmpty(entityTrackingNumber)) {
|
||||
if (StringUtils.isNotEmpty(shopOrderNumber)) {
|
||||
entityTrackingNumber = "ORDER_" + shopOrderNumber;
|
||||
entityTrackingNumber = BanmaConstants.TRACKING_PREFIX_ORDER + shopOrderNumber;
|
||||
} else if (StringUtils.isNotEmpty(productTitle)) {
|
||||
entityTrackingNumber = "PRODUCT_" + Math.abs(productTitle.hashCode());
|
||||
entityTrackingNumber = BanmaConstants.TRACKING_PREFIX_PRODUCT + Math.abs(productTitle.hashCode());
|
||||
} else {
|
||||
entityTrackingNumber = "UNKNOWN_" + System.currentTimeMillis();
|
||||
entityTrackingNumber = BanmaConstants.TRACKING_PREFIX_UNKNOWN + System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
entity.setTrackingNumber(entityTrackingNumber);
|
||||
try {
|
||||
entity.setOrderData(objectMapper.writeValueAsString(result));
|
||||
} catch (Exception e) {
|
||||
entity.setOrderData("{}");
|
||||
}
|
||||
|
||||
// 生成会话ID
|
||||
String sessionId = currentBatchSessionId != null ? currentBatchSessionId :
|
||||
Optional.ofNullable((String) result.get("orderedAt"))
|
||||
.filter(orderedAt -> orderedAt.length() >= 10)
|
||||
.map(orderedAt -> "SESSION_" + orderedAt.substring(0, 10))
|
||||
.orElse("SESSION_" + java.time.LocalDate.now().toString());
|
||||
Optional.ofNullable((String) result.get("orderedAt"))
|
||||
.filter(orderedAt -> orderedAt.length() >= 10)
|
||||
.map(orderedAt -> BanmaConstants.SESSION_PREFIX + orderedAt.substring(0, 10))
|
||||
.orElse(BanmaConstants.SESSION_PREFIX + java.time.LocalDate.now());
|
||||
|
||||
// 保存实体
|
||||
BanmaOrderEntity entity = new BanmaOrderEntity();
|
||||
entity.setTrackingNumber(entityTrackingNumber);
|
||||
entity.setSessionId(sessionId);
|
||||
entity.setCreatedAt(LocalDateTime.now());
|
||||
entity.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
try {
|
||||
entity.setOrderData(objectMapper.writeValueAsString(result));
|
||||
banmaOrderRepository.save(entity);
|
||||
} catch (Exception e) {
|
||||
logger.warn("保存订单数据失败,跳过: {}", entityTrackingNumber);
|
||||
logger.warn("保存订单失败: {}", entityTrackingNumber, e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取子订单字段
|
||||
*/
|
||||
private void extractSubOrderFields(Map<String, Object> simplifiedOrder, Map<String, Object> subOrder) {
|
||||
String[] basicFields = {"orderedAt", "timeSinceOrder", "createdAt", "poTrackingNumber"};
|
||||
String[] productFields = {"productTitle", "shopOrderNumber", "priceJpy", "productQuantity", "shippingFeeJpy", "productNumber", "serviceFee", "productImage"};
|
||||
@@ -244,18 +257,22 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
||||
Arrays.stream(purchaseFields).forEach(field -> simplifiedOrder.put(field, subOrder.get(field)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取物流信息
|
||||
*/
|
||||
private String fetchTrackingInfo(String trackingNumber) {
|
||||
Map<String, Object> trackInfoMap = (Map<String, Object>) new SagawaExpressSdk().getTrackingInfo(trackingNumber).get("trackInfo");
|
||||
if (trackInfoMap != null) {
|
||||
return trackInfoMap.get("dateTime") + " " + trackInfoMap.get("office") + " " + trackInfoMap.get("status");
|
||||
}
|
||||
ResponseEntity<Map> response = restTemplate.getForEntity(String.format(TRACKING_URL, trackingNumber), Map.class);
|
||||
ResponseEntity<Map> response = restTemplate.getForEntity(String.format(BanmaConstants.API_TRACKING, trackingNumber), Map.class);
|
||||
return Optional.ofNullable(response.getBody())
|
||||
.filter(body -> Integer.valueOf(0).equals(body.get("code")))
|
||||
.map(body -> (List<Map<String, Object>>) body.get("data"))
|
||||
.filter(list -> !list.isEmpty())
|
||||
.map(list -> list.get(0))
|
||||
.map(track -> (String) track.get("track"))
|
||||
.orElse("暂无物流信息");
|
||||
.filter(body -> Integer.valueOf(0).equals(body.get("code")))
|
||||
.map(body -> (List<Map<String, Object>>) body.get("data"))
|
||||
.filter(list -> !list.isEmpty())
|
||||
.map(list -> list.get(0))
|
||||
.map(track -> (String) track.get("track"))
|
||||
.orElse("暂无物流信息");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.tashow.erp.service.impl;
|
||||
|
||||
import com.tashow.erp.common.CacheConstants;
|
||||
import com.tashow.erp.entity.BrandTrademarkCacheEntity;
|
||||
import com.tashow.erp.repository.BrandTrademarkCacheRepository;
|
||||
import com.tashow.erp.service.BrandTrademarkCacheService;
|
||||
@@ -7,30 +8,40 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 品牌商标缓存服务实现
|
||||
* 提供商标查询结果的缓存管理
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class BrandTrademarkCacheServiceImpl implements BrandTrademarkCacheService {
|
||||
|
||||
@Autowired
|
||||
private BrandTrademarkCacheRepository repository;
|
||||
|
||||
/**
|
||||
* 从缓存获取品牌商标注册状态
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Boolean> getCached(List<String> brands) {
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(CacheConstants.TRADEMARK_CACHE_DAYS);
|
||||
List<BrandTrademarkCacheEntity> cached = repository.findByBrandInAndCreatedAtAfter(brands, cutoffTime);
|
||||
Map<String, Boolean> result = new HashMap<>();
|
||||
cached.forEach(e -> result.put(e.getBrand(), e.getRegistered()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存商标查询结果到缓存
|
||||
*/
|
||||
@Override
|
||||
public void saveResults(Map<String, Boolean> results) {
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(CacheConstants.TRADEMARK_CACHE_DAYS);
|
||||
results.forEach((brand, registered) -> {
|
||||
repository.findByBrandAndCreatedAtAfter(brand, cutoffTime)
|
||||
.ifPresentOrElse(
|
||||
@@ -49,10 +60,13 @@ public class BrandTrademarkCacheServiceImpl implements BrandTrademarkCacheServic
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期缓存数据
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void cleanExpired() {
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(CacheConstants.TRADEMARK_CACHE_DAYS);
|
||||
repository.deleteByCreatedAtBefore(cutoffTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.tashow.erp.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tashow.erp.common.FangzhouConstants;
|
||||
import com.tashow.erp.service.IFangzhouApiService;
|
||||
import com.tashow.erp.utils.ApiForwarder;
|
||||
import com.tashow.erp.utils.LoggerUtil;
|
||||
@@ -23,14 +24,12 @@ import java.security.MessageDigest;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 方舟精选 API 服务实现
|
||||
* 方舟精选API服务实现
|
||||
* 负责与方舟精选平台的API交互
|
||||
*/
|
||||
@Service
|
||||
public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||
private static final Logger logger = LoggerUtil.getLogger(FangzhouApiServiceImpl.class);
|
||||
private static final String API_SECRET = "e10adc3949ba59abbe56e057f20f883e";
|
||||
private static final String FANGZHOU_API_URL = "https://api.fangzhoujingxuan.com/Task";
|
||||
private static final int TOKEN_EXPIRED_CODE = -1006;
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
@@ -41,6 +40,9 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||
@Autowired
|
||||
private ApiForwarder apiForwarder;
|
||||
|
||||
/**
|
||||
* 获取API Token
|
||||
*/
|
||||
@Override
|
||||
public String getToken() {
|
||||
try {
|
||||
@@ -60,6 +62,9 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新API Token
|
||||
*/
|
||||
@Override
|
||||
public String refreshToken() {
|
||||
try {
|
||||
@@ -78,6 +83,9 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用方舟精选API
|
||||
*/
|
||||
@Override
|
||||
public JsonNode callApi(String command, String data, String token) {
|
||||
try {
|
||||
@@ -87,26 +95,26 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||
formData.add("c", command);
|
||||
formData.add("d", data);
|
||||
formData.add("t", token);
|
||||
formData.add("s", md5(ts + data + API_SECRET));
|
||||
formData.add("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||
formData.add("ts", String.valueOf(ts));
|
||||
formData.add("website", "1");
|
||||
formData.add("website", FangzhouConstants.WEBSITE_CODE);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
||||
|
||||
String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
||||
String result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||
JsonNode json = objectMapper.readTree(result);
|
||||
|
||||
// 处理 Token 失效
|
||||
int statusCode = json.get("S").asInt();
|
||||
if (statusCode == TOKEN_EXPIRED_CODE || statusCode == -1002) {
|
||||
if (statusCode == FangzhouConstants.TOKEN_EXPIRED_CODE || statusCode == -1002) {
|
||||
String newToken = statusCode == -1002 ? getToken() : refreshToken();
|
||||
logger.info("Token 失效({}), {}后重试", statusCode, statusCode == -1002 ? "重新注册" : "刷新");
|
||||
formData.set("t", newToken);
|
||||
formData.set("s", md5(ts + data + API_SECRET));
|
||||
formData.set("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||
requestEntity = new HttpEntity<>(formData, headers);
|
||||
result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
||||
result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||
json = objectMapper.readTree(result);
|
||||
}
|
||||
|
||||
@@ -119,14 +127,14 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||
formData.add("c", command);
|
||||
formData.add("d", data);
|
||||
formData.add("t", newToken);
|
||||
formData.add("s", md5(ts + data + API_SECRET));
|
||||
formData.add("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||
formData.add("ts", String.valueOf(ts));
|
||||
formData.add("website", "1");
|
||||
formData.add("website", FangzhouConstants.WEBSITE_CODE);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
||||
try {
|
||||
String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
||||
String result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||
return objectMapper.readTree(result);
|
||||
} catch (Exception ex) {
|
||||
logger.error("重试失败", ex);
|
||||
@@ -138,6 +146,9 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到方舟精选
|
||||
*/
|
||||
@Override
|
||||
public JsonNode uploadFile(MultipartFile file, String token) {
|
||||
try {
|
||||
@@ -149,26 +160,26 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||
formData.add("t", token);
|
||||
formData.add("ts", ts);
|
||||
formData.add("d", data);
|
||||
formData.add("s", md5(ts + data + API_SECRET));
|
||||
formData.add("website", "1");
|
||||
formData.add("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||
formData.add("website", FangzhouConstants.WEBSITE_CODE);
|
||||
formData.add("files", file.getResource());
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
|
||||
|
||||
String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
||||
String result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||
JsonNode json = objectMapper.readTree(result);
|
||||
|
||||
// 处理 Token 失效
|
||||
int statusCode = json.get("S").asInt();
|
||||
if (statusCode == TOKEN_EXPIRED_CODE || statusCode == -1002) {
|
||||
if (statusCode == FangzhouConstants.TOKEN_EXPIRED_CODE || statusCode == -1002) {
|
||||
String newToken = statusCode == -1002 ? getToken() : refreshToken();
|
||||
logger.info("Token 失效({}), {}后重试", statusCode, statusCode == -1002 ? "重新注册" : "刷新");
|
||||
formData.set("t", newToken);
|
||||
formData.set("s", md5(ts + data + API_SECRET));
|
||||
formData.set("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||
requestEntity = new HttpEntity<>(formData, headers);
|
||||
result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
||||
result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||
json = objectMapper.readTree(result);
|
||||
}
|
||||
|
||||
@@ -183,14 +194,14 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||
formData.add("t", newToken);
|
||||
formData.add("ts", ts);
|
||||
formData.add("d", data);
|
||||
formData.add("s", md5(ts + data + API_SECRET));
|
||||
formData.add("website", "1");
|
||||
formData.add("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||
formData.add("website", FangzhouConstants.WEBSITE_CODE);
|
||||
formData.add("files", file.getResource());
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
|
||||
try {
|
||||
String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
||||
String result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||
return objectMapper.readTree(result);
|
||||
} catch (Exception ex) {
|
||||
logger.error("重试失败", ex);
|
||||
|
||||
@@ -14,6 +14,10 @@ import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 跟卖精灵服务实现
|
||||
* 负责打开和操作跟卖精灵网站
|
||||
*/
|
||||
@Service
|
||||
public class GenmaiServiceImpl {
|
||||
@Value("${api.server.base-url}")
|
||||
@@ -21,6 +25,9 @@ public class GenmaiServiceImpl {
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 打开跟卖精灵网站
|
||||
*/
|
||||
public void openGenmaiWebsite(Long accountId, String username) throws Exception {
|
||||
WebDriverManager.chromedriver()
|
||||
.driverRepositoryUrl(new URL("https://registry.npmmirror.com/-/binary/chromedriver/"))
|
||||
@@ -46,6 +53,9 @@ public class GenmaiServiceImpl {
|
||||
driver.navigate().refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并验证Token
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private String getAndValidateToken(Long accountId, String username) {
|
||||
String url = serverApiUrl + "/tool/genmai/accounts?name=" + username;
|
||||
@@ -68,6 +78,9 @@ public class GenmaiServiceImpl {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Token是否有效
|
||||
*/
|
||||
private boolean validateToken(String token) {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
package com.tashow.erp.service.impl;
|
||||
|
||||
import com.tashow.erp.common.CacheConstants;
|
||||
import com.tashow.erp.entity.RakutenProductEntity;
|
||||
import com.tashow.erp.model.RakutenProduct;
|
||||
import com.tashow.erp.repository.RakutenProductRepository;
|
||||
@@ -61,10 +63,10 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
||||
.filter(name -> name != null && !name.trim().isEmpty())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 清理所有1小时前的旧数据,不分店铺全部清掉
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusHours(1);
|
||||
// 清理所有过期的旧数据,不分店铺全部清掉
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusHours(CacheConstants.RAKUTEN_CACHE_HOURS);
|
||||
repository.deleteAllDataBefore(cutoffTime);
|
||||
log.info("清理1小时前的所有旧数据");
|
||||
log.info("清理{}小时前的所有旧数据", CacheConstants.RAKUTEN_CACHE_HOURS);
|
||||
|
||||
List<RakutenProductEntity> entities = products.stream()
|
||||
.map(product -> {
|
||||
@@ -80,7 +82,7 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查店铺是否有1小时内的缓存数据(按用户隔离)
|
||||
* 检查店铺是否有缓存时间内的缓存数据(按用户隔离)
|
||||
*/
|
||||
@Override
|
||||
public boolean hasRecentData(String shopName, String username) {
|
||||
@@ -88,9 +90,9 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
||||
return false;
|
||||
}
|
||||
boolean hasRecent = repository.existsByOriginalShopNameAndSessionIdStartingWithAndCreatedAtAfter(
|
||||
shopName, username + "#", LocalDateTime.now().minusHours(1));
|
||||
shopName, username + "#", LocalDateTime.now().minusHours(CacheConstants.RAKUTEN_CACHE_HOURS));
|
||||
if (hasRecent) {
|
||||
log.info("店铺 {} 存在1小时内缓存数据(用户: {}),将使用缓存", shopName, username);
|
||||
log.info("店铺 {} 存在缓存时间内缓存数据(用户: {}),将使用缓存", shopName, username);
|
||||
}
|
||||
return hasRecent;
|
||||
}
|
||||
@@ -114,6 +116,9 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
||||
|
||||
/**
|
||||
* 更新指定店铺的所有产品的会话ID
|
||||
*
|
||||
* @param shopName 店铺名
|
||||
* @param newSessionId 新的会话ID
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -131,6 +136,9 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
||||
|
||||
/**
|
||||
* 更新指定产品列表的会话ID,只更新这些具体的产品
|
||||
*
|
||||
* @param products 产品列表
|
||||
* @param newSessionId 新的会话ID
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -167,7 +175,9 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理指定店铺1小时之前的旧数据,保留1小时内的缓存
|
||||
* 清理指定店铺的旧数据,保留1小时内的缓存
|
||||
*
|
||||
* @param shopName 店铺名
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -178,5 +188,4 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -52,6 +52,9 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
||||
return products;
|
||||
}
|
||||
|
||||
/**
|
||||
* 乐天页面解析器
|
||||
*/
|
||||
private class RakutenPageProcessor implements PageProcessor {
|
||||
private final List<RakutenProduct> products;
|
||||
private final ErrorReporter errorReporter;
|
||||
@@ -60,6 +63,9 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
||||
this.errorReporter = errorReporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析乐天页面数据
|
||||
*/
|
||||
@Override
|
||||
public void process(Page page) {
|
||||
List<String> rankings = page.getHtml().xpath("//div[@class='srhRnk']/span[@class='icon']/text()").all();
|
||||
|
||||
@@ -35,10 +35,9 @@ public class DataReportUtil {
|
||||
reportData.put("clientId", generateClientId(dataType));
|
||||
reportData.put("dataType", dataType);
|
||||
reportData.put("dataCount", dataCount);
|
||||
reportData.put("status", status);
|
||||
reportData.put("status", status != null ? status : "0");
|
||||
|
||||
sendReportData(reportData);
|
||||
logger.debug("数据上报成功: {} - {} 条", dataType, dataCount);
|
||||
} catch (Exception e) {
|
||||
logger.warn("数据上报失败: {}", e.getMessage());
|
||||
}
|
||||
|
||||
@@ -5,21 +5,17 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.io.StringWriter;
|
||||
import java.io.PrintWriter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import com.tashow.erp.service.impl.AuthServiceImpl;
|
||||
|
||||
@Component
|
||||
public class ErrorReporter {
|
||||
|
||||
@Value("${api.server.base-url}")
|
||||
private String serverUrl;
|
||||
|
||||
@Autowired
|
||||
private AuthServiceImpl authService;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
@Autowired
|
||||
private ApiForwarder apiForwarder;
|
||||
|
||||
/**
|
||||
* 上报启动失败错误
|
||||
@@ -67,17 +63,14 @@ public class ErrorReporter {
|
||||
errorData.put("errorType", errorType);
|
||||
errorData.put("errorMessage", errorMessage);
|
||||
errorData.put("stackTrace", getStackTrace(ex));
|
||||
|
||||
// 添加系统信息
|
||||
errorData.put("osName", System.getProperty("os.name"));
|
||||
errorData.put("osVersion", System.getProperty("os.version"));
|
||||
errorData.put("appVersion", System.getProperty("project.version", "unknown"));
|
||||
|
||||
String url = serverUrl + "/monitor/client/api/error";
|
||||
restTemplate.postForObject(url, errorData, Map.class);
|
||||
apiForwarder.post("/monitor/error", errorData, null);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("错误上报失败: " + e.getMessage());
|
||||
// 静默失败,不影响主业务
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.tashow.erp.utils;
|
||||
|
||||
|
||||
import com.tashow.erp.common.Constants;
|
||||
import com.tashow.erp.common.HttpConstants;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
import java.util.*;
|
||||
@@ -362,7 +361,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
|
||||
*/
|
||||
public static boolean ishttp(String link)
|
||||
{
|
||||
return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
|
||||
return StringUtils.startsWithAny(link, HttpConstants.HTTP, HttpConstants.HTTPS);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -635,9 +634,10 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
|
||||
{
|
||||
return false;
|
||||
}
|
||||
AntPathMatcher matcher = new AntPathMatcher();
|
||||
for (String pattern : strs)
|
||||
{
|
||||
if (isMatch(pattern, str))
|
||||
if (matcher.match(pattern, str))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -645,28 +645,6 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断url是否与规则配置:
|
||||
* ? 表示单个字符;
|
||||
* * 表示一层路径内的任意字符串,不可跨层级;
|
||||
* ** 表示任意层路径;
|
||||
*
|
||||
* @param pattern 匹配规则
|
||||
* @param url 需要匹配的url
|
||||
* @return
|
||||
*/
|
||||
public static boolean isMatch(String pattern, String url)
|
||||
{
|
||||
AntPathMatcher matcher = new AntPathMatcher();
|
||||
return matcher.match(pattern, url);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T cast(Object obj)
|
||||
{
|
||||
return (T) obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
|
||||
*
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.tashow.erp.utils;
|
||||
|
||||
import com.tashow.erp.common.AmazonConstants;
|
||||
import com.tashow.erp.common.RakutenConstants;
|
||||
|
||||
/**
|
||||
* URL构建工具类
|
||||
*/
|
||||
public class UrlBuilder {
|
||||
/**
|
||||
* 构建亚马逊商品URL
|
||||
*/
|
||||
public static String buildAmazonUrl(String region, String asin) {
|
||||
String domain = AmazonConstants.REGION_US.equals(region)
|
||||
? AmazonConstants.DOMAIN_US
|
||||
: AmazonConstants.DOMAIN_JP;
|
||||
return domain + AmazonConstants.URL_PRODUCT_PATH + asin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建乐天商品URL
|
||||
*/
|
||||
public static String buildRakutenUrl(String itemCode) {
|
||||
return RakutenConstants.DOMAIN + "/" + itemCode;
|
||||
}
|
||||
|
||||
private UrlBuilder() {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.tashow.erp.utils;
|
||||
|
||||
/**
|
||||
* 数据验证工具类
|
||||
*/
|
||||
public class ValidationUtils {
|
||||
/**
|
||||
* 判断字符串是否为空
|
||||
*/
|
||||
public static boolean isEmpty(String str) {
|
||||
return str == null || str.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否非空
|
||||
*/
|
||||
public static boolean isNotEmpty(String str) {
|
||||
return !isEmpty(str);
|
||||
}
|
||||
|
||||
private ValidationUtils() {}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ javafx:
|
||||
height: 800
|
||||
# style: DECORATED # javafx.stage.StageStyle [DECORATED, UNDECORATED, TRANSPARENT, UTILITY, UNIFIED]
|
||||
# resizable: false
|
||||
|
||||
spring:
|
||||
main:
|
||||
lazy-initialization: true
|
||||
@@ -47,8 +46,9 @@ server:
|
||||
api:
|
||||
server:
|
||||
# 主服务器API配置
|
||||
base-url: "http://8.138.23.49:8085"
|
||||
#base-url: "http://8.138.23.49:8085"
|
||||
#base-url: "http://192.168.1.89:8085"
|
||||
base-url: "http://127.0.0.1:8085"
|
||||
paths:
|
||||
monitor: "/monitor/client/api"
|
||||
login: "/monitor/account/login"
|
||||
|
||||
Reference in New Issue
Block a user