feat(client): 实现用户数据隔离与设备绑定优化- 添加用户会话ID构建逻辑,确保数据按用户隔离- 优化设备绑定流程,支持设备状态更新和绑定时间同步- 实现用户缓存清理功能,仅清除当前用户的数据- 增强客户端账号删除逻辑,级联删除相关数据
- 调整设备在线查询逻辑,确保只返回活跃绑定的设备 - 优化试用期逻辑,精确计算过期时间和类型- 添加账号管理弹窗和相关状态注入 -修复跟卖精灵按钮加载状态显示问题 - 增强文件上传区域UI,显示选中文件名 - 调整分页组件样式,优化界面展示效果- 优化反馈日志存储路径逻辑,默认使用用户目录 - 移除冗余代码和无用导入,提升代码整洁度
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
<groupId>com.tashow.erp</groupId>
|
||||
<artifactId>erp_client_sb</artifactId>
|
||||
<version>2.4.9</version>
|
||||
<version>2.5.2</version>
|
||||
<name>erp_client_sb</name>
|
||||
<description>erp客户端</description>
|
||||
<properties>
|
||||
@@ -95,11 +95,6 @@
|
||||
<artifactId>selenium-java</artifactId>
|
||||
<version>4.23.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.bonigarcia</groupId>
|
||||
<artifactId>webdrivermanager</artifactId>
|
||||
<version>5.9.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT parsing for local RS256 verification -->
|
||||
<dependency>
|
||||
|
||||
@@ -4,11 +4,13 @@ import com.tashow.erp.repository.AmazonProductRepository;
|
||||
import com.tashow.erp.service.AmazonScrapingService;
|
||||
import com.tashow.erp.utils.ExcelParseUtil;
|
||||
import com.tashow.erp.utils.JsonData;
|
||||
import com.tashow.erp.utils.JwtUtil;
|
||||
import com.tashow.erp.utils.LoggerUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -22,18 +24,25 @@ public class AmazonController {
|
||||
private AmazonProductRepository amazonProductRepository;
|
||||
|
||||
@PostMapping("/products/batch")
|
||||
public JsonData batchGetProducts(@RequestBody Map<String, Object> request) {
|
||||
public JsonData batchGetProducts(@RequestBody Map<String, Object> request, HttpServletRequest httpRequest) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> asinList = (List<String>) request.get("asinList");
|
||||
String batchId = (String) request.get("batchId");
|
||||
String region = (String) request.getOrDefault("region", "JP");
|
||||
List<AmazonProductEntity> products = amazonScrapingService.batchGetProductInfo(asinList, batchId, region);
|
||||
|
||||
// 从 token 中获取 username
|
||||
String username = JwtUtil.getUsernameFromRequest(httpRequest);
|
||||
// 构建带用户隔离的 sessionId
|
||||
String userSessionId = JwtUtil.buildUserSessionId(username, batchId);
|
||||
|
||||
List<AmazonProductEntity> products = amazonScrapingService.batchGetProductInfo(asinList, userSessionId, region);
|
||||
return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
|
||||
}
|
||||
|
||||
@GetMapping("/products/latest")
|
||||
public JsonData getLatestProducts() {
|
||||
List<AmazonProductEntity> products = amazonProductRepository.findLatestProducts();
|
||||
public JsonData getLatestProducts(HttpServletRequest request) {
|
||||
String username = JwtUtil.getUsernameFromRequest(request);
|
||||
List<AmazonProductEntity> products = amazonProductRepository.findLatestProducts(username);
|
||||
return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ package com.tashow.erp.controller;
|
||||
import com.tashow.erp.repository.BanmaOrderRepository;
|
||||
import com.tashow.erp.service.BanmaOrderService;
|
||||
import com.tashow.erp.utils.JsonData;
|
||||
import com.tashow.erp.utils.JwtUtil;
|
||||
import com.tashow.erp.utils.LoggerUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -27,9 +29,15 @@ public class BanmaOrderController {
|
||||
@RequestParam(defaultValue = "1", name = "page") int page,
|
||||
@RequestParam(defaultValue = "10", name = "pageSize") int pageSize,
|
||||
@RequestParam("batchId") String batchId,
|
||||
@RequestParam(required = false, name = "shopIds") String shopIds) {
|
||||
@RequestParam(required = false, name = "shopIds") String shopIds,
|
||||
HttpServletRequest request) {
|
||||
// 从 token 中获取 username
|
||||
String username = JwtUtil.getUsernameFromRequest(request);
|
||||
// 构建带用户隔离的 sessionId
|
||||
String userSessionId = JwtUtil.buildUserSessionId(username, batchId);
|
||||
|
||||
List<String> shopIdList = shopIds != null ? java.util.Arrays.asList(shopIds.split(",")) : null;
|
||||
Map<String, Object> result = banmaOrderService.getOrdersByPage(accountId, startDate, endDate, page, pageSize, batchId, shopIdList);
|
||||
Map<String, Object> result = banmaOrderService.getOrdersByPage(accountId, startDate, endDate, page, pageSize, userSessionId, shopIdList);
|
||||
return result.containsKey("success") && !(Boolean)result.get("success")
|
||||
? JsonData.buildError((String)result.get("message"))
|
||||
: JsonData.buildSuccess(result);
|
||||
@@ -41,9 +49,10 @@ public class BanmaOrderController {
|
||||
}
|
||||
|
||||
@GetMapping("/orders/latest")
|
||||
public JsonData getLatestOrders() {
|
||||
public JsonData getLatestOrders(HttpServletRequest request) {
|
||||
String username = JwtUtil.getUsernameFromRequest(request);
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
List<Map<String, Object>> orders = banmaOrderRepository.findLatestOrders()
|
||||
List<Map<String, Object>> orders = banmaOrderRepository.findLatestOrders(username)
|
||||
.parallelStream()
|
||||
.map(entity -> {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
package com.tashow.erp.controller;
|
||||
|
||||
import com.tashow.erp.model.RakutenProduct;
|
||||
import com.tashow.erp.model.SearchResult;
|
||||
import com.tashow.erp.service.Alibaba1688Service;
|
||||
@@ -8,14 +7,14 @@ import com.tashow.erp.service.RakutenScrapingService;
|
||||
import com.tashow.erp.utils.DataReportUtil;
|
||||
import com.tashow.erp.utils.ExcelParseUtil;
|
||||
import com.tashow.erp.utils.JsonData;
|
||||
import com.tashow.erp.utils.JwtUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rakuten")
|
||||
@Slf4j
|
||||
@@ -30,8 +29,13 @@ public class RakutenController {
|
||||
private DataReportUtil dataReportUtil;
|
||||
|
||||
@PostMapping(value = "/products")
|
||||
public JsonData getProducts(@RequestParam("file") MultipartFile file, @RequestParam(value = "batchId", required = false) String batchId) {
|
||||
public JsonData getProducts(@RequestParam("file") MultipartFile file, @RequestParam(value = "batchId", required = false) String batchId, HttpServletRequest request) {
|
||||
try {
|
||||
// 从 token 中获取 username
|
||||
String username = JwtUtil.getUsernameFromRequest(request);
|
||||
// 构建带用户隔离的 sessionId
|
||||
String userSessionId = JwtUtil.buildUserSessionId(username, batchId);
|
||||
|
||||
List<String> shopNames = ExcelParseUtil.parseFirstColumn(file);
|
||||
if (CollectionUtils.isEmpty(shopNames)) {
|
||||
return JsonData.buildError("Excel文件中未解析到店铺名");
|
||||
@@ -39,10 +43,10 @@ public class RakutenController {
|
||||
List<RakutenProduct> allProducts = new ArrayList<>();
|
||||
List<String> skippedShops = new ArrayList<>();
|
||||
for (String currentShopName : shopNames) {
|
||||
if (rakutenCacheService.hasRecentData(currentShopName)) {
|
||||
if (rakutenCacheService.hasRecentData(currentShopName, username)) {
|
||||
// 从缓存获取
|
||||
List<RakutenProduct> cached = rakutenCacheService.getProductsByShopName(currentShopName).stream().filter(p -> currentShopName.equals(p.getOriginalShopName())).toList();
|
||||
rakutenCacheService.updateSpecificProductsSessionId(cached, batchId);
|
||||
List<RakutenProduct> cached = rakutenCacheService.getProductsByShopName(currentShopName, username).stream().filter(p -> currentShopName.equals(p.getOriginalShopName())).toList();
|
||||
rakutenCacheService.updateSpecificProductsSessionId(cached, userSessionId);
|
||||
allProducts.addAll(cached);
|
||||
skippedShops.add(currentShopName);
|
||||
log.info("使用缓存数据,店铺: {},数量: {}", currentShopName, cached.size());
|
||||
@@ -57,7 +61,7 @@ public class RakutenController {
|
||||
}
|
||||
List<RakutenProduct> newProducts = allProducts.stream().filter(p -> !skippedShops.contains(p.getOriginalShopName())).toList();
|
||||
if (!newProducts.isEmpty()) {
|
||||
rakutenCacheService.saveProductsWithSessionId(newProducts, batchId);
|
||||
rakutenCacheService.saveProductsWithSessionId(newProducts, userSessionId);
|
||||
}
|
||||
int cachedCount = allProducts.size() - newProducts.size();
|
||||
if (cachedCount > 0) {
|
||||
@@ -77,12 +81,17 @@ public class RakutenController {
|
||||
}
|
||||
|
||||
@PostMapping("/search1688")
|
||||
public JsonData search1688(@RequestBody Map<String, Object> params) {
|
||||
public JsonData search1688(@RequestBody Map<String, Object> params, HttpServletRequest request) {
|
||||
String imageUrl = (String) params.get("imageUrl");
|
||||
String sessionId = (String) params.get("sessionId");
|
||||
try {
|
||||
// 从 token 中获取 username
|
||||
String username = JwtUtil.getUsernameFromRequest(request);
|
||||
// 构建带用户隔离的 sessionId
|
||||
String userSessionId = JwtUtil.buildUserSessionId(username, sessionId);
|
||||
|
||||
SearchResult result = alibaba1688Service.get1688Detail(imageUrl);
|
||||
rakutenScrapingService.update1688DataByImageUrl(result, sessionId, imageUrl);
|
||||
rakutenScrapingService.update1688DataByImageUrl(result, userSessionId, imageUrl);
|
||||
return JsonData.buildSuccess(result);
|
||||
} catch (Exception e) {
|
||||
log.error("1688识图搜索失败", e);
|
||||
@@ -91,9 +100,10 @@ public class RakutenController {
|
||||
}
|
||||
|
||||
@GetMapping("/products/latest")
|
||||
public JsonData getLatestProducts() {
|
||||
public JsonData getLatestProducts(HttpServletRequest request) {
|
||||
try {
|
||||
List<Map<String, Object>> products = rakutenScrapingService.getLatestProductsForDisplay();
|
||||
String username = JwtUtil.getUsernameFromRequest(request);
|
||||
List<Map<String, Object>> products = rakutenScrapingService.getLatestProductsForDisplay(username);
|
||||
return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
|
||||
} catch (Exception e) {
|
||||
log.error("获取最新商品数据失败", e);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.tashow.erp.controller;
|
||||
|
||||
import com.tashow.erp.entity.AuthTokenEntity;
|
||||
import com.tashow.erp.repository.AuthTokenRepository;
|
||||
import com.tashow.erp.service.CacheService;
|
||||
import com.tashow.erp.service.impl.GenmaiServiceImpl;
|
||||
import com.tashow.erp.utils.JsonData;
|
||||
import com.tashow.erp.utils.LoggerUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -13,7 +13,6 @@ import org.springframework.http.*;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/system")
|
||||
public class SystemController {
|
||||
@@ -68,7 +67,6 @@ public class SystemController {
|
||||
public JsonData getLocalIp() throws Exception {
|
||||
return JsonData.buildSuccess(java.net.InetAddress.getLocalHost().getHostAddress());
|
||||
}
|
||||
|
||||
@GetMapping("/computer-name")
|
||||
public JsonData getComputerName() {
|
||||
return JsonData.buildSuccess(System.getenv("COMPUTERNAME"));
|
||||
@@ -124,7 +122,6 @@ public class SystemController {
|
||||
responseHeaders.setContentType(MediaType.IMAGE_JPEG);
|
||||
}
|
||||
responseHeaders.setCacheControl("max-age=3600");
|
||||
|
||||
return new ResponseEntity<>(response.getBody(), responseHeaders, HttpStatus.OK);
|
||||
} catch (Exception e) {
|
||||
logger.error("代理图片失败: {}", imageUrl, e);
|
||||
@@ -133,8 +130,9 @@ public class SystemController {
|
||||
}
|
||||
|
||||
@PostMapping("/cache/clear")
|
||||
public JsonData clearCache() {
|
||||
cacheService.clearCache();
|
||||
public JsonData clearCache(HttpServletRequest request) {
|
||||
String username = com.tashow.erp.utils.JwtUtil.getUsernameFromRequest(request);
|
||||
cacheService.clearCache(username);
|
||||
return JsonData.buildSuccess("缓存清理成功");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,12 @@ public interface AmazonProductRepository extends JpaRepository<AmazonProductEnti
|
||||
*/
|
||||
@Query(value = "SELECT * FROM amazon_products WHERE session_id = (SELECT session_id FROM amazon_products GROUP BY session_id ORDER BY session_id DESC LIMIT 1) ORDER BY updated_at ", nativeQuery = true)
|
||||
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);
|
||||
|
||||
/**
|
||||
* 删除指定ASIN在指定时间后的数据(用于清理12小时内重复)
|
||||
|
||||
@@ -66,6 +66,12 @@ public interface BanmaOrderRepository extends JpaRepository<BanmaOrderEntity, Lo
|
||||
*/
|
||||
@Query(value = "SELECT * FROM banma_orders WHERE session_id = (SELECT session_id FROM banma_orders ORDER BY created_at DESC LIMIT 1) ORDER BY updated_at ASC, id ASC", nativeQuery = true)
|
||||
List<BanmaOrderEntity> findLatestOrders();
|
||||
|
||||
/**
|
||||
* 获取指定用户最新会话的订单数据(按用户隔离)
|
||||
*/
|
||||
@Query(value = "SELECT * FROM banma_orders WHERE session_id = (SELECT session_id FROM banma_orders WHERE session_id LIKE :username || '#%' ORDER BY created_at DESC LIMIT 1) ORDER BY updated_at ASC, id ASC", nativeQuery = true)
|
||||
List<BanmaOrderEntity> findLatestOrders(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* 删除指定追踪号在指定时间后的数据(用于清理12小时内重复)
|
||||
|
||||
@@ -52,6 +52,16 @@ public interface RakutenProductRepository extends JpaRepository<RakutenProductEn
|
||||
* 检查指定店铺在指定时间后是否有数据
|
||||
*/
|
||||
boolean existsByOriginalShopNameAndCreatedAtAfter(String originalShopName, LocalDateTime sinceTime);
|
||||
|
||||
/**
|
||||
* 检查指定店铺在指定时间后是否有数据(按用户隔离)
|
||||
*/
|
||||
boolean existsByOriginalShopNameAndSessionIdStartingWithAndCreatedAtAfter(String originalShopName, String sessionIdPrefix, LocalDateTime sinceTime);
|
||||
|
||||
/**
|
||||
* 根据店铺名和 sessionId 前缀获取产品(按用户隔离)
|
||||
*/
|
||||
List<RakutenProductEntity> findByOriginalShopNameAndSessionIdStartingWithOrderByCreatedAtAscIdAsc(String originalShopName, String sessionIdPrefix);
|
||||
|
||||
/**
|
||||
* 获取最新的会话ID
|
||||
@@ -64,6 +74,12 @@ public interface RakutenProductRepository extends JpaRepository<RakutenProductEn
|
||||
*/
|
||||
@Query(value = "SELECT * FROM rakuten_products WHERE session_id = (SELECT session_id FROM rakuten_products ORDER BY created_at DESC LIMIT 1) ORDER BY created_at ASC, id ASC", nativeQuery = true)
|
||||
List<RakutenProductEntity> findLatestProducts();
|
||||
|
||||
/**
|
||||
* 获取指定用户最新会话的产品数据(按用户隔离)
|
||||
*/
|
||||
@Query(value = "SELECT * FROM rakuten_products WHERE session_id = (SELECT session_id FROM rakuten_products WHERE session_id LIKE :username || '#%' ORDER BY created_at DESC LIMIT 1) ORDER BY created_at ASC, id ASC", nativeQuery = true)
|
||||
List<RakutenProductEntity> findLatestProducts(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* 删除指定商品URL在指定时间后的数据(用于清理12小时内重复)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
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;
|
||||
@@ -8,6 +11,7 @@ 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
|
||||
@@ -45,14 +49,39 @@ public class CacheService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void clearCache() {
|
||||
rakutenProductRepository.deleteAll();
|
||||
amazonProductRepository.deleteAll();
|
||||
alibaba1688ProductRepository.deleteAll();
|
||||
zebraOrderRepository.deleteAll();
|
||||
banmaOrderRepository.deleteAll();
|
||||
cacheDataRepository.deleteAll();
|
||||
updateStatusRepository.deleteAll();
|
||||
public void clearCache(String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
logger.warn("尝试清理缓存但未提供用户名,操作已跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("开始清理用户缓存: {}", username);
|
||||
|
||||
// 清理当前用户的 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());
|
||||
|
||||
// 清理当前用户的 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 订单数据
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ public interface RakutenCacheService {
|
||||
/**
|
||||
* 检查店铺是否有最近的数据(12小时内)
|
||||
*/
|
||||
boolean hasRecentData(String shopName);
|
||||
boolean hasRecentData(String shopName, String username);
|
||||
|
||||
/**
|
||||
* 根据店铺名获取已有数据
|
||||
*/
|
||||
List<RakutenProduct> getProductsByShopName(String shopName);
|
||||
List<RakutenProduct> getProductsByShopName(String shopName, String username);
|
||||
|
||||
/**
|
||||
* 更新指定店铺的产品sessionId
|
||||
|
||||
@@ -32,9 +32,9 @@ public interface RakutenScrapingService {
|
||||
void update1688DataByImageUrl(SearchResult searchResult, String sessionId, String imageUrl);
|
||||
|
||||
/**
|
||||
* 获取最新产品数据并转换为前端格式
|
||||
* 获取最新产品数据并转换为前端格式(按用户隔离)
|
||||
*/
|
||||
List<Map<String, Object>> getLatestProductsForDisplay();
|
||||
List<Map<String, Object>> getLatestProductsForDisplay(String username);
|
||||
|
||||
|
||||
}
|
||||
@@ -243,7 +243,6 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片并获取图片ID
|
||||
* @return
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.tashow.erp.service.impl;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qiniu.util.UrlUtils;
|
||||
import io.github.bonigarcia.wdm.WebDriverManager;
|
||||
import org.openqa.selenium.JavascriptExecutor;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.chrome.ChromeDriver;
|
||||
@@ -30,7 +29,6 @@ public class GenmaiServiceImpl {
|
||||
Runtime.getRuntime().exec("taskkill /f /im chrome.exe");
|
||||
Thread.sleep(1000); // 等待进程关闭
|
||||
|
||||
WebDriverManager.chromedriver().setup();
|
||||
ChromeOptions options = new ChromeOptions();
|
||||
String username = System.getProperty("user.name", "user");
|
||||
String safeUsername;
|
||||
|
||||
@@ -80,23 +80,30 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查店铺是否有1小时内的缓存数据
|
||||
* 检查店铺是否有1小时内的缓存数据(按用户隔离)
|
||||
*/
|
||||
@Override
|
||||
public boolean hasRecentData(String shopName) {
|
||||
boolean hasRecent = repository.existsByOriginalShopNameAndCreatedAtAfter(shopName, LocalDateTime.now().minusHours(1));
|
||||
public boolean hasRecentData(String shopName, String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean hasRecent = repository.existsByOriginalShopNameAndSessionIdStartingWithAndCreatedAtAfter(
|
||||
shopName, username + "#", LocalDateTime.now().minusHours(1));
|
||||
if (hasRecent) {
|
||||
log.info("店铺 {} 存在1小时内缓存数据,将使用缓存", shopName);
|
||||
log.info("店铺 {} 存在1小时内缓存数据(用户: {}),将使用缓存", shopName, username);
|
||||
}
|
||||
return hasRecent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据店铺名获取所有产品数据
|
||||
* 根据店铺名获取所有产品数据(按用户隔离)
|
||||
*/
|
||||
@Override
|
||||
public List<RakutenProduct> getProductsByShopName(String shopName) {
|
||||
return repository.findByOriginalShopNameOrderByCreatedAtAscIdAsc(shopName).stream()
|
||||
public List<RakutenProduct> getProductsByShopName(String shopName, String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return repository.findByOriginalShopNameAndSessionIdStartingWithOrderByCreatedAtAscIdAsc(shopName, username + "#").stream()
|
||||
.map(entity -> {
|
||||
RakutenProduct product = new RakutenProduct();
|
||||
BeanUtils.copyProperties(entity, product);
|
||||
|
||||
@@ -121,8 +121,9 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
||||
public void update1688DataByImageUrl(SearchResult searchResult, String sessionId, String imageUrl) {
|
||||
List<RakutenProductEntity> matchingProducts = rakutenProductRepository.findBySessionIdOrderByCreatedAtAscIdAsc(sessionId).stream().filter(product -> imageUrl.equals(product.getImgUrl())).peek(product -> {
|
||||
product.setMapRecognitionLink(searchResult.getMapRecognitionLink());
|
||||
product.setImage1688Url(searchResult.getMapRecognitionLink());
|
||||
product.setDetailUrl1688(searchResult.getMapRecognitionLink());
|
||||
product.setFreight(searchResult.getFreight());
|
||||
product.setSkuPriceJson(searchResult.getSkuPrice().toString());
|
||||
product.setMedian(searchResult.getMedian());
|
||||
product.setWeight(searchResult.getWeight());
|
||||
product.setSkuPriceJson(JSON.toJSONString(searchResult.getSkuPrice()));
|
||||
@@ -133,8 +134,11 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getLatestProductsForDisplay() {
|
||||
return rakutenProductRepository.findLatestProducts().stream().map(entity -> {
|
||||
public List<Map<String, Object>> getLatestProductsForDisplay(String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return rakutenProductRepository.findLatestProducts(username).stream().map(entity -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("originalShopName", entity.getOriginalShopName());
|
||||
result.put("productUrl", entity.getProductUrl());
|
||||
@@ -142,12 +146,14 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
||||
result.put("productTitle", entity.getProductTitle());
|
||||
result.put("price", entity.getPrice());
|
||||
result.put("ranking", entity.getRanking());
|
||||
result.put("price1688", entity.getPrice1688());
|
||||
result.put("image1688Url", entity.getImage1688Url());
|
||||
result.put("detailUrl1688", entity.getDetailUrl1688());
|
||||
result.put("mapRecognitionLink", entity.getMapRecognitionLink());
|
||||
result.put("freight", entity.getFreight());
|
||||
result.put("median", entity.getMedian());
|
||||
result.put("weight", entity.getWeight());
|
||||
result.put("skuPriceJson", entity.getSkuPriceJson());
|
||||
result.put("skuPrice", entity.getSkuPriceJson());
|
||||
return result;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
108
erp_client_sb/src/main/java/com/tashow/erp/utils/JwtUtil.java
Normal file
108
erp_client_sb/src/main/java/com/tashow/erp/utils/JwtUtil.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package com.tashow.erp.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* JWT 工具类
|
||||
* 用于从 HTTP 请求中解析 JWT token 并提取用户信息
|
||||
*/
|
||||
public class JwtUtil {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 从 HTTP 请求中提取 username
|
||||
* @param request HTTP 请求对象
|
||||
* @return username,如果解析失败返回空字符串
|
||||
*/
|
||||
public static String getUsernameFromRequest(HttpServletRequest request) {
|
||||
try {
|
||||
String token = extractToken(request);
|
||||
if (token == null || token.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return extractUsernameFromToken(token);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 token 字符串中提取 username
|
||||
* @param token JWT token
|
||||
* @return username
|
||||
*/
|
||||
public static String extractUsernameFromToken(String token) {
|
||||
try {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]));
|
||||
JsonNode payload = objectMapper.readTree(payloadJson);
|
||||
|
||||
// 尝试从 username 或 sub 字段获取用户名
|
||||
if (payload.has("username")) {
|
||||
return payload.get("username").asText();
|
||||
} else if (payload.has("sub")) {
|
||||
return payload.get("sub").asText();
|
||||
}
|
||||
return "";
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 HTTP 请求中提取 token
|
||||
*/
|
||||
private static String extractToken(HttpServletRequest request) {
|
||||
// 从 Authorization header 获取
|
||||
String auth = request.getHeader("Authorization");
|
||||
if (auth != null && auth.startsWith("Bearer ")) {
|
||||
return auth.substring(7).trim();
|
||||
}
|
||||
|
||||
// 从 Cookie 获取
|
||||
if (request.getCookies() != null) {
|
||||
for (jakarta.servlet.http.Cookie cookie : request.getCookies()) {
|
||||
if ("FX_TOKEN".equals(cookie.getName()) && cookie.getValue() != null) {
|
||||
return cookie.getValue().trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建带用户隔离的 sessionId
|
||||
* @param username 用户名
|
||||
* @param originalSessionId 原始 sessionId
|
||||
* @return 格式化的 sessionId: username#originalSessionId
|
||||
*/
|
||||
public static String buildUserSessionId(String username, String originalSessionId) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
return originalSessionId;
|
||||
}
|
||||
return username + "#" + originalSessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 sessionId 中提取原始 sessionId(去除用户前缀)
|
||||
* @param userSessionId 带用户前缀的 sessionId
|
||||
* @return 原始 sessionId
|
||||
*/
|
||||
public static String extractOriginalSessionId(String userSessionId) {
|
||||
if (userSessionId == null || !userSessionId.contains("#")) {
|
||||
return userSessionId;
|
||||
}
|
||||
int idx = userSessionId.indexOf("#");
|
||||
return userSessionId.substring(idx + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 固定日志路径到系统公共数据目录 -->
|
||||
<property name="LOG_HOME" value="C:/ProgramData/erp-logs" />
|
||||
<!-- 使用 Spring Boot 传递的日志路径 -->
|
||||
<property name="LOG_HOME" value="${LOG_PATH:-logs}" />
|
||||
|
||||
<!--
|
||||
日志上报说明:
|
||||
日志文件按天滚动存储在 ${LOG_HOME} 目录下
|
||||
格式:spring-boot-yyyy-MM-dd.log
|
||||
|
||||
用户反馈系统集成说明:
|
||||
- 客户端应用(Electron)可以读取本地日志文件
|
||||
- 用户在"设置-反馈"页面提交反馈时,可选择附带某一天的日志文件
|
||||
- 日志文件将随反馈内容一起上传到服务器
|
||||
- 服务器存储路径:C:/ProgramData/erp-logs/feedback/ (Windows) 或 /opt/erp/feedback-logs/ (Linux)
|
||||
- 管理员可在后台管理界面查看反馈并下载相应日志文件
|
||||
|
||||
日志保留策略:
|
||||
- 本地日志保留30天
|
||||
- 总大小限制1GB
|
||||
- 超过限制时自动删除最旧的日志文件
|
||||
日志说明:
|
||||
- 日志文件按天滚动存储在 ${LOG_HOME} 目录下
|
||||
- 格式:spring-boot-yyyy-MM-dd.log
|
||||
- 客户端应用可读取本地日志文件用于反馈
|
||||
- 日志保留30天,总大小限制1GB
|
||||
-->
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
|
||||
Reference in New Issue
Block a user