refactor(api):重构API服务接口与实现

- 移除多余的接口定义文件,简化依赖关系- 更新控制器和服务实现类的注入方式-优化请求参数处理逻辑
- 统一响应数据结构格式- 调整方法签名以提高一致性
- 删除冗余注释和无用代码- 修改系统API调用路径引用位置
- 简化认证服务实现并移除不必要的抽象层
- 优化Excel文件解析相关功能
- 清理无用的工具类和配置项
- 调整错误上报机制的依赖注入方式
- 更新跟卖精灵服务的实现细节- 优化HTTP请求工具函数结构
- 移除废弃的缓存管理服务接口定义
- 调整设备配额检查逻辑复用性
- 优化订单服务的数据返回格式
- 更新产品服务中的数据处理方式
- 重构客户端账户控制器中的设备限制检查逻辑
This commit is contained in:
2025-10-21 10:15:33 +08:00
parent 17f03c3ade
commit 281ae6a846
29 changed files with 237 additions and 599 deletions

View File

@@ -1,35 +1,17 @@
import { http } from './http';
export const amazonApi = {
// 上传Excel文件解析ASIN列表
importAsinFromExcel(file: File) {
const formData = new FormData();
formData.append('file', file);
return http.upload<{ code: number, data: { asinList: string[], total: number }, msg: string | null }>('/api/amazon/import/asin', formData);
},
getProductsBatch(asinList: string[], batchId: string, region: string) {
return http.post<{ code: number, data: { products: any[] }, msg: string | null }>('/api/amazon/products/batch', { asinList, batchId, region });
getProductsBatch(asinList: string[], batchId: string, region: string, signal?: AbortSignal) {
return http.post<{ code: number, data: { products: any[] }, msg: string | null }>('/api/amazon/products/batch', { asinList, batchId, region }, signal);
},
getLatestProducts() {
return http.get<{ code: number, data: { products: any[] }, msg: string | null }>('/api/amazon/products/latest');
},
getProductsByBatch(batchId: string) {
return http.get<{ products: any[] }>(`/api/amazon/products/batch/${batchId}`);
},
updateProduct(productData: unknown) {
return http.post('/api/amazon/products/update', productData);
},
deleteProduct(productId: string) {
return http.post('/api/amazon/products/delete', { id: productId });
},
getProductStats() {
return http.get('/api/amazon/stats');
},
searchProducts(searchParams: Record<string, unknown>) {
return http.get('/api/amazon/products/search', searchParams);
},
openGenmaiSpirit() {
return http.post('/api/system/genmai/open');
},
}
};

View File

