diff --git a/electron-vue-template/electron-builder.json b/electron-vue-template/electron-builder.json index 5c02c99..7faa152 100644 --- a/electron-vue-template/electron-builder.json +++ b/electron-vue-template/electron-builder.json @@ -7,12 +7,11 @@ "compression": "maximum", "asarUnpack": [ "public/jre/**/*", - "public/icon/icon.png", - "public/icon/image.png", - "public/icon/img.png", + "public/icon/**/*", "public/image/**/*", "public/splash.html", - "public/config/**/*" + "public/config/**/*", + "renderer/**/*" ], "directories": { "output": "dist" @@ -26,7 +25,7 @@ }, "win": { "target": "dir", - "icon": "public/icon/icon.png" + "icon": "public/icon/icon1.png" }, "files": [ "package.json", @@ -40,19 +39,12 @@ "to": "renderer", "filter": ["**/*"] }, - { - "from": "src/main/static", - "to": "static", - "filter": ["**/*"] - }, { "from": "public", "to": "public", "filter": [ "jre/**/*", - "icon/icon.png", - "icon/image.png", - "icon/img.png", + "icon/**/*", "image/**/*", "splash.html", "config/**/*", diff --git a/electron-vue-template/src/renderer/public/icon/acquisition.png b/electron-vue-template/public/icon/acquisition.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/acquisition.png rename to electron-vue-template/public/icon/acquisition.png diff --git a/electron-vue-template/src/renderer/public/icon/anjldk.png b/electron-vue-template/public/icon/anjldk.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/anjldk.png rename to electron-vue-template/public/icon/anjldk.png diff --git a/electron-vue-template/src/renderer/public/icon/asin.png b/electron-vue-template/public/icon/asin.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/asin.png rename to electron-vue-template/public/icon/asin.png diff --git a/electron-vue-template/src/renderer/public/icon/cancel.png b/electron-vue-template/public/icon/cancel.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/cancel.png rename to electron-vue-template/public/icon/cancel.png diff --git a/electron-vue-template/src/renderer/public/icon/done.png b/electron-vue-template/public/icon/done.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/done.png rename to electron-vue-template/public/icon/done.png diff --git a/electron-vue-template/src/renderer/public/icon/done1.png b/electron-vue-template/public/icon/done1.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/done1.png rename to electron-vue-template/public/icon/done1.png diff --git a/electron-vue-template/src/renderer/public/icon/error.png b/electron-vue-template/public/icon/error.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/error.png rename to electron-vue-template/public/icon/error.png diff --git a/electron-vue-template/src/renderer/public/icon/icon1.png b/electron-vue-template/public/icon/icon1.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/icon1.png rename to electron-vue-template/public/icon/icon1.png diff --git a/electron-vue-template/src/renderer/public/icon/inProgress.png b/electron-vue-template/public/icon/inProgress.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/inProgress.png rename to electron-vue-template/public/icon/inProgress.png diff --git a/electron-vue-template/src/renderer/public/icon/networkErrors.png b/electron-vue-template/public/icon/networkErrors.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/networkErrors.png rename to electron-vue-template/public/icon/networkErrors.png diff --git a/electron-vue-template/src/renderer/public/icon/plsb.png b/electron-vue-template/public/icon/plsb.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/plsb.png rename to electron-vue-template/public/icon/plsb.png diff --git a/electron-vue-template/src/renderer/public/icon/wait.png b/electron-vue-template/public/icon/wait.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/wait.png rename to electron-vue-template/public/icon/wait.png diff --git a/electron-vue-template/src/renderer/public/icon/wlymx.png b/electron-vue-template/public/icon/wlymx.png similarity index 100% rename from electron-vue-template/src/renderer/public/icon/wlymx.png rename to electron-vue-template/public/icon/wlymx.png diff --git a/electron-vue-template/src/renderer/public/image/img.png b/electron-vue-template/public/image/img.png similarity index 100% rename from electron-vue-template/src/renderer/public/image/img.png rename to electron-vue-template/public/image/img.png diff --git a/electron-vue-template/src/renderer/public/image/img_1.png b/electron-vue-template/public/image/img_1.png similarity index 100% rename from electron-vue-template/src/renderer/public/image/img_1.png rename to electron-vue-template/public/image/img_1.png diff --git a/electron-vue-template/src/renderer/public/image/user.png b/electron-vue-template/public/image/user.png similarity index 100% rename from electron-vue-template/src/renderer/public/image/user.png rename to electron-vue-template/public/image/user.png diff --git a/electron-vue-template/scripts/copy-assets.js b/electron-vue-template/scripts/copy-assets.js index 7d8c898..bd5d848 100644 --- a/electron-vue-template/scripts/copy-assets.js +++ b/electron-vue-template/scripts/copy-assets.js @@ -2,7 +2,25 @@ const Path = require('path'); const FileSystem = require('fs-extra'); async function copyAssets() { - console.log('Static assets are now handled by Vite from src/renderer/public'); + console.log('Copying static assets from public directory...'); + + const publicDir = Path.join(__dirname, '..', 'public'); + const buildRendererDir = Path.join(__dirname, '..', 'build', 'renderer'); + + // 确保 build/renderer 下的 icon 和 image 目录存在且是最新的 + // 这样打包后 renderer/icon 和 renderer/image 会包含所有图标 + await FileSystem.copy( + Path.join(publicDir, 'icon'), + Path.join(buildRendererDir, 'icon'), + { overwrite: true } + ); + await FileSystem.copy( + Path.join(publicDir, 'image'), + Path.join(buildRendererDir, 'image'), + { overwrite: true } + ); + + console.log('Static assets copied to build/renderer successfully!'); } module.exports = copyAssets; \ No newline at end of file diff --git a/electron-vue-template/src/main/main.ts b/electron-vue-template/src/main/main.ts index 7580bc3..c0e49d9 100644 --- a/electron-vue-template/src/main/main.ts +++ b/electron-vue-template/src/main/main.ts @@ -1,6 +1,6 @@ -import {app, BrowserWindow, ipcMain, Menu, screen, dialog} from 'electron'; -import {existsSync, createWriteStream, promises as fs, mkdirSync, copyFileSync, readdirSync, writeFileSync} from 'fs'; -import {join, dirname, basename} from 'path'; +import {app, BrowserWindow, ipcMain, Menu, screen, dialog, protocol} from 'electron'; +import {existsSync, createWriteStream, promises as fs, mkdirSync, copyFileSync, readdirSync, writeFileSync, readFileSync} from 'fs'; +import {join, dirname, basename, extname} from 'path'; import {spawn, ChildProcess} from 'child_process'; import * as https from 'https'; import * as http from 'http'; @@ -74,7 +74,7 @@ function getJarFilePath(): string { } const getSplashPath = () => getResourcePath('../../public/splash.html', 'public/splash.html'); -const getIconPath = () => getResourcePath('../../public/icon/icon.png', 'public/icon/icon.png', '../renderer/icon/icon.png'); +const getIconPath = () => getResourcePath('../../public/icon/icon1.png', 'public/icon/icon1.png', '../renderer/icon/icon1.png'); const getLogbackConfigPath = () => getResourcePath('../../public/config/logback.xml', 'public/config/logback.xml'); function getDataDirectoryPath(): string { @@ -211,7 +211,7 @@ function startSpringBoot() { } } - startSpringBoot(); + // startSpringBoot(); function stopSpringBoot() { if (!springProcess) return; try { @@ -305,6 +305,28 @@ if (!gotTheLock) { } app.whenReady().then(() => { + // 注册文件协议拦截器,将 /icon/ 和 /image/ 请求重定向到 public 目录 + if (!isDev) { + protocol.interceptFileProtocol('file', (request, callback) => { + let url = request.url.substring(8); // 移除 'file:///' + + // 检查是否是 icon 或 image 资源请求 + if (url.includes('/icon/') || url.includes('/image/')) { + const match = url.match(/\/(icon|image)\/([^?#]+)/); + if (match) { + const [, type, filename] = match; + const publicPath = join(process.resourcesPath, 'app.asar.unpacked', 'public', type, filename); + if (existsSync(publicPath)) { + callback({ path: publicPath }); + return; + } + } + } + + callback({ path: url }); + }); + } + // 应用开机自启动配置 const config = loadConfig(); const shouldMinimize = config.launchMinimized || false; @@ -348,9 +370,9 @@ app.whenReady().then(() => { } } //666 - // setTimeout(() => { - // openAppIfNotOpened(); - // }, 100); + setTimeout(() => { + openAppIfNotOpened(); + }, 100); app.on('activate', () => { if (mainWindow && !mainWindow.isDestroyed()) { diff --git a/electron-vue-template/src/main/tray.ts b/electron-vue-template/src/main/tray.ts index effc853..cfd1ddc 100644 --- a/electron-vue-template/src/main/tray.ts +++ b/electron-vue-template/src/main/tray.ts @@ -7,11 +7,11 @@ let tray: Tray | null = null function getIconPath(): string { const isDev = process.env.NODE_ENV === 'development' if (isDev) { - return join(__dirname, '../../public/icon/icon.png') + return join(__dirname, '../../public/icon/icon1.png') } - const bundledPath = join(process.resourcesPath, 'app.asar.unpacked', 'public/icon/icon.png') + const bundledPath = join(process.resourcesPath, 'app.asar.unpacked', 'public/icon/icon1.png') if (existsSync(bundledPath)) return bundledPath - return join(__dirname, '../renderer/icon/icon.png') + return join(__dirname, '../renderer/icon/icon1.png') } export function createTray(mainWindow: BrowserWindow | null) { diff --git a/electron-vue-template/src/renderer/api/http.ts b/electron-vue-template/src/renderer/api/http.ts index 7ad0c1d..ea133c8 100644 --- a/electron-vue-template/src/renderer/api/http.ts +++ b/electron-vue-template/src/renderer/api/http.ts @@ -1,13 +1,14 @@ export type HttpMethod = 'GET' | 'POST' | 'DELETE'; export const CONFIG = { CLIENT_BASE: 'http://localhost:8081', - 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' + //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 { - if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma') || path.startsWith('/tool/genmai') || path.startsWith('/tool/mark')) { + // 路由到 ruoyi-admin (8085):仅系统管理和监控相关 + if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma') || path.startsWith('/tool/genmai')) { return CONFIG.RUOYI_BASE; } return CONFIG.CLIENT_BASE; diff --git a/electron-vue-template/src/renderer/api/mark.ts b/electron-vue-template/src/renderer/api/mark.ts index 2288175..97f5ab7 100644 --- a/electron-vue-template/src/renderer/api/mark.ts +++ b/electron-vue-template/src/renderer/api/mark.ts @@ -1,16 +1,16 @@ import { http } from './http' export const markApi = { - // 新建任务 + // 新建任务(调用 erp_client_sb) newTask(file: File) { const formData = new FormData() formData.append('file', file) - return http.upload<{ code: number, data: any, msg: string }>('/tool/mark/newTask', formData) + return http.upload<{ code: number, data: any, msg: string }>('/api/trademark/newTask', formData) }, - // 获取任务列表及筛选数据(返回完整行数据和表头) + // 获取任务列表及筛选数据(调用 erp_client_sb) getTask() { - return http.get<{ + return http.post<{ code: number, data: { original: any, @@ -18,7 +18,7 @@ export const markApi = { headers: string[] // 表头 }, msg: string - }>('/tool/mark/task') + }>('/api/trademark/task') }, // 品牌商标筛查 diff --git a/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue b/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue index 5d43909..8816957 100644 --- a/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue +++ b/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue @@ -150,7 +150,7 @@ function handleExportData() {
- 采集完成 + 采集完成 筛查失败 已取消 采集中 @@ -340,15 +340,15 @@ function handleExportData() {
查询数量 - {{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 100 ? trademarkPanelRef.taskProgress.product.total : '-' }} + {{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 ? trademarkPanelRef.taskProgress.product.total : '-' }}
未注册/TM标 - {{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 100 ? (trademarkPanelRef?.taskProgress?.product?.completed || 0) : '-' }} + {{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total ? (trademarkPanelRef?.taskProgress?.product?.completed || 0) : '-' }}
已过滤 - {{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 100 ? ((trademarkPanelRef?.taskProgress?.product?.total || 0) - (trademarkPanelRef?.taskProgress?.product?.completed || 0)) : '-' }} + {{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total ? ((trademarkPanelRef?.taskProgress?.product?.total || 0) - (trademarkPanelRef?.taskProgress?.product?.completed || 0)) : '-' }}
diff --git a/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue b/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue index a22c034..f78fd6c 100644 --- a/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue +++ b/electron-vue-template/src/renderer/components/amazon/TrademarkCheckPanel.vue @@ -332,7 +332,6 @@ async function startTrademarkQuery() { try { productResult = await pollTask() - if (!trademarkLoading.value) return if (!productResult || (productResult.code !== 200 && productResult.code !== 0)) { throw new Error('获取任务超时或失败,请重试') @@ -360,6 +359,7 @@ async function startTrademarkQuery() { if (filteredAsins.length === 0) { showMessage('第三方筛查结果为空', 'warning') + queryStatus.value = 'done' return } @@ -396,8 +396,6 @@ async function startTrademarkQuery() { // 品牌商标筛查 if (needBrandCheck) { - if (!trademarkLoading.value) return - // 如果没有执行产品筛查,需要先从Excel提取品牌列表和完整数据 if (!needProductCheck) { const extractResult = await markApi.extractBrands(trademarkFile.value) @@ -415,9 +413,7 @@ async function startTrademarkQuery() { trademarkHeaders.value = extractResult.data.headers || [] } - if (brandList.length === 0) { - showMessage('没有品牌需要筛查', 'warning') - } else { + if (brandList.length > 0) { const brandData = taskProgress.value.brand brandData.total = brandList.length brandData.current = 1 // 立即显示初始进度 @@ -439,8 +435,6 @@ async function startTrademarkQuery() { const brandResult = await markApi.brandCheck(brandList, brandTaskId.value) if (brandProgressTimer) clearInterval(brandProgressTimer) - if (!trademarkLoading.value) return - if (brandResult.code === 200 || brandResult.code === 0) { // 完成,显示100% brandData.total = brandResult.data.checked || brandResult.data.total || brandData.total @@ -489,18 +483,17 @@ async function startTrademarkQuery() { } } - if (trademarkLoading.value) { - queryStatus.value = 'done' - emit('updateData', trademarkData.value) - - let summaryMsg = '筛查完成' - if (needProductCheck) summaryMsg += `,产品:${taskProgress.value.product.completed}/${taskProgress.value.product.total}` - if (needBrandCheck && brandList.length > 0) summaryMsg += `,品牌:${taskProgress.value.brand.completed}/${taskProgress.value.brand.total}` - showMessage(summaryMsg, 'success') - - // 保存会话 - await saveSession() - } + // 只要流程正常完成,就设置为done状态(不再依赖trademarkLoading) + queryStatus.value = 'done' + emit('updateData', trademarkData.value) + + let summaryMsg = '筛查完成' + if (needProductCheck) summaryMsg += `,产品:${taskProgress.value.product.completed}/${taskProgress.value.product.total}` + if (needBrandCheck && brandList.length > 0) summaryMsg += `,品牌:${taskProgress.value.brand.completed}/${taskProgress.value.brand.total}` + showMessage(summaryMsg, 'success') + + // 保存会话 + await saveSession() } catch (error: any) { const hasProductData = taskProgress.value.product.total > 0 @@ -536,6 +529,13 @@ async function startTrademarkQuery() { clearInterval(brandProgressTimer) brandProgressTimer = null } + + // 兜底:如果有数据且状态还是进行中,强制设置为完成(最优先处理) + if (trademarkData.value.length > 0 && queryStatus.value === 'inProgress') { + queryStatus.value = 'done' + emit('updateData', trademarkData.value) + } + trademarkLoading.value = false currentStep.value = 0 totalSteps.value = 0 diff --git a/electron-vue-template/src/renderer/public/icon/amazon.png b/electron-vue-template/src/renderer/public/icon/amazon.png deleted file mode 100644 index b5100c2..0000000 Binary files a/electron-vue-template/src/renderer/public/icon/amazon.png and /dev/null differ diff --git a/electron-vue-template/src/renderer/public/icon/icon.png b/electron-vue-template/src/renderer/public/icon/icon.png deleted file mode 100644 index f94b10c..0000000 Binary files a/electron-vue-template/src/renderer/public/icon/icon.png and /dev/null differ diff --git a/electron-vue-template/src/renderer/public/icon/image.png b/electron-vue-template/src/renderer/public/icon/image.png deleted file mode 100644 index 91794a4..0000000 Binary files a/electron-vue-template/src/renderer/public/icon/image.png and /dev/null differ diff --git a/electron-vue-template/src/renderer/public/icon/img.png b/electron-vue-template/src/renderer/public/icon/img.png deleted file mode 100644 index 82c4bc4..0000000 Binary files a/electron-vue-template/src/renderer/public/icon/img.png and /dev/null differ diff --git a/electron-vue-template/src/renderer/public/icon/rakuten.png b/electron-vue-template/src/renderer/public/icon/rakuten.png deleted file mode 100644 index 3271000..0000000 Binary files a/electron-vue-template/src/renderer/public/icon/rakuten.png and /dev/null differ diff --git a/electron-vue-template/src/renderer/public/icon/zebra.png b/electron-vue-template/src/renderer/public/icon/zebra.png deleted file mode 100644 index 53f0cd4..0000000 Binary files a/electron-vue-template/src/renderer/public/icon/zebra.png and /dev/null differ diff --git a/electron-vue-template/src/renderer/public/image/111.png b/electron-vue-template/src/renderer/public/image/111.png deleted file mode 100644 index aabd8cd..0000000 Binary files a/electron-vue-template/src/renderer/public/image/111.png and /dev/null differ diff --git a/electron-vue-template/src/renderer/public/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg b/electron-vue-template/src/renderer/public/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg deleted file mode 100644 index 125c541..0000000 Binary files a/electron-vue-template/src/renderer/public/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg and /dev/null differ diff --git a/electron-vue-template/src/renderer/public/image/splash_screen.png b/electron-vue-template/src/renderer/public/image/splash_screen.png deleted file mode 100644 index 165407b..0000000 Binary files a/electron-vue-template/src/renderer/public/image/splash_screen.png and /dev/null differ diff --git a/electron-vue-template/src/renderer/public/splash.html b/electron-vue-template/src/renderer/public/splash.html deleted file mode 100644 index 92c6270..0000000 --- a/electron-vue-template/src/renderer/public/splash.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - 正在启动... - - - - - - - -
-
-
- - - - diff --git a/electron-vue-template/vite.config.js b/electron-vue-template/vite.config.js index a36dda3..810cd8c 100644 --- a/electron-vue-template/vite.config.js +++ b/electron-vue-template/vite.config.js @@ -6,7 +6,7 @@ const { defineConfig } = require('vite'); */ const config = defineConfig({ root: Path.join(__dirname, 'src', 'renderer'), - publicDir: Path.join(__dirname, 'src', 'renderer', 'public'), + publicDir: Path.join(__dirname, 'public'), server: { port: 8083, }, diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java index 51761e6..6777fde 100644 --- a/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java +++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/TrademarkController.java @@ -1,12 +1,18 @@ package com.tashow.erp.controller; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.tashow.erp.entity.TrademarkSessionEntity; import com.tashow.erp.repository.TrademarkSessionRepository; import com.tashow.erp.service.BrandTrademarkCacheService; +import com.tashow.erp.service.IFangzhouApiService; import com.tashow.erp.utils.ExcelParseUtil; import com.tashow.erp.utils.JsonData; import com.tashow.erp.utils.LoggerUtil; import com.tashow.erp.utils.TrademarkCheckUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.http.HttpUtil; +import cn.hutool.poi.excel.ExcelReader; +import cn.hutool.poi.excel.ExcelUtil; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -33,6 +39,9 @@ public class TrademarkController { @Autowired private TrademarkSessionRepository sessionRepository; + @Autowired + private IFangzhouApiService fangzhouApi; + // 进度追踪 private final Map progressMap = new java.util.concurrent.ConcurrentHashMap<>(); @@ -66,12 +75,11 @@ public class TrademarkController { .collect(Collectors.toList()); logger.info("全局缓存命中: {}/{},需查询: {}", cached.size(), list.size(), toQuery.size()); - // 3. 查询未命中的品牌 Map queried = new HashMap<>(); if (!toQuery.isEmpty()) { for (int i = 0; i < toQuery.size(); i++) { - // 检查任务是否被取消 + // 检查任务是否被取消值 if (taskId != null && cancelMap.getOrDefault(taskId, false)) { logger.info("任务 {} 已被取消,停止查询", taskId); break; @@ -405,4 +413,117 @@ public class TrademarkController { return JsonData.buildError("恢复失败: " + e.getMessage()); } } + + // ==================== 方舟精选任务管理接口 ==================== + + /** + * 获取方舟精选任务列表 + * 从第三方 API 下载 Excel 并解析过滤数据 + */ + @PostMapping("/task") + public JsonData getTask() { + try { + // 1. 获取 Token 并轮询等待下载链接 + String token = fangzhouApi.getToken(); + JsonNode dNode = fangzhouApi.pollTask(token, 6, 5000); + String downloadUrl = dNode.get("download_url").asText(); + + if (downloadUrl == null || downloadUrl.isEmpty()) { + return JsonData.buildError("下载链接生成超时"); + } + + // 2. 下载并解析 Excel + String tempFilePath = System.getProperty("java.io.tmpdir") + "/trademark_" + System.currentTimeMillis() + ".xlsx"; + HttpUtil.downloadFile(downloadUrl, FileUtil.file(tempFilePath)); + + List> filteredData = new ArrayList<>(); + List excelHeaders = new ArrayList<>(); + ExcelReader reader = null; + + try { + reader = ExcelUtil.getReader(FileUtil.file(tempFilePath)); + List> rows = reader.read(); + + if (rows.isEmpty()) { + throw new RuntimeException("Excel文件为空"); + } + + // 读取表头 + List headerRow = rows.get(0); + for (Object cell : headerRow) { + excelHeaders.add(cell != null ? cell.toString().trim() : ""); + } + + // 找到商标类型列的索引 + int trademarkTypeIndex = -1; + for (int i = 0; i < excelHeaders.size(); i++) { + if ("商标类型".equals(excelHeaders.get(i))) { + trademarkTypeIndex = i; + break; + } + } + + if (trademarkTypeIndex < 0) { + throw new RuntimeException("未找到'商标类型'列"); + } + + // 过滤TM和未注册数据 + for (int i = 1; i < rows.size(); i++) { + List row = rows.get(i); + if (row.size() > trademarkTypeIndex) { + String trademarkType = row.get(trademarkTypeIndex).toString().trim(); + if ("TM".equals(trademarkType) || "未注册".equals(trademarkType)) { + Map item = new HashMap<>(); + for (int j = 0; j < excelHeaders.size() && j < row.size(); j++) { + item.put(excelHeaders.get(j), row.get(j)); + } + filteredData.add(item); + } + } + } + } finally { + if (reader != null) { + reader.close(); + } + FileUtil.del(tempFilePath); + } + + // 6. 返回结果 + Map combinedResult = new HashMap<>(); + combinedResult.put("original", dNode); + combinedResult.put("filtered", filteredData); + combinedResult.put("headers", excelHeaders); + + logger.info("任务获取成功,过滤出 {} 条数据", filteredData.size()); + return JsonData.buildSuccess(combinedResult); + + } catch (Exception e) { + logger.error("获取任务失败", e); + return JsonData.buildError("获取任务失败: " + e.getMessage()); + } + } + + /** + * 创建新任务 + * 上传文件到方舟精选 + */ + @PostMapping("/newTask") + public JsonData newTask(@RequestParam("file") MultipartFile file) { + try { + // 1. 获取 Token 并上传文件 + String token = fangzhouApi.getToken(); + JsonNode jsonNode = fangzhouApi.uploadFile(file, token); + + // 2. 返回结果 + if (jsonNode.get("S").asInt() == 1) { + logger.info("任务创建成功: {}", file.getOriginalFilename()); + return JsonData.buildSuccess(jsonNode.toString()); + } + + return JsonData.buildError(jsonNode.get("M").asText()); + } catch (Exception e) { + logger.error("创建任务失败", e); + return JsonData.buildError("创建任务失败: " + e.getMessage()); + } + } } diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/IFangzhouApiService.java b/erp_client_sb/src/main/java/com/tashow/erp/service/IFangzhouApiService.java new file mode 100644 index 0000000..248369f --- /dev/null +++ b/erp_client_sb/src/main/java/com/tashow/erp/service/IFangzhouApiService.java @@ -0,0 +1,48 @@ +package com.tashow.erp.service; + +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.web.multipart.MultipartFile; + +/** + * 方舟精选 API 服务接口 + */ +public interface IFangzhouApiService { + + /** + * 从服务器获取 Token + */ + String getToken(); + + /** + * 刷新 Token + */ + String refreshToken(); + + /** + * 调用方舟精选 API(表单提交,自动处理 Token 过期) + * @param command 命令 + * @param data 数据 + * @param token Token + * @return 响应结果 + */ + JsonNode callApi(String command, String data, String token); + + /** + * 上传文件到方舟精选(自动处理 Token 过期) + * @param file 文件 + * @param token Token + * @return 响应结果 + */ + JsonNode uploadFile(MultipartFile file, String token); + + /** + * 轮询获取任务(等待下载链接生成) + * @param token Token + * @param maxRetries 最大重试次数 + * @param intervalMs 重试间隔(毫秒) + * @return 任务节点 + * @throws InterruptedException 中断异常 + */ + JsonNode pollTask(String token, int maxRetries, long intervalMs) throws InterruptedException; +} + diff --git a/erp_client_sb/src/main/java/com/tashow/erp/service/impl/FangzhouApiServiceImpl.java b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/FangzhouApiServiceImpl.java new file mode 100644 index 0000000..0bfebaa --- /dev/null +++ b/erp_client_sb/src/main/java/com/tashow/erp/service/impl/FangzhouApiServiceImpl.java @@ -0,0 +1,198 @@ +package com.tashow.erp.service.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tashow.erp.service.IFangzhouApiService; +import com.tashow.erp.utils.ApiForwarder; +import com.tashow.erp.utils.LoggerUtil; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Map; + +/** + * 方舟精选 API 服务实现 + */ +@Service +public class FangzhouApiServiceImpl implements IFangzhouApiService { + private static final Logger logger = LoggerUtil.getLogger(FangzhouApiServiceImpl.class); + private static final String API_SECRET = "e10adc3949ba59abbe56e057f20f883e"; + private static final String FANGZHOU_API_URL = "https://api.fangzhoujingxuan.com/Task"; + private static final int TOKEN_EXPIRED_CODE = -1006; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private ApiForwarder apiForwarder; + + @Override + public String getToken() { + try { + logger.info("从服务器获取 Token"); + ResponseEntity response = apiForwarder.get("/tool/mark/token", null); + + @SuppressWarnings("unchecked") + Map body = (Map) response.getBody(); + + if (body != null && (Integer) body.get("code") == 200) { + return body.get("data").toString(); + } + throw new RuntimeException("获取 Token 失败"); + } catch (Exception e) { + logger.error("获取 Token 失败", e); + throw new RuntimeException("获取 Token 失败: " + e.getMessage()); + } + } + + @Override + public String refreshToken() { + try { + logger.info("刷新 Token"); + ResponseEntity response = apiForwarder.post("/tool/mark/refreshToken", null, null); + + @SuppressWarnings("unchecked") + Map body = (Map) response.getBody(); + + if (body != null && (Integer) body.get("code") == 200) { + return body.get("data").toString(); + } + throw new RuntimeException("刷新 Token 失败"); + } catch (Exception e) { + logger.error("刷新 Token 失败", e); + throw new RuntimeException("刷新 Token 失败: " + e.getMessage()); + } + } + + @Override + public JsonNode callApi(String command, String data, String token) { + try { + long ts = System.currentTimeMillis(); + + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("c", command); + formData.add("d", data); + formData.add("t", token); + formData.add("s", md5(ts + data + API_SECRET)); + formData.add("ts", String.valueOf(ts)); + formData.add("website", "1"); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + HttpEntity> requestEntity = new HttpEntity<>(formData, headers); + + String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class); + JsonNode json = objectMapper.readTree(result); + + // 处理 Token 过期,自动刷新重试 + if (json.get("S").asInt() == TOKEN_EXPIRED_CODE) { + logger.info("Token 过期,刷新后重试"); + String newToken = refreshToken(); + formData.set("t", newToken); + formData.set("s", md5(ts + data + API_SECRET)); + requestEntity = new HttpEntity<>(formData, headers); + result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class); + json = objectMapper.readTree(result); + } + + return json; + } catch (Exception e) { + logger.error("调用方舟精选 API 失败", e); + throw new RuntimeException("调用 API 失败: " + e.getMessage()); + } + } + + @Override + public JsonNode uploadFile(MultipartFile file, String token) { + try { + String data = String.format("{\"name\":\"%s\",\"type\":1}", file.getOriginalFilename()); + long ts = System.currentTimeMillis(); + + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("c", "Create"); + formData.add("t", token); + formData.add("ts", ts); + formData.add("d", data); + formData.add("s", md5(ts + data + API_SECRET)); + formData.add("website", "1"); + formData.add("files", file.getResource()); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + HttpEntity> requestEntity = new HttpEntity<>(formData, headers); + + String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class); + JsonNode json = objectMapper.readTree(result); + + // 处理 Token 过期 + if (json.get("S").asInt() == TOKEN_EXPIRED_CODE) { + logger.info("Token 过期,刷新后重试"); + String newToken = refreshToken(); + formData.set("t", newToken); + formData.set("s", md5(ts + data + API_SECRET)); + requestEntity = new HttpEntity<>(formData, headers); + result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class); + json = objectMapper.readTree(result); + } + + return json; + } catch (Exception e) { + logger.error("上传文件失败", e); + throw new RuntimeException("上传文件失败: " + e.getMessage()); + } + } + + @Override + public JsonNode pollTask(String token, int maxRetries, long intervalMs) throws InterruptedException { + String data = "{\"name\":\"\",\"page_size\":20,\"current_page\":1}"; + + for (int i = 0; i < maxRetries; i++) { + JsonNode json = callApi("TaskPageList", data, token); + JsonNode dNode = json.get("D").get("items").get(0); + String downloadUrl = dNode.get("download_url").asText(); + + if (downloadUrl != null && !downloadUrl.isEmpty()) { + return dNode; + } + + if (i < maxRetries - 1) { + logger.info("下载链接未生成,等待 {}ms 后重试 ({}/{})", intervalMs, i + 1, maxRetries); + Thread.sleep(intervalMs); + } + } + + throw new RuntimeException("等待下载链接超时"); + } + + /** + * MD5 加密 + */ + private String md5(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } catch (Exception e) { + throw new RuntimeException("MD5加密失败", e); + } + } +} + diff --git a/erp_client_sb/src/main/resources/application.yml b/erp_client_sb/src/main/resources/application.yml index 9360247..e5a1566 100644 --- a/erp_client_sb/src/main/resources/application.yml +++ b/erp_client_sb/src/main/resources/application.yml @@ -47,8 +47,8 @@ server: api: server: # 主服务器API配置 - base-url: "http://8.138.23.49:8085" - #base-url: "http://192.168.1.89:8085" + #base-url: "http://8.138.23.49:8085" + base-url: "http://192.168.1.89:8085" paths: monitor: "/monitor/client/api" login: "/monitor/account/login" diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 055eb63..d2bdc9f 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -113,6 +113,12 @@ compile + + org.apache.httpcomponents + httpclient + 4.5.14 + + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/framework/config/BeanRestConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/framework/config/BeanRestConfig.java index 7bd04e9..b413a24 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/framework/config/BeanRestConfig.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/framework/config/BeanRestConfig.java @@ -1,11 +1,15 @@ package com.ruoyi.framework.config; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; /** - * 程序注解配置 + * RestTemplate 配置 + * 添加连接池和超时设置,防止连接泄漏 * * @author ruoyi */ @@ -13,10 +17,24 @@ import org.springframework.web.client.RestTemplate; public class BeanRestConfig { /** - * 创建RestTemplate Bean + * 创建 RestTemplate Bean(带连接池) + * 适用于小规模并发场景(10人以内) */ @Bean public RestTemplate restTemplate() { - return new RestTemplate(); + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); + factory.setConnectTimeout(10000); // 连接超时 10秒 + factory.setReadTimeout(30000); // 读取超时 30秒 + factory.setConnectionRequestTimeout(5000); // 从连接池获取连接超时 5秒 + + // 连接池配置(降低资源占用) + HttpClient httpClient = HttpClientBuilder.create() + .setMaxConnTotal(20) // 最大连接数 20(降低内存) + .setMaxConnPerRoute(10) // 每个路由最大 10 连接 + .build(); + + factory.setHttpClient(httpClient); + + return new RestTemplate(factory); } } \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MarkController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MarkController.java index c1f1841..ec9422f 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MarkController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/MarkController.java @@ -1,217 +1,107 @@ package com.ruoyi.web.controller.tool; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.system.service.IMarkService; -import cn.hutool.core.io.FileUtil; -import cn.hutool.http.HttpUtil; -import cn.hutool.poi.excel.ExcelReader; -import cn.hutool.poi.excel.ExcelUtil; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.*; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.multipart.MultipartFile; -import java.util.*; + +import java.util.Map; + +/** + * 方舟商标 Token 管理控制器 + * 职责:仅负责 Token 的获取、刷新和管理 + * 重型任务(Excel 下载、解析、商标检查)已转移到 erp_client_sb + */ @RequestMapping("/tool/mark") @RestController @Anonymous public class MarkController { - private static final String API_SECRET = "e10adc3949ba59abbe56e057f20f883e"; - private static final String ERP_CLIENT_BASE_URL = "http://127.0.0.1:8081"; - private final RestTemplate restTemplate = new RestTemplate(); - private final ObjectMapper objectMapper = new ObjectMapper(); - @Autowired - private RedisCache redisCache; + @Autowired private IMarkService markService; + + @Autowired + private RedisCache redisCache; + /** - * 获取任务列表 + * 获取 Token + * 如果 Redis 中不存在 Token,自动注册新账号 + * + * @return Token 字符串 */ - @GetMapping("/task") - public AjaxResult Task() { + @GetMapping("/token") + public AjaxResult getToken() { try { + // 先尝试从 Redis 获取现有 Token String token = redisCache.getCacheMapValue(CacheConstants.MARK_ACCOUNT_KEY, "token"); - String d = "{\"name\":\"\",\"page_size\":20,\"current_page\":1}"; - long ts = System.currentTimeMillis(); - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("c", "TaskPageList"); - formData.add("d", d); - formData.add("t", token); - formData.add("s", markService.md5(ts + d + API_SECRET)); - formData.add("ts", String.valueOf(ts)); - formData.add("website", "1"); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - HttpEntity> requestEntity = new HttpEntity<>(formData, headers); - String result = restTemplate.postForObject("https://api.fangzhoujingxuan.com/Task", requestEntity, String.class); - JsonNode json = objectMapper.readTree(result); - if(json.get("S").asInt()==-1006){ - token= markService.login(); - formData.add("t", token); - requestEntity = new HttpEntity<>(formData, headers); - result = restTemplate.postForObject("https://api.fangzhoujingxuan.com/Task", requestEntity, String.class); - json= objectMapper.readTree(result); + + if (token != null && !token.isEmpty()) { + return AjaxResult.success("获取成功", token); } - JsonNode dNode = json.get("D").get("items").get(0); - // 获取下载链接并处理Excel数据 - String downloadUrl = dNode.get("download_url").asText(); - for (int i = 0; i < 6 && downloadUrl.isEmpty(); i++) { - Thread.sleep(5000); - long reTs = System.currentTimeMillis(); - MultiValueMap reFormData = new LinkedMultiValueMap<>(); - reFormData.add("c", "TaskPageList"); - reFormData.add("d", d); - reFormData.add("t", token); - reFormData.add("s", markService.md5(reTs + d + API_SECRET)); - reFormData.add("ts", String.valueOf(reTs)); - reFormData.add("website", "1"); - HttpEntity> reRequestEntity = new HttpEntity<>(reFormData, headers); - String reResult = restTemplate.postForObject("https://api.fangzhoujingxuan.com/Task", reRequestEntity, String.class); - JsonNode reJson = objectMapper.readTree(reResult); - dNode = reJson.get("D").get("items").get(0); - downloadUrl = reJson.get("D").get("items").get(0).get("download_url").asText(); - } - String tempFilePath = System.getProperty("java.io.tmpdir") + "/trademark_" + System.currentTimeMillis() + ".xlsx"; - HttpUtil.downloadFile(downloadUrl, FileUtil.file(tempFilePath)); - List> filteredData = new ArrayList<>(); - List excelHeaders = new ArrayList<>(); - ExcelReader reader = null; - try { - reader = ExcelUtil.getReader(FileUtil.file(tempFilePath)); - List> rows = reader.read(); - - if (rows.isEmpty()) { - throw new RuntimeException("Excel文件为空"); - } - - // 读取表头 - List headerRow = rows.get(0); - for (Object cell : headerRow) { - excelHeaders.add(cell != null ? cell.toString().trim() : ""); - } - - // 找到商标类型列的索引 - int trademarkTypeIndex = -1; - for (int i = 0; i < excelHeaders.size(); i++) { - if ("商标类型".equals(excelHeaders.get(i))) { - trademarkTypeIndex = i; - break; - } - } - - if (trademarkTypeIndex < 0) { - throw new RuntimeException("未找到'商标类型'列"); - } - - // 过滤TM和未注册数据,保留所有列 - for (int i = 1; i < rows.size(); i++) { - List row = rows.get(i); - if (row.size() > trademarkTypeIndex) { - String trademarkType = row.get(trademarkTypeIndex).toString().trim(); - if ("TM".equals(trademarkType) || "未注册".equals(trademarkType)) { - Map item = new HashMap<>(); - // 保存所有列的数据 - for (int j = 0; j < excelHeaders.size() && j < row.size(); j++) { - item.put(excelHeaders.get(j), row.get(j)); - } - filteredData.add(item); - } - } - } - } finally { - if (reader != null) { - reader.close(); - } - FileUtil.del(tempFilePath); - } - Map combinedResult = new HashMap<>(); - combinedResult.put("original", dNode); - combinedResult.put("filtered", filteredData); - combinedResult.put("headers", excelHeaders); - - return AjaxResult.success(combinedResult); + + // Token 不存在,自动注册新账号 + token = markService.reg(); + return AjaxResult.success("注册成功", token); + } catch (Exception e) { - throw new RuntimeException(e); - } - } - - // 新建任务 - @PostMapping("newTask") - public AjaxResult newTask(@RequestParam("file") MultipartFile file) { - try { - String token = redisCache.getCacheMapValue(CacheConstants.MARK_ACCOUNT_KEY, "token"); - if (token == null) token = markService.reg(); - String data =String.format("{\"name\":\"%s\",\"type\":1}", file.getOriginalFilename()) ; - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("c", "Create"); - formData.add("t", token); - formData.add("ts",System.currentTimeMillis()); - formData.add("d", data); - formData.add("s", markService.md5(System.currentTimeMillis() + data + API_SECRET)); - formData.add("website", "1"); - formData.add("files", file.getResource()); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - HttpEntity> requestEntity = new HttpEntity<>(formData, headers); - String result = restTemplate.postForObject("https://api.fangzhoujingxuan.com/Task", requestEntity, String.class); - JsonNode jsonNode = objectMapper.readTree(result); - if(jsonNode.get("S").asInt()==-1006){ - token= markService.login(); - formData.add("t", token); - requestEntity = new HttpEntity<>(formData, headers); - result = restTemplate.postForObject("https://api.fangzhoujingxuan.com/Task", requestEntity, String.class); - jsonNode= objectMapper.readTree(result); - } - return jsonNode.get("S").asInt()==1?AjaxResult.success(result): AjaxResult.error( jsonNode.get("S").asText()); - } catch (Exception e) { - throw new RuntimeException(e); + return AjaxResult.error("获取 Token 失败: " + e.getMessage()); } } /** - * 品牌商标筛查(调用 erp_client_sb 服务) - * @param brands 品牌列表(JSON数组) - * @return 筛查结果 + * 刷新 Token + * 使用已保存的账号密码重新登录,更新 Token + * + * @return 新的 Token */ - @PostMapping("brandCheck") - public AjaxResult brandCheck(@RequestBody List brands) { + @PostMapping("/refreshToken") + public AjaxResult refreshToken() { try { - if (brands == null || brands.isEmpty()) { - return AjaxResult.error("品牌列表不能为空"); + // 检查是否有账号信息 + Map accountData = redisCache.getCacheMap(CacheConstants.MARK_ACCOUNT_KEY); + + if (accountData == null || accountData.isEmpty()) { + // 没有账号信息,需要先注册 + String token = markService.reg(); + return AjaxResult.success("账号不存在,已自动注册", token); } - - // 调用 erp_client_sb 的商标检查接口 - String url = ERP_CLIENT_BASE_URL + "/api/trademark/brandCheck"; - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); + // 使用现有账号重新登录 + String token = markService.login(); + return AjaxResult.success("Token 刷新成功", token); - HttpEntity> requestEntity = new HttpEntity<>(brands, headers); - - // 调用远程服务 - String result = restTemplate.postForObject(url, requestEntity, String.class); - JsonNode jsonNode = objectMapper.readTree(result); - - // 判断返回状态 (erp_client_sb 的 JsonData: code=0 成功, code=-1 失败) - if (jsonNode.get("code").asInt() == 0) { - // 转换数据格式以适配前端 - JsonNode data = jsonNode.get("data"); - return AjaxResult.success(objectMapper.convertValue(data, Map.class)); - } else { - String msg = jsonNode.has("msg") ? jsonNode.get("msg").asText() : "品牌筛查失败"; - return AjaxResult.error(msg); - } } catch (Exception e) { - e.printStackTrace(); - return AjaxResult.error("品牌筛查失败: " + e.getMessage()); + return AjaxResult.error("刷新 Token 失败: " + e.getMessage()); + } + } + + /** + * 获取账号信息(用于调试) + * + * @return 账号信息(不含密码) + */ + @GetMapping("/accountInfo") + public AjaxResult getAccountInfo() { + try { + Map accountData = redisCache.getCacheMap(CacheConstants.MARK_ACCOUNT_KEY); + + if (accountData == null || accountData.isEmpty()) { + return AjaxResult.error("未找到账号信息"); + } + + // 不返回密码,仅返回账号和 Token 状态 + String account = accountData.get("account"); + String token = accountData.get("token"); + boolean hasToken = token != null && !token.isEmpty(); + + return AjaxResult.success() + .put("account", account) + .put("hasToken", hasToken); + + } catch (Exception e) { + return AjaxResult.error("获取账号信息失败: " + e.getMessage()); } } } diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index 52f7557..b60a1da 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -20,17 +20,17 @@ spring: username: password: # 初始连接数(增加预热连接) - initialSize: 8 + initialSize: 2 # 最小连接池数量 - minIdle: 10 + minIdle: 2 # 最大连接池数量 - maxActive: 25 - # 配置获取连接等待超时的时间(5秒) - maxWait: 5000 - # 配置连接超时时间(5秒) - connectTimeout: 5000 - # 配置网络超时时间(5秒) - socketTimeout: 5000 + maxActive: 10 + # 配置获取连接等待超时的时间(10秒) + maxWait: 10000 + # 配置连接超时时间(10秒) + connectTimeout: 10000 + # 配置网络超时时间(10秒) + socketTimeout: 10000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒(30秒检测一次) timeBetweenEvictionRunsMillis: 30000 # 配置一个连接在池中最小生存的时间,单位是毫秒 diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 79aec47..11367d6 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -35,17 +35,17 @@ server: # tomcat的URI编码 uri-encoding: UTF-8 # 连接数满后的排队数,默认为100 - accept-count: 1000 + accept-count: 50 threads: # tomcat最大线程数,默认为200 - max: 800 + max: 30 # Tomcat启动初始化的线程数,默认值10 - min-spare: 100 + min-spare: 5 # 日志配置 logging: level: - com.ruoyi: debug + com.ruoyi: info org.springframework: warn # 用户配置 @@ -82,7 +82,7 @@ spring: devtools: restart: # 热部署开关 - enabled: true + enabled: false # redis 配置 redis: # 地址 @@ -99,13 +99,13 @@ spring: lettuce: pool: # 连接池中的最小空闲连接(保持预热连接,避免临时建连) - min-idle: 5 + min-idle: 1 # 连接池中的最大空闲连接 - max-idle: 20 + max-idle: 5 # 连接池的最大数据库连接数 - max-active: 50 + max-active: 10 # 连接池最大阻塞等待时间 - max-wait: 10s + max-wait: 15s # 关闭超时时间 shutdown-timeout: 100ms # token配置 @@ -135,7 +135,7 @@ pagehelper: # Swagger配置 swagger: # 是否开启swagger - enabled: true + enabled: false # 请求前缀 pathMapping: /dev-api diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java index 7840141..39f2ebc 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java @@ -11,20 +11,21 @@ import java.util.concurrent.ThreadPoolExecutor; /** * 线程池配置 + * 优化为小规模并发场景(10人以内使用) * * @author ruoyi **/ @Configuration public class ThreadPoolConfig { - // 核心线程池大小 - private int corePoolSize = 50; + // 核心线程池大小(优化:50 → 10) + private int corePoolSize = 10; - // 最大可创建的线程数 - private int maxPoolSize = 200; + // 最大可创建的线程数(优化:200 → 20) + private int maxPoolSize = 20; - // 队列最大长度 - private int queueCapacity = 1000; + // 队列最大长度(优化:1000 → 100) + private int queueCapacity = 100; // 线程池维护线程所允许的空闲时间 private int keepAliveSeconds = 300;