diff --git a/mall-common/src/main/java/com/suisung/mall/common/service/CommonService.java b/mall-common/src/main/java/com/suisung/mall/common/service/CommonService.java
new file mode 100644
index 00000000..1176bd03
--- /dev/null
+++ b/mall-common/src/main/java/com/suisung/mall/common/service/CommonService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
+ * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
+ * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
+ * Vestibulum commodo. Ut rhoncus gravida arcu.
+ */
+
+package com.suisung.mall.common.service;
+
+/**
+ * 通用服务接口
+ */
+public interface CommonService {
+
+
+ /**
+ * 尝试获取分布式锁
+ *
+ * @param lockKey 锁的key
+ * @param expireSeconds 锁过期时间(秒)
+ * @return 锁标识(解锁时需用),加锁失败返回null
+ */
+ String tryDistributedLock(String lockKey, long expireSeconds);
+
+ /**
+ * 释放分布式锁
+ *
+ * @param lockKey 锁的key
+ * @param lockValue 加锁时返回的value,确保只有持有锁的线程能解锁
+ * @return 是否释放成功
+ */
+ boolean releaseLock(String lockKey, String lockValue);
+}
diff --git a/mall-common/src/main/java/com/suisung/mall/common/service/impl/CommonServiceImpl.java b/mall-common/src/main/java/com/suisung/mall/common/service/impl/CommonServiceImpl.java
new file mode 100644
index 00000000..c6397880
--- /dev/null
+++ b/mall-common/src/main/java/com/suisung/mall/common/service/impl/CommonServiceImpl.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
+ * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
+ * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
+ * Vestibulum commodo. Ut rhoncus gravida arcu.
+ */
+
+package com.suisung.mall.common.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+public class CommonServiceImpl {
+
+ @Lazy
+ @Resource
+ private RedisTemplate redisTemplate;
+
+ /**
+ * 尝试获取分布式锁
+ *
+ * 使用实例:
+ * String lockKey = "order:123";
+ * long expireSeconds = 10;
+ * String lockValue = commonServiceImpl.tryDistributedLock(lockKey, expireSeconds);
+ * if (lockValue != null) {
+ * try {
+ * // 执行业务逻辑
+ * } finally {
+ * commonServiceImpl.releaseLock(lockKey, lockValue);
+ * }
+ * } else {
+ * // 获取锁失败,做相应处理
+ * }
+ *
+ * @param lockKey 锁的key
+ * @param expireSeconds 锁过期时间(秒)
+ * @return 锁标识(解锁时需用),加锁失败返回null
+ */
+ public String tryDistributedLock(String lockKey, long expireSeconds) {
+ String lockValue = UUID.randomUUID().toString();
+ try {
+ Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireSeconds, TimeUnit.SECONDS);
+ return Boolean.TRUE.equals(success) ? lockValue : null;
+ } catch (Exception e) {
+ // 记录异常日志,实际项目可用Logger
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 释放分布式锁
+ *
+ * @param lockKey 锁的key
+ * @param lockValue 加锁时返回的value,确保只有持有锁的线程能解锁
+ * @return 是否释放成功
+ */
+ public boolean releaseLock(String lockKey, String lockValue) {
+ String luaScript =
+ "if redis.call('get', KEYS[1]) == ARGV[1] then " +
+ " return redis.call('del', KEYS[1]) " +
+ "else " +
+ " return 0 " +
+ "end";
+ try {
+ DefaultRedisScript script = new DefaultRedisScript<>(luaScript, Long.class);
+ Long result = (Long) redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
+ return result != null && result > 0;
+ } catch (Exception e) {
+ log.error("释放分布式锁异常,key={}, error={}", lockKey, e.getMessage(), e);
+ return false;
+ }
+ }
+}
diff --git a/mall-common/src/main/resources/application-local.yml b/mall-common/src/main/resources/application-local.yml
index e58d5ab0..213d045f 100644
--- a/mall-common/src/main/resources/application-local.yml
+++ b/mall-common/src/main/resources/application-local.yml
@@ -28,6 +28,16 @@ redis:
separator: ":"
expire: 3600
+redisson:
+ address: redis://@redis.host@:@redis.port@
+ database: @redis.database@ # Redis 库索引
+ password: @redis.password@ # Redis 密码
+ connectionPoolSize: 64 # 连接池大小
+ connectionMinimumIdleSize: 10 # 最小空闲连接数
+ idleConnectionTimeout: 10000 # 空闲连接超时时间(毫秒)
+ connectTimeout: 10000 # 连接超时时间(毫秒)
+ timeout: 3000 # 命令等待超时时间(毫秒)
+
baidu:
map:
app_id: 116444176
diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/sync/service/SyncThirdDataService.java b/mall-shop/src/main/java/com/suisung/mall/shop/sync/service/SyncThirdDataService.java
index a8b9baf7..e7db8458 100644
--- a/mall-shop/src/main/java/com/suisung/mall/shop/sync/service/SyncThirdDataService.java
+++ b/mall-shop/src/main/java/com/suisung/mall/shop/sync/service/SyncThirdDataService.java
@@ -10,14 +10,12 @@ package com.suisung.mall.shop.sync.service;
import cn.hutool.json.JSONArray;
import com.suisung.mall.common.api.CommonResult;
-import com.suisung.mall.common.modules.sync.StoreDbConfig;
import com.suisung.mall.common.pojo.req.SyncThirdMemberReq;
import com.suisung.mall.common.pojo.res.ThirdApiRes;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
-import java.io.IOException;
import java.util.List;
import java.util.Map;
@@ -41,6 +39,7 @@ public interface SyncThirdDataService {
/**
* 批量保存商品记录
+ *
* @param goodsListJSON
* @return
*/
@@ -48,6 +47,7 @@ public interface SyncThirdDataService {
/**
* 批量保存会员记录
+ *
* @param memberList
* @return
*/
@@ -55,6 +55,7 @@ public interface SyncThirdDataService {
/**
* 手动触发同步
+ *
* @param storeId
* @param syncType
* @return
@@ -62,42 +63,43 @@ public interface SyncThirdDataService {
CommonResult syncManual(String storeId, Integer syncType);
/**
- *
* @param appKey
* @param sign
* @param multipartFile
* @return
*/
- ThirdApiRes fileUpload(String appKey, String sign,String path,String syncType, MultipartFile multipartFile);
+ ThirdApiRes fileUpload(String appKey, String sign, String path, String syncType, MultipartFile multipartFile);
/**
- *
* @param appKey
* @param sign
* @param folders
* @return
*/
- void SyncReadSxFileData(String appKey, String sign,String syncType, List folders);
+ void SyncReadSxFileData(String appKey, String sign, String syncType, List folders);
/**
* 下载客户端更新包
+ *
* @param primaryKey
* @return
*/
- ResponseEntity downloadToClient(String primaryKey,String clienVersionName);
+ ResponseEntity downloadToClient(String primaryKey, String clienVersionName);
/**
* 获取客户端数据库配置
+ *
* @param appKey
* @param sign
* @return
*/
- ThirdApiRes getStoreDbConfig(String appKey,String sign);
+ ThirdApiRes getStoreDbConfig(String appKey, String sign);
/**
* 同步商品数据库存到客户端
+ *
* @param appKey
* @param sign
* @return
@@ -105,18 +107,33 @@ public interface SyncThirdDataService {
ThirdApiRes getStoreDataRelease(String appKey, String sign);
/**
- * 存储扣减商品到redis
+ * 保存一个或多个商品刚刚变化的库存到 redis hash 缓存
+ *
* @param storeData
*/
- void saveStoreRealeas(Map storeData);
+// void saveStoreRelease(Map storeData);
+
+ /**
+ * 从 Redis 中获取商品(有变动的)库存数据
+ *
+ * @return
+ */
+ Map getProductStockFromRedis();
+
+ /**
+ * 下单或支付后,批量累加减商品库存,使用 Redis Hash 的原子自增操作,保证并发安全
+ *
+ * @param stockDeltaMap key 为商品唯一key,value 为库存增降量 例如 {"1234567890123": 100, "1234567890124": 50} 库存数为正负整数,单位可能是个数或重量(克)
+ * 数量为正数时,库存数增加;数量为负数时,库存数减少
+ */
+ void incrProductStockToRedis(Map stockDeltaMap);
/**
- *
* @param appKey
* @param sign
* @param folders
* @return
*/
- ThirdApiRes fileUploadToOss(String appKey, String sign,String syncType, List folders);
+ ThirdApiRes fileUploadToOss(String appKey, String sign, String syncType, List folders);
}
diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/sync/service/impl/SyncThirdDataServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/sync/service/impl/SyncThirdDataServiceImpl.java
index 628be020..4c72b8fe 100644
--- a/mall-shop/src/main/java/com/suisung/mall/shop/sync/service/impl/SyncThirdDataServiceImpl.java
+++ b/mall-shop/src/main/java/com/suisung/mall/shop/sync/service/impl/SyncThirdDataServiceImpl.java
@@ -9,27 +9,22 @@
package com.suisung.mall.shop.sync.service.impl;
import cn.hutool.core.collection.CollUtil;
-
import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.json.JSONArray;
-
import cn.hutool.json.JSONUtil;
-
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qcloud.cos.model.COSObjectSummary;
import com.suisung.mall.common.api.CommonResult;
-
import com.suisung.mall.common.enums.DicEnum;
import com.suisung.mall.common.modules.base.ShopBaseProductBrand;
import com.suisung.mall.common.modules.base.ShopBaseProductCategory;
-
import com.suisung.mall.common.modules.sixun.SxSyncGoods;
import com.suisung.mall.common.modules.sixun.SxSyncVip;
import com.suisung.mall.common.modules.sync.StoreDbConfig;
@@ -38,9 +33,7 @@ import com.suisung.mall.common.modules.sync.SyncConfig;
import com.suisung.mall.common.modules.sync.SyncFileLog;
import com.suisung.mall.common.pojo.req.SyncThirdMemberReq;
import com.suisung.mall.common.pojo.res.ThirdApiRes;
-
import com.suisung.mall.common.utils.I18nUtil;
-
import com.suisung.mall.common.utils.StringUtils;
import com.suisung.mall.core.web.service.RedisService;
import com.suisung.mall.shop.base.service.ShopBaseProductCategoryService;
@@ -60,15 +53,15 @@ import com.suisung.mall.shop.sixun.utils.FileUtils;
import com.suisung.mall.shop.sync.Utils.ThreadFileUtils;
import com.suisung.mall.shop.sync.keymanage.RedisKey;
import com.suisung.mall.shop.sync.service.*;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -76,6 +69,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
+
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -85,17 +79,19 @@ import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
-import java.util.concurrent.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
-
@Service
-public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements SyncThirdDataService {
- private static Logger logger = LoggerFactory.getLogger(SyncThirdDataServiceImpl.class);
+public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements SyncThirdDataService {
+ private static final Logger logger = LoggerFactory.getLogger(SyncThirdDataServiceImpl.class);
private final int limitCnt = 300;
+ private final AtomicLong threadNum = new AtomicLong(0);
@Value("${client.path}")
public String clientPath;
@Autowired
@@ -104,15 +100,12 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
private SyncConfigService syncConfigService;
@Autowired
private SxSyncCategoryService sxSyncCategoryService;
-
@Autowired
private SxSyncGoodsService sxSyncGoodsService;
-
@Autowired
private SxSyncVipService sxSyncVipService;
@Autowired
private ShopNumberSeqService shopNumberSeqService;
- private final AtomicLong threadNum=new AtomicLong(0);
@Autowired
private SyncFileLogService syncFileLogService;
@Autowired
@@ -121,6 +114,11 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
@Autowired
private RedisService redisService;
+ @Lazy
+ @Autowired
+ private RedisTemplate redisTemplate;
+
+
@Autowired
private StoreDbConfigService storeDbConfigService;
@@ -135,6 +133,7 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
@Value("#{accountBaseConfigService.getConfig('tengxun_default_dir')}")
private String TENGXUN_DEFA;
+
/**
* 批量保存商品的分类
*
@@ -163,7 +162,7 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
return new ThirdApiRes().fail(1004, I18nUtil._("单次同步记录最多" + limitCnt + "条!"));
}
- int count=baseSaveOrUpdateShopBaseProductCategoryBatch(list,categoryListJSON,storeId);
+ int count = baseSaveOrUpdateShopBaseProductCategoryBatch(list, categoryListJSON, storeId);
Map resp = new HashMap<>();
resp.put("count", count);
@@ -198,7 +197,7 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
return new ThirdApiRes().fail(1004, I18nUtil._("单次同步记录最多" + limitCnt + "条!"));
}
- int count=baseSaveOrUpdateShopBaseProductBrandBatch(goodBrandList,storeId,brandListJSON);
+ int count = baseSaveOrUpdateShopBaseProductBrandBatch(goodBrandList, storeId, brandListJSON);
Map resp = new HashMap<>();
resp.put("count", count);
@@ -227,7 +226,7 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
return new ThirdApiRes().fail(1004, I18nUtil._("单次同步记录最多" + limitCnt + "条!"));
}
- int count=baseSaveOrUpdateGoods(goodsListJSON,storeId);
+ int count = baseSaveOrUpdateGoods(goodsListJSON, storeId);
Map resp = new HashMap<>();
resp.put("count", count);
@@ -257,7 +256,7 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
return new ThirdApiRes().fail(1004, I18nUtil._("单次同步记录最多" + limitCnt + "条!"));
}
- int count =baseSaveOrUpdateMemberBatch(memberList,storeId);
+ int count = baseSaveOrUpdateMemberBatch(memberList, storeId);
Map resp = new HashMap<>();
resp.put("count", count);
@@ -299,13 +298,13 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
//1-品牌,2-分类,3-商品,4-会员
switch (syncType) {
case 1:
- return syncProductBrand(new DataBaseInfo(),storeId);//todo
+ return syncProductBrand(new DataBaseInfo(), storeId);//todo
case 2:
- return syncProductClazz(new DataBaseInfo(),storeId);//todo 测试
+ return syncProductClazz(new DataBaseInfo(), storeId);//todo 测试
case 3:
- return syncProduct(new DataBaseInfo(),storeId);//todo 没做完
+ return syncProduct(new DataBaseInfo(), storeId);//todo 没做完
case 4:
- return syncVip(new DataBaseInfo(),storeId);//todo 测试
+ return syncVip(new DataBaseInfo(), storeId);//todo 测试
}
return CommonResult.success();
}
@@ -316,26 +315,26 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
*
* @return
*/
- public CommonResult syncProduct(DataBaseInfo dataBaseInfo,String storeId) {
- int total= sxSyncGoodsService.getGoodsTotal(dataBaseInfo);
+ public CommonResult syncProduct(DataBaseInfo dataBaseInfo, String storeId) {
+ int total = sxSyncGoodsService.getGoodsTotal(dataBaseInfo);
// 总页数
int pages = CommonUtil.getPagesCount(total, SxDataDao.PAGESIZE);
- int syncCount =0;
+ int syncCount = 0;
for (int i = 1; i < pages; i++) {
- int count=0;
- List sxSyncGoodsList= sxSyncGoodsService.findGoodsListPage(dataBaseInfo,i,pages);
+ int count = 0;
+ List sxSyncGoodsList = sxSyncGoodsService.findGoodsListPage(dataBaseInfo, i, pages);
//todo 数据转换
- List sxGoosModelList= CvtToGoosModel(sxSyncGoodsList);
- if(CollectionUtil.isEmpty(sxSyncGoodsList)){
+ List sxGoosModelList = CvtToGoosModel(sxSyncGoodsList);
+ if (CollectionUtil.isEmpty(sxSyncGoodsList)) {
continue;
}
- count= baseSaveOrUpdateGoods(JSONUtil.parseArray(sxGoosModelList),storeId);
- if(count<=0){
+ count = baseSaveOrUpdateGoods(JSONUtil.parseArray(sxGoosModelList), storeId);
+ if (count <= 0) {
continue;
}
- syncCount+=count;
+ syncCount += count;
}
- logger.info("同步商品的总数为{},成功数量为{}",total,syncCount);
+ logger.info("同步商品的总数为{},成功数量为{}", total, syncCount);
return CommonResult.success();
}
@@ -345,29 +344,29 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
*
* @return
*/
- public CommonResult syncVip(DataBaseInfo dataBaseInfo,String storeId) {
+ public CommonResult syncVip(DataBaseInfo dataBaseInfo, String storeId) {
// 记录总数
Integer total = sxSyncVipService.getVipMembersTotal(dataBaseInfo);
// 总页数
int pages = CommonUtil.getPagesCount(total, SxDataDao.PAGESIZE);
- List memberList=new ArrayList<>();
- SyncThirdMemberReq syncThirdMemberReq=null;
- int syncCount =0;
+ List memberList = new ArrayList<>();
+ SyncThirdMemberReq syncThirdMemberReq = null;
+ int syncCount = 0;
for (int i = 1; i < pages; i++) {
memberList.clear();
int count = 0;
- syncThirdMemberReq=new SyncThirdMemberReq();
- List sxSyncVipList= sxSyncVipService.findVipMemberPage(dataBaseInfo,i,SxDataDao.PAGESIZE);
+ syncThirdMemberReq = new SyncThirdMemberReq();
+ List sxSyncVipList = sxSyncVipService.findVipMemberPage(dataBaseInfo, i, SxDataDao.PAGESIZE);
//处理数据转换SxSyncVip>SyncThirdMemberReq
- memberList=ConverList(sxSyncVipList);
+ memberList = ConverList(sxSyncVipList);
memberList.add(syncThirdMemberReq);
- count=baseSaveOrUpdateMemberBatch(memberList,storeId);
+ count = baseSaveOrUpdateMemberBatch(memberList, storeId);
if (count <= 0) {
continue;
}
- syncCount+=count;
+ syncCount += count;
}
- logger.info("vip会员总共有{}条数据,同步完成{}条",total,syncCount);
+ logger.info("vip会员总共有{}条数据,同步完成{}条", total, syncCount);
return CommonResult.success();
}
@@ -376,61 +375,63 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
*
* @return
*/
- public CommonResult syncProductBrand(DataBaseInfo dataBaseInfo,String storeId) {
+ public CommonResult syncProductBrand(DataBaseInfo dataBaseInfo, String storeId) {
return null;
}
/**
* 同步商品分类
+ *
* @return
*/
- public CommonResult syncProductClazz(DataBaseInfo dataBaseInfo,String storeId) {
+ public CommonResult syncProductClazz(DataBaseInfo dataBaseInfo, String storeId) {
// 记录总数
Integer total = sxSyncCategoryService.getCategoryTotal(dataBaseInfo);
// 总页数
int pages = CommonUtil.getPagesCount(total, SxDataDao.PAGESIZE);
- int syncCount =0;
+ int syncCount = 0;
for (int i = 1; i <= pages; i++) {
int count = 0;
- List list = sxSyncCategoryService.getCategoryByDataBasePage(dataBaseInfo,i,SxDataDao.PAGESIZE);
+ List list = sxSyncCategoryService.getCategoryByDataBasePage(dataBaseInfo, i, SxDataDao.PAGESIZE);
if (CollUtil.isEmpty(list)) {
continue;
}
- JSONArray categoryListJSON=JSONUtil.parseArray(list);
+ JSONArray categoryListJSON = JSONUtil.parseArray(list);
List shopBaseProductCategories = JSONUtil.toList(categoryListJSON, ShopBaseProductCategory.class);
if (shopBaseProductCategories == null) {
- logger.info("转换类型为空,类方法为{}","com.suisung.mall.shop.sync.service.impl.SyncThirdDataServiceImpl.syncProductClazz");
+ logger.info("转换类型为空,类方法为{}", "com.suisung.mall.shop.sync.service.impl.SyncThirdDataServiceImpl.syncProductClazz");
continue;
}
- count = baseSaveOrUpdateShopBaseProductCategoryBatch(shopBaseProductCategories,categoryListJSON,storeId);
+ count = baseSaveOrUpdateShopBaseProductCategoryBatch(shopBaseProductCategories, categoryListJSON, storeId);
if (count <= 0) {
continue;
}
- syncCount+=count;
+ syncCount += count;
}
- logger.info("商品分类总共有{}条数据,同步完成{}条",total,syncCount);
+ logger.info("商品分类总共有{}条数据,同步完成{}条", total, syncCount);
return CommonResult.success();
}
/**
* 文件上传
+ *
* @param appKey
* @param sign
- * @param page 分页
+ * @param page 分页
* @param syncType
* @param multipartFile
* @return
*/
@Override
- public ThirdApiRes fileUpload(String appKey, String sign,String page,String syncType, MultipartFile multipartFile) {
- if (StrUtil.isBlank(appKey) || StrUtil.isBlank(sign) ) {
+ public ThirdApiRes fileUpload(String appKey, String sign, String page, String syncType, MultipartFile multipartFile) {
+ if (StrUtil.isBlank(appKey) || StrUtil.isBlank(sign)) {
return new ThirdApiRes().fail(1003, I18nUtil._("缺少必要参数!"));
}
// 验签、appid,必要参数判断
SyncApp syncAppO = syncAppService.getOne(new LambdaQueryWrapper()
- .select(SyncApp::getApp_key, SyncApp::getApp_secret,SyncApp::getStore_id)
+ .select(SyncApp::getApp_key, SyncApp::getApp_secret, SyncApp::getStore_id)
.eq(SyncApp::getApp_key, appKey)
- .eq(SyncApp::getApp_secret,sign));
+ .eq(SyncApp::getApp_secret, sign));
if (syncAppO == null) {
return new ThirdApiRes().fail(1001, I18nUtil._("签名有误!"));
}
@@ -441,13 +442,13 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
return new ThirdApiRes().fail(1001, I18nUtil._("文件不能为空!"));
}
byte[] bytes = multipartFile.getBytes();
- String folder=new FileUtils().getSyncTypeFlag(syncType,clientPath)+storeId+FileUtils.pathSeparator+page+FileUtils.pathSeparator;
- String filName=multipartFile.getOriginalFilename();
- String filePath= FileUtils.createFolderAndFileUsingFile(folder,filName);
+ String folder = new FileUtils().getSyncTypeFlag(syncType, clientPath) + storeId + FileUtils.pathSeparator + page + FileUtils.pathSeparator;
+ String filName = multipartFile.getOriginalFilename();
+ String filePath = FileUtils.createFolderAndFileUsingFile(folder, filName);
Path path = Paths.get(filePath);
Files.write(path, bytes);
- logger.info("path-{},parent-{},filename-{},root-{}",path.toString(),path.getParent(),path.getFileName().toString(),path.getRoot());
- // String filaPath=path.toString();
+ logger.info("path-{},parent-{},filename-{},root-{}", path, path.getParent(), path.getFileName().toString(), path.getRoot());
+ // String filaPath=path.toString();
// if(filePath.contains(":")){
// filePath=filePath.substring(filePath.indexOf(":")+1);
// }
@@ -460,12 +461,13 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
// .toUriString();
return new ThirdApiRes().success("文件上传成功");
} catch (IOException e) {
- return new ThirdApiRes().fail(500,"文件上传失败");
+ return new ThirdApiRes().fail(500, "文件上传失败");
}
}
/**
* 多线程处理文件
+ *
* @param appKey
* @param sign
* @param syncType
@@ -475,24 +477,24 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
@Async
public void SyncReadSxFileData(String appKey, String sign, String syncType, List folders) {
SyncApp syncApp = syncAppService.getOne(new LambdaQueryWrapper()
- .select(SyncApp::getApp_key, SyncApp::getApp_secret,SyncApp::getStore_id)
+ .select(SyncApp::getApp_key, SyncApp::getApp_secret, SyncApp::getStore_id)
.eq(SyncApp::getApp_key, appKey)
- .eq(SyncApp::getApp_secret,sign));
+ .eq(SyncApp::getApp_secret, sign));
String storeId = syncApp.getStore_id();
Date tenMinutesAgo = Date.from(Instant.now().minus(Duration.ofMinutes(5)));//校准误差
- Date date= DateUtil.date(tenMinutesAgo);
- if(null==syncApp.getStore_id()|| syncApp.getStore_id().isEmpty()){
+ Date date = DateUtil.date(tenMinutesAgo);
+ if (null == syncApp.getStore_id() || syncApp.getStore_id().isEmpty()) {
logger.info("商品id为空");
return;
}
- if(folders==null||folders.isEmpty()){
+ if (folders == null || folders.isEmpty()) {
logger.info("没有商品数据");
return;
}
- List newFolders=new ArrayList<>();
- folders.forEach(page->{
- String newfolder=new FileUtils().getSyncTypeFlag(syncType,clientPath)+storeId+FileUtils.pathSeparator+page+FileUtils.pathSeparator;
+ List newFolders = new ArrayList<>();
+ folders.forEach(page -> {
+ String newfolder = new FileUtils().getSyncTypeFlag(syncType, clientPath) + storeId + FileUtils.pathSeparator + page + FileUtils.pathSeparator;
newFolders.add(newfolder);
});
@@ -505,35 +507,35 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
ExecutorService executor = Executors.newFixedThreadPool(6);
List> futures = new ArrayList<>();
// 提交任务
- AtomicInteger success= new AtomicInteger();
- AtomicInteger fails= new AtomicInteger();
- List failFolders=new ArrayList<>();
- List failMessage=new ArrayList<>();
+ AtomicInteger success = new AtomicInteger();
+ AtomicInteger fails = new AtomicInteger();
+ List failFolders = new ArrayList<>();
+ List failMessage = new ArrayList<>();
shopBaseProductCategoryService.getCategoryListByStoreId(storeId);
for (int i = 0; i < newFolders.size(); i++) {
final int taskId = i;
threadNum.incrementAndGet();
- futures.add(executor.submit(() -> {
+ futures.add(executor.submit(() -> {
int count = 0;//失败重试机制,当失败重试一次,再次失败则记录到数据库中
- while (true){
+ while (true) {
count++;
- String taskName=newFolders.get(taskId);
- String fileName="good_"+(taskId+1)+".txt";
- JSONArray jsonArray=new ThreadFileUtils().processFolder(taskName,newFolders.get(taskId));
+ String taskName = newFolders.get(taskId);
+ String fileName = "good_" + (taskId + 1) + ".txt";
+ JSONArray jsonArray = new ThreadFileUtils().processFolder(taskName, newFolders.get(taskId));
try {
- baseSaveOrUpdateGoodsBatch(jsonArray,storeId);
+ baseSaveOrUpdateGoodsBatch(jsonArray, storeId);
success.getAndIncrement();
threadNum.decrementAndGet();
return "成功" + taskId;
- }catch (Exception e){
- if(count<2){
+ } catch (Exception e) {
+ if (count < 2) {
//Thread.sleep(100);
continue;
}
fails.getAndIncrement();
- failFolders.add(newFolders.get(taskId)+fileName);
- failMessage.add(taskId+"_"+e.getMessage());
- return "失败"+newFolders.get(taskId);
+ failFolders.add(newFolders.get(taskId) + fileName);
+ failMessage.add(taskId + "_" + e.getMessage());
+ return "失败" + newFolders.get(taskId);
}
}
}));
@@ -553,13 +555,13 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
shopBaseProductCategoryService.clearCategoryCache(storeId);
shopProductSpecItemService.clearExistItem(Integer.valueOf(storeId));
baseProductSpecService.clearShopBaseProductSpecMap(Integer.valueOf(storeId));
- List syncFileLogs=new ArrayList<>();
+ List syncFileLogs = new ArrayList<>();
for (int i = 0; i < failFolders.size(); i++) {
- String path=failFolders.get(i);
- String taskId=failMessage.get(i).split("_")[0];
- SyncFileLog syncFileLog=new SyncFileLog();
+ String path = failFolders.get(i);
+ String taskId = failMessage.get(i).split("_")[0];
+ SyncFileLog syncFileLog = new SyncFileLog();
syncFileLog.setSyncType(syncType);
- syncFileLog.setFileName(path.substring(path.lastIndexOf(FileUtils.pathSeparator)+1));
+ syncFileLog.setFileName(path.substring(path.lastIndexOf(FileUtils.pathSeparator) + 1));
syncFileLog.setSyncStatus(DicEnum.FAILED.getCode());
syncFileLog.setSyncTaskId(taskId);
syncFileLog.setSyncStoreId(storeId);
@@ -569,56 +571,56 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
syncFileLog.setTargetSystem(DicEnum.SOURCE_SYSTEM_TYPE_SELF.getCode());
syncFileLogs.add(syncFileLog);
}
- if(CollUtil.isNotEmpty(syncFileLogs)){
- syncFileLogService.saveBatch(syncFileLogs,syncFileLogs.size());
+ if (CollUtil.isNotEmpty(syncFileLogs)) {
+ syncFileLogService.saveBatch(syncFileLogs, syncFileLogs.size());
}
//todo 定时清理文件,建议用服务器脚本
- logger.info("执行成功{}个文件,失败{}个文件",success,fails);
+ logger.info("执行成功{}个文件,失败{}个文件", success, fails);
logger.info("同步商品数据执行结束");
- //更新当前的获取时间,用户客户端获取
+ //更新当前的获取时间,用户客户端获取
try {
QueryWrapper storeDbConfigQueryWrapper = new QueryWrapper<>();
storeDbConfigQueryWrapper.eq("store_id", storeId);
- StoreDbConfig storeDbConfig=storeDbConfigService.getOne(storeDbConfigQueryWrapper);
- if(ObjectUtil.isNotEmpty(storeDbConfig)){
+ StoreDbConfig storeDbConfig = storeDbConfigService.getOne(storeDbConfigQueryWrapper);
+ if (ObjectUtil.isNotEmpty(storeDbConfig)) {
storeDbConfig.setRefreshTime(date);
storeDbConfigService.saveOrUpdate(storeDbConfig);
}
- }catch (RuntimeException e){
- logger.error("同步时间失败"+e.getMessage());
+ } catch (RuntimeException e) {
+ logger.error("同步时间失败" + e.getMessage());
}
}
@Override
- public ResponseEntity downloadToClient(String primaryKey,String clienVersionName) {
- logger.info("primaryKey:{}",primaryKey);
- boolean checked= syncAppService.checkPrimaryKey(primaryKey);
- if(checked){
- String tempFilePath=System.getProperty("user.home");
- String clientJarPath="";
- COSObjectSummary cosObjectSummary= ossService.findNewestFile(FileUtils.OSSCLIENTFOLDER);
- String jarFileName=cosObjectSummary.getKey().substring(cosObjectSummary.getKey().lastIndexOf("/")+1);
- if(jarFileName.equals(clienVersionName+".jar")){
- logger.error("没有版本更新");
- return ResponseEntity.ok()
- .contentType(MediaType.APPLICATION_OCTET_STREAM)
- .header(HttpHeaders.CONTENT_DISPOSITION,
- "attachment; filename=error.txt")
- .header("error","noVersion")
- .body(new ByteArrayResource(clienVersionName.getBytes(StandardCharsets.UTF_8)));
- }else {
- String filePath= cosObjectSummary.getKey();
- clientJarPath= ossService.download(filePath,tempFilePath+FileUtils.pathSeparator+filePath);
- }
- if(StringUtils.isNotEmpty(clientJarPath)){
+ public ResponseEntity downloadToClient(String primaryKey, String clienVersionName) {
+ logger.info("primaryKey:{}", primaryKey);
+ boolean checked = syncAppService.checkPrimaryKey(primaryKey);
+ if (checked) {
+ String tempFilePath = System.getProperty("user.home");
+ String clientJarPath = "";
+ COSObjectSummary cosObjectSummary = ossService.findNewestFile(FileUtils.OSSCLIENTFOLDER);
+ String jarFileName = cosObjectSummary.getKey().substring(cosObjectSummary.getKey().lastIndexOf("/") + 1);
+ if (jarFileName.equals(clienVersionName + ".jar")) {
+ logger.error("没有版本更新");
+ return ResponseEntity.ok()
+ .contentType(MediaType.APPLICATION_OCTET_STREAM)
+ .header(HttpHeaders.CONTENT_DISPOSITION,
+ "attachment; filename=error.txt")
+ .header("error", "noVersion")
+ .body(new ByteArrayResource(clienVersionName.getBytes(StandardCharsets.UTF_8)));
+ } else {
+ String filePath = cosObjectSummary.getKey();
+ clientJarPath = ossService.download(filePath, tempFilePath + FileUtils.pathSeparator + filePath);
+ }
+ if (StringUtils.isNotEmpty(clientJarPath)) {
// 构建文件路径
Path filePath = Paths.get(clientJarPath);
File file = filePath.toFile();
// 检查文件是否存在
if (!file.exists()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
- .header("error" ,"noFile")
+ .header("error", "noFile")
.body(new ByteArrayResource(jarFileName.getBytes()));
}
// 创建Resource对象
@@ -629,13 +631,13 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
contentType = Files.probeContentType(filePath);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
- .header("error" ,"500")
+ .header("error", "500")
.body(new ByteArrayResource(jarFileName.getBytes()));
}
if (contentType == null) {
contentType = "application/octet-stream";
}
- // redisService.set(RedisKey.SXCLIENTKEYVERSION,jarFileName);
+ // redisService.set(RedisKey.SXCLIENTKEYVERSION,jarFileName);
// 构建响应
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
@@ -646,69 +648,120 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
}
logger.info("!校验失败");
return ResponseEntity.status(HttpStatus.NOT_FOUND)
- .header("error" ,"noValid")
+ .header("error", "noValid")
.body(new ByteArrayResource("version".getBytes()));
}
@Override
- public ThirdApiRes getStoreDbConfig(String appKey,String sign) {
- if (StrUtil.isBlank(appKey) || StrUtil.isBlank(sign) ) {
+ public ThirdApiRes getStoreDbConfig(String appKey, String sign) {
+ if (StrUtil.isBlank(appKey) || StrUtil.isBlank(sign)) {
return new ThirdApiRes().fail(1003, I18nUtil._("缺少必要参数!"));
}
// 验签、appid,必要参数判断
SyncApp syncAppO = syncAppService.getOne(new LambdaQueryWrapper()
- .select(SyncApp::getApp_key, SyncApp::getApp_secret,SyncApp::getStore_id)
+ .select(SyncApp::getApp_key, SyncApp::getApp_secret, SyncApp::getStore_id)
.eq(SyncApp::getApp_key, appKey)
- .eq(SyncApp::getApp_secret,sign));
+ .eq(SyncApp::getApp_secret, sign));
if (syncAppO == null) {
return new ThirdApiRes().fail(1001, I18nUtil._("签名有误!"));
}
String storeId = syncAppO.getStore_id();
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("store_id", storeId);
- queryWrapper.eq("has_start",DicEnum.YESORNO_1.getCode());
- StoreDbConfig storeDbConfig= storeDbConfigService.getOne(queryWrapper);
+ queryWrapper.eq("has_start", DicEnum.YESORNO_1.getCode());
+ StoreDbConfig storeDbConfig = storeDbConfigService.getOne(queryWrapper);
if (storeDbConfig == null) {
return new ThirdApiRes().fail(1003, I18nUtil._("服务器配置缺少配置信息!"));
}
- return new ThirdApiRes().success("成功",storeDbConfig);
+ return new ThirdApiRes().success("成功", storeDbConfig);
}
@Override
public ThirdApiRes getStoreDataRelease(String appKey, String sign) {
- if (StrUtil.isBlank(appKey) || StrUtil.isBlank(sign) ) {
+ if (StrUtil.isBlank(appKey) || StrUtil.isBlank(sign)) {
return new ThirdApiRes().fail(1003, I18nUtil._("缺少必要参数!"));
}
+
// 验签、appid,必要参数判断
SyncApp syncAppO = syncAppService.getOne(new LambdaQueryWrapper()
- .select(SyncApp::getApp_key, SyncApp::getApp_secret,SyncApp::getStore_id)
+ .select(SyncApp::getApp_key, SyncApp::getApp_secret, SyncApp::getStore_id)
.eq(SyncApp::getApp_key, appKey)
- .eq(SyncApp::getApp_secret,sign));
+ .eq(SyncApp::getApp_secret, sign));
if (syncAppO == null) {
return new ThirdApiRes().fail(1001, I18nUtil._("签名有误!"));
}
- Object obRst= redisService.get(RedisKey.STOREDATARELEASE);//商品库存扣减
- Map storeDataResultMap=new HashMap();
- if(obRst!=null){
- Map resultMap=(Map)obRst;
- Set sme= resultMap.entrySet();
- storeDataResultMap= sme.stream().filter(m->!(m.getValue().equals((double)0))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+// Object obRst = redisService.get(RedisKey.STOREDATARELEASE);//商品库存扣减
+// Map storeDataResultMap = new HashMap();
+// if (obRst != null) {
+// Map resultMap = (Map) obRst;
+// Set sme = resultMap.entrySet();
+// storeDataResultMap = sme.stream().filter(m -> !(m.getValue().equals((double) 0))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+// }
+
+ Map storeDataResultMap = getProductStockFromRedis();
+
+ return new ThirdApiRes().success("success", storeDataResultMap);
+ }
+
+// @Override
+// public void saveStoreRelease(Map storeData) {
+// // RMK: 这样写,存在严重的线程安全问题,可能会导致数据丢失或覆盖,
+// // 改成 redis 原子级别的 hash 类别存储
+// if (CollectionUtil.isEmpty(storeData)) {
+// return;
+// }
+// Object obRst = redisService.get(RedisKey.STOREDATARELEASE);
+// if (obRst != null) {
+// Map map = (Map) obRst;
+// map.putAll(storeData);
+// redisService.set(RedisKey.STOREDATARELEASE, map);
+// } else {
+// redisService.set(RedisKey.STOREDATARELEASE, storeData);
+// }
+// }
+
+
+ @Override
+ public Map getProductStockFromRedis() {
+ try {
+ // 从 Redis 获取 hash 结构的所有键值对
+ Map