@@ -1,16 +1,13 @@
// HTTP 工具:统一管理后端服务配置和请求
export type HttpMethod = 'GET' | 'POST' | 'DELETE';
// 集中管理所有后端服务配置
export const CONFIG = {
CLIENT_BASE: 'http://localhost:8081',
// RUOYI_BASE: 'http://192.168.1.89:8085',
RUOYI_BASE: 'http://8.138.23.49:8085',
SSE_URL: 'http://8.138.23.49:8085/monitor/account/events'
//RUOYI_BASE: 'http://8.138.23.49:8085',
RUOYI_BASE: 'http://192.168.1.89:8085',
SSE_URL: 'http://192.168.1.89:8085/monitor/account/events'
} as const;
function resolveBase(path: string): string {
// RuoYi 后端路径:鉴权、设备、反馈、版本、工具
if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma')) {
return CONFIG.RUOYI_BASE;
}
@@ -26,16 +23,17 @@ function buildQuery(params?: Record<string, unknown>): string {
return query.toString() ? `?${query}` : '';
}
async function request<T>(path: string, options: RequestInit): Promise<T> {
// 获取token
let token = '';
async function getToken(): Promise<string> {
try {
const tokenModule = await import('../utils/token');
token = tokenModule.getToken() || '';
} catch (e) {
console.warn('获取token失败:', e);
return tokenModule.getToken() || '';
} catch {
return '';
}
}
async function request<T>(path: string, options: RequestInit & { signal?: AbortSignal }): Promise<T> {
const token = await getToken();
const res = await fetch(`${resolveBase(path)}${path}`, {
credentials: 'omit',
cache: 'no-store',
@@ -55,9 +53,6 @@ async function request<T>(path: string, options: RequestInit): Promise<T> {
const contentType = res.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
const json: any = await res.json();
// 业务状态码判断:支持两种格式
// - erp_client_sb (本地服务): code=0 表示成功
// - RuoYi 后端: code=200 表示成功
if (json.code !== undefined && json.code !== 0 && json.code !== 200) {
throw new Error(json.msg || '请求失败');
}
@@ -68,13 +63,14 @@ async function request<T>(path: string, options: RequestInit): Promise<T> {
}
export const http = {
get<T>(path: string, params?: Record<string, unknown>) {
return request<T>(`${path}${buildQuery(params)}`, { method: 'GET' });
get<T>(path: string, params?: Record<string, unknown>, signal?: AbortSignal) {
return request<T>(`${path}${buildQuery(params)}`, { method: 'GET', signal });
},
post<T>(path: string, body?: unknown) {
post<T>(path: string, body?: unknown, signal?: AbortSignal) {
return request<T>(path, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined
body: body ? JSON.stringify(body) : undefined,
signal
});
},
@@ -82,32 +78,22 @@ export const http = {
return request<T>(path, { method: 'DELETE' });
},
async upload<T>(path: string, form: FormData) {
// 获取token
let token = '';
try {
const tokenModule = await import('../utils/token');
token = tokenModule.getToken() || '';
} catch (e) {
console.warn('获取token失败:', e);
}
const headers: Record<string, string> = {};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return fetch(`${resolveBase(path)}${path}`, {
async upload<T>(path: string, form: FormData, signal?: AbortSignal) {
const token = await getToken();
const res = await fetch(`${resolveBase(path)}${path}`, {
method: 'POST',
body: form,
credentials: 'omit',
cache: 'no-store',
headers
}).then(async res => {
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
signal
});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(text || `HTTP ${res.status}`);
}
const contentType = res.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
const json: any = await res.json();
@@ -117,7 +103,6 @@ export const http = {
return json as T;
}
return (await res.text()) as unknown as T;
});
}
};

View File

@@ -0,0 +1,12 @@
import { http } from './http';
export const systemApi = {
openGenmaiSpirit() {
return http.post('/api/system/genmai/open');
},
clearCache() {
return http.post('/api/system/cache/clear');
}
};

View File

@@ -18,23 +18,11 @@ export const zebraApi = {
return http.get('/api/banma/shops', params as Record<string, unknown>)
},
getOrders(params: any) {
return http.get('/api/banma/orders', params as Record<string, unknown>)
},
getOrdersByBatch(batchId: string) {
return http.get(`/api/banma/orders/batch/${batchId}`)
getOrders(params: any, signal?: AbortSignal) {
return http.get('/api/banma/orders', params as Record<string, unknown>, signal)
},
getLatestOrders() {
return http.get('/api/banma/orders/latest')
},
getOrderStats() {
return http.get('/api/banma/orders/stats')
},
searchOrders(searchParams: Record<string, unknown>) {
return http.get('/api/banma/orders/search', searchParams)
}
}

View File

@@ -2,6 +2,7 @@
import { ref, computed, onMounted, defineAsyncComponent, inject } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { amazonApi } from '../../api/amazon'
import { systemApi } from '../../api/system'
import { handlePlatformFileExport } from '../../utils/settings'
const TrialExpiredDialog = defineAsyncComponent(() => import('../common/TrialExpiredDialog.vue'))
@@ -21,6 +22,7 @@ const progressVisible = ref(false) // 进度条是否显示(完成后仍保留
const localProductData = ref<any[]>([]) // 本地产品数据
const currentAsin = ref('') // 当前处理的ASIN
const genmaiLoading = ref(false) // Genmai Spirit加载状态
let abortController: AbortController | null = null // 请求取消控制器
// 分页配置
const currentPage = ref(1)
@@ -130,7 +132,7 @@ async function batchGetProductInfo(asinList: string[]) {
currentAsin.value = `正在处理第${i + 1}/${totalBatches}批 (${batchAsins.join(', ')})`
try {
const result = await amazonApi.getProductsBatch(batchAsins, batchId, region.value)
const result = await amazonApi.getProductsBatch(batchAsins, batchId, region.value, abortController?.signal)
if (result?.data?.products?.length > 0) {
localProductData.value.push(...result.data.products)
@@ -142,7 +144,8 @@ async function batchGetProductInfo(asinList: string[]) {
const actualCount = result?.data?.products?.length || 0
failedCount += Math.max(0, expectedCount - actualCount)
} catch (error) {
} catch (error: any) {
if (error.name === 'AbortError') break
failedCount += batchAsins.length
console.error(`批次${i + 1}失败:`, error)
}
@@ -164,8 +167,10 @@ async function batchGetProductInfo(asinList: string[]) {
} catch (error: any) {
if (error.name !== 'AbortError') {
showMessage(error.message || '批量获取产品信息失败', 'error')
currentAsin.value = '处理失败'
}
} finally {
tableLoading.value = false
}
@@ -177,6 +182,7 @@ async function startQueuedFetch() {
showMessage('请先导入ASIN列表', 'warning')
return
}
abortController = new AbortController()
loading.value = true
progressVisible.value = true
tableLoading.value = true
@@ -185,6 +191,7 @@ async function startQueuedFetch() {
} finally {
tableLoading.value = false
loading.value = false
abortController = null
}
}
@@ -240,12 +247,13 @@ function isOutOfStock(product: any) {
// 停止获取操作
function stopFetch() {
abortController?.abort()
abortController = null
loading.value = false
currentAsin.value = '已停止'
showMessage('已停止获取产品数据', 'info')
}
// 打开Genmai Spirit工具
async function openGenmaiSpirit() {
try {
await ElMessageBox.confirm('打开跟卖精灵会关闭所有谷歌浏览器进程,是否继续?', '提示', {
@@ -255,7 +263,7 @@ async function openGenmaiSpirit() {
})
genmaiLoading.value = true
try {
await amazonApi.openGenmaiSpirit()
await systemApi.openGenmaiSpirit()
} catch (error: any) {
showMessage(error.message || '打开跟卖精灵失败', 'error')
} finally {

View File

@@ -40,6 +40,7 @@ const fetchCurrentPage = ref(1)
const fetchTotalPages = ref(0)
const fetchTotalItems = ref(0)
const isFetching = ref(false)
let abortController: AbortController | null = null
// 试用期过期弹框
const showTrialExpiredDialog = ref(false)
@@ -113,6 +114,7 @@ async function fetchData() {
return
}
abortController = new AbortController()
loading.value = true
isFetching.value = true
showProgress.value = true
@@ -130,7 +132,7 @@ async function fetchPageData(startDate: string, endDate: string) {
if (!isFetching.value) return
try {
const data = await zebraApi.getOrders({
const response = await zebraApi.getOrders({
accountId: Number(accountId.value) || undefined,
startDate,
endDate,
@@ -138,8 +140,9 @@ async function fetchPageData(startDate: string, endDate: string) {
pageSize: 50,
shopIds: selectedShops.value.join(','),
batchId: currentBatchId.value
})
}, abortController?.signal)
const data = (response as any)?.data || response
const orders = data.orders || []
allOrderData.value = [...allOrderData.value, ...orders]
@@ -154,8 +157,10 @@ async function fetchPageData(startDate: string, endDate: string) {
progressPercentage.value = 100
finishFetching()
}
} catch (e) {
} catch (e: any) {
if (e.name !== 'AbortError') {
console.error('获取订单数据失败:', e)
}
finishFetching()
}
}
@@ -163,6 +168,7 @@ async function fetchPageData(startDate: string, endDate: string) {
function finishFetching() {
isFetching.value = false
loading.value = false
abortController = null
// 确保进度条完全填满
progressPercentage.value = 100
currentPage.value = 1
@@ -170,6 +176,8 @@ function finishFetching() {
}
function stopFetch() {
abortController?.abort()
abortController = null
isFetching.value = false
loading.value = false
// 进度条保留显示,不自动隐藏

View File

@@ -1,63 +1,42 @@
package com.tashow.erp.controller;
import com.tashow.erp.entity.AmazonProductEntity;
import com.tashow.erp.repository.AmazonProductRepository;
import com.tashow.erp.service.IAmazonScrapingService;
import com.tashow.erp.service.AmazonScrapingService;
import com.tashow.erp.utils.ExcelParseUtil;
import com.tashow.erp.utils.ExcelExportUtil;
import com.tashow.erp.utils.JsonData;
import com.tashow.erp.utils.LoggerUtil;
import com.tashow.erp.fx.controller.JavaBridge;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.Optional;
@RestController
@RequestMapping("/api/amazon")
public class AmazonController {
private static final Logger logger = LoggerUtil.getLogger(AmazonController.class);
@Autowired
private IAmazonScrapingService amazonScrapingService;
private AmazonScrapingService amazonScrapingService;
@Autowired
private AmazonProductRepository amazonProductRepository;
/**
* 批量获取亚马逊产品信息
*/
@PostMapping("/products/batch")
public JsonData batchGetProducts(@RequestBody Object request) {
public JsonData batchGetProducts(@RequestBody Map<String, Object> request) {
@SuppressWarnings("unchecked")
Map<String, Object> requestMap = (Map<String, Object>) request;
List<String> asinList = (List<String>) requestMap.get("asinList");
String batchId = (String) requestMap.get("batchId");
String region = (String) requestMap.getOrDefault("region", "JP");
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);
Map<String, Object> result = new HashMap<>();
result.put("products", products);
result.put("total", products.size());
return JsonData.buildSuccess(result);
return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
}
/**
* 获取最新产品数据
*/
@GetMapping("/products/latest")
public JsonData getLatestProducts() {
List<AmazonProductEntity> products = amazonProductRepository.findLatestProducts();
Map<String, Object> result = new HashMap<>();
result.put("products", products);
result.put("total", products.size());
return JsonData.buildSuccess(result);
return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
}
/**
* 解析Excel文件获取ASIN列表
*/
@PostMapping("/import/asin")
public JsonData importAsinFromExcel(@RequestParam("file") MultipartFile file) {
try {
@@ -65,16 +44,10 @@ public class AmazonController {
if (asinList.isEmpty()) {
return JsonData.buildError("未从文件中解析到ASIN数据");
}
Map<String, Object> result = new HashMap<>();
result.put("asinList", asinList);
result.put("total", asinList.size());
return JsonData.buildSuccess(result);
return JsonData.buildSuccess(Map.of("asinList", asinList, "total", asinList.size()));
} catch (Exception e) {
logger.error("解析文件失败: {}", e.getMessage(), e);
return JsonData.buildError("解析失败: " + e.getMessage());
}
}
}

View File

@@ -1,57 +1,45 @@
package com.tashow.erp.controller;
import com.tashow.erp.fx.controller.JavaBridge;
import com.tashow.erp.repository.BanmaOrderRepository;
import com.tashow.erp.service.IBanmaOrderService;
import com.tashow.erp.utils.ExcelExportUtil;
import com.tashow.erp.service.BanmaOrderService;
import com.tashow.erp.utils.JsonData;
import com.tashow.erp.utils.LoggerUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/banma")
public class BanmaOrderController {
private static final Logger logger = LoggerUtil.getLogger(BanmaOrderController.class);
@Autowired
IBanmaOrderService banmaOrderService;
BanmaOrderService banmaOrderService;
@Autowired
BanmaOrderRepository banmaOrderRepository;
@GetMapping("/orders")
public ResponseEntity<Map<String, Object>> getOrders(
public JsonData getOrders(
@RequestParam(required = false, name = "accountId") Long accountId,
@RequestParam(required = false, name = "startDate") String startDate,
@RequestParam(required = false, name = "endDate") String endDate,
@RequestParam(defaultValue = "1", name = "page") int page,
@RequestParam(defaultValue = "10", name = "pageSize") int pageSize,
@RequestParam( "batchId") String batchId,
@RequestParam("batchId") String batchId,
@RequestParam(required = false, name = "shopIds") String shopIds) {
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);
return ResponseEntity.ok(result);
return result.containsKey("success") && !(Boolean)result.get("success")
? JsonData.buildError((String)result.get("message"))
: JsonData.buildSuccess(result);
}
/**
* 获取店铺列表
*/
@GetMapping("/shops")
public JsonData getShops(@RequestParam(required = false, name = "accountId") Long accountId) {
try {
Map<String, Object> response = banmaOrderService.getShops(accountId);
return JsonData.buildSuccess(response);
} catch (Exception e) {
logger.error("获取店铺列表失败: {}", e.getMessage(), e);
return JsonData.buildError("获取店铺列表失败: " + e.getMessage());
}
}
/**
* 获取最新订单数据
*/
@GetMapping("/orders/latest")
public JsonData getLatestOrders() {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
@@ -68,8 +56,6 @@ public class BanmaOrderController {
})
.filter(order -> !order.isEmpty())
.toList();
return JsonData.buildSuccess(Map.of("orders", orders, "total", orders.size()));
}
}

View File

@@ -2,31 +2,19 @@ package com.tashow.erp.controller;
import com.tashow.erp.model.RakutenProduct;
import com.tashow.erp.model.SearchResult;
import com.tashow.erp.repository.RakutenProductRepository;
import com.tashow.erp.service.Alibaba1688Service;
import com.tashow.erp.service.IRakutenCacheService;
import com.tashow.erp.service.RakutenCacheService;
import com.tashow.erp.service.RakutenScrapingService;
import com.tashow.erp.service.impl.Alibaba1688ServiceImpl;
import com.tashow.erp.utils.DataReportUtil;
import com.tashow.erp.utils.ExcelParseUtil;
import com.tashow.erp.utils.JsonData;
import com.tashow.erp.utils.QiniuUtil;
import com.tashow.erp.fx.controller.JavaBridge;
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 org.springframework.web.client.RestTemplate;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@RestController
@RequestMapping("/api/rakuten")
@@ -37,19 +25,10 @@ public class RakutenController {
@Autowired
private Alibaba1688Service alibaba1688Service;
@Autowired
private IRakutenCacheService rakutenCacheService;
@Autowired
private JavaBridge javaBridge;
private RakutenCacheService rakutenCacheService;
@Autowired
private DataReportUtil dataReportUtil;
/**
* 获取乐天商品数据
*
* @param file Excel文件首列为店铺名
* @param batchId 可选,批次号
* @return JsonData 响应
*/
@PostMapping(value = "/products")
public JsonData getProducts(@RequestParam("file") MultipartFile file, @RequestParam(value = "batchId", required = false) String batchId) {
try {
@@ -80,21 +59,23 @@ public class RakutenController {
if (!newProducts.isEmpty()) {
rakutenCacheService.saveProductsWithSessionId(newProducts, batchId);
}
// 4. 上报缓存数据使用情况
int cachedCount = allProducts.size() - newProducts.size();
if (cachedCount > 0) {
dataReportUtil.reportDataCollection("RAKUTEN_CACHE", cachedCount, "0");
}
return JsonData.buildSuccess(Map.of("products", allProducts, "total", allProducts.size(), "sessionId", batchId, "skippedShops", skippedShops, "newProductsCount", newProducts.size()));
return JsonData.buildSuccess(Map.of(
"products", allProducts,
"total", allProducts.size(),
"sessionId", batchId,
"skippedShops", skippedShops,
"newProductsCount", newProducts.size()
));
} catch (Exception e) {
log.error("获取乐天商品失败", e);
return JsonData.buildError("获取乐天商品失败: " + e.getMessage());
}
}
/**
* 1688识图搜索API - 自动保存1688搜索结果
*/
@PostMapping("/search1688")
public JsonData search1688(@RequestBody Map<String, Object> params) {
String imageUrl = (String) params.get("imageUrl");
@@ -109,21 +90,14 @@ public class RakutenController {
}
}
@GetMapping("/products/latest")
public JsonData getLatestProducts() {
try {
List<Map<String, Object>> products = rakutenScrapingService.getLatestProductsForDisplay();
return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
} catch (Exception e) {
e.printStackTrace();
log.info("获取最新商品数据失败", e);
log.error("获取最新商品数据失败", e);
return JsonData.buildError("获取最新数据失败: " + e.getMessage());
}
}
}

View File

@@ -2,49 +2,36 @@ package com.tashow.erp.controller;
import com.tashow.erp.entity.AuthTokenEntity;
import com.tashow.erp.repository.AuthTokenRepository;
import com.tashow.erp.service.impl.CacheManagementServiceImpl;
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 org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
/**
* 系统级接口控制器
* 整合:认证、配置、版本、工具、代理等功能
*/
@RestController
@RequestMapping("/api/system")
public class SystemController {
private static final Logger logger = LoggerUtil.getLogger(SystemController.class);
@Autowired
private AuthTokenRepository authTokenRepository;
@Autowired
private GenmaiServiceImpl genmaiService;
@Autowired
private CacheManagementServiceImpl cacheManagementService;
private CacheService cacheService;
@Autowired
private RestTemplate restTemplate;
@Value("${project.version:2.3.6}")
private String currentVersion;
@Value("${project.build.time:}")
private String buildTime;
@Value("${api.server.base-url}")
private String serverBaseUrl;
/**
* 保存认证密钥
*/
@PostMapping("/auth/save")
public JsonData saveAuth(@RequestBody Map<String, Object> data) {
String serviceName = (String) data.get("serviceName");
@@ -52,17 +39,13 @@ public class SystemController {
if (serviceName == null || authKey == null) {
return JsonData.buildError("serviceName和authKey不能为空");
}
AuthTokenEntity entity = authTokenRepository.findByServiceName(serviceName)
.orElse(new AuthTokenEntity());
AuthTokenEntity entity = authTokenRepository.findByServiceName(serviceName).orElse(new AuthTokenEntity());
entity.setServiceName(serviceName);
entity.setToken(authKey);
authTokenRepository.save(entity);
return JsonData.buildSuccess("认证信息保存成功");
}
/**
* 获取认证密钥
*/
@GetMapping("/auth/get")
public JsonData getAuth(@RequestParam String serviceName) {
return JsonData.buildSuccess(authTokenRepository.findByServiceName(serviceName)
@@ -70,82 +53,41 @@ public class SystemController {
.orElse(null));
}
/**
* 删除认证密钥
*/
@DeleteMapping("/auth/remove")
public JsonData removeAuth(@RequestParam String serviceName) {
authTokenRepository.findByServiceName(serviceName)
.ifPresent(authTokenRepository::delete);
authTokenRepository.findByServiceName(serviceName).ifPresent(authTokenRepository::delete);
return JsonData.buildSuccess("认证信息删除成功");
}
// ==================== 设备管理 ====================
/**
* 获取设备ID
*/
@GetMapping("/device-id")
public JsonData getDeviceId() {
String deviceId = com.tashow.erp.utils.DeviceUtils.generateDeviceId();
return JsonData.buildSuccess(deviceId);
return JsonData.buildSuccess(com.tashow.erp.utils.DeviceUtils.generateDeviceId());
}
/**
* 获取本机内网IP地址
*/
@GetMapping("/local-ip")
public JsonData getLocalIp() {
try {
java.net.InetAddress localHost = java.net.InetAddress.getLocalHost();
return JsonData.buildSuccess(localHost.getHostAddress());
return JsonData.buildSuccess(java.net.InetAddress.getLocalHost().getHostAddress());
} catch (Exception e) {
return JsonData.buildSuccess("127.0.0.1");
}
}
// ==================== 版本信息 ====================
/**
* 获取当前版本号
*/
@GetMapping("/version")
public Map<String, Object> getVersion() {
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("currentVersion", currentVersion);
result.put("buildTime", buildTime);
return result;
return Map.of("success", true, "currentVersion", currentVersion, "buildTime", buildTime);
}
// ==================== 配置信息 ====================
/**
* 获取服务器配置
*/
@GetMapping("/config/server")
public Map<String, Object> getServerConfig() {
return Map.of(
"baseUrl", serverBaseUrl,
"sseUrl", serverBaseUrl + "/monitor/account/events"
);
return Map.of("baseUrl", serverBaseUrl, "sseUrl", serverBaseUrl + "/monitor/account/events");
}
// ==================== 工具功能 ====================
/**
* 打开跟卖精灵网页
*/
@PostMapping("/genmai/open")
public void openGenmaiWebsite() {
genmaiService.openGenmaiWebsite();
}
// ==================== 图片代理 ====================
/**
* 代理获取图片解决CORS跨域问题
*/
@GetMapping("/proxy/image")
public ResponseEntity<byte[]> proxyImage(@RequestParam("url") String imageUrl) {
if (imageUrl == null || imageUrl.isEmpty()) {
@@ -178,24 +120,15 @@ public class SystemController {
return new ResponseEntity<>(response.getBody(), responseHeaders, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
logger.error("代理图片失败: {}", imageUrl, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// ==================== 缓存管理 ====================
/**
* 清理缓存
*/
@PostMapping("/cache/clear")
public JsonData clearCache() {
try {
cacheManagementService.clearCache();
cacheService.clearCache();
return JsonData.buildSuccess("缓存清理成功");
} catch (Exception e) {
return JsonData.buildError("清理缓存失败: " + e.getMessage());
}
}
}

View File

@@ -1,7 +1,7 @@
package com.tashow.erp.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tashow.erp.service.IAuthService;
import com.tashow.erp.service.impl.AuthServiceImpl;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
@@ -10,16 +10,13 @@ import org.springframework.web.servlet.HandlerInterceptor;
import java.io.IOException;
import java.util.Base64;
/**
* 本地拦截器
*/
@Component
public class LocalJwtAuthInterceptor implements HandlerInterceptor {
private final IAuthService authService;
private final AuthServiceImpl authService;
private final ObjectMapper objectMapper = new ObjectMapper();
public LocalJwtAuthInterceptor(IAuthService authService) {
public LocalJwtAuthInterceptor(AuthServiceImpl authService) {
this.authService = authService;
}

View File

@@ -3,12 +3,7 @@ package com.tashow.erp.service;
import com.tashow.erp.entity.AmazonProductEntity;
import java.util.List;
/**
* 亚马逊数据采集服务接口
*
* @author ruoyi
*/
public interface IAmazonScrapingService {
public interface AmazonScrapingService {
/**
* 批量获取亚马逊产品信息
@@ -19,10 +14,5 @@ public interface IAmazonScrapingService {
* @return 产品信息列表
*/
List<AmazonProductEntity> batchGetProductInfo(List<String> asinList, String batchId, String region);
}

View File

@@ -3,12 +3,7 @@ package com.tashow.erp.service;
import java.util.List;
import java.util.Map;
/**
* 斑马订单服务接口
*
* @author ruoyi
*/
public interface IBanmaOrderService {
public interface BanmaOrderService {
// 客户端不再暴露刷新认证Token
@@ -30,3 +25,4 @@ public interface IBanmaOrderService {
*/
Map<String, Object> getOrdersByPage(Long accountId, String startDate, String endDate, int page, int pageSize, String batchId, List<String> shopIds);
}

View File

@@ -1,39 +1,49 @@
package com.tashow.erp.service.impl;
package com.tashow.erp.service;
import com.tashow.erp.entity.AuthTokenEntity;
import com.tashow.erp.repository.*;
import com.tashow.erp.service.ICacheManagementService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Optional;
/**
* 缓存管理服务实现
*/
@Service
public class CacheManagementServiceImpl implements ICacheManagementService {
public class CacheService {
private static final Logger logger = LoggerFactory.getLogger(CacheService.class);
@Autowired
private AuthTokenRepository authTokenRepository;
@Autowired
private RakutenProductRepository rakutenProductRepository;
@Autowired
private AmazonProductRepository amazonProductRepository;
@Autowired
private Alibaba1688ProductRepository alibaba1688ProductRepository;
@Autowired
private ZebraOrderRepository zebraOrderRepository;
@Autowired
private BanmaOrderRepository banmaOrderRepository;
@Autowired
private CacheDataRepository cacheDataRepository;
@Autowired
private UpdateStatusRepository updateStatusRepository;
@Override
public void saveAuthToken(String service, String token, long expireTimeMillis) {
try {
Optional<AuthTokenEntity> existing = authTokenRepository.findByServiceName(service);
AuthTokenEntity entity = existing.orElse(new AuthTokenEntity());
entity.setServiceName(service);
entity.setToken(token);
entity.setExpireTime(LocalDateTime.now().plusSeconds(expireTimeMillis / 1000));
authTokenRepository.save(entity);
} catch (Exception e) {
logger.error("保存认证令牌失败: {}", service, e);
}
}
@Transactional
public void clearCache() {
rakutenProductRepository.deleteAll();

View File

@@ -1,52 +0,0 @@
package com.tashow.erp.service;
import java.util.Map;
/**
* 认证服务接口
*/
public interface IAuthService {
/**
* 客户端登录认证
*/
Map<String, Object> login(String username, String password);
/**
* 获取客户端信息
*/
Map<String, Object> getClientInfo();
/**
* 上报错误
*/
void reportError(String errorType, String errorMessage, Exception e);
/**
* 检查版本更新
*/
String checkVersion(String currentVersion);
/**
* 获取客户端ID
*/
String getClientId();
void logout();
/**
* 验证token
*/
Map<String, Object> verifyToken(String token);
/**
* 注册新账号
*/
Map<String, Object> register(String username, String password);
/**
* 检查用户名是否可用
*/
boolean checkUsername(String username);
}

View File

@@ -1,12 +0,0 @@
package com.tashow.erp.service;
/**
* 缓存服务接口
*/
public interface ICacheService {
/**
* 保存认证令牌
*/
void saveAuthToken(String service, String token, long expireTimeMillis);
}

View File

@@ -1,18 +0,0 @@
package com.tashow.erp.service;
/**
* 跟卖精灵服务接口
*
* @author ruoyi
*/
public interface IGenmaiService {
/**
* 使用Playwright打开跟卖精灵网页
*
* @return 是否成功打开
*/
void openGenmaiWebsite();
}

View File

@@ -1,13 +1,9 @@
package com.tashow.erp.service;
import com.tashow.erp.model.RakutenProduct;
import java.util.List;
/**
* 乐天产品缓存服务接口
*/
public interface IRakutenCacheService {
public interface RakutenCacheService {
/**
* 保存产品数据
@@ -19,8 +15,6 @@ public interface IRakutenCacheService {
*/
void saveProductsWithSessionId(List<RakutenProduct> products, String sessionId);
/**
* 检查店铺是否有最近的数据12小时内
*/
@@ -45,6 +39,5 @@ public interface IRakutenCacheService {
* 清理指定店铺12小时内的重复数据为新采集做准备
*/
void cleanRecentDuplicateData(String shopName);
}

View File

@@ -1,6 +1,7 @@
package com.tashow.erp.service.impl;
import com.tashow.erp.entity.AmazonProductEntity;
import com.tashow.erp.repository.AmazonProductRepository;
import com.tashow.erp.service.AmazonScrapingService;
import com.tashow.erp.utils.DataReportUtil;
import com.tashow.erp.utils.ErrorReporter;
import com.tashow.erp.utils.RakutenProxyUtil;
@@ -20,7 +21,7 @@ import java.util.concurrent.ConcurrentHashMap;
* @author ruoyi
*/
@Service
public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PageProcessor {
public class AmazonScrapingServiceImpl implements AmazonScrapingService, PageProcessor {
@Autowired
private AmazonProductRepository amazonProductRepository;
@Autowired
@@ -68,8 +69,9 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
if (isEmpty(price)) errorReporter.reportDataEmpty("amazon", asin, price);
if (isEmpty(seller)) errorReporter.reportDataEmpty("amazon", asin, seller);
AmazonProductEntity entity = new AmazonProductEntity();
entity.setAsin(asin != null ? asin : "");
entity.setAsin(asin == null ? "" : asin);
entity.setPrice(price);
entity.setSeller(seller);
resultCache.put(asin, entity);

View File

@@ -1,24 +1,17 @@
package com.tashow.erp.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tashow.erp.service.IAuthService;
import com.tashow.erp.utils.ApiForwarder;
import com.tashow.erp.utils.DeviceUtils;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 认证服务实现类 - 简化版
* 仅提供错误上报和客户端信息获取功能
*/
@Service
public class AuthServiceImpl implements IAuthService {
public class AuthServiceImpl {
@Value("${project.version:2.1.0}")
private String appVersion;
@@ -29,10 +22,6 @@ public class AuthServiceImpl implements IAuthService {
@Getter
private String clientId = DeviceUtils.generateDeviceId();
/**
* 获取客户端基本信息
*/
@Override
public Map<String, Object> getClientInfo() {
Map<String, Object> info = new HashMap<>();
info.put("clientId", clientId);
@@ -41,10 +30,6 @@ public class AuthServiceImpl implements IAuthService {
return info;
}
/**
* 上报错误信息
*/
@Override
public void reportError(String errorType, String errorMessage, Exception e) {
try {
Map<String, Object> errorData = new HashMap<>();

View File

@@ -2,6 +2,8 @@ package com.tashow.erp.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tashow.erp.entity.BanmaOrderEntity;
import com.tashow.erp.repository.BanmaOrderRepository;
import com.tashow.erp.service.BanmaOrderService;
import com.tashow.erp.service.CacheService;
import com.tashow.erp.utils.DataReportUtil;
import com.tashow.erp.utils.ErrorReporter;
import com.tashow.erp.utils.LoggerUtil;
@@ -24,7 +26,7 @@ import java.util.stream.Collectors;
* @author ruoyi
*/
@Service
public class BanmaOrderServiceImpl {
public class BanmaOrderServiceImpl implements BanmaOrderService {
private static final Logger logger = LoggerUtil.getLogger(BanmaOrderServiceImpl.class);
private static final String API_URL = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&_t=%d";
private static final String API_URL_WITH_TIME = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&orderedAtStart=%s&orderedAtEnd=%s&_t=%d";
@@ -34,7 +36,7 @@ public class BanmaOrderServiceImpl {
private String ruoyiAdminBase;
private RestTemplate restTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
private final CacheServiceImpl cacheService;
private final CacheService cacheService;
private final BanmaOrderRepository banmaOrderRepository;
private final DataReportUtil dataReportUtil;
private final ErrorReporter errorReporter;
@@ -44,7 +46,7 @@ public class BanmaOrderServiceImpl {
private String currentBatchSessionId = null;
// 物流信息缓存,避免重复查询
private final Map<String, String> trackingInfoCache = new ConcurrentHashMap<>();
public BanmaOrderServiceImpl(BanmaOrderRepository banmaOrderRepository, CacheServiceImpl cacheService, DataReportUtil dataReportUtil, ErrorReporter errorReporter) {
public BanmaOrderServiceImpl(BanmaOrderRepository banmaOrderRepository, CacheService cacheService, DataReportUtil dataReportUtil, ErrorReporter errorReporter) {
this.banmaOrderRepository = banmaOrderRepository;
this.cacheService = cacheService;
this.dataReportUtil = dataReportUtil;
@@ -233,9 +235,6 @@ public class BanmaOrderServiceImpl {
return result;
}
/**
* 提取子订单字段
*/
private void extractSubOrderFields(Map<String, Object> simplifiedOrder, Map<String, Object> subOrder) {
String[] basicFields = {"orderedAt", "timeSinceOrder", "createdAt", "poTrackingNumber"};
String[] productFields = {"productTitle", "shopOrderNumber", "priceJpy", "productQuantity", "shippingFeeJpy", "productNumber", "serviceFee", "productImage"};
@@ -245,20 +244,11 @@ public class BanmaOrderServiceImpl {
Arrays.stream(purchaseFields).forEach(field -> simplifiedOrder.put(field, subOrder.get(field)));
}
/**
* 从API获取物流信息
*/
private String fetchTrackingInfo(String trackingNumber) {
// 优先尝试佐川物流
Map<String, Object> trackInfoMap = (Map<String, Object>) new SagawaExpressSdk().getTrackingInfo(trackingNumber).get("trackInfo");
if (trackInfoMap != null) {
return trackInfoMap.get("dateTime") + " " + trackInfoMap.get("office") + " " + trackInfoMap.get("status");
}
// 斑马API
ResponseEntity<Map> response = restTemplate.getForEntity(String.format(TRACKING_URL, trackingNumber), Map.class);
return Optional.ofNullable(response.getBody())
.filter(body -> Integer.valueOf(0).equals(body.get("code")))
@@ -268,9 +258,4 @@ public class BanmaOrderServiceImpl {
.map(track -> (String) track.get("track"))
.orElse("暂无物流信息");
}
}

View File

@@ -1,34 +0,0 @@
package com.tashow.erp.service.impl;
import com.tashow.erp.entity.AuthTokenEntity;
import com.tashow.erp.repository.AuthTokenRepository;
import com.tashow.erp.service.ICacheService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Optional;
@Service
public class CacheServiceImpl implements ICacheService {
private static final Logger logger = LoggerFactory.getLogger(CacheServiceImpl.class);
@Autowired
private AuthTokenRepository authTokenRepository;
@Override
public void saveAuthToken(String service, String token, long expireTimeMillis) {
try {
Optional<AuthTokenEntity> existing = authTokenRepository.findByServiceName(service);
AuthTokenEntity entity = existing.orElse(new AuthTokenEntity());
entity.setServiceName(service);
entity.setToken(token);
entity.setExpireTime(LocalDateTime.now().plusSeconds(expireTimeMillis / 1000));
authTokenRepository.save(entity);
} catch (Exception e) {
logger.error("保存认证令牌失败: {}", service, e);
}
}
}

View File

@@ -8,20 +8,15 @@ import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import com.tashow.erp.service.IGenmaiService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
/**
* 跟卖精灵服务实现类
*
* @author ruoyi
*/
@Service
public class GenmaiServiceImpl implements IGenmaiService {
public class GenmaiServiceImpl {
@Value("${api.server.base-url}")
private String serverApiUrl;
@Value("${api.server.paths.getGenmaijlToken}")
@@ -30,7 +25,6 @@ public class GenmaiServiceImpl implements IGenmaiService {
private String updateGenmaijlToken;
private final RestTemplate restTemplate = new RestTemplate();
ObjectMapper objectMapper = new ObjectMapper();
@Override
@SneakyThrows
public void openGenmaiWebsite() {
// 先关闭所有Chrome进程

View File

@@ -2,7 +2,7 @@ package com.tashow.erp.service.impl;
import com.tashow.erp.entity.RakutenProductEntity;
import com.tashow.erp.model.RakutenProduct;
import com.tashow.erp.repository.RakutenProductRepository;
import com.tashow.erp.service.IRakutenCacheService;
import com.tashow.erp.service.RakutenCacheService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -20,7 +20,7 @@ import java.util.stream.Collectors;
*/
@Slf4j
@Service
public class RakutenCacheServiceImpl implements IRakutenCacheService {
public class RakutenCacheServiceImpl implements RakutenCacheService {
@Autowired
private RakutenProductRepository repository;

View File

@@ -132,30 +132,24 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
}
}
/**
* 获取最新的产品数据用于前端展示
*/
@Override
public List<Map<String, Object>> getLatestProductsForDisplay() {
return rakutenProductRepository.findLatestProducts().stream().map(entity -> {
Map<String, Object> result = new HashMap<>();
result.put("originalShopName", Optional.ofNullable(entity.getOriginalShopName()).orElse(""));
result.put("productUrl", Optional.ofNullable(entity.getProductUrl()).orElse(""));
result.put("imgUrl", Optional.ofNullable(entity.getImgUrl()).orElse(""));
result.put("productTitle", Optional.ofNullable(entity.getProductTitle()).orElse(""));
result.put("price", Optional.ofNullable(entity.getPrice()).orElse(""));
result.put("ranking", Optional.ofNullable(entity.getRanking()).orElse(""));
result.put("image1688Url", Optional.ofNullable(entity.getImage1688Url()).orElse(""));
result.put("detailUrl1688", Optional.ofNullable(entity.getDetailUrl1688()).orElse(""));
// 使用正确的实体字段
result.put("mapRecognitionLink", Optional.ofNullable(entity.getMapRecognitionLink()).orElse(""));
result.put("originalShopName", entity.getOriginalShopName());
result.put("productUrl", entity.getProductUrl());
result.put("imgUrl", entity.getImgUrl());
result.put("productTitle", entity.getProductTitle());
result.put("price", entity.getPrice());
result.put("ranking", entity.getRanking());
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", Optional.ofNullable(entity.getWeight()).orElse(""));
result.put("skuPrice",entity.getSkuPriceJson());
result.put("weight", entity.getWeight());
result.put("skuPrice", entity.getSkuPriceJson());
return result;
}).collect(Collectors.toList());
}
}

View File

@@ -8,12 +8,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.tashow.erp.service.IAuthService;
/**
* 错误上报工具类 - 立即上报所有错误
*
* @author ERP
*/
import com.tashow.erp.service.impl.AuthServiceImpl;
@Component
public class ErrorReporter {
@@ -21,7 +17,7 @@ public class ErrorReporter {
private String serverUrl;
@Autowired
private IAuthService authService;
private AuthServiceImpl authService;
private final RestTemplate restTemplate = new RestTemplate();

View File

@@ -3,7 +3,6 @@ package com.ruoyi.web.controller.monitor;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import com.ruoyi.common.annotation.Anonymous;
import org.springframework.beans.factory.annotation.Autowired;
@@ -58,6 +57,17 @@ public class ClientAccountController extends BaseController {
@Autowired
private ClientDeviceMapper clientDeviceMapper;
private AjaxResult checkDeviceLimit(String username, String deviceId, int deviceLimit) {
List<ClientDevice> userDevices = clientDeviceMapper.selectByUsername(username);
int userDevice = userDevices.size();
boolean exists = userDevices.stream().anyMatch(d -> deviceId.equals(d.getDeviceId()));
if (exists) userDevice--;
if (userDevice >= deviceLimit) {
return AjaxResult.error("设备数量已达上限(" + deviceLimit + "个),请先移除其他设备");
}
return null;
}
/**
* 查询账号列表
*/
@@ -140,15 +150,8 @@ public class ClientAccountController extends BaseController {
return AjaxResult.error("账号已被停用");
}
// 检查设备数量限制
int deviceLimit = account.getDeviceLimit();
List<ClientDevice> userDevices = clientDeviceMapper.selectByUsername(username);
int userDevice = userDevices.size();
boolean exists = userDevices.stream().anyMatch(d -> clientId.equals(d.getDeviceId()));
if (exists) userDevice--;
if (userDevice >= deviceLimit) {
return AjaxResult.error("设备数量已达上限(" + deviceLimit + "个),请先移除其他设备");
}
AjaxResult limitCheck = checkDeviceLimit(username, clientId, account.getDeviceLimit());
if (limitCheck != null) return limitCheck;
String token = Jwts.builder()
.setHeaderParam("kid", jwtRsaKeyService.getKeyId())
.setSubject(username)
@@ -160,7 +163,6 @@ public class ClientAccountController extends BaseController {
.signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey())
.compact();
// 检查设备试用期仅对trial账号生效
boolean deviceTrialExpired = false;
if ("trial".equals(account.getAccountType())) {
ClientDevice device = clientDeviceMapper.selectByDeviceIdAndUsername(clientId, username);
@@ -169,14 +171,14 @@ public class ClientAccountController extends BaseController {
&& new Date().after(device.getTrialExpireTime());
}
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("permissions", account.getPermissions());
data.put("accountName", account.getAccountName());
data.put("expireTime", account.getExpireTime());
data.put("accountType", account.getAccountType());
data.put("deviceTrialExpired", deviceTrialExpired);
return AjaxResult.success(data);
return AjaxResult.success(Map.of(
"token", token,
"permissions", account.getPermissions(),
"accountName", account.getAccountName(),
"expireTime", account.getExpireTime(),
"accountType", account.getAccountType(),
"deviceTrialExpired", deviceTrialExpired
));
}
/**
* 验证token
@@ -202,7 +204,6 @@ public class ClientAccountController extends BaseController {
clientAccountService.updateClientAccount(account);
}
// 检查设备试用期仅对trial账号生效
boolean deviceTrialExpired = false;
if ("trial".equals(account.getAccountType())) {
ClientDevice device = clientDeviceMapper.selectByDeviceIdAndUsername(clientId, username);
@@ -211,14 +212,14 @@ public class ClientAccountController extends BaseController {
&& new Date().after(device.getTrialExpireTime());
}
Map<String, Object> result = new HashMap<>();
result.put("username", username);
result.put("permissions", account.getPermissions());
result.put("accountName", account.getAccountName());
result.put("expireTime", account.getExpireTime());
result.put("accountType", account.getAccountType());
result.put("deviceTrialExpired", deviceTrialExpired);
return AjaxResult.success(result);
return AjaxResult.success(Map.of(
"username", username,
"permissions", account.getPermissions(),
"accountName", account.getAccountName(),
"expireTime", account.getExpireTime(),
"accountType", account.getAccountType(),
"deviceTrialExpired", deviceTrialExpired
));
}
/**
@@ -276,14 +277,14 @@ public class ClientAccountController extends BaseController {
.signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey())
.compact();
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("permissions", clientAccount.getPermissions());
data.put("accountName", clientAccount.getAccountName());
data.put("expireTime", clientAccount.getExpireTime());
data.put("accountType", clientAccount.getAccountType());
data.put("deviceTrialExpired", false);
return AjaxResult.success(data);
return AjaxResult.success(Map.of(
"token", token,
"permissions", clientAccount.getPermissions(),
"accountName", clientAccount.getAccountName(),
"expireTime", clientAccount.getExpireTime(),
"accountType", clientAccount.getAccountType(),
"deviceTrialExpired", false
));
}
/**
@@ -324,13 +325,10 @@ public class ClientAccountController extends BaseController {
account.setUpdateBy(getUsername());
clientAccountService.updateClientAccount(account);
// 推送续费通知
sseHubService.sendEventToAllDevices(account.getUsername(), "VIP_RENEWED",
"{\"expireTime\":\"" + newExpireTime + "\"}");
Map<String, Object> result = new HashMap<>();
result.put("expireTime", newExpireTime);
return AjaxResult.success(result);
return AjaxResult.success(Map.of("expireTime", newExpireTime));
}
}

View File

@@ -1,7 +1,6 @@
package com.ruoyi.web.controller.system;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.system.domain.ClientDevice;
import com.ruoyi.system.mapper.ClientDeviceMapper;
import com.ruoyi.system.mapper.ClientAccountMapper;
@@ -10,7 +9,6 @@ import com.ruoyi.web.sse.SseHubService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -24,13 +22,19 @@ public class ClientDeviceController {
private ClientAccountMapper clientAccountMapper;
@Autowired
private SseHubService sseHubService;
private AjaxResult checkDeviceLimit(String username, String deviceId) {
ClientAccount account = clientAccountMapper.selectClientAccountByUsername(username);
int deviceLimit = (account != null && account.getDeviceLimit() != null) ? account.getDeviceLimit() : 3;
List<ClientDevice> userDevices = clientDeviceMapper.selectByUsername(username);
int userDevice = userDevices.size();
boolean deviceExists = userDevices.stream().anyMatch(d -> deviceId.equals(d.getDeviceId()));
if (deviceExists) userDevice--;
if (userDevice >= deviceLimit) {
return AjaxResult.error("设备数量已达上限(" + deviceLimit + "个),请先移除其他设备");
}
return null;
}
/**
* 查询设备配额与已使用数量
*
* @param username 用户名为空时返回0
* @return 配额信息
*/
@GetMapping("/quota")
public AjaxResult quota(@RequestParam(value = "username", required = false) String username) {
List<ClientDevice> all = clientDeviceMapper.selectByUsername(username);
@@ -40,10 +44,7 @@ public class ClientDeviceController {
}
ClientAccount account = clientAccountMapper.selectClientAccountByUsername(username);
int limit = (account != null && account.getDeviceLimit() != null) ? account.getDeviceLimit() : 3;
Map<String, Object> map = new HashMap<>();
map.put("limit", limit);
map.put("used", used);
return AjaxResult.success(map);
return AjaxResult.success(Map.of("limit", limit, "used", used));
}
/**
* 按用户名查询设备列表(最近活动优先)
@@ -70,16 +71,8 @@ public class ClientDeviceController {
String os = device.getOs();
String deviceName = username + "@" + ip + " (" + os + ")";
// 检查设备数量限制
ClientAccount account = clientAccountMapper.selectClientAccountByUsername(username);
int deviceLimit = (account != null && account.getDeviceLimit() != null) ? account.getDeviceLimit() : 3;
List<ClientDevice> userDevices = clientDeviceMapper.selectByUsername(username);
int userDevice = userDevices.size();
boolean deviceExists = userDevices.stream().anyMatch(d -> deviceId.equals(d.getDeviceId()));
if (deviceExists) userDevice--;
if (userDevice >= deviceLimit) {
return AjaxResult.error("设备数量已达上限(" + deviceLimit + "个),请先移除其他设备");
}
AjaxResult limitCheck = checkDeviceLimit(username, deviceId);
if (limitCheck != null) return limitCheck;
ClientDevice exists = clientDeviceMapper.selectByDeviceIdAndUsername(deviceId, username);
if (exists == null) {

View File

@@ -1,28 +0,0 @@
package com.ruoyi.web.controller.tool;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Anonymous
public class GenmaijlController {
@Autowired
RedisCache redisCache;
@GetMapping("/getToken")
public String getToken(){
return redisCache.getCacheObject("genmaijlToken");
}
@PostMapping("/saveToken")
public int saveToken(@RequestBody String token){
redisCache.setCacheObject("genmaijlToken", token);
return 1;
}
}