客户端更新代码优化,双向功能实现

This commit is contained in:
liyj 2025-05-27 16:26:45 +08:00
parent 8d8c0b1c45
commit 2b1c3afcc7
18 changed files with 298 additions and 64 deletions

View File

@ -83,9 +83,10 @@ public class DynamicTaskScheduler {
commentModel =sxDataService.getCommentModel();
}
DataBaseInfo dataBaseInfo=sxDataService.getDataBaseInfo(commentModel);
sxDataService.syncStoreData(dataBaseInfo,commentModel);
sxDataService.SyncBranchList(dataBaseInfo,commentModel);
sxDataService.SyncCategory(dataBaseInfo,commentModel);
sxDataService.SyncGoods(dataBaseInfo,commentModel);//todo 暂时同步全部的商品如果后期修改需要增加服务器的字段test
sxDataService.SyncGoods(dataBaseInfo,commentModel);//todo 暂时同步全部的商品如果后期修改需要增加服务器的字段
sxDataService.SyncVipList(dataBaseInfo,commentModel);
isRuning=false;
}

View File

@ -38,6 +38,8 @@ public class HttpUtils {
public static final String URL_SYNC_GET_STOREdBCONFIG="/shop/sync/third/getStoreDbConfig";//文件下载
public static final String URL_SYNC_GET_STOR_DATA_RELEASE="/shop/sync/third/syncStoreDataRelease";//库存同步
public static String postData(RestTemplate restTemplate, String url,Object modelObject){
// 创建表单参数
// MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
@ -80,12 +82,6 @@ public class HttpUtils {
}
public static StoreDbConfig postDataGetConfig(RestTemplate restTemplate, String url, Object modelObject){
// 创建表单参数
// MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
// map.add("key1", "value1");
// map.add("key2", "value2");
// 设置Content-Type为application/x-www-form-urlencoded
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
log.info(modelObject.toString());

View File

@ -60,6 +60,8 @@ public class WebController {
// sxDataService.getAppSign();
// }
@RequestMapping("/downLoadClient")
public void downLoadClient(){
log.info("downLoadClient");
@ -76,4 +78,9 @@ public class WebController {
sxDataService.getDataBaseInfo(sxDataService.getCommentModel());
}
@RequestMapping("/syncStoreData")
public void syncStoreData(){
sxDataService.syncStoreData(new DataBaseInfo(),sxDataService.getCommentModel());
}
}

View File

@ -3,6 +3,7 @@ package com.small.client.dao;
import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
import com.microsoft.sqlserver.jdbc.SQLServerException;
import com.small.client.dto.DataBaseInfo;
import com.small.client.dto.ResultDto;
import lombok.extern.slf4j.Slf4j;
@ -243,4 +244,5 @@ public class BaseDao {
return resultDto;
}
}

View File

@ -1,17 +1,16 @@
package com.small.client.dao;
import cn.hutool.core.collection.CollectionUtil;
import com.small.client.dto.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.sql.*;
import java.util.*;
import java.util.Date;
/**
* 考虑到每个思迅软件都是自己的数据所以采用动态获取的方式获取数据
@ -445,4 +444,82 @@ public class SxDataDao extends BaseDao{
return specPriceDtos;
}
/**
* 批量更新商品库存
* @param dataBaseInfo
* @param map
*/
public void updateStoreData(DataBaseInfo dataBaseInfo, Map map){
if(CollectionUtil.isEmpty(map)){
log.info("同步数据为空");
return;
}
Connection conn =getConnection(dataBaseInfo.getIp(),dataBaseInfo.getUserName(),
dataBaseInfo.getPassword(), dataBaseInfo.getDbPort(),dataBaseInfo.getDataBaseName());
try {
conn.setAutoCommit(false); // 关闭自动提交开启事务
String sql = "update t_im_branch_stock set stock_qty= stock_qty+?,oper_date=? where item_no=?";
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
try (PreparedStatement ps = conn.prepareStatement(sql)) {
int batchSize = 1000; // 每批处理1000条
int count = 0;
Set<Map.Entry> sme=map.entrySet();
for (Map.Entry entry : sme) {
ps.setDouble(1, (double) entry.getValue());
ps.setTimestamp(2, timestamp);
ps.setString(3, (String) entry.getKey());
ps.addBatch(); // 添加至批处理
count++;
if (count % batchSize == 0) {
ps.executeBatch();
ps.clearBatch();
log.info("已提交批次: {}", count);
}
}
// 执行剩余未满 batchSize 的批次
int[] remainingCounts = ps.executeBatch();
log.info("剩余批次更新数: {}", Arrays.toString(remainingCounts));
conn.commit(); // 最终提交事务
log.info("批量更新完成,总记录数: {}" , count);
baseUpdateImBrancStock(dataBaseInfo);
} catch (SQLException e) {
conn.rollback(); // 出错时回滚整个事务
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 更新库存为负数的数据避免超卖
* @param dataBaseInfo
* @throws SQLException
*/
public void baseUpdateImBrancStock(DataBaseInfo dataBaseInfo) throws SQLException {
Connection connection=getConnection(dataBaseInfo.getIp(),dataBaseInfo.getUserName(),dataBaseInfo.getPassword()
,dataBaseInfo.getDbPort(),dataBaseInfo.getDataBaseName());
String sql = "update t_im_branch_stock set stock_qty= 0,oper_date=? where stock_qty<0";
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setTimestamp(1,timestamp);
ps.executeUpdate();
}catch (RuntimeException e){
throw new RuntimeException(e.getMessage());
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
if (connection!=null){
connection.close();
}
}
}
}

View File

@ -50,4 +50,11 @@ public interface SxDataService {
DataBaseInfo getDataBaseInfo(CommentModel commentModel);
/**
* 获取服务器扣减数据
* @param commentModel
* @return
*/
void syncStoreData(DataBaseInfo dataBaseInfo,CommentModel commentModel);
}

View File

@ -59,6 +59,8 @@ public class SxDataServiceImp extends SxDataAbstService implements SxDataService
@Autowired
private WebClientService webClientService;
@Value("${versionName}")
private String versionName;
/**
* 同步商品分类数据
@ -375,7 +377,7 @@ public class SxDataServiceImp extends SxDataAbstService implements SxDataService
FileUtils fileUtils= new FileUtils();
File file=fileUtils.createFile(path);
String filePath=file.getAbsolutePath();
String date= DateUtil.format(new Date(),"yyyy-MM-dd");
String date= DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss");
fileUtils.writeFile(filePath,FileUtils.REFLESHDATE,date);
}
@ -539,7 +541,8 @@ public class SxDataServiceImp extends SxDataAbstService implements SxDataService
String encryptedData = getPrimaryKey();
ResponseEntity<Resource> response = restTemplate.exchange(
remoteIp+HttpUtils.URL_SYNC_GET_DOWNCLIENTJAR+"?primaryKey="+encryptedData,
remoteIp+HttpUtils.URL_SYNC_GET_DOWNCLIENTJAR+"?primaryKey="+encryptedData
+"&clienVersionName="+versionName,
HttpMethod.GET,
requestEntity,
Resource.class);
@ -610,7 +613,13 @@ public class SxDataServiceImp extends SxDataAbstService implements SxDataService
File parentDir = classDirFile.getParentFile();
// 执行脚本并退出当前应用
log.info(parentDir.getAbsolutePath()+"/bin/run.bat");
Runtime.getRuntime().exec(parentDir.getAbsolutePath()+"/bin/run.bat");
// 1. 启动 .bat 进程不等待
ProcessBuilder processBuilder = new ProcessBuilder(
"cmd.exe", "/c",
"start", parentDir.getAbsolutePath()+"/bin/run.bat" // 使用 `start` 命令异步执行
);
processBuilder.start();
//Runtime.getRuntime().exec(parentDir.getAbsolutePath()+"/bin/run.bat");
log.info(parentDir.getAbsolutePath()+"/bin/run.bat");
System.exit(0);
} catch (IOException e) {
@ -641,4 +650,21 @@ public class SxDataServiceImp extends SxDataAbstService implements SxDataService
return new DataBaseInfo();
}
@Override
public void syncStoreData(DataBaseInfo dataBaseInfo,CommentModel commentModel) {
JSONObject jsonObject= restTemplate.getForObject(remoteIp+HttpUtils.URL_SYNC_GET_STOR_DATA_RELEASE
+"?appKey="+commentModel.getAppKey()
+"&sign="+commentModel.getSign(),JSONObject.class);
if(null!=jsonObject.get("result")){
Map map=(Map)jsonObject.get("result");
sxDataDao.updateStoreData(dataBaseInfo,map);
}
}
private String checkUpdate(){
return null;
}
}

View File

@ -12,4 +12,8 @@ logging:
charset:
console: UTF-8
remoteIp: https://mall.gpxscs.cn/api
#remoteIp: https://mall.gpxscs.cn/api
remoteIp: http://192.168.0.104:8089
versionName: client-v1

View File

@ -6,6 +6,10 @@
<!--使用默认的控制台日志输出实现-->
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!-- 定义日志输出目录 -->
<property name="LOG_PATH" value="../log/myapp" />
<property name="LOG_FILE" value="${LOG_PATH}/app.log" />
<!--接口访问记录日志输出到LogStash-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
@ -13,7 +17,24 @@
<pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天分割日志 -->
<fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留30天日志 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>

View File

@ -39,14 +39,14 @@ public class ContextUtil {
*/
public static UserDto getCurrentUser() {
try {
// UserDto loginUser = staticUserInfoService.getUser();
// log.info("##### 当前登录用户:{}###", JsonUtil.object2json(loginUser));
// return loginUser;//todo 测试去除
UserDto user= new UserDto();
user.setStore_id("1");
user.setRole_id(9);
user.setUser_account("18260885688");
return user;
UserDto loginUser = staticUserInfoService.getUser();
log.info("##### 当前登录用户:{}###", JsonUtil.object2json(loginUser));
return loginUser;//todo 测试去除
// UserDto user= new UserDto();
// user.setStore_id("1");
// user.setRole_id(9);
// user.setUser_account("18260885688");
// return user;
} catch (Exception e) {
System.out.println(e.getMessage());
}

View File

@ -66,4 +66,11 @@ public interface OssService {
*/
String download(String ossFolder, String localFolder);
/**
* 查找oss的最新文件
* @param folder
* @return
*/
COSObjectSummary findNewestFile(String folder);
}

View File

@ -600,6 +600,37 @@ public class OssServiceImpl implements OssService {
return files;
}
/**
* 查找指定目录下的最新文件
*/
@Override
public COSObjectSummary findNewestFile(String folder) {
COSClient ossCli = initCOSClient();
ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
listObjectsRequest.setBucketName(TENGXUN_BUCKET_NAME);
listObjectsRequest.setPrefix(folder);
listObjectsRequest.setDelimiter("");
COSObjectSummary newestFile = null;
ObjectListing objectListing;
do {
objectListing = ossCli.listObjects(listObjectsRequest);
// 遍历当前页文件
for (COSObjectSummary object : objectListing.getObjectSummaries()) {
// 跳过目录标记"your-directory/subdir/"
if (object.getKey().endsWith("/")) continue;
if (newestFile == null || object.getLastModified().after(newestFile.getLastModified())) {
newestFile = object;
}
}
// 设置分页标记继续查询
listObjectsRequest.setMarker(objectListing.getNextMarker());
} while (objectListing.isTruncated()); // 是否还有下一页
return newestFile;
}
/**
*
* @param ossFolder 如folder/example.txt

View File

@ -15,6 +15,8 @@ public class FileUtils {
public static final String BRAND = "brand"+pathSeparator;//品牌
public static final String MEMBER= "member"+pathSeparator;//会员
public static final String OSSCLIENTFOLDER= "/sxclient/version";//客户端的oss版本路径
public String getSyncTypeFlag(String syncType,String path){
Calendar calendar=Calendar.getInstance();
int year=calendar.get(Calendar.YEAR);

View File

@ -8,10 +8,7 @@ import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
@Api(tags = "第三方数据同步")
@Controller
@ -23,7 +20,8 @@ public class ClientController {
@ApiOperation(value = "下载客户端应用", notes = "获取加密密钥")
@RequestMapping(value = "/downClientJar",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE, method = RequestMethod.GET)
public ResponseEntity<Resource> downClientJar(@RequestParam String primaryKey) {
return syncThirdDataService.downloadToClient(primaryKey);
public ResponseEntity<Resource> downClientJar(@RequestParam String primaryKey,String clienVersionName) {
return syncThirdDataService.downloadToClient(primaryKey,clienVersionName);
}
}

View File

@ -144,5 +144,10 @@ public class SyncThirdDataController {
return syncThirdDataService.getStoreDbConfig(appKey,sign);
}
@ApiOperation(value = "同步商品库存给商瑞,对商瑞系统进行库存扣减", notes = "同步商品库存给商瑞,对商瑞系统进行库存扣减")
@RequestMapping(value = "/syncStoreDataRelease", method = RequestMethod.GET)
public ThirdApiRes syncStoreDataRelease(@RequestParam String appKey,@RequestParam String sign) {
return syncThirdDataService.getStoreDataRelease(appKey,sign);
}
}

View File

@ -2,5 +2,7 @@ package com.suisung.mall.shop.sync.keymanage;
public class RedisKey {
public static final String SXCLIENTKEYVERSION="sxclientKey:version";
//public static final String SXCLIENTKEYVERSION="sxclientKey:version";//客户端版本
public static final String STOREDATARELEASE="storedata:release";
}

View File

@ -19,6 +19,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public interface SyncThirdDataService {
@ -80,7 +81,33 @@ public interface SyncThirdDataService {
void SyncReadSxFileData(String appKey, String sign,String syncType, List<String> folders);
ResponseEntity<Resource> downloadToClient(String primaryKey);
/**
* 下载客户端更新包
* @param primaryKey
* @return
*/
ResponseEntity<Resource> downloadToClient(String primaryKey,String clienVersionName);
/**
* 获取客户端数据库配置
* @param appKey
* @param sign
* @return
*/
ThirdApiRes getStoreDbConfig(String appKey,String sign);
/**
* 同步商品数据库存到客户端
* @param appKey
* @param sign
* @return
*/
ThirdApiRes getStoreDataRelease(String appKey, String sign);
/**
* 存储扣减商品到redis
* @param storeData
*/
void saveStoreRealeas(Map storeData);
}

View File

@ -22,6 +22,7 @@ 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.domain.UserDto;
@ -126,6 +127,8 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
@Autowired
private ShopBaseProductCategoryService shopBaseProductCategoryService;
@Autowired
private FileUtils fileUtils;
/**
* 批量保存商品的分类
@ -551,45 +554,25 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
}
@Override
public ResponseEntity<Resource> downloadToClient(String primaryKey) {
public ResponseEntity<Resource> downloadToClient(String primaryKey,String clienVersionName) {
logger.info("primaryKey:{}",primaryKey);
boolean checked= syncAppService.checkPrimaryKey(primaryKey);
// UserDto userDto=ContextUtil.getCurrentUser();
//redisService.del(RedisKey.SXCLIENTKEYVERSION);//todo 删除
String version= (String) redisService.get(RedisKey.SXCLIENTKEYVERSION);
boolean checked= syncAppService.checkPrimaryKey(primaryKey);
if(checked){
String tempFilePath=System.getProperty("user.home");
String clientJarPath="";
if(StringUtils.isEmpty(version)){
version="v1";
//String ossFolder="/sxclient/"+version;
redisService.set(RedisKey.SXCLIENTKEYVERSION,version);
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(version.getBytes(StandardCharsets.UTF_8)));
.body(new ByteArrayResource(clienVersionName.getBytes(StandardCharsets.UTF_8)));
}else {
List<String> folderList= ossService.listFolders(CLIENTFILEPATH);
version= String.valueOf(Convert.toInt(version.split("v")[1])+1);
version="v"+version;
String finalVersion =CLIENTFILEPATH + version+"/";
String folder= folderList.stream().filter(finalVersion::equals).collect(Collectors.joining());
if(StringUtils.isNotEmpty(folder)){
//String ossFolder="/sxclient/"+folder;
String filePath= ossService.listDocuments(folder).get(0).getKey();
clientJarPath= ossService.download(filePath,tempFilePath+FileUtils.pathSeparator+filePath);
}else {
logger.error("查找没有版本更新");
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=error.txt")
.header("error","noVersion")
.body(new ByteArrayResource(version.getBytes(StandardCharsets.UTF_8)));
}
String filePath= cosObjectSummary.getKey();
clientJarPath= ossService.download(filePath,tempFilePath+FileUtils.pathSeparator+filePath);
}
if(StringUtils.isNotEmpty(clientJarPath)){
// 构建文件路径
@ -599,11 +582,10 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
if (!file.exists()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.header("error" ,"noFile")
.body(new ByteArrayResource(version.getBytes()));
.body(new ByteArrayResource(jarFileName.getBytes()));
}
// 创建Resource对象
Resource resource = new FileSystemResource(file);
// 获取文件MIME类型
String contentType = null;
try {
@ -611,12 +593,12 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.header("error" ,"500")
.body(new ByteArrayResource(version.getBytes()));
.body(new ByteArrayResource(jarFileName.getBytes()));
}
if (contentType == null) {
contentType = "application/octet-stream";
}
redisService.set(RedisKey.SXCLIENTKEYVERSION,version);
// redisService.set(RedisKey.SXCLIENTKEYVERSION,jarFileName);
// 构建响应
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
@ -655,4 +637,43 @@ public class SyncThirdDataServiceImpl extends SyncBaseThirdSxAbstract implements
return new ThirdApiRes().success("成功",storeDbConfig);
}
@Override
public ThirdApiRes getStoreDataRelease(String appKey, String sign) {
if (StrUtil.isBlank(appKey) || StrUtil.isBlank(sign) ) {
return new ThirdApiRes().fail(1003, I18nUtil._("缺少必要参数!"));
}
// 验签appid必要参数判断
SyncApp syncAppO = syncAppService.getOne(new LambdaQueryWrapper<SyncApp>()
.select(SyncApp::getApp_key, SyncApp::getApp_secret,SyncApp::getStore_id)
.eq(SyncApp::getApp_key, appKey)
.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<Map.Entry> sme= resultMap.entrySet();
storeDataResultMap= sme.stream().filter(m->!(m.getValue().equals((double)0))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
return new ThirdApiRes().success("success",storeDataResultMap);
}
@Override
public void saveStoreRealeas(Map storeData) {
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);
}
}
}