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

@@ -178,7 +178,7 @@ async function resetAllSettings() {
async function handleClearCache() { async function handleClearCache() {
try { try {
await ElMessageBox.confirm( await ElMessageBox.confirm(
'确定要清理客户端缓存吗?将清除所有缓存数据、更新文件及相关状态(不影响登录状态)', '确定要清理客户端缓存吗?将清除本地设备的所有缓存数据、更新文件及相关状态(不影响登录状态)',
'确认清理', '确认清理',
{ {
confirmButtonText: '确定', confirmButtonText: '确定',
@@ -503,7 +503,7 @@ onMounted(() => {
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<span class="cache-desc">清理应用缓存数据不影响登录状态</span> <span class="cache-desc">清理本地设备的所有缓存数据不影响登录状态</span>
<el-button <el-button
type="primary" type="primary"
size="small" size="small"

View File

@@ -10,7 +10,7 @@
</parent> </parent>
<groupId>com.tashow.erp</groupId> <groupId>com.tashow.erp</groupId>
<artifactId>erp_client_sb</artifactId> <artifactId>erp_client_sb</artifactId>
<version>2.5.3</version> <version>2.5.5</version>
<name>erp_client_sb</name> <name>erp_client_sb</name>
<description>erp客户端</description> <description>erp客户端</description>
<properties> <properties>

View File

@@ -42,7 +42,17 @@ public class AmazonController {
@GetMapping("/products/latest") @GetMapping("/products/latest")
public JsonData getLatestProducts(HttpServletRequest request) { public JsonData getLatestProducts(HttpServletRequest request) {
String username = JwtUtil.getUsernameFromRequest(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())); return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
} }

View File

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

View File

@@ -64,10 +64,10 @@ public interface AmazonProductRepository extends JpaRepository<AmazonProductEnti
List<AmazonProductEntity> findLatestProducts(); 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) @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); List<AmazonProductEntity> findLatestProducts(@Param("username") String username, @Param("region") String region);
/** /**
* 删除指定ASIN在指定时间后的数据用于清理12小时内重复 * 删除指定ASIN在指定时间后的数据用于清理12小时内重复

View File

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

View File

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

View File

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

View File

@@ -42,6 +42,7 @@ public class GenmaiAccountController {
public R<?> saveAccount(@RequestBody GenmaiAccount body, String name) { public R<?> saveAccount(@RequestBody GenmaiAccount body, String name) {
if (body.getId() == null) { if (body.getId() == null) {
String token = accountService.validateAndGetToken(body.getUsername(), body.getPassword()); String token = accountService.validateAndGetToken(body.getUsername(), body.getPassword());
if (token == null) throw new RuntimeException("账号密码验证失败,请检查后重试");
body.setToken(token); body.setToken(token);
body.setTokenExpireAt(new Date(System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000)); body.setTokenExpireAt(new Date(System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000));
} }

View File

@@ -7,6 +7,7 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@@ -76,10 +77,11 @@ public class GenmaiAccountServiceImpl implements IGenmaiAccountService {
@Override @Override
public boolean validateToken(String token) { public boolean validateToken(String token) {
try { try {
HttpEntity<String> entity = new HttpEntity<>(null, null); HttpHeaders headers = new HttpHeaders();
headers.set("Token", token);
ResponseEntity<String> response = restTemplate.exchange( ResponseEntity<String> response = restTemplate.exchange(
"https://www.genmaijl.com/manage/user/balance", "https://www.genmaijl.com/manage/user/balance",
HttpMethod.GET, entity, String.class); HttpMethod.GET, new HttpEntity<>(headers), String.class);
return objectMapper.readTree(response.getBody()).get("code").asInt() == 0; return objectMapper.readTree(response.getBody()).get("code").asInt() == 0;
} catch (Exception e) { } catch (Exception e) {
return false; return false;