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'; import { http } from './http';
export const amazonApi = { export const amazonApi = {
// 上传Excel文件解析ASIN列表
importAsinFromExcel(file: File) { importAsinFromExcel(file: File) {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
return http.upload<{ code: number, data: { asinList: string[], total: number }, msg: string | null }>('/api/amazon/import/asin', formData); 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) { 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 }); return http.post<{ code: number, data: { products: any[] }, msg: string | null }>('/api/amazon/products/batch', { asinList, batchId, region }, signal);
}, },
getLatestProducts() { getLatestProducts() {
return http.get<{ code: number, data: { products: any[] }, msg: string | null }>('/api/amazon/products/latest'); 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 type HttpMethod = 'GET' | 'POST' | 'DELETE';
// 集中管理所有后端服务配置
export const CONFIG = { export const CONFIG = {
CLIENT_BASE: 'http://localhost:8081', CLIENT_BASE: 'http://localhost:8081',
// RUOYI_BASE: 'http://192.168.1.89:8085', //RUOYI_BASE: 'http://8.138.23.49:8085',
RUOYI_BASE: 'http://8.138.23.49:8085', RUOYI_BASE: 'http://192.168.1.89:8085',
SSE_URL: 'http://8.138.23.49:8085/monitor/account/events' SSE_URL: 'http://192.168.1.89:8085/monitor/account/events'
} as const; } as const;
function resolveBase(path: string): string { function resolveBase(path: string): string {
// RuoYi 后端路径:鉴权、设备、反馈、版本、工具
if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma')) { if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma')) {
return CONFIG.RUOYI_BASE; return CONFIG.RUOYI_BASE;
} }
@@ -26,16 +23,17 @@ function buildQuery(params?: Record<string, unknown>): string {
return query.toString() ? `?${query}` : ''; return query.toString() ? `?${query}` : '';
} }
async function request<T>(path: string, options: RequestInit): Promise<T> { async function getToken(): Promise<string> {
// 获取token
let token = '';
try { try {
const tokenModule = await import('../utils/token'); const tokenModule = await import('../utils/token');
token = tokenModule.getToken() || ''; return tokenModule.getToken() || '';
} catch (e) { } catch {
console.warn('获取token失败:', e); return '';
} }
}
async function request<T>(path: string, options: RequestInit & { signal?: AbortSignal }): Promise<T> {
const token = await getToken();
const res = await fetch(`${resolveBase(path)}${path}`, { const res = await fetch(`${resolveBase(path)}${path}`, {
credentials: 'omit', credentials: 'omit',
cache: 'no-store', cache: 'no-store',
@@ -55,9 +53,6 @@ async function request<T>(path: string, options: RequestInit): Promise<T> {
const contentType = res.headers.get('content-type') || ''; const contentType = res.headers.get('content-type') || '';
if (contentType.includes('application/json')) { if (contentType.includes('application/json')) {
const json: any = await res.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) { if (json.code !== undefined && json.code !== 0 && json.code !== 200) {
throw new Error(json.msg || '请求失败'); throw new Error(json.msg || '请求失败');
} }
@@ -68,13 +63,14 @@ async function request<T>(path: string, options: RequestInit): Promise<T> {
} }
export const http = { export const http = {
get<T>(path: string, params?: Record<string, unknown>) { get<T>(path: string, params?: Record<string, unknown>, signal?: AbortSignal) {
return request<T>(`${path}${buildQuery(params)}`, { method: 'GET' }); 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, { return request<T>(path, {
method: 'POST', method: 'POST',
body: body ? JSON.stringify(body) : undefined body: body ? JSON.stringify(body) : undefined,
signal
}); });
}, },
@@ -82,42 +78,31 @@ export const http = {
return request<T>(path, { method: 'DELETE' }); return request<T>(path, { method: 'DELETE' });
}, },
async upload<T>(path: string, form: FormData) { async upload<T>(path: string, form: FormData, signal?: AbortSignal) {
// 获取token const token = await getToken();
let token = ''; const res = await fetch(`${resolveBase(path)}${path}`, {
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}`, {
method: 'POST', method: 'POST',
body: form, body: form,
credentials: 'omit', credentials: 'omit',
cache: 'no-store', cache: 'no-store',
headers headers: token ? { 'Authorization': `Bearer ${token}` } : {},
}).then(async res => { 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();
if (json.code !== undefined && json.code !== 0 && json.code !== 200) {
throw new Error(json.msg || '请求失败');
}
return json as T;
}
return (await res.text()) as unknown as T;
}); });
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();
if (json.code !== undefined && json.code !== 0 && json.code !== 200) {
throw new Error(json.msg || '请求失败');
}
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>) return http.get('/api/banma/shops', params as Record<string, unknown>)
}, },
getOrders(params: any) { getOrders(params: any, signal?: AbortSignal) {
return http.get('/api/banma/orders', params as Record<string, unknown>) return http.get('/api/banma/orders', params as Record<string, unknown>, signal)
},
getOrdersByBatch(batchId: string) {
return http.get(`/api/banma/orders/batch/${batchId}`)
}, },
getLatestOrders() { getLatestOrders() {
return http.get('/api/banma/orders/latest') 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 { ref, computed, onMounted, defineAsyncComponent, inject } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { amazonApi } from '../../api/amazon' import { amazonApi } from '../../api/amazon'
import { systemApi } from '../../api/system'
import { handlePlatformFileExport } from '../../utils/settings' import { handlePlatformFileExport } from '../../utils/settings'
const TrialExpiredDialog = defineAsyncComponent(() => import('../common/TrialExpiredDialog.vue')) const TrialExpiredDialog = defineAsyncComponent(() => import('../common/TrialExpiredDialog.vue'))
@@ -21,6 +22,7 @@ const progressVisible = ref(false) // 进度条是否显示(完成后仍保留
const localProductData = ref<any[]>([]) // 本地产品数据 const localProductData = ref<any[]>([]) // 本地产品数据
const currentAsin = ref('') // 当前处理的ASIN const currentAsin = ref('') // 当前处理的ASIN
const genmaiLoading = ref(false) // Genmai Spirit加载状态 const genmaiLoading = ref(false) // Genmai Spirit加载状态
let abortController: AbortController | null = null // 请求取消控制器
// 分页配置 // 分页配置
const currentPage = ref(1) const currentPage = ref(1)
@@ -130,7 +132,7 @@ async function batchGetProductInfo(asinList: string[]) {
currentAsin.value = `正在处理第${i + 1}/${totalBatches}批 (${batchAsins.join(', ')})` currentAsin.value = `正在处理第${i + 1}/${totalBatches}批 (${batchAsins.join(', ')})`
try { 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) { if (result?.data?.products?.length > 0) {
localProductData.value.push(...result.data.products) localProductData.value.push(...result.data.products)
@@ -142,7 +144,8 @@ async function batchGetProductInfo(asinList: string[]) {
const actualCount = result?.data?.products?.length || 0 const actualCount = result?.data?.products?.length || 0
failedCount += Math.max(0, expectedCount - actualCount) failedCount += Math.max(0, expectedCount - actualCount)
} catch (error) { } catch (error: any) {
if (error.name === 'AbortError') break
failedCount += batchAsins.length failedCount += batchAsins.length
console.error(`批次${i + 1}失败:`, error) console.error(`批次${i + 1}失败:`, error)
} }
@@ -164,8 +167,10 @@ async function batchGetProductInfo(asinList: string[]) {
} catch (error: any) { } catch (error: any) {
showMessage(error.message || '批量获取产品信息失败', 'error') if (error.name !== 'AbortError') {
currentAsin.value = '处理失败' showMessage(error.message || '批量获取产品信息失败', 'error')
currentAsin.value = '处理失败'
}
} finally { } finally {
tableLoading.value = false tableLoading.value = false
} }
@@ -177,6 +182,7 @@ async function startQueuedFetch() {
showMessage('请先导入ASIN列表', 'warning') showMessage('请先导入ASIN列表', 'warning')
return return
} }
abortController = new AbortController()
loading.value = true loading.value = true
progressVisible.value = true progressVisible.value = true
tableLoading.value = true tableLoading.value = true
@@ -185,6 +191,7 @@ async function startQueuedFetch() {
} finally { } finally {
tableLoading.value = false tableLoading.value = false
loading.value = false loading.value = false
abortController = null
} }
} }
@@ -240,12 +247,13 @@ function isOutOfStock(product: any) {
// 停止获取操作 // 停止获取操作
function stopFetch() { function stopFetch() {
abortController?.abort()
abortController = null
loading.value = false loading.value = false
currentAsin.value = '已停止' currentAsin.value = '已停止'
showMessage('已停止获取产品数据', 'info') showMessage('已停止获取产品数据', 'info')
} }
// 打开Genmai Spirit工具
async function openGenmaiSpirit() { async function openGenmaiSpirit() {
try { try {
await ElMessageBox.confirm('打开跟卖精灵会关闭所有谷歌浏览器进程,是否继续?', '提示', { await ElMessageBox.confirm('打开跟卖精灵会关闭所有谷歌浏览器进程,是否继续?', '提示', {
@@ -255,7 +263,7 @@ async function openGenmaiSpirit() {
}) })
genmaiLoading.value = true genmaiLoading.value = true
try { try {
await amazonApi.openGenmaiSpirit() await systemApi.openGenmaiSpirit()
} catch (error: any) { } catch (error: any) {
showMessage(error.message || '打开跟卖精灵失败', 'error') showMessage(error.message || '打开跟卖精灵失败', 'error')
} finally { } finally {

View File

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

View File

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

View File

@@ -1,57 +1,45 @@
package com.tashow.erp.controller; package com.tashow.erp.controller;
import com.tashow.erp.fx.controller.JavaBridge;
import com.tashow.erp.repository.BanmaOrderRepository; import com.tashow.erp.repository.BanmaOrderRepository;
import com.tashow.erp.service.IBanmaOrderService; import com.tashow.erp.service.BanmaOrderService;
import com.tashow.erp.utils.ExcelExportUtil;
import com.tashow.erp.utils.JsonData; import com.tashow.erp.utils.JsonData;
import com.tashow.erp.utils.LoggerUtil; import com.tashow.erp.utils.LoggerUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@RestController @RestController
@RequestMapping("/api/banma") @RequestMapping("/api/banma")
public class BanmaOrderController { public class BanmaOrderController {
private static final Logger logger = LoggerUtil.getLogger(BanmaOrderController.class); private static final Logger logger = LoggerUtil.getLogger(BanmaOrderController.class);
@Autowired @Autowired
IBanmaOrderService banmaOrderService; BanmaOrderService banmaOrderService;
@Autowired @Autowired
BanmaOrderRepository banmaOrderRepository; BanmaOrderRepository banmaOrderRepository;
@GetMapping("/orders") @GetMapping("/orders")
public ResponseEntity<Map<String, Object>> getOrders( public JsonData getOrders(
@RequestParam(required = false, name = "accountId") Long accountId, @RequestParam(required = false, name = "accountId") Long accountId,
@RequestParam(required = false, name = "startDate") String startDate, @RequestParam(required = false, name = "startDate") String startDate,
@RequestParam(required = false, name = "endDate") String endDate, @RequestParam(required = false, name = "endDate") String endDate,
@RequestParam(defaultValue = "1", name = "page") int page, @RequestParam(defaultValue = "1", name = "page") int page,
@RequestParam(defaultValue = "10", name = "pageSize") int pageSize, @RequestParam(defaultValue = "10", name = "pageSize") int pageSize,
@RequestParam( "batchId") String batchId, @RequestParam("batchId") String batchId,
@RequestParam(required = false, name = "shopIds") String shopIds) { @RequestParam(required = false, name = "shopIds") String shopIds) {
List<String> shopIdList = shopIds != null ? java.util.Arrays.asList(shopIds.split(",")) : null; 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, 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") @GetMapping("/shops")
public JsonData getShops(@RequestParam(required = false, name = "accountId") Long accountId) { public JsonData getShops(@RequestParam(required = false, name = "accountId") Long accountId) {
try { Map<String, Object> response = banmaOrderService.getShops(accountId);
Map<String, Object> response = banmaOrderService.getShops(accountId); return JsonData.buildSuccess(response);
return JsonData.buildSuccess(response);
} catch (Exception e) {
logger.error("获取店铺列表失败: {}", e.getMessage(), e);
return JsonData.buildError("获取店铺列表失败: " + e.getMessage());
}
} }
/**
* 获取最新订单数据
*/
@GetMapping("/orders/latest") @GetMapping("/orders/latest")
public JsonData getLatestOrders() { public JsonData getLatestOrders() {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
@@ -68,8 +56,6 @@ public class BanmaOrderController {
}) })
.filter(order -> !order.isEmpty()) .filter(order -> !order.isEmpty())
.toList(); .toList();
return JsonData.buildSuccess(Map.of("orders", orders, "total", orders.size())); 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.RakutenProduct;
import com.tashow.erp.model.SearchResult; import com.tashow.erp.model.SearchResult;
import com.tashow.erp.repository.RakutenProductRepository;
import com.tashow.erp.service.Alibaba1688Service; 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.RakutenScrapingService;
import com.tashow.erp.service.impl.Alibaba1688ServiceImpl;
import com.tashow.erp.utils.DataReportUtil; import com.tashow.erp.utils.DataReportUtil;
import com.tashow.erp.utils.ExcelParseUtil; import com.tashow.erp.utils.ExcelParseUtil;
import com.tashow.erp.utils.JsonData; 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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; 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.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@RestController @RestController
@RequestMapping("/api/rakuten") @RequestMapping("/api/rakuten")
@@ -37,19 +25,10 @@ public class RakutenController {
@Autowired @Autowired
private Alibaba1688Service alibaba1688Service; private Alibaba1688Service alibaba1688Service;
@Autowired @Autowired
private IRakutenCacheService rakutenCacheService; private RakutenCacheService rakutenCacheService;
@Autowired
private JavaBridge javaBridge;
@Autowired @Autowired
private DataReportUtil dataReportUtil; private DataReportUtil dataReportUtil;
/**
* 获取乐天商品数据
*
* @param file Excel文件首列为店铺名
* @param batchId 可选,批次号
* @return JsonData 响应
*/
@PostMapping(value = "/products") @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) {
try { try {
@@ -80,21 +59,23 @@ public class RakutenController {
if (!newProducts.isEmpty()) { if (!newProducts.isEmpty()) {
rakutenCacheService.saveProductsWithSessionId(newProducts, batchId); rakutenCacheService.saveProductsWithSessionId(newProducts, batchId);
} }
// 4. 上报缓存数据使用情况
int cachedCount = allProducts.size() - newProducts.size(); int cachedCount = allProducts.size() - newProducts.size();
if (cachedCount > 0) { if (cachedCount > 0) {
dataReportUtil.reportDataCollection("RAKUTEN_CACHE", 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) { } catch (Exception e) {
log.error("获取乐天商品失败", e); log.error("获取乐天商品失败", e);
return JsonData.buildError("获取乐天商品失败: " + e.getMessage()); return JsonData.buildError("获取乐天商品失败: " + e.getMessage());
} }
} }
/**
* 1688识图搜索API - 自动保存1688搜索结果
*/
@PostMapping("/search1688") @PostMapping("/search1688")
public JsonData search1688(@RequestBody Map<String, Object> params) { public JsonData search1688(@RequestBody Map<String, Object> params) {
String imageUrl = (String) params.get("imageUrl"); String imageUrl = (String) params.get("imageUrl");
@@ -109,21 +90,14 @@ public class RakutenController {
} }
} }
@GetMapping("/products/latest") @GetMapping("/products/latest")
public JsonData getLatestProducts() { public JsonData getLatestProducts() {
try { try {
List<Map<String, Object>> products = rakutenScrapingService.getLatestProductsForDisplay(); List<Map<String, Object>> products = rakutenScrapingService.getLatestProductsForDisplay();
return JsonData.buildSuccess(Map.of("products", products, "total", products.size())); return JsonData.buildSuccess(Map.of("products", products, "total", products.size()));
} catch (Exception e) { } catch (Exception e) {
log.error("获取最新商品数据失败", e);
e.printStackTrace();
log.info("获取最新商品数据失败", e);
return JsonData.buildError("获取最新数据失败: " + e.getMessage()); 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.entity.AuthTokenEntity;
import com.tashow.erp.repository.AuthTokenRepository; 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.service.impl.GenmaiServiceImpl;
import com.tashow.erp.utils.JsonData; 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.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* 系统级接口控制器
* 整合:认证、配置、版本、工具、代理等功能
*/
@RestController @RestController
@RequestMapping("/api/system") @RequestMapping("/api/system")
public class SystemController { public class SystemController {
private static final Logger logger = LoggerUtil.getLogger(SystemController.class);
@Autowired @Autowired
private AuthTokenRepository authTokenRepository; private AuthTokenRepository authTokenRepository;
@Autowired @Autowired
private GenmaiServiceImpl genmaiService; private GenmaiServiceImpl genmaiService;
@Autowired @Autowired
private CacheManagementServiceImpl cacheManagementService; private CacheService cacheService;
@Autowired @Autowired
private RestTemplate restTemplate; private RestTemplate restTemplate;
@Value("${project.version:2.3.6}") @Value("${project.version:2.3.6}")
private String currentVersion; private String currentVersion;
@Value("${project.build.time:}") @Value("${project.build.time:}")
private String buildTime; private String buildTime;
@Value("${api.server.base-url}") @Value("${api.server.base-url}")
private String serverBaseUrl; private String serverBaseUrl;
/**
* 保存认证密钥
*/
@PostMapping("/auth/save") @PostMapping("/auth/save")
public JsonData saveAuth(@RequestBody Map<String, Object> data) { public JsonData saveAuth(@RequestBody Map<String, Object> data) {
String serviceName = (String) data.get("serviceName"); String serviceName = (String) data.get("serviceName");
@@ -52,17 +39,13 @@ public class SystemController {
if (serviceName == null || authKey == null) { if (serviceName == null || authKey == null) {
return JsonData.buildError("serviceName和authKey不能为空"); return JsonData.buildError("serviceName和authKey不能为空");
} }
AuthTokenEntity entity = authTokenRepository.findByServiceName(serviceName) AuthTokenEntity entity = authTokenRepository.findByServiceName(serviceName).orElse(new AuthTokenEntity());
.orElse(new AuthTokenEntity());
entity.setServiceName(serviceName); entity.setServiceName(serviceName);
entity.setToken(authKey); entity.setToken(authKey);
authTokenRepository.save(entity); authTokenRepository.save(entity);
return JsonData.buildSuccess("认证信息保存成功"); return JsonData.buildSuccess("认证信息保存成功");
} }
/**
* 获取认证密钥
*/
@GetMapping("/auth/get") @GetMapping("/auth/get")
public JsonData getAuth(@RequestParam String serviceName) { public JsonData getAuth(@RequestParam String serviceName) {
return JsonData.buildSuccess(authTokenRepository.findByServiceName(serviceName) return JsonData.buildSuccess(authTokenRepository.findByServiceName(serviceName)
@@ -70,82 +53,41 @@ public class SystemController {
.orElse(null)); .orElse(null));
} }
/**
* 删除认证密钥
*/
@DeleteMapping("/auth/remove") @DeleteMapping("/auth/remove")
public JsonData removeAuth(@RequestParam String serviceName) { public JsonData removeAuth(@RequestParam String serviceName) {
authTokenRepository.findByServiceName(serviceName) authTokenRepository.findByServiceName(serviceName).ifPresent(authTokenRepository::delete);
.ifPresent(authTokenRepository::delete);
return JsonData.buildSuccess("认证信息删除成功"); return JsonData.buildSuccess("认证信息删除成功");
} }
// ==================== 设备管理 ====================
/**
* 获取设备ID
*/
@GetMapping("/device-id") @GetMapping("/device-id")
public JsonData getDeviceId() { public JsonData getDeviceId() {
String deviceId = com.tashow.erp.utils.DeviceUtils.generateDeviceId(); return JsonData.buildSuccess(com.tashow.erp.utils.DeviceUtils.generateDeviceId());
return JsonData.buildSuccess(deviceId);
} }
/**
* 获取本机内网IP地址
*/
@GetMapping("/local-ip") @GetMapping("/local-ip")
public JsonData getLocalIp() { public JsonData getLocalIp() {
try { try {
java.net.InetAddress localHost = java.net.InetAddress.getLocalHost(); return JsonData.buildSuccess(java.net.InetAddress.getLocalHost().getHostAddress());
return JsonData.buildSuccess(localHost.getHostAddress());
} catch (Exception e) { } catch (Exception e) {
return JsonData.buildSuccess("127.0.0.1"); return JsonData.buildSuccess("127.0.0.1");
} }
} }
// ==================== 版本信息 ====================
/**
* 获取当前版本号
*/
@GetMapping("/version") @GetMapping("/version")
public Map<String, Object> getVersion() { public Map<String, Object> getVersion() {
Map<String, Object> result = new HashMap<>(); return Map.of("success", true, "currentVersion", currentVersion, "buildTime", buildTime);
result.put("success", true);
result.put("currentVersion", currentVersion);
result.put("buildTime", buildTime);
return result;
} }
// ==================== 配置信息 ====================
/**
* 获取服务器配置
*/
@GetMapping("/config/server") @GetMapping("/config/server")
public Map<String, Object> getServerConfig() { public Map<String, Object> getServerConfig() {
return Map.of( return Map.of("baseUrl", serverBaseUrl, "sseUrl", serverBaseUrl + "/monitor/account/events");
"baseUrl", serverBaseUrl,
"sseUrl", serverBaseUrl + "/monitor/account/events"
);
} }
// ==================== 工具功能 ====================
/**
* 打开跟卖精灵网页
*/
@PostMapping("/genmai/open") @PostMapping("/genmai/open")
public void openGenmaiWebsite() { public void openGenmaiWebsite() {
genmaiService.openGenmaiWebsite(); genmaiService.openGenmaiWebsite();
} }
// ==================== 图片代理 ====================
/**
* 代理获取图片解决CORS跨域问题
*/
@GetMapping("/proxy/image") @GetMapping("/proxy/image")
public ResponseEntity<byte[]> proxyImage(@RequestParam("url") String imageUrl) { public ResponseEntity<byte[]> proxyImage(@RequestParam("url") String imageUrl) {
if (imageUrl == null || imageUrl.isEmpty()) { if (imageUrl == null || imageUrl.isEmpty()) {
@@ -178,24 +120,15 @@ public class SystemController {
return new ResponseEntity<>(response.getBody(), responseHeaders, HttpStatus.OK); return new ResponseEntity<>(response.getBody(), responseHeaders, HttpStatus.OK);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); logger.error("代理图片失败: {}", imageUrl, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
} }
} }
// ==================== 缓存管理 ====================
/**
* 清理缓存
*/
@PostMapping("/cache/clear") @PostMapping("/cache/clear")
public JsonData clearCache() { public JsonData clearCache() {
try { cacheService.clearCache();
cacheManagementService.clearCache(); return JsonData.buildSuccess("缓存清理成功");
return JsonData.buildSuccess("缓存清理成功");
} catch (Exception e) {
return JsonData.buildError("清理缓存失败: " + e.getMessage());
}
} }
} }

View File

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

View File

@@ -3,12 +3,7 @@ package com.tashow.erp.service;
import com.tashow.erp.entity.AmazonProductEntity; import com.tashow.erp.entity.AmazonProductEntity;
import java.util.List; import java.util.List;
/** public interface AmazonScrapingService {
* 亚马逊数据采集服务接口
*
* @author ruoyi
*/
public interface IAmazonScrapingService {
/** /**
* 批量获取亚马逊产品信息 * 批量获取亚马逊产品信息
@@ -19,10 +14,5 @@ public interface IAmazonScrapingService {
* @return 产品信息列表 * @return 产品信息列表
*/ */
List<AmazonProductEntity> batchGetProductInfo(List<String> asinList, String batchId, String region); 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.List;
import java.util.Map; import java.util.Map;
/** public interface BanmaOrderService {
* 斑马订单服务接口
*
* @author ruoyi
*/
public interface IBanmaOrderService {
// 客户端不再暴露刷新认证Token // 客户端不再暴露刷新认证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); 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.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.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.util.Optional;
/**
* 缓存管理服务实现
*/
@Service @Service
public class CacheManagementServiceImpl implements ICacheManagementService { public class CacheService {
private static final Logger logger = LoggerFactory.getLogger(CacheService.class);
@Autowired
private AuthTokenRepository authTokenRepository;
@Autowired @Autowired
private RakutenProductRepository rakutenProductRepository; private RakutenProductRepository rakutenProductRepository;
@Autowired @Autowired
private AmazonProductRepository amazonProductRepository; private AmazonProductRepository amazonProductRepository;
@Autowired @Autowired
private Alibaba1688ProductRepository alibaba1688ProductRepository; private Alibaba1688ProductRepository alibaba1688ProductRepository;
@Autowired @Autowired
private ZebraOrderRepository zebraOrderRepository; private ZebraOrderRepository zebraOrderRepository;
@Autowired @Autowired
private BanmaOrderRepository banmaOrderRepository; private BanmaOrderRepository banmaOrderRepository;
@Autowired @Autowired
private CacheDataRepository cacheDataRepository; private CacheDataRepository cacheDataRepository;
@Autowired @Autowired
private UpdateStatusRepository updateStatusRepository; 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 @Transactional
public void clearCache() { public void clearCache() {
rakutenProductRepository.deleteAll(); 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; package com.tashow.erp.service;
import com.tashow.erp.model.RakutenProduct; import com.tashow.erp.model.RakutenProduct;
import java.util.List; import java.util.List;
/** public interface RakutenCacheService {
* 乐天产品缓存服务接口
*/
public interface IRakutenCacheService {
/** /**
* 保存产品数据 * 保存产品数据
@@ -19,8 +15,6 @@ public interface IRakutenCacheService {
*/ */
void saveProductsWithSessionId(List<RakutenProduct> products, String sessionId); void saveProductsWithSessionId(List<RakutenProduct> products, String sessionId);
/** /**
* 检查店铺是否有最近的数据12小时内 * 检查店铺是否有最近的数据12小时内
*/ */
@@ -45,6 +39,5 @@ public interface IRakutenCacheService {
* 清理指定店铺12小时内的重复数据为新采集做准备 * 清理指定店铺12小时内的重复数据为新采集做准备
*/ */
void cleanRecentDuplicateData(String shopName); void cleanRecentDuplicateData(String shopName);
} }

View File

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

View File

@@ -1,24 +1,17 @@
package com.tashow.erp.service.impl; package com.tashow.erp.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.tashow.erp.service.IAuthService;
import com.tashow.erp.utils.ApiForwarder; import com.tashow.erp.utils.ApiForwarder;
import com.tashow.erp.utils.DeviceUtils; import com.tashow.erp.utils.DeviceUtils;
import lombok.Getter; import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* 认证服务实现类 - 简化版
* 仅提供错误上报和客户端信息获取功能
*/
@Service @Service
public class AuthServiceImpl implements IAuthService { public class AuthServiceImpl {
@Value("${project.version:2.1.0}") @Value("${project.version:2.1.0}")
private String appVersion; private String appVersion;
@@ -29,10 +22,6 @@ public class AuthServiceImpl implements IAuthService {
@Getter @Getter
private String clientId = DeviceUtils.generateDeviceId(); private String clientId = DeviceUtils.generateDeviceId();
/**
* 获取客户端基本信息
*/
@Override
public Map<String, Object> getClientInfo() { public Map<String, Object> getClientInfo() {
Map<String, Object> info = new HashMap<>(); Map<String, Object> info = new HashMap<>();
info.put("clientId", clientId); info.put("clientId", clientId);
@@ -41,10 +30,6 @@ public class AuthServiceImpl implements IAuthService {
return info; return info;
} }
/**
* 上报错误信息
*/
@Override
public void reportError(String errorType, String errorMessage, Exception e) { public void reportError(String errorType, String errorMessage, Exception e) {
try { try {
Map<String, Object> errorData = new HashMap<>(); 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.fasterxml.jackson.databind.ObjectMapper;
import com.tashow.erp.entity.BanmaOrderEntity; import com.tashow.erp.entity.BanmaOrderEntity;
import com.tashow.erp.repository.BanmaOrderRepository; 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.DataReportUtil;
import com.tashow.erp.utils.ErrorReporter; import com.tashow.erp.utils.ErrorReporter;
import com.tashow.erp.utils.LoggerUtil; import com.tashow.erp.utils.LoggerUtil;
@@ -24,7 +26,7 @@ import java.util.stream.Collectors;
* @author ruoyi * @author ruoyi
*/ */
@Service @Service
public class BanmaOrderServiceImpl { public class BanmaOrderServiceImpl implements BanmaOrderService {
private static final Logger logger = LoggerUtil.getLogger(BanmaOrderServiceImpl.class); 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 = "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"; 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 String ruoyiAdminBase;
private RestTemplate restTemplate; private RestTemplate restTemplate;
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();
private final CacheServiceImpl cacheService; private final CacheService cacheService;
private final BanmaOrderRepository banmaOrderRepository; private final BanmaOrderRepository banmaOrderRepository;
private final DataReportUtil dataReportUtil; private final DataReportUtil dataReportUtil;
private final ErrorReporter errorReporter; private final ErrorReporter errorReporter;
@@ -44,7 +46,7 @@ public class BanmaOrderServiceImpl {
private String currentBatchSessionId = null; private String currentBatchSessionId = null;
// 物流信息缓存,避免重复查询 // 物流信息缓存,避免重复查询
private final Map<String, String> trackingInfoCache = new ConcurrentHashMap<>(); 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.banmaOrderRepository = banmaOrderRepository;
this.cacheService = cacheService; this.cacheService = cacheService;
this.dataReportUtil = dataReportUtil; this.dataReportUtil = dataReportUtil;
@@ -233,9 +235,6 @@ public class BanmaOrderServiceImpl {
return result; return result;
} }
/**
* 提取子订单字段
*/
private void extractSubOrderFields(Map<String, Object> simplifiedOrder, Map<String, Object> subOrder) { private void extractSubOrderFields(Map<String, Object> simplifiedOrder, Map<String, Object> subOrder) {
String[] basicFields = {"orderedAt", "timeSinceOrder", "createdAt", "poTrackingNumber"}; String[] basicFields = {"orderedAt", "timeSinceOrder", "createdAt", "poTrackingNumber"};
String[] productFields = {"productTitle", "shopOrderNumber", "priceJpy", "productQuantity", "shippingFeeJpy", "productNumber", "serviceFee", "productImage"}; 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))); Arrays.stream(purchaseFields).forEach(field -> simplifiedOrder.put(field, subOrder.get(field)));
} }
/**
* 从API获取物流信息
*/
private String fetchTrackingInfo(String trackingNumber) { private String fetchTrackingInfo(String trackingNumber) {
// 优先尝试佐川物流
Map<String, Object> trackInfoMap = (Map<String, Object>) new SagawaExpressSdk().getTrackingInfo(trackingNumber).get("trackInfo"); Map<String, Object> trackInfoMap = (Map<String, Object>) new SagawaExpressSdk().getTrackingInfo(trackingNumber).get("trackInfo");
if (trackInfoMap != null) { if (trackInfoMap != null) {
return trackInfoMap.get("dateTime") + " " + trackInfoMap.get("office") + " " + trackInfoMap.get("status"); return trackInfoMap.get("dateTime") + " " + trackInfoMap.get("office") + " " + trackInfoMap.get("status");
} }
// 斑马API
ResponseEntity<Map> response = restTemplate.getForEntity(String.format(TRACKING_URL, trackingNumber), Map.class); ResponseEntity<Map> response = restTemplate.getForEntity(String.format(TRACKING_URL, trackingNumber), Map.class);
return Optional.ofNullable(response.getBody()) return Optional.ofNullable(response.getBody())
.filter(body -> Integer.valueOf(0).equals(body.get("code"))) .filter(body -> Integer.valueOf(0).equals(body.get("code")))
@@ -268,9 +258,4 @@ public class BanmaOrderServiceImpl {
.map(track -> (String) track.get("track")) .map(track -> (String) track.get("track"))
.orElse("暂无物流信息"); .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.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.chrome.ChromeOptions;
import com.tashow.erp.service.IGenmaiService;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* 跟卖精灵服务实现类
*
* @author ruoyi
*/
@Service @Service
public class GenmaiServiceImpl implements IGenmaiService { public class GenmaiServiceImpl {
@Value("${api.server.base-url}") @Value("${api.server.base-url}")
private String serverApiUrl; private String serverApiUrl;
@Value("${api.server.paths.getGenmaijlToken}") @Value("${api.server.paths.getGenmaijlToken}")
@@ -30,7 +25,6 @@ public class GenmaiServiceImpl implements IGenmaiService {
private String updateGenmaijlToken; private String updateGenmaijlToken;
private final RestTemplate restTemplate = new RestTemplate(); private final RestTemplate restTemplate = new RestTemplate();
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
@Override
@SneakyThrows @SneakyThrows
public void openGenmaiWebsite() { public void openGenmaiWebsite() {
// 先关闭所有Chrome进程 // 先关闭所有Chrome进程

View File

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

View File

@@ -132,30 +132,24 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
} }
} }
/**
* 获取最新的产品数据用于前端展示
*/
@Override @Override
public List<Map<String, Object>> getLatestProductsForDisplay() { public List<Map<String, Object>> getLatestProductsForDisplay() {
return rakutenProductRepository.findLatestProducts().stream().map(entity -> { return rakutenProductRepository.findLatestProducts().stream().map(entity -> {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("originalShopName", Optional.ofNullable(entity.getOriginalShopName()).orElse("")); result.put("originalShopName", entity.getOriginalShopName());
result.put("productUrl", Optional.ofNullable(entity.getProductUrl()).orElse("")); result.put("productUrl", entity.getProductUrl());
result.put("imgUrl", Optional.ofNullable(entity.getImgUrl()).orElse("")); result.put("imgUrl", entity.getImgUrl());
result.put("productTitle", Optional.ofNullable(entity.getProductTitle()).orElse("")); result.put("productTitle", entity.getProductTitle());
result.put("price", Optional.ofNullable(entity.getPrice()).orElse("")); result.put("price", entity.getPrice());
result.put("ranking", Optional.ofNullable(entity.getRanking()).orElse("")); result.put("ranking", entity.getRanking());
result.put("image1688Url", Optional.ofNullable(entity.getImage1688Url()).orElse("")); result.put("image1688Url", entity.getImage1688Url());
result.put("detailUrl1688", Optional.ofNullable(entity.getDetailUrl1688()).orElse("")); result.put("detailUrl1688", entity.getDetailUrl1688());
// 使用正确的实体字段 result.put("mapRecognitionLink", entity.getMapRecognitionLink());
result.put("mapRecognitionLink", Optional.ofNullable(entity.getMapRecognitionLink()).orElse(""));
result.put("freight", entity.getFreight()); result.put("freight", entity.getFreight());
result.put("median", entity.getMedian()); result.put("median", entity.getMedian());
result.put("weight", Optional.ofNullable(entity.getWeight()).orElse("")); result.put("weight", entity.getWeight());
result.put("skuPrice",entity.getSkuPriceJson()); result.put("skuPrice", entity.getSkuPriceJson());
return result; return result;
}).collect(Collectors.toList()); }).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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import com.tashow.erp.service.IAuthService; import com.tashow.erp.service.impl.AuthServiceImpl;
/**
* 错误上报工具类 - 立即上报所有错误
*
* @author ERP
*/
@Component @Component
public class ErrorReporter { public class ErrorReporter {
@@ -21,7 +17,7 @@ public class ErrorReporter {
private String serverUrl; private String serverUrl;
@Autowired @Autowired
private IAuthService authService; private AuthServiceImpl authService;
private final RestTemplate restTemplate = new RestTemplate(); 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.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.HashMap;
import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.annotation.Anonymous;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -58,6 +57,17 @@ public class ClientAccountController extends BaseController {
@Autowired @Autowired
private ClientDeviceMapper clientDeviceMapper; 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("账号已被停用"); return AjaxResult.error("账号已被停用");
} }
// 检查设备数量限制 AjaxResult limitCheck = checkDeviceLimit(username, clientId, account.getDeviceLimit());
int deviceLimit = account.getDeviceLimit(); if (limitCheck != null) return limitCheck;
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 + "个),请先移除其他设备");
}
String token = Jwts.builder() String token = Jwts.builder()
.setHeaderParam("kid", jwtRsaKeyService.getKeyId()) .setHeaderParam("kid", jwtRsaKeyService.getKeyId())
.setSubject(username) .setSubject(username)
@@ -160,7 +163,6 @@ public class ClientAccountController extends BaseController {
.signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey()) .signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey())
.compact(); .compact();
// 检查设备试用期仅对trial账号生效
boolean deviceTrialExpired = false; boolean deviceTrialExpired = false;
if ("trial".equals(account.getAccountType())) { if ("trial".equals(account.getAccountType())) {
ClientDevice device = clientDeviceMapper.selectByDeviceIdAndUsername(clientId, username); ClientDevice device = clientDeviceMapper.selectByDeviceIdAndUsername(clientId, username);
@@ -169,14 +171,14 @@ public class ClientAccountController extends BaseController {
&& new Date().after(device.getTrialExpireTime()); && new Date().after(device.getTrialExpireTime());
} }
Map<String, Object> data = new HashMap<>(); return AjaxResult.success(Map.of(
data.put("token", token); "token", token,
data.put("permissions", account.getPermissions()); "permissions", account.getPermissions(),
data.put("accountName", account.getAccountName()); "accountName", account.getAccountName(),
data.put("expireTime", account.getExpireTime()); "expireTime", account.getExpireTime(),
data.put("accountType", account.getAccountType()); "accountType", account.getAccountType(),
data.put("deviceTrialExpired", deviceTrialExpired); "deviceTrialExpired", deviceTrialExpired
return AjaxResult.success(data); ));
} }
/** /**
* 验证token * 验证token
@@ -202,7 +204,6 @@ public class ClientAccountController extends BaseController {
clientAccountService.updateClientAccount(account); clientAccountService.updateClientAccount(account);
} }
// 检查设备试用期仅对trial账号生效
boolean deviceTrialExpired = false; boolean deviceTrialExpired = false;
if ("trial".equals(account.getAccountType())) { if ("trial".equals(account.getAccountType())) {
ClientDevice device = clientDeviceMapper.selectByDeviceIdAndUsername(clientId, username); ClientDevice device = clientDeviceMapper.selectByDeviceIdAndUsername(clientId, username);
@@ -211,14 +212,14 @@ public class ClientAccountController extends BaseController {
&& new Date().after(device.getTrialExpireTime()); && new Date().after(device.getTrialExpireTime());
} }
Map<String, Object> result = new HashMap<>(); return AjaxResult.success(Map.of(
result.put("username", username); "username", username,
result.put("permissions", account.getPermissions()); "permissions", account.getPermissions(),
result.put("accountName", account.getAccountName()); "accountName", account.getAccountName(),
result.put("expireTime", account.getExpireTime()); "expireTime", account.getExpireTime(),
result.put("accountType", account.getAccountType()); "accountType", account.getAccountType(),
result.put("deviceTrialExpired", deviceTrialExpired); "deviceTrialExpired", deviceTrialExpired
return AjaxResult.success(result); ));
} }
/** /**
@@ -276,14 +277,14 @@ public class ClientAccountController extends BaseController {
.signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey()) .signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey())
.compact(); .compact();
Map<String, Object> data = new HashMap<>(); return AjaxResult.success(Map.of(
data.put("token", token); "token", token,
data.put("permissions", clientAccount.getPermissions()); "permissions", clientAccount.getPermissions(),
data.put("accountName", clientAccount.getAccountName()); "accountName", clientAccount.getAccountName(),
data.put("expireTime", clientAccount.getExpireTime()); "expireTime", clientAccount.getExpireTime(),
data.put("accountType", clientAccount.getAccountType()); "accountType", clientAccount.getAccountType(),
data.put("deviceTrialExpired", false); "deviceTrialExpired", false
return AjaxResult.success(data); ));
} }
/** /**
@@ -324,13 +325,10 @@ public class ClientAccountController extends BaseController {
account.setUpdateBy(getUsername()); account.setUpdateBy(getUsername());
clientAccountService.updateClientAccount(account); clientAccountService.updateClientAccount(account);
// 推送续费通知
sseHubService.sendEventToAllDevices(account.getUsername(), "VIP_RENEWED", sseHubService.sendEventToAllDevices(account.getUsername(), "VIP_RENEWED",
"{\"expireTime\":\"" + newExpireTime + "\"}"); "{\"expireTime\":\"" + newExpireTime + "\"}");
Map<String, Object> result = new HashMap<>(); return AjaxResult.success(Map.of("expireTime", newExpireTime));
result.put("expireTime", newExpireTime);
return AjaxResult.success(result);
} }
} }

View File

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