feat(erp): 实现按用户地区隔离的最新产品查询逻辑

- 修改AmazonController以支持按用户和区域筛选最新会话数据
- 更新AmazonProductRepository中的findLatestProducts方法,增加region参数实现数据隔离
-优化AmazonScrapingServiceImpl的数据处理流程,增强缓存清理机制
- 调整GenmaiServiceImpl的token验证逻辑并改进Chrome启动配置- 升级系统版本至2.5.5并完善相关依赖管理
- 改进前端设置对话框中关于缓存清理描述的信息准确性
-重构SystemController接口,移除不必要的用户名参数传递- 强化GenmaiAccountController和服务层的安全校验逻辑
This commit is contained in:
2025-10-27 13:34:25 +08:00
parent 0be60bc103
commit 7e065c1a0b
10 changed files with 62 additions and 62 deletions

View File

@@ -42,7 +42,17 @@ public class AmazonController {
@GetMapping("/products/latest")
public JsonData getLatestProducts(HttpServletRequest request) {
String username = JwtUtil.getUsernameFromRequest(request);
List<AmazonProductEntity> products = amazonProductRepository.findLatestProducts(username);
List<String> recentSessions = amazonProductRepository.findRecentSessionIds(org.springframework.data.domain.PageRequest.of(0, 1));
String latestSession = recentSessions.stream()
.filter(sid -> sid != null && sid.startsWith(username + "#"))
.findFirst()
.orElse("");
if (latestSession.isEmpty()) {
return JsonData.buildSuccess(Map.of("products", List.of(), "total", 0));
}
List<AmazonProductEntity> products = amazonProductRepository.findBySessionIdOrderByCreatedAtDesc(latestSession);
return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
}

View File

@@ -83,9 +83,10 @@ public class SystemController {
}
@PostMapping("/genmai/open")
public JsonData openGenmaiWebsite(@RequestParam(required = false) Long accountId) {
public JsonData openGenmaiWebsite(@RequestParam(required = false) Long accountId, HttpServletRequest request) {
try {
genmaiService.openGenmaiWebsite(accountId);
String username = com.tashow.erp.utils.JwtUtil.getUsernameFromRequest(request);
genmaiService.openGenmaiWebsite(accountId, username);
return JsonData.buildSuccess("跟卖精灵已打开");
} catch (Exception e) {
logger.error("打开跟卖精灵失败", e);
@@ -130,9 +131,8 @@ public class SystemController {
}
@PostMapping("/cache/clear")
public JsonData clearCache(HttpServletRequest request) {
String username = com.tashow.erp.utils.JwtUtil.getUsernameFromRequest(request);
cacheService.clearCache(username);
public JsonData clearCache() {
cacheService.clearCache();
return JsonData.buildSuccess("缓存清理成功");
}
}

View File

@@ -64,10 +64,10 @@ public interface AmazonProductRepository extends JpaRepository<AmazonProductEnti
List<AmazonProductEntity> findLatestProducts();
/**
* 获取指定用户最新会话的产品数据(按用户隔离)
* 获取指定用户最新会话的产品数据(按用户和地区隔离)
*/
@Query(value = "SELECT * FROM amazon_products WHERE session_id = (SELECT session_id FROM amazon_products WHERE session_id LIKE :username || '#%' GROUP BY session_id ORDER BY session_id DESC LIMIT 1) ORDER BY updated_at ", nativeQuery = true)
List<AmazonProductEntity> findLatestProducts(@Param("username") String username);
@Query(value = "SELECT * FROM amazon_products WHERE session_id = (SELECT session_id FROM amazon_products WHERE session_id LIKE :username || '#%' AND region = :region GROUP BY session_id ORDER BY session_id DESC LIMIT 1) ORDER BY updated_at ", nativeQuery = true)
List<AmazonProductEntity> findLatestProducts(@Param("username") String username, @Param("region") String region);
/**
* 删除指定ASIN在指定时间后的数据用于清理12小时内重复

View File

@@ -1,9 +1,6 @@
package com.tashow.erp.service;
import com.tashow.erp.entity.AuthTokenEntity;
import com.tashow.erp.entity.RakutenProductEntity;
import com.tashow.erp.entity.AmazonProductEntity;
import com.tashow.erp.entity.BanmaOrderEntity;
import com.tashow.erp.repository.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -11,7 +8,6 @@ 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.List;
import java.util.Optional;
@Service
@@ -49,39 +45,26 @@ public class CacheService {
}
@Transactional
public void clearCache(String username) {
if (username == null || username.isEmpty()) {
logger.warn("尝试清理缓存但未提供用户名,操作已跳过");
return;
}
public void clearCache() {
logger.info("开始清理用户缓存: {}", username);
// 清理所有产品数据
rakutenProductRepository.deleteAll();
// 清理当前用户的 Rakuten 产品数据
List<RakutenProductEntity> rakutenProducts = rakutenProductRepository.findAll();
List<RakutenProductEntity> userRakutenProducts = rakutenProducts.stream()
.filter(p -> p.getSessionId() != null && p.getSessionId().startsWith(username + "#"))
.toList();
rakutenProductRepository.deleteAll(userRakutenProducts);
logger.info("已清理 {} 条 Rakuten 产品数据", userRakutenProducts.size());
amazonProductRepository.deleteAll();
// 清理当前用户的 Amazon 产品数据
List<AmazonProductEntity> amazonProducts = amazonProductRepository.findAll();
List<AmazonProductEntity> userAmazonProducts = amazonProducts.stream()
.filter(p -> p.getSessionId() != null && p.getSessionId().startsWith(username + "#"))
.toList();
amazonProductRepository.deleteAll(userAmazonProducts);
logger.info("已清理 {} 条 Amazon 产品数据", userAmazonProducts.size());
alibaba1688ProductRepository.deleteAll();
// 清理当前用户的 Banma 订单数据
List<BanmaOrderEntity> banmaOrders = banmaOrderRepository.findAll();
List<BanmaOrderEntity> userBanmaOrders = banmaOrders.stream()
.filter(o -> o.getSessionId() != null && o.getSessionId().startsWith(username + "#"))
.toList();
banmaOrderRepository.deleteAll(userBanmaOrders);
logger.info("已清理 {} 条 Banma 订单数据", userBanmaOrders.size());
logger.info("用户 {} 的缓存清理完成", username);
// 清理所有订单数据
banmaOrderRepository.deleteAll();
zebraOrderRepository.deleteAll();
// 清理通用缓存和更新状态
cacheDataRepository.deleteAll();
}
}

View File

@@ -61,7 +61,6 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
if (isEmpty(seller)) {
seller = html.xpath("//span[@class='a-size-small offer-display-feature-text-message']/text()").toString();
}
// 关键数据为空时重试
if (isEmpty(price) && isEmpty(seller)) {
throw new RuntimeException("Retry this page");
@@ -94,6 +93,8 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
String sessionId = (batchId != null) ? batchId : "SINGLE_" + UUID.randomUUID();
LocalDateTime batchTime = LocalDateTime.now();
resultCache.clear();
// 第一步清理1小时前的所有旧数据
amazonProductRepository.deleteAllDataBefore(LocalDateTime.now().minusHours(1));
@@ -110,7 +111,12 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
Optional<AmazonProductEntity> cached = amazonProductRepository.findByAsinAndRegion(cleanAsin, region);
if (cached.isPresent() && !isEmpty(cached.get().getPrice()) && !isEmpty(cached.get().getSeller())) {
AmazonProductEntity entity = cached.get();
AmazonProductEntity cachedEntity = cached.get();
AmazonProductEntity entity = new AmazonProductEntity();
entity.setAsin(cachedEntity.getAsin());
entity.setRegion(cachedEntity.getRegion());
entity.setPrice(cachedEntity.getPrice());
entity.setSeller(cachedEntity.getSeller());
entity.setSessionId(sessionId);
entity.setUpdatedAt(LocalDateTime.now());
amazonProductRepository.save(entity);

View File

@@ -19,17 +19,17 @@ public class GenmaiServiceImpl {
private final RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
public void openGenmaiWebsite(Long accountId) throws Exception {
String token = getAndValidateToken(accountId);
public void openGenmaiWebsite(Long accountId, String username) throws Exception {
String token = getAndValidateToken(accountId, username);
Runtime.getRuntime().exec("taskkill /f /im chrome.exe");
Thread.sleep(1000);
ChromeOptions options = new ChromeOptions();
String username = System.getProperty("user.name");
char firstChar = username.charAt(0);
String systemUser = System.getProperty("user.name");
char firstChar = systemUser.charAt(0);
char flipped = Character.isUpperCase(firstChar) ? Character.toLowerCase(firstChar) : Character.toUpperCase(firstChar);
String chromeUserData = System.getProperty("user.home")
.replace(username, UrlUtils.urlEncode(flipped + username.substring(1)))
.replace(systemUser, UrlUtils.urlEncode(flipped + systemUser.substring(1)))
+ "\\AppData\\Local\\Google\\Chrome\\User Data";
options.addArguments("user-data-dir=" + chromeUserData.toLowerCase(), "profile-directory=Default");
@@ -40,8 +40,9 @@ public class GenmaiServiceImpl {
}
@SuppressWarnings("unchecked")
private String getAndValidateToken(Long accountId) {
Map<String, Object> resp = restTemplate.getForObject(serverApiUrl + "/tool/genmai/accounts", Map.class);
private String getAndValidateToken(Long accountId, String username) {
String url = serverApiUrl + "/tool/genmai/accounts?name=" + username;
Map<String, Object> resp = restTemplate.getForObject(url, Map.class);
List<Map<String, Object>> list = (List<Map<String, Object>>) resp.get("data");
Map<String, Object> account = accountId != null
@@ -49,12 +50,10 @@ public class GenmaiServiceImpl {
: list.get(0);
String token = (String) account.get("token");
Long id = ((Number) account.get("id")).longValue();
// 验证token是否有效
if (!validateToken(token)) {
// token无效调用服务器刷新
restTemplate.postForObject(serverApiUrl + "/tool/genmai/accounts/" + id + "/validate", null, Map.class);
// 重新获取token
resp = restTemplate.getForObject(serverApiUrl + "/tool/genmai/accounts", Map.class);
resp = restTemplate.getForObject(url, Map.class);
list = (List<Map<String, Object>>) resp.get("data");
account = list.stream().filter(m -> id.equals(((Number) m.get("id")).longValue())).findFirst().orElse(null);
token = (String) account.get("token");
@@ -69,8 +68,7 @@ public class GenmaiServiceImpl {
ResponseEntity<String> response = restTemplate.exchange(
"https://www.genmaijl.com/manage/user/balance",
HttpMethod.GET, new HttpEntity<>(headers), String.class);
JsonNode root = objectMapper.readTree(response.getBody());
return root.get("code").asInt() == 0;
return objectMapper.readTree(response.getBody()).get("code").asInt() == 0;
} catch (Exception e) {
return false;
}