feat(electron):优化商标筛查面板与资源加载逻辑
- 将多个 v-if 条件渲染改为 v-show,提升组件切换性能 - 优化商标任务完成状态判断逻辑,确保准确显示采集完成图标- 调整任务统计数据显示条件,支持零数据展示- 更新 API 配置地址,切换至本地开发环境地址 - 降低 Spring Boot 线程池与数据库连接池配置,适应小规模并发- 禁用 devtools 热部署与 Swagger 接口文档,优化生产环境性能 - 配置 RestTemplate 使用 HttpClient 连接池,增强 HTTP 请求稳定性 - 改进静态资源拷贝脚本,确保 icon 与 image 文件夹正确复制 - 更新 electron-builder 配置,优化资源打包路径与应用图标 - 修改 HTTP 路由规则,明确区分客户端与管理端接口路径- 注册文件协议拦截器,解决生产环境下 icon/image 资源加载问题 - 调整商标 API 接口路径,指向 erp_client_sb服务 -重构 MarkController 控制器,专注 Token 管理功能 - 优化线程池参数,适配低并发业务场景- 强化商标筛查流程控制,完善任务取消与异常处理机制 - 新增方舟精选任务管理接口,实现 Excel 下载与数据解析功能
@@ -7,12 +7,11 @@
|
|||||||
"compression": "maximum",
|
"compression": "maximum",
|
||||||
"asarUnpack": [
|
"asarUnpack": [
|
||||||
"public/jre/**/*",
|
"public/jre/**/*",
|
||||||
"public/icon/icon.png",
|
"public/icon/**/*",
|
||||||
"public/icon/image.png",
|
|
||||||
"public/icon/img.png",
|
|
||||||
"public/image/**/*",
|
"public/image/**/*",
|
||||||
"public/splash.html",
|
"public/splash.html",
|
||||||
"public/config/**/*"
|
"public/config/**/*",
|
||||||
|
"renderer/**/*"
|
||||||
],
|
],
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist"
|
"output": "dist"
|
||||||
@@ -26,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": "dir",
|
"target": "dir",
|
||||||
"icon": "public/icon/icon.png"
|
"icon": "public/icon/icon1.png"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"package.json",
|
"package.json",
|
||||||
@@ -40,19 +39,12 @@
|
|||||||
"to": "renderer",
|
"to": "renderer",
|
||||||
"filter": ["**/*"]
|
"filter": ["**/*"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"from": "src/main/static",
|
|
||||||
"to": "static",
|
|
||||||
"filter": ["**/*"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"from": "public",
|
"from": "public",
|
||||||
"to": "public",
|
"to": "public",
|
||||||
"filter": [
|
"filter": [
|
||||||
"jre/**/*",
|
"jre/**/*",
|
||||||
"icon/icon.png",
|
"icon/**/*",
|
||||||
"icon/image.png",
|
|
||||||
"icon/img.png",
|
|
||||||
"image/**/*",
|
"image/**/*",
|
||||||
"splash.html",
|
"splash.html",
|
||||||
"config/**/*",
|
"config/**/*",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 533 B After Width: | Height: | Size: 533 B |
|
Before Width: | Height: | Size: 638 B After Width: | Height: | Size: 638 B |
|
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
|
Before Width: | Height: | Size: 913 B After Width: | Height: | Size: 913 B |
|
Before Width: | Height: | Size: 731 B After Width: | Height: | Size: 731 B |
|
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 894 B |
|
Before Width: | Height: | Size: 870 B After Width: | Height: | Size: 870 B |
|
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.1 MiB |
|
Before Width: | Height: | Size: 968 B After Width: | Height: | Size: 968 B |
|
Before Width: | Height: | Size: 628 B After Width: | Height: | Size: 628 B |
|
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 894 B |
|
Before Width: | Height: | Size: 533 B After Width: | Height: | Size: 533 B |
|
Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 384 B |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
@@ -2,7 +2,25 @@ const Path = require('path');
|
|||||||
const FileSystem = require('fs-extra');
|
const FileSystem = require('fs-extra');
|
||||||
|
|
||||||
async function copyAssets() {
|
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;
|
module.exports = copyAssets;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import {app, BrowserWindow, ipcMain, Menu, screen, dialog} from 'electron';
|
import {app, BrowserWindow, ipcMain, Menu, screen, dialog, protocol} from 'electron';
|
||||||
import {existsSync, createWriteStream, promises as fs, mkdirSync, copyFileSync, readdirSync, writeFileSync} from 'fs';
|
import {existsSync, createWriteStream, promises as fs, mkdirSync, copyFileSync, readdirSync, writeFileSync, readFileSync} from 'fs';
|
||||||
import {join, dirname, basename} from 'path';
|
import {join, dirname, basename, extname} from 'path';
|
||||||
import {spawn, ChildProcess} from 'child_process';
|
import {spawn, ChildProcess} from 'child_process';
|
||||||
import * as https from 'https';
|
import * as https from 'https';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
@@ -74,7 +74,7 @@ function getJarFilePath(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getSplashPath = () => getResourcePath('../../public/splash.html', 'public/splash.html');
|
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');
|
const getLogbackConfigPath = () => getResourcePath('../../public/config/logback.xml', 'public/config/logback.xml');
|
||||||
|
|
||||||
function getDataDirectoryPath(): string {
|
function getDataDirectoryPath(): string {
|
||||||
@@ -211,7 +211,7 @@ function startSpringBoot() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startSpringBoot();
|
// startSpringBoot();
|
||||||
function stopSpringBoot() {
|
function stopSpringBoot() {
|
||||||
if (!springProcess) return;
|
if (!springProcess) return;
|
||||||
try {
|
try {
|
||||||
@@ -305,6 +305,28 @@ if (!gotTheLock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
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 config = loadConfig();
|
||||||
const shouldMinimize = config.launchMinimized || false;
|
const shouldMinimize = config.launchMinimized || false;
|
||||||
@@ -348,9 +370,9 @@ app.whenReady().then(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//666
|
//666
|
||||||
// setTimeout(() => {
|
setTimeout(() => {
|
||||||
// openAppIfNotOpened();
|
openAppIfNotOpened();
|
||||||
// }, 100);
|
}, 100);
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ let tray: Tray | null = null
|
|||||||
function getIconPath(): string {
|
function getIconPath(): string {
|
||||||
const isDev = process.env.NODE_ENV === 'development'
|
const isDev = process.env.NODE_ENV === 'development'
|
||||||
if (isDev) {
|
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
|
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) {
|
export function createTray(mainWindow: BrowserWindow | null) {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
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://8.138.23.49:8085',
|
//RUOYI_BASE: 'http://8.138.23.49:8085',
|
||||||
//RUOYI_BASE: 'http://192.168.1.89: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 {
|
||||||
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.RUOYI_BASE;
|
||||||
}
|
}
|
||||||
return CONFIG.CLIENT_BASE;
|
return CONFIG.CLIENT_BASE;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { http } from './http'
|
import { http } from './http'
|
||||||
|
|
||||||
export const markApi = {
|
export const markApi = {
|
||||||
// 新建任务
|
// 新建任务(调用 erp_client_sb)
|
||||||
newTask(file: File) {
|
newTask(file: File) {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
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() {
|
getTask() {
|
||||||
return http.get<{
|
return http.post<{
|
||||||
code: number,
|
code: number,
|
||||||
data: {
|
data: {
|
||||||
original: any,
|
original: any,
|
||||||
@@ -18,7 +18,7 @@ export const markApi = {
|
|||||||
headers: string[] // 表头
|
headers: string[] // 表头
|
||||||
},
|
},
|
||||||
msg: string
|
msg: string
|
||||||
}>('/tool/mark/task')
|
}>('/api/trademark/task')
|
||||||
},
|
},
|
||||||
|
|
||||||
// 品牌商标筛查
|
// 品牌商标筛查
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ function handleExportData() {
|
|||||||
|
|
||||||
<!-- ASIN查询面板 -->
|
<!-- ASIN查询面板 -->
|
||||||
<AsinQueryPanel
|
<AsinQueryPanel
|
||||||
v-if="currentTab === 'asin'"
|
v-show="currentTab === 'asin'"
|
||||||
ref="asinPanelRef"
|
ref="asinPanelRef"
|
||||||
:is-vip="isVip"
|
:is-vip="isVip"
|
||||||
@update-data="handleAsinDataUpdate"
|
@update-data="handleAsinDataUpdate"
|
||||||
@@ -158,13 +158,13 @@ function handleExportData() {
|
|||||||
|
|
||||||
<!-- 跟卖精灵面板 -->
|
<!-- 跟卖精灵面板 -->
|
||||||
<GenmaiSpiritPanel
|
<GenmaiSpiritPanel
|
||||||
v-if="currentTab === 'genmai'"
|
v-show="currentTab === 'genmai'"
|
||||||
:is-vip="isVip"
|
:is-vip="isVip"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 商标筛查面板 -->
|
<!-- 商标筛查面板 -->
|
||||||
<TrademarkCheckPanel
|
<TrademarkCheckPanel
|
||||||
v-if="currentTab === 'trademark'"
|
v-show="currentTab === 'trademark'"
|
||||||
ref="trademarkPanelRef"
|
ref="trademarkPanelRef"
|
||||||
:is-vip="isVip"
|
:is-vip="isVip"
|
||||||
@update-data="handleTrademarkDataUpdate"
|
@update-data="handleTrademarkDataUpdate"
|
||||||
@@ -296,7 +296,7 @@ function handleExportData() {
|
|||||||
<div class="status-column">
|
<div class="status-column">
|
||||||
<!-- 任务1:产品商标筛查 -->
|
<!-- 任务1:产品商标筛查 -->
|
||||||
<div class="status-item">
|
<div class="status-item">
|
||||||
<img v-if="(trademarkPanelRef?.taskProgress?.product?.total || 0) > 100" src="/icon/done.png" alt="采集完成" class="status-indicator-icon" />
|
<img v-if="(trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total" src="/icon/done.png" alt="采集完成" class="status-indicator-icon" />
|
||||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && (trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError')" src="/icon/error.png" alt="筛查失败" class="status-indicator-icon" />
|
<img v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && (trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError')" src="/icon/error.png" alt="筛查失败" class="status-indicator-icon" />
|
||||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && trademarkPanelRef?.queryStatus === 'cancel'" src="/icon/cancel.png" alt="已取消" class="status-indicator-icon" />
|
<img v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && trademarkPanelRef?.queryStatus === 'cancel'" src="/icon/cancel.png" alt="已取消" class="status-indicator-icon" />
|
||||||
<img v-else-if="(trademarkPanelRef?.taskProgress?.product?.current || 0) > 0" src="/icon/inProgress.png" alt="采集中" class="status-indicator-icon" />
|
<img v-else-if="(trademarkPanelRef?.taskProgress?.product?.current || 0) > 0" src="/icon/inProgress.png" alt="采集中" class="status-indicator-icon" />
|
||||||
@@ -340,15 +340,15 @@ function handleExportData() {
|
|||||||
<div class="task-stats">
|
<div class="task-stats">
|
||||||
<div class="task-stat">
|
<div class="task-stat">
|
||||||
<span class="stat-label">查询数量</span>
|
<span class="stat-label">查询数量</span>
|
||||||
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 100 ? trademarkPanelRef.taskProgress.product.total : '-' }}</span>
|
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 ? trademarkPanelRef.taskProgress.product.total : '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-stat highlight">
|
<div class="task-stat highlight">
|
||||||
<span class="stat-label">未注册/TM标</span>
|
<span class="stat-label">未注册/TM标</span>
|
||||||
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 100 ? (trademarkPanelRef?.taskProgress?.product?.completed || 0) : '-' }}</span>
|
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total ? (trademarkPanelRef?.taskProgress?.product?.completed || 0) : '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-stat">
|
<div class="task-stat">
|
||||||
<span class="stat-label">已过滤</span>
|
<span class="stat-label">已过滤</span>
|
||||||
<span class="stat-value">{{ (trademarkPanelRef?.taskProgress?.product?.total || 0) > 100 ? ((trademarkPanelRef?.taskProgress?.product?.total || 0) - (trademarkPanelRef?.taskProgress?.product?.completed || 0)) : '-' }}</span>
|
<span class="stat-value">{{ (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)) : '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -332,7 +332,6 @@ async function startTrademarkQuery() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
productResult = await pollTask()
|
productResult = await pollTask()
|
||||||
if (!trademarkLoading.value) return
|
|
||||||
|
|
||||||
if (!productResult || (productResult.code !== 200 && productResult.code !== 0)) {
|
if (!productResult || (productResult.code !== 200 && productResult.code !== 0)) {
|
||||||
throw new Error('获取任务超时或失败,请重试')
|
throw new Error('获取任务超时或失败,请重试')
|
||||||
@@ -360,6 +359,7 @@ async function startTrademarkQuery() {
|
|||||||
|
|
||||||
if (filteredAsins.length === 0) {
|
if (filteredAsins.length === 0) {
|
||||||
showMessage('第三方筛查结果为空', 'warning')
|
showMessage('第三方筛查结果为空', 'warning')
|
||||||
|
queryStatus.value = 'done'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,8 +396,6 @@ async function startTrademarkQuery() {
|
|||||||
|
|
||||||
// 品牌商标筛查
|
// 品牌商标筛查
|
||||||
if (needBrandCheck) {
|
if (needBrandCheck) {
|
||||||
if (!trademarkLoading.value) return
|
|
||||||
|
|
||||||
// 如果没有执行产品筛查,需要先从Excel提取品牌列表和完整数据
|
// 如果没有执行产品筛查,需要先从Excel提取品牌列表和完整数据
|
||||||
if (!needProductCheck) {
|
if (!needProductCheck) {
|
||||||
const extractResult = await markApi.extractBrands(trademarkFile.value)
|
const extractResult = await markApi.extractBrands(trademarkFile.value)
|
||||||
@@ -415,9 +413,7 @@ async function startTrademarkQuery() {
|
|||||||
trademarkHeaders.value = extractResult.data.headers || []
|
trademarkHeaders.value = extractResult.data.headers || []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (brandList.length === 0) {
|
if (brandList.length > 0) {
|
||||||
showMessage('没有品牌需要筛查', 'warning')
|
|
||||||
} else {
|
|
||||||
const brandData = taskProgress.value.brand
|
const brandData = taskProgress.value.brand
|
||||||
brandData.total = brandList.length
|
brandData.total = brandList.length
|
||||||
brandData.current = 1 // 立即显示初始进度
|
brandData.current = 1 // 立即显示初始进度
|
||||||
@@ -439,8 +435,6 @@ async function startTrademarkQuery() {
|
|||||||
const brandResult = await markApi.brandCheck(brandList, brandTaskId.value)
|
const brandResult = await markApi.brandCheck(brandList, brandTaskId.value)
|
||||||
if (brandProgressTimer) clearInterval(brandProgressTimer)
|
if (brandProgressTimer) clearInterval(brandProgressTimer)
|
||||||
|
|
||||||
if (!trademarkLoading.value) return
|
|
||||||
|
|
||||||
if (brandResult.code === 200 || brandResult.code === 0) {
|
if (brandResult.code === 200 || brandResult.code === 0) {
|
||||||
// 完成,显示100%
|
// 完成,显示100%
|
||||||
brandData.total = brandResult.data.checked || brandResult.data.total || brandData.total
|
brandData.total = brandResult.data.checked || brandResult.data.total || brandData.total
|
||||||
@@ -489,18 +483,17 @@ async function startTrademarkQuery() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trademarkLoading.value) {
|
// 只要流程正常完成,就设置为done状态(不再依赖trademarkLoading)
|
||||||
queryStatus.value = 'done'
|
queryStatus.value = 'done'
|
||||||
emit('updateData', trademarkData.value)
|
emit('updateData', trademarkData.value)
|
||||||
|
|
||||||
let summaryMsg = '筛查完成'
|
let summaryMsg = '筛查完成'
|
||||||
if (needProductCheck) summaryMsg += `,产品:${taskProgress.value.product.completed}/${taskProgress.value.product.total}`
|
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}`
|
if (needBrandCheck && brandList.length > 0) summaryMsg += `,品牌:${taskProgress.value.brand.completed}/${taskProgress.value.brand.total}`
|
||||||
showMessage(summaryMsg, 'success')
|
showMessage(summaryMsg, 'success')
|
||||||
|
|
||||||
// 保存会话
|
// 保存会话
|
||||||
await saveSession()
|
await saveSession()
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const hasProductData = taskProgress.value.product.total > 0
|
const hasProductData = taskProgress.value.product.total > 0
|
||||||
|
|
||||||
@@ -536,6 +529,13 @@ async function startTrademarkQuery() {
|
|||||||
clearInterval(brandProgressTimer)
|
clearInterval(brandProgressTimer)
|
||||||
brandProgressTimer = null
|
brandProgressTimer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兜底:如果有数据且状态还是进行中,强制设置为完成(最优先处理)
|
||||||
|
if (trademarkData.value.length > 0 && queryStatus.value === 'inProgress') {
|
||||||
|
queryStatus.value = 'done'
|
||||||
|
emit('updateData', trademarkData.value)
|
||||||
|
}
|
||||||
|
|
||||||
trademarkLoading.value = false
|
trademarkLoading.value = false
|
||||||
currentStep.value = 0
|
currentStep.value = 0
|
||||||
totalSteps.value = 0
|
totalSteps.value = 0
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 804 B |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 43 KiB |
@@ -1,33 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>正在启动...</title>
|
|
||||||
<style>
|
|
||||||
html, body { height: 100%; margin: 0; }
|
|
||||||
body {
|
|
||||||
background: #fff; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;
|
|
||||||
background-image: url('./image/splash_screen.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
.box { position: fixed; left: 0; right: 0; bottom: 28px; padding: 0 0; }
|
|
||||||
.progress { position: relative; width: 100vw; height: 6px; background: rgba(0,0,0,0.08); }
|
|
||||||
.bar { position: absolute; left: 0; top: 0; height: 100%; width: 20vw; min-width: 120px; background: linear-gradient(90deg, #67C23A, #409EFF); animation: slide 1s ease-in-out infinite alternate; }
|
|
||||||
@keyframes slide { 0% { left: 0; } 100% { left: calc(100vw - 20vw); } }
|
|
||||||
</style>
|
|
||||||
<link rel="icon" href="icon/icon.png">
|
|
||||||
<link rel="apple-touch-icon" href="icon/icon.png">
|
|
||||||
<meta name="theme-color" content="#ffffff">
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="box">
|
|
||||||
<div class="progress"><div class="bar"></div></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ const { defineConfig } = require('vite');
|
|||||||
*/
|
*/
|
||||||
const config = defineConfig({
|
const config = defineConfig({
|
||||||
root: Path.join(__dirname, 'src', 'renderer'),
|
root: Path.join(__dirname, 'src', 'renderer'),
|
||||||
publicDir: Path.join(__dirname, 'src', 'renderer', 'public'),
|
publicDir: Path.join(__dirname, 'public'),
|
||||||
server: {
|
server: {
|
||||||
port: 8083,
|
port: 8083,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
package com.tashow.erp.controller;
|
package com.tashow.erp.controller;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.tashow.erp.entity.TrademarkSessionEntity;
|
import com.tashow.erp.entity.TrademarkSessionEntity;
|
||||||
import com.tashow.erp.repository.TrademarkSessionRepository;
|
import com.tashow.erp.repository.TrademarkSessionRepository;
|
||||||
import com.tashow.erp.service.BrandTrademarkCacheService;
|
import com.tashow.erp.service.BrandTrademarkCacheService;
|
||||||
|
import com.tashow.erp.service.IFangzhouApiService;
|
||||||
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.LoggerUtil;
|
import com.tashow.erp.utils.LoggerUtil;
|
||||||
import com.tashow.erp.utils.TrademarkCheckUtil;
|
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.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.*;
|
||||||
@@ -33,6 +39,9 @@ public class TrademarkController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private TrademarkSessionRepository sessionRepository;
|
private TrademarkSessionRepository sessionRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IFangzhouApiService fangzhouApi;
|
||||||
|
|
||||||
// 进度追踪
|
// 进度追踪
|
||||||
private final Map<String, Integer> progressMap = new java.util.concurrent.ConcurrentHashMap<>();
|
private final Map<String, Integer> progressMap = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
@@ -66,12 +75,11 @@ public class TrademarkController {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
logger.info("全局缓存命中: {}/{},需查询: {}", cached.size(), list.size(), toQuery.size());
|
logger.info("全局缓存命中: {}/{},需查询: {}", cached.size(), list.size(), toQuery.size());
|
||||||
|
|
||||||
// 3. 查询未命中的品牌
|
// 3. 查询未命中的品牌
|
||||||
Map<String, Boolean> queried = new HashMap<>();
|
Map<String, Boolean> queried = new HashMap<>();
|
||||||
if (!toQuery.isEmpty()) {
|
if (!toQuery.isEmpty()) {
|
||||||
for (int i = 0; i < toQuery.size(); i++) {
|
for (int i = 0; i < toQuery.size(); i++) {
|
||||||
// 检查任务是否被取消
|
// 检查任务是否被取消值
|
||||||
if (taskId != null && cancelMap.getOrDefault(taskId, false)) {
|
if (taskId != null && cancelMap.getOrDefault(taskId, false)) {
|
||||||
logger.info("任务 {} 已被取消,停止查询", taskId);
|
logger.info("任务 {} 已被取消,停止查询", taskId);
|
||||||
break;
|
break;
|
||||||
@@ -405,4 +413,117 @@ public class TrademarkController {
|
|||||||
return JsonData.buildError("恢复失败: " + e.getMessage());
|
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<Map<String, Object>> filteredData = new ArrayList<>();
|
||||||
|
List<String> excelHeaders = new ArrayList<>();
|
||||||
|
ExcelReader reader = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader = ExcelUtil.getReader(FileUtil.file(tempFilePath));
|
||||||
|
List<List<Object>> rows = reader.read();
|
||||||
|
|
||||||
|
if (rows.isEmpty()) {
|
||||||
|
throw new RuntimeException("Excel文件为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取表头
|
||||||
|
List<Object> 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<Object> row = rows.get(i);
|
||||||
|
if (row.size() > trademarkTypeIndex) {
|
||||||
|
String trademarkType = row.get(trademarkTypeIndex).toString().trim();
|
||||||
|
if ("TM".equals(trademarkType) || "未注册".equals(trademarkType)) {
|
||||||
|
Map<String, Object> 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<String, Object> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<String, Object> body = (Map<String, Object>) 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<String, Object> body = (Map<String, Object>) 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<String, String> 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<MultiValueMap<String, String>> 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<String, Object> 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<MultiValueMap<String, Object>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ server:
|
|||||||
api:
|
api:
|
||||||
server:
|
server:
|
||||||
# 主服务器API配置
|
# 主服务器API配置
|
||||||
base-url: "http://8.138.23.49:8085"
|
#base-url: "http://8.138.23.49:8085"
|
||||||
#base-url: "http://192.168.1.89:8085"
|
base-url: "http://192.168.1.89:8085"
|
||||||
paths:
|
paths:
|
||||||
monitor: "/monitor/client/api"
|
monitor: "/monitor/client/api"
|
||||||
login: "/monitor/account/login"
|
login: "/monitor/account/login"
|
||||||
|
|||||||
@@ -113,6 +113,12 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.14</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.ruoyi.framework.config;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 程序注解配置
|
* RestTemplate 配置
|
||||||
|
* 添加连接池和超时设置,防止连接泄漏
|
||||||
*
|
*
|
||||||
* @author ruoyi
|
* @author ruoyi
|
||||||
*/
|
*/
|
||||||
@@ -13,10 +17,24 @@ import org.springframework.web.client.RestTemplate;
|
|||||||
public class BeanRestConfig
|
public class BeanRestConfig
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 创建RestTemplate Bean
|
* 创建 RestTemplate Bean(带连接池)
|
||||||
|
* 适用于小规模并发场景(10人以内)
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public RestTemplate restTemplate() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,217 +1,107 @@
|
|||||||
package com.ruoyi.web.controller.tool;
|
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.annotation.Anonymous;
|
||||||
import com.ruoyi.common.constant.CacheConstants;
|
import com.ruoyi.common.constant.CacheConstants;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import com.ruoyi.common.core.redis.RedisCache;
|
import com.ruoyi.common.core.redis.RedisCache;
|
||||||
import com.ruoyi.system.service.IMarkService;
|
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.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.bind.annotation.*;
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import java.util.Map;
|
||||||
import java.util.*;
|
|
||||||
|
/**
|
||||||
|
* 方舟商标 Token 管理控制器
|
||||||
|
* 职责:仅负责 Token 的获取、刷新和管理
|
||||||
|
* 重型任务(Excel 下载、解析、商标检查)已转移到 erp_client_sb
|
||||||
|
*/
|
||||||
@RequestMapping("/tool/mark")
|
@RequestMapping("/tool/mark")
|
||||||
@RestController
|
@RestController
|
||||||
@Anonymous
|
@Anonymous
|
||||||
public class MarkController {
|
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
|
@Autowired
|
||||||
private IMarkService markService;
|
private IMarkService markService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisCache redisCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取任务列表
|
* 获取 Token
|
||||||
|
* 如果 Redis 中不存在 Token,自动注册新账号
|
||||||
|
*
|
||||||
|
* @return Token 字符串
|
||||||
*/
|
*/
|
||||||
@GetMapping("/task")
|
@GetMapping("/token")
|
||||||
public AjaxResult Task() {
|
public AjaxResult getToken() {
|
||||||
try {
|
try {
|
||||||
|
// 先尝试从 Redis 获取现有 Token
|
||||||
String token = redisCache.getCacheMapValue(CacheConstants.MARK_ACCOUNT_KEY, "token");
|
String token = redisCache.getCacheMapValue(CacheConstants.MARK_ACCOUNT_KEY, "token");
|
||||||
String d = "{\"name\":\"\",\"page_size\":20,\"current_page\":1}";
|
|
||||||
long ts = System.currentTimeMillis();
|
if (token != null && !token.isEmpty()) {
|
||||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
return AjaxResult.success("获取成功", token);
|
||||||
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<MultiValueMap<String, String>> 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);
|
|
||||||
}
|
}
|
||||||
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<String, String> 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<MultiValueMap<String, String>> 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<Map<String, Object>> filteredData = new ArrayList<>();
|
|
||||||
List<String> excelHeaders = new ArrayList<>();
|
|
||||||
ExcelReader reader = null;
|
|
||||||
try {
|
|
||||||
reader = ExcelUtil.getReader(FileUtil.file(tempFilePath));
|
|
||||||
List<List<Object>> rows = reader.read();
|
|
||||||
|
|
||||||
if (rows.isEmpty()) {
|
// Token 不存在,自动注册新账号
|
||||||
throw new RuntimeException("Excel文件为空");
|
token = markService.reg();
|
||||||
}
|
return AjaxResult.success("注册成功", token);
|
||||||
|
|
||||||
// 读取表头
|
|
||||||
List<Object> 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<Object> row = rows.get(i);
|
|
||||||
if (row.size() > trademarkTypeIndex) {
|
|
||||||
String trademarkType = row.get(trademarkTypeIndex).toString().trim();
|
|
||||||
if ("TM".equals(trademarkType) || "未注册".equals(trademarkType)) {
|
|
||||||
Map<String, Object> 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<String, Object> combinedResult = new HashMap<>();
|
|
||||||
combinedResult.put("original", dNode);
|
|
||||||
combinedResult.put("filtered", filteredData);
|
|
||||||
combinedResult.put("headers", excelHeaders);
|
|
||||||
|
|
||||||
return AjaxResult.success(combinedResult);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
return AjaxResult.error("获取 Token 失败: " + e.getMessage());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新建任务
|
|
||||||
@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<String, Object> 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<MultiValueMap<String, Object>> 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 品牌商标筛查(调用 erp_client_sb 服务)
|
* 刷新 Token
|
||||||
* @param brands 品牌列表(JSON数组)
|
* 使用已保存的账号密码重新登录,更新 Token
|
||||||
* @return 筛查结果
|
*
|
||||||
|
* @return 新的 Token
|
||||||
*/
|
*/
|
||||||
@PostMapping("brandCheck")
|
@PostMapping("/refreshToken")
|
||||||
public AjaxResult brandCheck(@RequestBody List<String> brands) {
|
public AjaxResult refreshToken() {
|
||||||
try {
|
try {
|
||||||
if (brands == null || brands.isEmpty()) {
|
// 检查是否有账号信息
|
||||||
return AjaxResult.error("品牌列表不能为空");
|
Map<String, String> 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";
|
String token = markService.login();
|
||||||
|
return AjaxResult.success("Token 刷新成功", token);
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
||||||
|
|
||||||
HttpEntity<List<String>> 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) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
return AjaxResult.error("刷新 Token 失败: " + e.getMessage());
|
||||||
return AjaxResult.error("品牌筛查失败: " + e.getMessage());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取账号信息(用于调试)
|
||||||
|
*
|
||||||
|
* @return 账号信息(不含密码)
|
||||||
|
*/
|
||||||
|
@GetMapping("/accountInfo")
|
||||||
|
public AjaxResult getAccountInfo() {
|
||||||
|
try {
|
||||||
|
Map<String, String> 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,17 +20,17 @@ spring:
|
|||||||
username:
|
username:
|
||||||
password:
|
password:
|
||||||
# 初始连接数(增加预热连接)
|
# 初始连接数(增加预热连接)
|
||||||
initialSize: 8
|
initialSize: 2
|
||||||
# 最小连接池数量
|
# 最小连接池数量
|
||||||
minIdle: 10
|
minIdle: 2
|
||||||
# 最大连接池数量
|
# 最大连接池数量
|
||||||
maxActive: 25
|
maxActive: 10
|
||||||
# 配置获取连接等待超时的时间(5秒)
|
# 配置获取连接等待超时的时间(10秒)
|
||||||
maxWait: 5000
|
maxWait: 10000
|
||||||
# 配置连接超时时间(5秒)
|
# 配置连接超时时间(10秒)
|
||||||
connectTimeout: 5000
|
connectTimeout: 10000
|
||||||
# 配置网络超时时间(5秒)
|
# 配置网络超时时间(10秒)
|
||||||
socketTimeout: 5000
|
socketTimeout: 10000
|
||||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒(30秒检测一次)
|
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒(30秒检测一次)
|
||||||
timeBetweenEvictionRunsMillis: 30000
|
timeBetweenEvictionRunsMillis: 30000
|
||||||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||||
|
|||||||
@@ -35,17 +35,17 @@ server:
|
|||||||
# tomcat的URI编码
|
# tomcat的URI编码
|
||||||
uri-encoding: UTF-8
|
uri-encoding: UTF-8
|
||||||
# 连接数满后的排队数,默认为100
|
# 连接数满后的排队数,默认为100
|
||||||
accept-count: 1000
|
accept-count: 50
|
||||||
threads:
|
threads:
|
||||||
# tomcat最大线程数,默认为200
|
# tomcat最大线程数,默认为200
|
||||||
max: 800
|
max: 30
|
||||||
# Tomcat启动初始化的线程数,默认值10
|
# Tomcat启动初始化的线程数,默认值10
|
||||||
min-spare: 100
|
min-spare: 5
|
||||||
|
|
||||||
# 日志配置
|
# 日志配置
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
com.ruoyi: debug
|
com.ruoyi: info
|
||||||
org.springframework: warn
|
org.springframework: warn
|
||||||
|
|
||||||
# 用户配置
|
# 用户配置
|
||||||
@@ -82,7 +82,7 @@ spring:
|
|||||||
devtools:
|
devtools:
|
||||||
restart:
|
restart:
|
||||||
# 热部署开关
|
# 热部署开关
|
||||||
enabled: true
|
enabled: false
|
||||||
# redis 配置
|
# redis 配置
|
||||||
redis:
|
redis:
|
||||||
# 地址
|
# 地址
|
||||||
@@ -99,13 +99,13 @@ spring:
|
|||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
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
|
shutdown-timeout: 100ms
|
||||||
# token配置
|
# token配置
|
||||||
@@ -135,7 +135,7 @@ pagehelper:
|
|||||||
# Swagger配置
|
# Swagger配置
|
||||||
swagger:
|
swagger:
|
||||||
# 是否开启swagger
|
# 是否开启swagger
|
||||||
enabled: true
|
enabled: false
|
||||||
# 请求前缀
|
# 请求前缀
|
||||||
pathMapping: /dev-api
|
pathMapping: /dev-api
|
||||||
|
|
||||||
|
|||||||
@@ -11,20 +11,21 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 线程池配置
|
* 线程池配置
|
||||||
|
* 优化为小规模并发场景(10人以内使用)
|
||||||
*
|
*
|
||||||
* @author ruoyi
|
* @author ruoyi
|
||||||
**/
|
**/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class ThreadPoolConfig
|
public class ThreadPoolConfig
|
||||||
{
|
{
|
||||||
// 核心线程池大小
|
// 核心线程池大小(优化:50 → 10)
|
||||||
private int corePoolSize = 50;
|
private int corePoolSize = 10;
|
||||||
|
|
||||||
// 最大可创建的线程数
|
// 最大可创建的线程数(优化:200 → 20)
|
||||||
private int maxPoolSize = 200;
|
private int maxPoolSize = 20;
|
||||||
|
|
||||||
// 队列最大长度
|
// 队列最大长度(优化:1000 → 100)
|
||||||
private int queueCapacity = 1000;
|
private int queueCapacity = 100;
|
||||||
|
|
||||||
// 线程池维护线程所允许的空闲时间
|
// 线程池维护线程所允许的空闲时间
|
||||||
private int keepAliveSeconds = 300;
|
private int keepAliveSeconds = 300;
|
||||||
|
|||||||