refactor(auth):重构认证服务并移除冗余代码
- 移除了 AuthServiceImpl 中的登录、注册、token 验证等方法,仅保留错误上报和客户端信息功能 - 删除了设备注册和离线通知相关逻辑 - 移除了 IAuthService 接口中的登录、注册、验证 token 等方法定义 - 清理了 AccountManager.vue 中的无关注释文字-优化了阿里巴巴1688 服务中的图片上传处理逻辑- 移除了 AmazonScrapingServiceImpl 中未使用的日志导入和空行 - 统一了 Vue 组件中的同步导入方式,替换异步组件定义 - 更新了应用配置文件中的服务器地址和懒加载设置 - 新增缓存管理服务用于统一清理各类缓存数据 - 优化了设备 IP 地址获取逻辑并在注册时传递给后端- 调整了构建配置以减小安装包体积并支持多语言 - 修改了主进程窗口加载逻辑以适配开发与生产环境- 添加了全局样式限制图片预览器尺寸 - 移除了设备 ID 测试类和部分无用的正则表达式导入
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
"electronLanguages": ["zh-CN", "en-US"],
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"perMachine": false,
|
||||
@@ -22,7 +23,7 @@
|
||||
"shortcutName": "erpClient"
|
||||
},
|
||||
"win": {
|
||||
"target": "nsis",
|
||||
"target": "dir",
|
||||
"icon": "public/icon/icon.png"
|
||||
},
|
||||
"files": [
|
||||
@@ -53,23 +54,10 @@
|
||||
"config/**/*",
|
||||
"!erp_client_sb-*.jar",
|
||||
"!data/**/*",
|
||||
"!jre/bin/jab*.exe",
|
||||
"!jre/bin/jac*.exe",
|
||||
"!jre/bin/jar*.exe",
|
||||
"!jre/bin/jc*.exe",
|
||||
"!jre/bin/jd*.exe",
|
||||
"!jre/bin/jf*.exe",
|
||||
"!jre/bin/jh*.exe",
|
||||
"!jre/bin/ji*.exe",
|
||||
"!jre/bin/jl*.exe",
|
||||
"!jre/bin/jm*.exe",
|
||||
"!jre/bin/jp*.exe",
|
||||
"!jre/bin/jr*.exe",
|
||||
"!jre/bin/jsh*.exe",
|
||||
"!jre/bin/jst*.exe",
|
||||
"!jre/bin/k*.exe",
|
||||
"!jre/bin/rmi*.exe",
|
||||
"!jre/bin/serial*.exe",
|
||||
"!jre/bin/*.exe",
|
||||
"jre/bin/java.exe",
|
||||
"jre/bin/javaw.exe",
|
||||
"jre/bin/keytool.exe",
|
||||
"!jre/include/**",
|
||||
"!jre/lib/src.zip",
|
||||
"!jre/lib/ct.sym",
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"main": "main/main.js",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev-server.js",
|
||||
"build": "node scripts/build.js && electron-builder",
|
||||
"build:win": "node scripts/build.js && electron-builder --win",
|
||||
"build:mac": "node scripts/build.js && electron-builder --mac",
|
||||
"build:linux": "node scripts/build.js && electron-builder --linux"
|
||||
"build": "node scripts/build.js && electron-builder --dir",
|
||||
"build:win": "node scripts/build.js && electron-builder --win --dir",
|
||||
"build:mac": "node scripts/build.js && electron-builder --mac --dir",
|
||||
"build:linux": "node scripts/build.js && electron-builder --linux --dir"
|
||||
},
|
||||
"repository": "https://github.com/deluze/electron-vue-template",
|
||||
"author": {
|
||||
|
||||
@@ -23,23 +23,27 @@ function openAppIfNotOpened() {
|
||||
if (appOpened) return;
|
||||
appOpened = true;
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
isDev
|
||||
? mainWindow.loadURL(`http://localhost:${process.argv[2] || 8083}`)
|
||||
: mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
|
||||
|
||||
mainWindow.webContents.once('did-finish-load', () => {
|
||||
setTimeout(() => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
const config = loadConfig();
|
||||
const shouldMinimize = config.launchMinimized || false;
|
||||
if (!shouldMinimize) {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
}
|
||||
if (isDev) mainWindow.webContents.openDevTools();
|
||||
}
|
||||
if (splashWindow && !splashWindow.isDestroyed()) {
|
||||
splashWindow.close();
|
||||
splashWindow = null;
|
||||
}
|
||||
}, 1000);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
isDev
|
||||
? mainWindow.loadURL(`http://localhost:${process.argv[2] || 8083}`)
|
||||
: mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,11 +82,17 @@ function getDataDirectoryPath(): string {
|
||||
return dataDir;
|
||||
}
|
||||
|
||||
interface AppConfig {
|
||||
closeAction?: 'quit' | 'minimize' | 'tray';
|
||||
autoLaunch?: boolean;
|
||||
launchMinimized?: boolean;
|
||||
}
|
||||
|
||||
function getConfigPath(): string {
|
||||
return join(app.getPath('userData'), 'config.json');
|
||||
}
|
||||
|
||||
function loadConfig(): { closeAction?: 'quit' | 'minimize' | 'tray' } {
|
||||
function loadConfig(): AppConfig {
|
||||
try {
|
||||
const configPath = getConfigPath();
|
||||
if (existsSync(configPath)) {
|
||||
@@ -94,7 +104,7 @@ function loadConfig(): { closeAction?: 'quit' | 'minimize' | 'tray' } {
|
||||
return {};
|
||||
}
|
||||
|
||||
function saveConfig(config: any) {
|
||||
function saveConfig(config: AppConfig) {
|
||||
try {
|
||||
const configPath = getConfigPath();
|
||||
require('fs').writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
@@ -138,7 +148,6 @@ function startSpringBoot() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Spring Boot启动参数配置
|
||||
const springArgs = [
|
||||
'-jar', jarPath,
|
||||
`--spring.datasource.url=jdbc:sqlite:${dataDir}/erp-cache.db`,
|
||||
@@ -146,32 +155,20 @@ function startSpringBoot() {
|
||||
`--logging.config=file:${logbackConfigPath}`
|
||||
];
|
||||
|
||||
// 工作目录设为数据目录,这样Spring Boot会在数据目录下创建临时文件
|
||||
springProcess = spawn(javaPath, springArgs, {
|
||||
cwd: dataDir,
|
||||
detached: false
|
||||
detached: false,
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
let startupCompleted = false;
|
||||
|
||||
springProcess.stdout?.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
console.log('[Spring Boot]', output.trim());
|
||||
|
||||
if (!startupCompleted && (output.includes('Started Success') || output.includes('Started ErpClientSbApplication'))) {
|
||||
startupCompleted = true;
|
||||
openAppIfNotOpened();
|
||||
}
|
||||
console.log('[Spring Boot]', data.toString().trim());
|
||||
});
|
||||
|
||||
springProcess.stderr?.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
console.log('[Spring Boot]', output.trim());
|
||||
|
||||
if (!startupCompleted && (output.includes('Started Success') || output.includes('Started ErpClientSbApplication'))) {
|
||||
startupCompleted = true;
|
||||
openAppIfNotOpened();
|
||||
}
|
||||
console.log('[Spring Boot]', data.toString().trim());
|
||||
});
|
||||
|
||||
springProcess.on('close', (code) => {
|
||||
@@ -187,8 +184,25 @@ function startSpringBoot() {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
const checkHealth = () => {
|
||||
if (startupCompleted) return;
|
||||
|
||||
http.get('http://127.0.0.1:8081', (res) => {
|
||||
if (!startupCompleted) {
|
||||
startupCompleted = true;
|
||||
console.log('[Spring Boot] 服务已就绪');
|
||||
openAppIfNotOpened();
|
||||
}
|
||||
}).on('error', () => {
|
||||
setTimeout(checkHealth, 200);
|
||||
});
|
||||
};
|
||||
|
||||
setTimeout(checkHealth, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
if (!startupCompleted) {
|
||||
console.log('[Spring Boot] 启动超时,强制打开窗口');
|
||||
openAppIfNotOpened();
|
||||
}
|
||||
}, 15000);
|
||||
@@ -199,7 +213,7 @@ function startSpringBoot() {
|
||||
}
|
||||
}
|
||||
|
||||
startSpringBoot();
|
||||
// startSpringBoot();
|
||||
|
||||
function stopSpringBoot() {
|
||||
if (!springProcess) return;
|
||||
@@ -225,9 +239,10 @@ function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 800,
|
||||
show: false,
|
||||
show: false, //
|
||||
autoHideMenuBar: true,
|
||||
icon: getIconPath(),
|
||||
backgroundColor: '#f5f5f5',
|
||||
webPreferences: {
|
||||
preload: join(__dirname, 'preload.js'),
|
||||
nodeIntegration: false,
|
||||
@@ -266,38 +281,52 @@ function createWindow() {
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// 应用开机自启动配置
|
||||
const config = loadConfig();
|
||||
const shouldMinimize = config.launchMinimized || false;
|
||||
|
||||
if (config.autoLaunch !== undefined) {
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: config.autoLaunch,
|
||||
openAsHidden: shouldMinimize
|
||||
});
|
||||
}
|
||||
|
||||
createWindow();
|
||||
createTray(mainWindow);
|
||||
|
||||
splashWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 675,
|
||||
frame: false,
|
||||
transparent: false,
|
||||
resizable: false,
|
||||
alwaysOnTop: false,
|
||||
show: true,
|
||||
center: true,
|
||||
icon: getIconPath(),
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
// 只有在不需要最小化启动时才显示 splash 窗口
|
||||
if (!shouldMinimize) {
|
||||
splashWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 675,
|
||||
frame: false,
|
||||
transparent: false,
|
||||
resizable: false,
|
||||
alwaysOnTop: false,
|
||||
show: true,
|
||||
center: true,
|
||||
icon: getIconPath(),
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
}
|
||||
});
|
||||
|
||||
// 监听启动窗口关闭事件
|
||||
splashWindow.on('closed', () => {
|
||||
splashWindow = null;
|
||||
});
|
||||
|
||||
const splashPath = getSplashPath();
|
||||
if (existsSync(splashPath)) {
|
||||
splashWindow.loadFile(splashPath);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听启动窗口关闭事件
|
||||
splashWindow.on('closed', () => {
|
||||
splashWindow = null;
|
||||
});
|
||||
|
||||
const splashPath = getSplashPath();
|
||||
if (existsSync(splashPath)) {
|
||||
splashWindow.loadFile(splashPath);
|
||||
}
|
||||
|
||||
// setTimeout(() => {
|
||||
// openAppIfNotOpened();
|
||||
// }, 2000);
|
||||
setTimeout(() => {
|
||||
openAppIfNotOpened();
|
||||
}, 2000);
|
||||
|
||||
app.on('activate', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
@@ -709,6 +738,46 @@ ipcMain.handle('set-close-action', (event, action: 'quit' | 'minimize' | 'tray')
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// 清理缓存
|
||||
ipcMain.handle('clear-cache', async () => {
|
||||
try {
|
||||
const response = await fetch('http://127.0.0.1:8081/api/system/cache/clear', {
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
console.error('清理缓存失败:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// 获取启动配置
|
||||
ipcMain.handle('get-launch-config', () => {
|
||||
const config = loadConfig();
|
||||
const loginSettings = app.getLoginItemSettings();
|
||||
return {
|
||||
autoLaunch: config.autoLaunch !== undefined ? config.autoLaunch : loginSettings.openAtLogin,
|
||||
launchMinimized: config.launchMinimized || false
|
||||
};
|
||||
});
|
||||
|
||||
// 设置启动配置
|
||||
ipcMain.handle('set-launch-config', (event, launchConfig: { autoLaunch: boolean; launchMinimized: boolean }) => {
|
||||
const config = loadConfig();
|
||||
config.autoLaunch = launchConfig.autoLaunch;
|
||||
config.launchMinimized = launchConfig.launchMinimized;
|
||||
saveConfig(config);
|
||||
|
||||
// 立即应用开机自启动设置
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: launchConfig.autoLaunch,
|
||||
openAsHidden: launchConfig.launchMinimized
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
|
||||
async function getFileSize(url: string): Promise<number> {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -27,6 +27,13 @@ const electronAPI = {
|
||||
getCloseAction: () => ipcRenderer.invoke('get-close-action'),
|
||||
setCloseAction: (action: 'quit' | 'minimize' | 'tray') => ipcRenderer.invoke('set-close-action', action),
|
||||
|
||||
// 缓存管理 API
|
||||
clearCache: () => ipcRenderer.invoke('clear-cache'),
|
||||
|
||||
// 启动配置 API
|
||||
getLaunchConfig: () => ipcRenderer.invoke('get-launch-config'),
|
||||
setLaunchConfig: (config: { autoLaunch: boolean; launchMinimized: boolean }) => ipcRenderer.invoke('set-launch-config', config),
|
||||
|
||||
onDownloadProgress: (callback: (progress: any) => void) => {
|
||||
ipcRenderer.removeAllListeners('download-progress')
|
||||
ipcRenderer.on('download-progress', (event, progress) => callback(progress))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref, computed, defineAsyncComponent, type Component, onUnmounted, provide} from 'vue'
|
||||
import {onMounted, ref, computed, type Component, onUnmounted, provide} from 'vue'
|
||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import 'element-plus/dist/index.css'
|
||||
@@ -9,15 +9,15 @@ import {getOrCreateDeviceId} from './utils/deviceId'
|
||||
import {getToken, setToken, removeToken, getUsernameFromToken, getClientIdFromToken} from './utils/token'
|
||||
import {CONFIG} from './api/http'
|
||||
import {getSettings} from './utils/settings'
|
||||
const LoginDialog = defineAsyncComponent(() => import('./components/auth/LoginDialog.vue'))
|
||||
const RegisterDialog = defineAsyncComponent(() => import('./components/auth/RegisterDialog.vue'))
|
||||
const NavigationBar = defineAsyncComponent(() => import('./components/layout/NavigationBar.vue'))
|
||||
const RakutenDashboard = defineAsyncComponent(() => import('./components/rakuten/RakutenDashboard.vue'))
|
||||
const AmazonDashboard = defineAsyncComponent(() => import('./components/amazon/AmazonDashboard.vue'))
|
||||
const ZebraDashboard = defineAsyncComponent(() => import('./components/zebra/ZebraDashboard.vue'))
|
||||
const UpdateDialog = defineAsyncComponent(() => import('./components/common/UpdateDialog.vue'))
|
||||
const SettingsDialog = defineAsyncComponent(() => import('./components/common/SettingsDialog.vue'))
|
||||
const TrialExpiredDialog = defineAsyncComponent(() => import('./components/common/TrialExpiredDialog.vue'))
|
||||
import LoginDialog from './components/auth/LoginDialog.vue'
|
||||
import RegisterDialog from './components/auth/RegisterDialog.vue'
|
||||
import NavigationBar from './components/layout/NavigationBar.vue'
|
||||
import RakutenDashboard from './components/rakuten/RakutenDashboard.vue'
|
||||
import AmazonDashboard from './components/amazon/AmazonDashboard.vue'
|
||||
import ZebraDashboard from './components/zebra/ZebraDashboard.vue'
|
||||
import UpdateDialog from './components/common/UpdateDialog.vue'
|
||||
import SettingsDialog from './components/common/SettingsDialog.vue'
|
||||
import TrialExpiredDialog from './components/common/TrialExpiredDialog.vue'
|
||||
|
||||
const dashboardsMap: Record<string, Component> = {
|
||||
rakuten: RakutenDashboard,
|
||||
@@ -480,12 +480,14 @@ function handleAutoUpdateChanged(enabled: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理打开更新对话框
|
||||
function handleOpenUpdateDialog() {
|
||||
showUpdateDialog.value = true
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
SSEManager.disconnect()
|
||||
})
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -582,7 +584,7 @@ onUnmounted(() => {
|
||||
<UpdateDialog ref="updateDialogRef" v-model="showUpdateDialog" />
|
||||
|
||||
<!-- 设置对话框 -->
|
||||
<SettingsDialog v-model="showSettingsDialog" @auto-update-changed="handleAutoUpdateChanged" />
|
||||
<SettingsDialog v-model="showSettingsDialog" @auto-update-changed="handleAutoUpdateChanged" @open-update-dialog="handleOpenUpdateDialog" />
|
||||
|
||||
<!-- 试用期过期弹框 -->
|
||||
<TrialExpiredDialog v-model="showTrialExpiredDialog" :expired-type="trialExpiredType" />
|
||||
@@ -1007,3 +1009,13 @@ onUnmounted(() => {
|
||||
color: #B0B0B0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局样式:限制图片预览器大小 */
|
||||
.el-image-viewer__img {
|
||||
max-width: 50vw !important;
|
||||
max-height: 50vh !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
</style>
|
||||
@@ -14,6 +14,18 @@ export interface DeviceQuota {
|
||||
used: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本机内网IP地址
|
||||
*/
|
||||
async function getLocalIP(): Promise<string> {
|
||||
try {
|
||||
const res = await http.get<{ data: string }>('/api/system/local-ip')
|
||||
return res.data
|
||||
} catch {
|
||||
return '127.0.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
export const deviceApi = {
|
||||
getQuota(username: string) {
|
||||
return http.get<{ data: DeviceQuota }>('/monitor/device/quota', { username })
|
||||
@@ -23,8 +35,9 @@ export const deviceApi = {
|
||||
return http.get<{ data: DeviceItem[] }>('/monitor/device/list', { username })
|
||||
},
|
||||
|
||||
register(payload: { username: string; deviceId: string; os?: string }) {
|
||||
return http.post('/monitor/device/register', payload)
|
||||
async register(payload: { username: string; deviceId: string; os?: string }) {
|
||||
const ip = await getLocalIP()
|
||||
return http.post('/monitor/device/register', { ...payload, ip })
|
||||
},
|
||||
|
||||
remove(payload: { deviceId: string }) {
|
||||
|
||||
@@ -4,8 +4,9 @@ export type HttpMethod = 'GET' | 'POST' | 'DELETE';
|
||||
// 集中管理所有后端服务配置
|
||||
export const CONFIG = {
|
||||
CLIENT_BASE: 'http://localhost:8081',
|
||||
RUOYI_BASE: 'http://192.168.1.89:8085',
|
||||
SSE_URL: 'http://192.168.1.89:8085/monitor/account/events'
|
||||
// RUOYI_BASE: 'http://192.168.1.89:8085',
|
||||
RUOYI_BASE: 'http://8.138.23.49:8085',
|
||||
SSE_URL: 'http://8.138.23.49:8085/monitor/account/events'
|
||||
} as const;
|
||||
|
||||
function resolveBase(path: string): string {
|
||||
@@ -13,7 +14,6 @@ function resolveBase(path: string): string {
|
||||
if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma')) {
|
||||
return CONFIG.RUOYI_BASE;
|
||||
}
|
||||
// 其他走客户端服务
|
||||
return CONFIG.CLIENT_BASE;
|
||||
}
|
||||
|
||||
|
||||
@@ -247,13 +247,22 @@ function stopFetch() {
|
||||
|
||||
// 打开Genmai Spirit工具
|
||||
async function openGenmaiSpirit() {
|
||||
genmaiLoading.value = true
|
||||
try {
|
||||
await amazonApi.openGenmaiSpirit()
|
||||
} catch (error: any) {
|
||||
showMessage(error.message || '打开跟卖精灵失败', 'error')
|
||||
} finally {
|
||||
genmaiLoading.value = false
|
||||
await ElMessageBox.confirm('打开跟卖精灵会关闭所有谷歌浏览器进程,是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
genmaiLoading.value = true
|
||||
try {
|
||||
await amazonApi.openGenmaiSpirit()
|
||||
} catch (error: any) {
|
||||
showMessage(error.message || '打开跟卖精灵失败', 'error')
|
||||
} finally {
|
||||
genmaiLoading.value = false
|
||||
}
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ref, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { User } from '@element-plus/icons-vue'
|
||||
import { authApi } from '../../api/auth'
|
||||
import { deviceApi } from '../../api/device'
|
||||
import { getOrCreateDeviceId } from '../../utils/deviceId'
|
||||
|
||||
interface Props {
|
||||
@@ -35,13 +34,6 @@ async function handleAuth() {
|
||||
// 获取或生成设备ID
|
||||
const deviceId = await getOrCreateDeviceId()
|
||||
|
||||
// 注册设备
|
||||
await deviceApi.register({
|
||||
username: authForm.value.username,
|
||||
deviceId: deviceId,
|
||||
os: navigator.platform
|
||||
})
|
||||
|
||||
// 登录
|
||||
const loginRes: any = await authApi.login({
|
||||
...authForm.value,
|
||||
|
||||
@@ -74,7 +74,7 @@ export default defineComponent({ name: 'AccountManager' })
|
||||
<div class="top">
|
||||
<img src="/icon/image.png" class="hero" alt="logo" />
|
||||
<div class="head-main">
|
||||
<div class="main-title">在线账号管理(3/3)</div>
|
||||
<div class="main-title">在线账号管理</div>
|
||||
<div class="main-sub">
|
||||
您当前订阅可同时托管3家 Shopee 店铺<br>
|
||||
如需扩增同时托管店铺数,请 <span class="upgrade">升级订阅</span>。
|
||||
|
||||
@@ -20,6 +20,7 @@ interface Props {
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'autoUpdateChanged', value: boolean): void
|
||||
(e: 'openUpdateDialog'): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -50,7 +51,14 @@ const feedbackSubmitting = ref(false)
|
||||
const logDates = ref<string[]>([])
|
||||
|
||||
// 关闭行为配置
|
||||
const closeAction = ref<'quit' | 'minimize' | 'tray'>('tray')
|
||||
const closeAction = ref<'quit' | 'minimize' | 'tray'>('quit')
|
||||
|
||||
// 缓存相关
|
||||
const clearingCache = ref(false)
|
||||
|
||||
// 启动配置
|
||||
const autoLaunch = ref(false)
|
||||
const launchMinimized = ref(false)
|
||||
|
||||
// 更新相关
|
||||
const currentVersion = ref('')
|
||||
@@ -95,6 +103,12 @@ async function saveAllSettings() {
|
||||
// 保存关闭行为配置
|
||||
await (window as any).electronAPI.setCloseAction(closeAction.value)
|
||||
|
||||
// 保存启动配置
|
||||
await (window as any).electronAPI.setLaunchConfig({
|
||||
autoLaunch: autoLaunch.value,
|
||||
launchMinimized: launchMinimized.value
|
||||
})
|
||||
|
||||
ElMessage({ message: '设置已保存', type: 'success' })
|
||||
show.value = false
|
||||
|
||||
@@ -123,10 +137,89 @@ function resetPlatformSettings(platform: Platform) {
|
||||
}
|
||||
|
||||
// 重置所有设置
|
||||
function resetAllSettings() {
|
||||
platforms.forEach(platform => {
|
||||
resetPlatformSettings(platform.key)
|
||||
})
|
||||
async function resetAllSettings() {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要重置所有设置吗?此操作不可恢复。',
|
||||
'确认重置',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
// 重置所有平台设置
|
||||
platforms.forEach(platform => {
|
||||
resetPlatformSettings(platform.key)
|
||||
})
|
||||
|
||||
// 重置自动更新配置
|
||||
autoUpdate.value = false
|
||||
|
||||
// 重置关闭行为配置
|
||||
closeAction.value = 'quit'
|
||||
|
||||
// 重置启动配置
|
||||
autoLaunch.value = false
|
||||
launchMinimized.value = false
|
||||
|
||||
ElMessage.success('所有设置已重置')
|
||||
} catch {
|
||||
// 用户取消操作
|
||||
}
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
async function handleClearCache() {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要清理客户端缓存吗?将清除所有缓存数据、更新文件及相关状态(不影响登录状态)',
|
||||
'确认清理',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
clearingCache.value = true
|
||||
|
||||
// 1. 清理后端数据库缓存
|
||||
const result = await (window as any).electronAPI.clearCache()
|
||||
if (!result || result.code !== 0) {
|
||||
ElMessage.error('缓存清理失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 清除更新相关的 localStorage 状态
|
||||
localStorage.removeItem('skipped_version')
|
||||
localStorage.removeItem('remind_later_time')
|
||||
|
||||
// 3. 清除已下载的更新文件
|
||||
await (window as any).electronAPI.clearUpdateFiles()
|
||||
|
||||
ElMessage.success('缓存清理成功')
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('缓存清理失败')
|
||||
}
|
||||
} finally {
|
||||
clearingCache.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载启动配置
|
||||
async function loadLaunchConfig() {
|
||||
try {
|
||||
const config = await (window as any).electronAPI.getLaunchConfig()
|
||||
if (config) {
|
||||
autoLaunch.value = config.autoLaunch || false
|
||||
launchMinimized.value = config.launchMinimized || false
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取启动配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动到指定区域
|
||||
@@ -179,7 +272,7 @@ function handleScroll() {
|
||||
const container = settingsMainRef.value
|
||||
if (!container) return
|
||||
|
||||
const sections = ['export', 'update', 'feedback', 'general']
|
||||
const sections = ['export', 'update', 'cache', 'startup', 'feedback', 'general']
|
||||
const scrollTop = container.scrollTop
|
||||
const containerHeight = container.clientHeight
|
||||
const scrollHeight = container.scrollHeight
|
||||
@@ -249,6 +342,12 @@ async function checkForUpdates() {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理立即升级按钮点击
|
||||
function handleUpgradeClick() {
|
||||
show.value = false
|
||||
emit('openUpdateDialog')
|
||||
}
|
||||
|
||||
// 加载当前版本
|
||||
async function loadCurrentVersion() {
|
||||
try {
|
||||
@@ -315,6 +414,7 @@ onMounted(() => {
|
||||
loadLogDates()
|
||||
loadCloseAction()
|
||||
loadCurrentVersion()
|
||||
loadLaunchConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -339,6 +439,18 @@ onMounted(() => {
|
||||
<span class="sidebar-icon">🔄</span>
|
||||
<span class="sidebar-text">更新</span>
|
||||
</div>
|
||||
<div
|
||||
:class="['sidebar-item', { active: activeTab === 'cache' }]"
|
||||
@click="scrollToSection('cache')">
|
||||
<span class="sidebar-icon">💾</span>
|
||||
<span class="sidebar-text">缓存</span>
|
||||
</div>
|
||||
<div
|
||||
:class="['sidebar-item', { active: activeTab === 'startup' }]"
|
||||
@click="scrollToSection('startup')">
|
||||
<span class="sidebar-icon">🚀</span>
|
||||
<span class="sidebar-text">启动</span>
|
||||
</div>
|
||||
<div
|
||||
:class="['sidebar-item', { active: activeTab === 'feedback' }]"
|
||||
@click="scrollToSection('feedback')">
|
||||
@@ -409,7 +521,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="checkingUpdate"
|
||||
@click="checkForUpdates">
|
||||
@click="hasUpdate ? handleUpgradeClick() : checkForUpdates()">
|
||||
{{ checkingUpdate ? '检查中...' : (hasUpdate ? '立即升级' : '检查更新') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -435,6 +547,46 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 缓存设置 -->
|
||||
<div id="section-cache" class="setting-section" @mouseenter="activeTab = 'cache'">
|
||||
<div class="section-title">缓存设置</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-row">
|
||||
<span class="cache-desc">清理应用缓存数据,不影响登录状态</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="clearingCache"
|
||||
:disabled="clearingCache"
|
||||
@click="handleClearCache">
|
||||
{{ clearingCache ? '清理中' : '清理缓存' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 启动设置 -->
|
||||
<div id="section-startup" class="setting-section" @mouseenter="activeTab = 'startup'">
|
||||
<div class="section-title">启动</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="checkbox-row">
|
||||
<el-checkbox v-model="autoLaunch" size="default">
|
||||
开机时自动启动
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item" style="margin-top: 10px;">
|
||||
<div class="checkbox-row">
|
||||
<el-checkbox v-model="launchMinimized" size="default">
|
||||
启动后最小化到程序坞
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 反馈页面 -->
|
||||
<div id="section-feedback" class="setting-section" @mouseenter="activeTab = 'feedback'">
|
||||
<div class="section-title">反馈</div>
|
||||
@@ -726,6 +878,11 @@ onMounted(() => {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.cache-desc {
|
||||
font-size: 13px;
|
||||
color: #86909C;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -134,17 +134,28 @@ async function loadLatest() {
|
||||
allProducts.value = products.map((p: any) => ({...p, skuPrices: parseSkuPrices(p)}))
|
||||
}
|
||||
|
||||
function hasValid1688Data(data: any) {
|
||||
if (!data) return false
|
||||
const skuJson = data.skuPriceJson || data.skuPrice
|
||||
const prices = parseSkuPrices({ skuPriceJson: skuJson })
|
||||
if (!data.mapRecognitionLink) return false
|
||||
if (!Array.isArray(prices) || !prices.length) return false
|
||||
if (!data.freight || data.freight <= 0) return false
|
||||
if (!data.median || data.median <= 0) return false
|
||||
return true
|
||||
}
|
||||
|
||||
async function searchProductInternal(product: any) {
|
||||
if (!product || !product.imgUrl) return
|
||||
if (!needsSearch(product)) return
|
||||
if (!product || !product.imgUrl) return false
|
||||
if (!needsSearch(product)) return true
|
||||
if (!props.isVip) {
|
||||
if (checkExpiredType) trialExpiredType.value = checkExpiredType()
|
||||
showTrialExpiredDialog.value = true
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
const res: any = await rakutenApi.search1688(product.imgUrl, currentBatchId.value)
|
||||
const data = res.data
|
||||
if (!hasValid1688Data(data)) return false
|
||||
const skuJson = data.skuPriceJson || data.skuPrice
|
||||
Object.assign(product, {
|
||||
mapRecognitionLink: data.mapRecognitionLink,
|
||||
@@ -157,6 +168,20 @@ async function searchProductInternal(product: any) {
|
||||
image1688Url: data.mapRecognitionLink,
|
||||
detailUrl1688: data.mapRecognitionLink,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
async function searchProductWithRetry(product: any, maxRetry = 2) {
|
||||
for (let attempt = 1; attempt <= maxRetry; attempt++) {
|
||||
try {
|
||||
const ok = await searchProductInternal(product)
|
||||
if (ok) return true
|
||||
} catch (e) {
|
||||
console.warn('search1688 failed', e)
|
||||
}
|
||||
if (attempt < maxRetry) await delay(600)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function beforeUpload(file: File) {
|
||||
@@ -305,7 +330,7 @@ async function serialSearch1688(products: any[]) {
|
||||
const product = products[i]
|
||||
product.searching1688 = true
|
||||
await nextTickSafe()
|
||||
await searchProductInternal(product)
|
||||
await searchProductWithRetry(product)
|
||||
product.searching1688 = false
|
||||
processedProducts.value++
|
||||
progressPercentage.value = Math.floor((processedProducts.value / Math.max(1, totalProducts.value)) * 100)
|
||||
@@ -560,7 +585,7 @@ onMounted(loadLatest)
|
||||
</td>
|
||||
<td>
|
||||
<div class="image-container" v-if="row.imgUrl">
|
||||
<img :src="row.imgUrl" class="thumb" alt="thumb"/>
|
||||
<el-image :src="row.imgUrl" class="thumb" fit="contain" :preview-src-list="[row.imgUrl]" />
|
||||
</div>
|
||||
<span v-else>无图片</span>
|
||||
</td>
|
||||
|
||||
@@ -474,7 +474,7 @@ async function removeCurrentAccount() {
|
||||
<td>{{ row.orderedAt || '-' }}</td>
|
||||
<td>
|
||||
<div class="image-container" v-if="row.productImage">
|
||||
<img :src="row.productImage" class="thumb" alt="thumb" />
|
||||
<el-image :src="row.productImage" class="thumb" fit="contain" :preview-src-list="[row.productImage]" />
|
||||
</div>
|
||||
<span v-else>无图片</span>
|
||||
</td>
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<title>erpClient</title>
|
||||
<link rel="icon" href="/icon/icon.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<style>
|
||||
body { margin: 0; background-color: #f5f5f5; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -64,9 +64,15 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 拆分 hutool-all 为按需依赖,减少 JAR 包体积 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>5.8.36</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
<version>5.8.36</version>
|
||||
</dependency>
|
||||
<!-- SQLite数据库支持 -->
|
||||
@@ -94,12 +100,6 @@
|
||||
<artifactId>webdrivermanager</artifactId>
|
||||
<version>5.9.2</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.python/jython-standalone -->
|
||||
<dependency>
|
||||
<groupId>org.python</groupId>
|
||||
<artifactId>jython-standalone</artifactId>
|
||||
<version>2.7.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT parsing for local RS256 verification -->
|
||||
<dependency>
|
||||
@@ -173,6 +173,8 @@
|
||||
</exclude>
|
||||
</excludes>
|
||||
<mainClass>com.tashow.erp.ErpClientSbApplication</mainClass>
|
||||
<executable>false</executable>
|
||||
<layout>ZIP</layout>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -7,7 +7,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootApplication
|
||||
@SpringBootApplication(
|
||||
exclude = {
|
||||
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration.class,
|
||||
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration.class
|
||||
}
|
||||
)
|
||||
public class ErpClientSbApplication {
|
||||
public static void main(String[] args) {
|
||||
ConfigurableApplicationContext applicationContext = SpringApplication.run(ErpClientSbApplication.class, args);
|
||||
@@ -16,10 +21,8 @@ public class ErpClientSbApplication {
|
||||
log.error("捕获到未处理异常: " + ex.getMessage(), ex);
|
||||
errorReporter.reportSystemError("未捕获异常: " + thread.getName(), (Exception) ex);
|
||||
});
|
||||
log.info("Started Success");
|
||||
ResourcePreloader.init();
|
||||
ResourcePreloader.preloadErpDashboard();
|
||||
ResourcePreloader.executePreloading();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,6 @@ public class RakutenController {
|
||||
}
|
||||
List<RakutenProduct> allProducts = new ArrayList<>();
|
||||
List<String> skippedShops = new ArrayList<>();
|
||||
|
||||
// 2. 遍历店铺,优先缓存,缺失则爬取
|
||||
for (String currentShopName : shopNames) {
|
||||
if (rakutenCacheService.hasRecentData(currentShopName)) {
|
||||
// 从缓存获取
|
||||
@@ -87,7 +85,6 @@ public class RakutenController {
|
||||
if (cachedCount > 0) {
|
||||
dataReportUtil.reportDataCollection("RAKUTEN_CACHE", cachedCount, "0");
|
||||
}
|
||||
|
||||
return JsonData.buildSuccess(Map.of("products", allProducts, "total", allProducts.size(), "sessionId", batchId, "skippedShops", skippedShops, "newProductsCount", newProducts.size()));
|
||||
} catch (Exception e) {
|
||||
log.error("获取乐天商品失败", e);
|
||||
@@ -127,24 +124,6 @@ public class RakutenController {
|
||||
}
|
||||
|
||||
|
||||
// 解析 skuPriceJson 或 skuPrice 字段中的价格键,返回从小到大排序的价格列表
|
||||
private static List<Double> parseSkuPriceList(Object skuPriceJson, Object skuPrice) {
|
||||
String src = skuPriceJson != null ? String.valueOf(skuPriceJson) : (skuPrice != null ? String.valueOf(skuPrice) : null);
|
||||
if (src == null || src.isEmpty()) return Collections.emptyList();
|
||||
try {
|
||||
Pattern pattern = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*:");
|
||||
Matcher m = pattern.matcher(src);
|
||||
List<Double> prices = new ArrayList<>();
|
||||
while (m.find()) {
|
||||
String num = m.group(1);
|
||||
try { prices.add(Double.parseDouble(num)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
Collections.sort(prices);
|
||||
return prices;
|
||||
} catch (Exception ignored) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,7 +2,8 @@ package com.tashow.erp.controller;
|
||||
|
||||
import com.tashow.erp.entity.AuthTokenEntity;
|
||||
import com.tashow.erp.repository.AuthTokenRepository;
|
||||
import com.tashow.erp.service.IGenmaiService;
|
||||
import com.tashow.erp.service.impl.CacheManagementServiceImpl;
|
||||
import com.tashow.erp.service.impl.GenmaiServiceImpl;
|
||||
import com.tashow.erp.utils.JsonData;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -25,7 +26,10 @@ public class SystemController {
|
||||
private AuthTokenRepository authTokenRepository;
|
||||
|
||||
@Autowired
|
||||
private IGenmaiService genmaiService;
|
||||
private GenmaiServiceImpl genmaiService;
|
||||
|
||||
@Autowired
|
||||
private CacheManagementServiceImpl cacheManagementService;
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
@@ -87,6 +91,19 @@ public class SystemController {
|
||||
return JsonData.buildSuccess(deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本机内网IP地址
|
||||
*/
|
||||
@GetMapping("/local-ip")
|
||||
public JsonData getLocalIp() {
|
||||
try {
|
||||
java.net.InetAddress localHost = java.net.InetAddress.getLocalHost();
|
||||
return JsonData.buildSuccess(localHost.getHostAddress());
|
||||
} catch (Exception e) {
|
||||
return JsonData.buildSuccess("127.0.0.1");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 版本信息 ====================
|
||||
|
||||
/**
|
||||
@@ -165,5 +182,20 @@ public class SystemController {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 缓存管理 ====================
|
||||
|
||||
/**
|
||||
* 清理缓存
|
||||
*/
|
||||
@PostMapping("/cache/clear")
|
||||
public JsonData clearCache() {
|
||||
try {
|
||||
cacheManagementService.clearCache();
|
||||
return JsonData.buildSuccess("缓存清理成功");
|
||||
} catch (Exception e) {
|
||||
return JsonData.buildError("清理缓存失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.tashow.erp.service.Alibaba1688Service;
|
||||
import com.tashow.erp.utils.Alibaba1688CookieUtil;
|
||||
import com.tashow.erp.utils.ErrorReporter;
|
||||
import com.tashow.erp.utils.QiniuUtil;
|
||||
import org.openqa.selenium.chrome.ChromeDriver;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -27,8 +26,6 @@ import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 1688识图搜索服务
|
||||
@@ -42,7 +39,6 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
||||
private final RestTemplate noSslRestTemplate = createNoSslRestTemplate();
|
||||
@Autowired
|
||||
private ErrorReporter errorReporter;
|
||||
|
||||
private RestTemplate createNoSslRestTemplate() {
|
||||
try {
|
||||
TrustManager[] trustManagers = new TrustManager[] {
|
||||
@@ -56,7 +52,6 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
||||
sslContext.init(null, trustManagers, new java.security.SecureRandom());
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
|
||||
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
|
||||
|
||||
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory() {
|
||||
@Override
|
||||
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
|
||||
@@ -73,15 +68,12 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过1688 API获取商品详情链接,价格
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public SearchResult get1688Detail(String uploadedUrl) {
|
||||
uploadedUrl = uploadedUrl.split("\\?")[0];
|
||||
String fileName = "temp_" + System.currentTimeMillis() + ".png";
|
||||
List<String> detailUrls = new ArrayList<>();
|
||||
SearchResult result = new SearchResult();
|
||||
@@ -119,14 +111,12 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
||||
.filter(fee -> fee > 0)
|
||||
.ifPresent(freight::add);
|
||||
}
|
||||
|
||||
offerIterator = null;
|
||||
for (int retry = 0; retry < 3 && (offerIterator == null || !offerIterator.hasNext()); retry++) {
|
||||
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
|
||||
JsonNode root = objectMapper.readTree(response.getBody());
|
||||
offerIterator = root.path("data").path("offerList").path("offers").elements();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10 && offerIterator.hasNext(); i++) {
|
||||
JsonNode offer = offerIterator.next();
|
||||
String offerId = offer.path("id").asText();
|
||||
@@ -160,7 +150,7 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
||||
|
||||
result.setSkuPrice(skuPrices);
|
||||
result.setMedian( median);
|
||||
result.setMapRecognitionLink( uploadImageBase64(imageUrl));
|
||||
result.setMapRecognitionLink( uploadImageBase64(uploadedUrl));
|
||||
result.setFreight(freightFee.isEmpty() ? 0.0 : freightFee.get(Math.max(0, freightFee.size()/2-1)));
|
||||
// String weight = getWeight(detailUrls);
|
||||
// result.setWeight(weight);
|
||||
@@ -238,7 +228,6 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
||||
if (priceNode.isNumber() && priceNode.asDouble() > 0) {
|
||||
return priceNode.asDouble();
|
||||
}
|
||||
|
||||
// 如果price是数组且有元素,检查第一个元素
|
||||
if (priceNode.isArray() && priceNode.size() > 0) {
|
||||
JsonNode firstPrice = priceNode.get(0);
|
||||
@@ -284,6 +273,4 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
||||
JSONObject json = JSON.parseObject(response.getBody());
|
||||
return "https://s.1688.com/youyuan/index.htm?tab=imageSearch&imageId=" + json.getJSONObject("data").getString("imageId");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
package com.tashow.erp.service.impl;
|
||||
|
||||
import com.tashow.erp.entity.AmazonProductEntity;
|
||||
import com.tashow.erp.repository.AmazonProductRepository;
|
||||
import com.tashow.erp.service.IAmazonScrapingService;
|
||||
import com.tashow.erp.utils.DataReportUtil;
|
||||
import com.tashow.erp.utils.ErrorReporter;
|
||||
import com.tashow.erp.utils.RakutenProxyUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import us.codecraft.webmagic.Page;
|
||||
@@ -15,11 +11,9 @@ import us.codecraft.webmagic.Site;
|
||||
import us.codecraft.webmagic.Spider;
|
||||
import us.codecraft.webmagic.processor.PageProcessor;
|
||||
import us.codecraft.webmagic.selector.Html;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 亚马逊数据采集服务实现类
|
||||
*
|
||||
@@ -38,7 +32,6 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
|
||||
private static final Object spiderLock = new Object();
|
||||
private final Map<String, AmazonProductEntity> resultCache = new ConcurrentHashMap<>();
|
||||
private Site site;
|
||||
|
||||
/**
|
||||
* 处理亚马逊页面数据提取
|
||||
*/
|
||||
@@ -46,15 +39,12 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
|
||||
public void process(Page page) {
|
||||
Html html = page.getHtml();
|
||||
String url = page.getUrl().toString();
|
||||
|
||||
|
||||
// 提取ASIN
|
||||
String asin = html.xpath("//input[@id='ASIN']/@value").toString();
|
||||
if (isEmpty(asin)) {
|
||||
String[] parts = url.split("/dp/");
|
||||
if (parts.length > 1) asin = parts[1].split("/")[0].split("\\?")[0];
|
||||
}
|
||||
|
||||
// 提取价格
|
||||
String priceSymbol = html.xpath("//span[@class='a-price-symbol']/text()").toString();
|
||||
String priceWhole = html.xpath("//span[@class='a-price-whole']/text()").toString();
|
||||
@@ -82,7 +72,6 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
|
||||
entity.setAsin(asin != null ? asin : "");
|
||||
entity.setPrice(price);
|
||||
entity.setSeller(seller);
|
||||
|
||||
resultCache.put(asin, entity);
|
||||
page.putField("entity", entity);
|
||||
}
|
||||
@@ -138,7 +127,6 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
|
||||
allProducts.put(cleanAsin, entity);
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(allProducts.values());
|
||||
}
|
||||
|
||||
@@ -155,7 +143,6 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
|
||||
}
|
||||
return "https://www.amazon.co.jp/dp/" + asin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据地区配置Site
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package com.tashow.erp.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tashow.erp.service.IAuthService;
|
||||
@@ -8,13 +9,13 @@ import lombok.Getter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* 认证服务实现类 - 简化版
|
||||
* 提供基础的登录、注册、token验证功能
|
||||
* 仅提供错误上报和客户端信息获取功能
|
||||
*/
|
||||
@Service
|
||||
public class AuthServiceImpl implements IAuthService {
|
||||
@@ -25,72 +26,8 @@ public class AuthServiceImpl implements IAuthService {
|
||||
@Autowired
|
||||
private ApiForwarder apiForwarder;
|
||||
|
||||
@Autowired
|
||||
private com.tashow.erp.repository.CacheDataRepository cacheDataRepository;
|
||||
|
||||
@Getter
|
||||
private String clientId = DeviceUtils.generateDeviceId();
|
||||
private String accessToken;
|
||||
private String refreshToken;
|
||||
|
||||
/**
|
||||
* 应用启动时从SQLite恢复token
|
||||
*/
|
||||
@PostConstruct
|
||||
private void initTokenFromCache() {
|
||||
try {
|
||||
cacheDataRepository.findByCacheKey("token").ifPresent(entity ->
|
||||
accessToken = entity.getCacheValue()
|
||||
);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存token到SQLite
|
||||
*/
|
||||
private void saveTokenToCache(String token) {
|
||||
try {
|
||||
com.tashow.erp.entity.CacheDataEntity entity = cacheDataRepository.findByCacheKey("token")
|
||||
.orElse(new com.tashow.erp.entity.CacheDataEntity());
|
||||
entity.setCacheKey("token");
|
||||
entity.setCacheValue(token);
|
||||
cacheDataRepository.save(entity);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端登录认证
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> login(String username, String password) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
Map<String, Object> loginData = new HashMap<>();
|
||||
loginData.put("username", username);
|
||||
loginData.put("password", password);
|
||||
loginData.putAll(getClientInfo());
|
||||
|
||||
JsonNode response = sendPostRequest("/monitor/account/login", loginData);
|
||||
if (response.has("code") && response.get("code").asInt() == 200) {
|
||||
JsonNode data = response.has("data") ? response.get("data") : response;
|
||||
accessToken = data.has("accessToken") ? data.get("accessToken").asText() :
|
||||
data.has("token") ? data.get("token").asText() : null;
|
||||
refreshToken = data.has("refreshToken") ? data.get("refreshToken").asText() : null;
|
||||
|
||||
if (accessToken != null) {
|
||||
saveTokenToCache(accessToken);
|
||||
registerDeviceOnLogin(username);
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "登录成功");
|
||||
result.put("token", accessToken);
|
||||
result.put("permissions", data.has("permissions") ? data.get("permissions").asText() : null);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", response.has("msg") ? response.get("msg").asText() : "登录失败");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端基本信息
|
||||
@@ -114,161 +51,10 @@ public class AuthServiceImpl implements IAuthService {
|
||||
errorData.put("clientId", clientId);
|
||||
errorData.put("errorType", errorType);
|
||||
errorData.put("errorMessage", errorMessage);
|
||||
sendPostRequest("/monitor/error", errorData);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查版本更新
|
||||
*/
|
||||
@Override
|
||||
public String checkVersion(String currentVersion) {
|
||||
JsonNode response = sendGetRequest("/monitor/version?currentVersion=" + currentVersion);
|
||||
return response.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送POST请求
|
||||
*/
|
||||
private JsonNode sendPostRequest(String path, Map<String, Object> data){
|
||||
if (data != null) {
|
||||
data.putIfAbsent("_clientUser", System.getProperty("user.name"));
|
||||
data.putIfAbsent("_clientOs", System.getProperty("os.name"));
|
||||
errorData.putIfAbsent("_clientUser", System.getProperty("user.name"));
|
||||
errorData.putIfAbsent("_clientOs", System.getProperty("os.name"));
|
||||
apiForwarder.post("/monitor/error", errorData, null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
org.springframework.http.ResponseEntity<?> resp = apiForwarder.post(path, data, buildAuthHeader());
|
||||
return objectMapper.valueToTree(resp.getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送GET请求
|
||||
*/
|
||||
private JsonNode sendGetRequest(String path){
|
||||
org.springframework.http.ResponseEntity<?> resp = apiForwarder.get(path, buildAuthHeader());
|
||||
return objectMapper.valueToTree(resp.getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建认证头
|
||||
*/
|
||||
private String buildAuthHeader(){
|
||||
return (accessToken == null || accessToken.isEmpty()) ? null : ("Bearer " + accessToken.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证token
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> verifyToken(String token) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
Map<String, Object> verifyData = new HashMap<>();
|
||||
verifyData.put("token", token);
|
||||
JsonNode response = sendPostRequest("/monitor/account/verify", verifyData);
|
||||
if (response.has("code") && response.get("code").asInt() == 200) {
|
||||
JsonNode dataNode = response.has("data") ? response.get("data") : response;
|
||||
result.put("success", true);
|
||||
result.put("username", dataNode.get("username").asText());
|
||||
result.put("permissions", dataNode.get("permissions").asText());
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "token验证失败");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册新账号
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> register(String username, String password) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
Map<String, Object> registerData = new HashMap<>();
|
||||
registerData.put("accountName", username);
|
||||
registerData.put("username", username);
|
||||
registerData.put("password", password);
|
||||
registerData.putAll(getClientInfo());
|
||||
JsonNode response = sendPostRequest("/monitor/account/register", registerData);
|
||||
if (response.has("code") && response.get("code").asInt() == 200) {
|
||||
JsonNode data = response.has("data") ? response.get("data") : response;
|
||||
String newAccessToken = data.has("accessToken") ? data.get("accessToken").asText() : null;
|
||||
String newRefreshToken = data.has("refreshToken") ? data.get("refreshToken").asText() : null;
|
||||
|
||||
if (newAccessToken != null) {
|
||||
accessToken = newAccessToken;
|
||||
refreshToken = newRefreshToken;
|
||||
saveTokenToCache(newAccessToken);
|
||||
registerDeviceOnLogin(username);
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "注册成功");
|
||||
} else {
|
||||
String errorMessage = response.has("msg") ? response.get("msg").asText() :
|
||||
(response.has("message") ? response.get("message").asText() : "注册失败");
|
||||
result.put("success", false);
|
||||
result.put("message", errorMessage);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "注册失败:" + e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户名是否可用
|
||||
*/
|
||||
@Override
|
||||
public boolean checkUsername(String username) {
|
||||
try {
|
||||
JsonNode response = sendGetRequest("/monitor/account/check-username?username=" + username);
|
||||
if (response.has("code") && response.get("code").asInt() == 200) {
|
||||
JsonNode dataNode = response.has("data") ? response.get("data") : response;
|
||||
return dataNode.asBoolean();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录,清除本地token
|
||||
*/
|
||||
public void logout() {
|
||||
try {
|
||||
// 通知服务器设备离线
|
||||
setDeviceOffline();
|
||||
|
||||
// 清除内存中的token
|
||||
accessToken = null;
|
||||
refreshToken = null;
|
||||
|
||||
// 删除SQLite中的token缓存
|
||||
cacheDataRepository.deleteByCacheKey("token");
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录时注册设备
|
||||
*/
|
||||
private void registerDeviceOnLogin(String username) {
|
||||
try {
|
||||
Map<String, Object> deviceData = new HashMap<>();
|
||||
deviceData.put("username", username);
|
||||
deviceData.put("deviceId", clientId);
|
||||
deviceData.put("os", System.getProperty("os.name"));
|
||||
apiForwarder.post("/monitor/device/register", deviceData, buildAuthHeader());
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备离线
|
||||
*/
|
||||
private void setDeviceOffline() {
|
||||
try {
|
||||
Map<String, Object> offlineData = new HashMap<>();
|
||||
offlineData.put("deviceId", clientId);
|
||||
apiForwarder.post("/monitor/device/offline", offlineData, buildAuthHeader());
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,13 @@ package com.tashow.erp.service.impl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tashow.erp.entity.BanmaOrderEntity;
|
||||
import com.tashow.erp.repository.BanmaOrderRepository;
|
||||
import com.tashow.erp.service.ICacheService;
|
||||
import com.tashow.erp.service.IBanmaOrderService;
|
||||
import com.tashow.erp.utils.DataReportUtil;
|
||||
import com.tashow.erp.utils.ErrorReporter;
|
||||
import com.tashow.erp.utils.LoggerUtil;
|
||||
import com.tashow.erp.utils.SagawaExpressSdk;
|
||||
import com.tashow.erp.utils.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -25,16 +24,17 @@ import java.util.stream.Collectors;
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
public class BanmaOrderServiceImpl implements IBanmaOrderService {
|
||||
public class BanmaOrderServiceImpl {
|
||||
private static final Logger logger = LoggerUtil.getLogger(BanmaOrderServiceImpl.class);
|
||||
private static final String SERVICE_NAME = "banma";
|
||||
private static final String RUOYI_ADMIN_BASE = "http://192.168.1.89:8085";
|
||||
private static final String API_URL = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&_t=%d";
|
||||
private static final String API_URL_WITH_TIME = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&orderedAtStart=%s&orderedAtEnd=%s&_t=%d";
|
||||
private static final String TRACKING_URL = "https://banma365.cn/zebraExpressHub/web/tracking/getByExpressNumber/%s";
|
||||
|
||||
@Value("${api.server.base-url}")
|
||||
private String ruoyiAdminBase;
|
||||
private RestTemplate restTemplate;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final ICacheService cacheService;
|
||||
private final CacheServiceImpl cacheService;
|
||||
private final BanmaOrderRepository banmaOrderRepository;
|
||||
private final DataReportUtil dataReportUtil;
|
||||
private final ErrorReporter errorReporter;
|
||||
@@ -44,7 +44,7 @@ public class BanmaOrderServiceImpl implements IBanmaOrderService {
|
||||
private String currentBatchSessionId = null;
|
||||
// 物流信息缓存,避免重复查询
|
||||
private final Map<String, String> trackingInfoCache = new ConcurrentHashMap<>();
|
||||
public BanmaOrderServiceImpl(BanmaOrderRepository banmaOrderRepository, ICacheService cacheService, DataReportUtil dataReportUtil, ErrorReporter errorReporter) {
|
||||
public BanmaOrderServiceImpl(BanmaOrderRepository banmaOrderRepository, CacheServiceImpl cacheService, DataReportUtil dataReportUtil, ErrorReporter errorReporter) {
|
||||
this.banmaOrderRepository = banmaOrderRepository;
|
||||
this.cacheService = cacheService;
|
||||
this.dataReportUtil = dataReportUtil;
|
||||
@@ -56,7 +56,7 @@ public class BanmaOrderServiceImpl implements IBanmaOrderService {
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
private void fetchTokenFromServer(Long accountId) {
|
||||
Map<String, Object> resp = restTemplate.getForObject(RUOYI_ADMIN_BASE + "/tool/banma/accounts", Map.class);
|
||||
Map<String, Object> resp = restTemplate.getForObject(ruoyiAdminBase + "/tool/banma/accounts", Map.class);
|
||||
List<Map<String, Object>> list = (List<Map<String, Object>>) resp.get("data");
|
||||
if (list == null || list.isEmpty()) return;
|
||||
Map<String, Object> account = accountId != null
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.tashow.erp.service.impl;
|
||||
|
||||
import com.tashow.erp.repository.*;
|
||||
import com.tashow.erp.service.ICacheManagementService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 缓存管理服务实现
|
||||
*/
|
||||
@Service
|
||||
public class CacheManagementServiceImpl implements ICacheManagementService {
|
||||
|
||||
@Autowired
|
||||
private RakutenProductRepository rakutenProductRepository;
|
||||
|
||||
@Autowired
|
||||
private AmazonProductRepository amazonProductRepository;
|
||||
|
||||
@Autowired
|
||||
private Alibaba1688ProductRepository alibaba1688ProductRepository;
|
||||
|
||||
@Autowired
|
||||
private ZebraOrderRepository zebraOrderRepository;
|
||||
|
||||
@Autowired
|
||||
private BanmaOrderRepository banmaOrderRepository;
|
||||
|
||||
@Autowired
|
||||
private CacheDataRepository cacheDataRepository;
|
||||
|
||||
@Autowired
|
||||
private UpdateStatusRepository updateStatusRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void clearCache() {
|
||||
rakutenProductRepository.deleteAll();
|
||||
amazonProductRepository.deleteAll();
|
||||
alibaba1688ProductRepository.deleteAll();
|
||||
zebraOrderRepository.deleteAll();
|
||||
banmaOrderRepository.deleteAll();
|
||||
cacheDataRepository.deleteAll();
|
||||
updateStatusRepository.deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,8 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
||||
public List<RakutenProduct> scrapeProductsWithSearch(String shopName) {
|
||||
String url = "https://ranking.rakuten.co.jp/search?stx=" + URLEncoder.encode(shopName, StandardCharsets.UTF_8);
|
||||
List<RakutenProduct> products = new ArrayList<>();
|
||||
|
||||
Spider spider = Spider.create(new RakutenPageProcessor(products, errorReporter)).addUrl(url).setDownloader(new RakutenProxyUtil().createProxyDownloader(new RakutenProxyUtil().detectSystemProxy(url))).thread(1);
|
||||
spider.run();
|
||||
|
||||
log.info("采集完成,店铺: {},数量: {}", shopName, products.size());
|
||||
return products;
|
||||
}
|
||||
@@ -57,7 +55,6 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
||||
private class RakutenPageProcessor implements PageProcessor {
|
||||
private final List<RakutenProduct> products;
|
||||
private final ErrorReporter errorReporter;
|
||||
|
||||
RakutenPageProcessor(List<RakutenProduct> products, ErrorReporter errorReporter) {
|
||||
this.products = products;
|
||||
this.errorReporter = errorReporter;
|
||||
@@ -129,8 +126,6 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
||||
product.setMedian(searchResult.getMedian());
|
||||
product.setWeight(searchResult.getWeight());
|
||||
product.setSkuPriceJson(JSON.toJSONString(searchResult.getSkuPrice()));
|
||||
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
if (!matchingProducts.isEmpty()) {
|
||||
rakutenProductRepository.saveAll(matchingProducts);
|
||||
|
||||
@@ -8,6 +8,8 @@ javafx:
|
||||
# resizable: false
|
||||
|
||||
spring:
|
||||
main:
|
||||
lazy-initialization: false
|
||||
datasource:
|
||||
url: jdbc:sqlite:./data/erp-cache.db?journal_mode=WAL&synchronous=NORMAL&cache_size=10000&temp_store=memory&busy_timeout=30000
|
||||
driver-class-name: org.sqlite.JDBC
|
||||
@@ -42,8 +44,8 @@ server:
|
||||
api:
|
||||
server:
|
||||
# 主服务器API配置
|
||||
# base-url: "http://8.138.23.49:8080"
|
||||
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"
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
package com.tashow.erp;
|
||||
|
||||
/**
|
||||
* 设备ID生成测试类
|
||||
* 用于验证设备ID生成功能是否正常
|
||||
*/
|
||||
public class DeviceIdTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== 设备ID生成测试 ===");
|
||||
|
||||
// 测试主板序列号获取
|
||||
testMotherboardSerial();
|
||||
|
||||
// 测试CPU序列号获取
|
||||
testCpuSerial();
|
||||
|
||||
// 测试MAC地址获取
|
||||
testMacAddress();
|
||||
|
||||
// 测试系统信息获取
|
||||
testSystemInfo();
|
||||
}
|
||||
|
||||
private static void testMotherboardSerial() {
|
||||
System.out.println("\n1. 测试主板序列号获取:");
|
||||
try {
|
||||
if (cn.hutool.system.SystemUtil.getOsInfo().isWindows()) {
|
||||
Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber");
|
||||
java.io.BufferedReader reader = new java.io.BufferedReader(
|
||||
new java.io.InputStreamReader(process.getInputStream(), "GBK"));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (!line.isEmpty() && !"SerialNumber".equalsIgnoreCase(line)) {
|
||||
String deviceId = "MB_" + cn.hutool.crypto.SecureUtil.md5(line).substring(0, 16).toUpperCase();
|
||||
System.out.println("主板序列号: " + line);
|
||||
System.out.println("生成的设备ID: " + deviceId);
|
||||
reader.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
}
|
||||
System.out.println("无法获取主板序列号");
|
||||
} catch (Exception e) {
|
||||
System.out.println("获取主板序列号失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void testCpuSerial() {
|
||||
System.out.println("\n2. 测试CPU序列号获取:");
|
||||
try {
|
||||
if (cn.hutool.system.SystemUtil.getOsInfo().isWindows()) {
|
||||
Process process = Runtime.getRuntime().exec("wmic cpu get processorid");
|
||||
java.io.BufferedReader reader = new java.io.BufferedReader(
|
||||
new java.io.InputStreamReader(process.getInputStream(), "GBK"));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (!line.isEmpty() && !"ProcessorId".equalsIgnoreCase(line)) {
|
||||
String deviceId = "CPU_" + cn.hutool.crypto.SecureUtil.md5(line).substring(0, 16).toUpperCase();
|
||||
System.out.println("CPU序列号: " + line);
|
||||
System.out.println("生成的设备ID: " + deviceId);
|
||||
reader.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
}
|
||||
System.out.println("无法获取CPU序列号");
|
||||
} catch (Exception e) {
|
||||
System.out.println("获取CPU序列号失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void testMacAddress() {
|
||||
System.out.println("\n3. 测试MAC地址获取:");
|
||||
try {
|
||||
String macAddress = cn.hutool.system.SystemUtil.getHostInfo().getAddress();
|
||||
if (macAddress != null && !macAddress.isEmpty()) {
|
||||
String deviceId = "MAC_" + cn.hutool.crypto.SecureUtil.md5(macAddress).substring(0, 16).toUpperCase();
|
||||
System.out.println("MAC地址: " + macAddress);
|
||||
System.out.println("生成的设备ID: " + deviceId);
|
||||
} else {
|
||||
System.out.println("无法获取MAC地址");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("获取MAC地址失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void testSystemInfo() {
|
||||
System.out.println("\n4. 测试系统信息获取:");
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(System.getProperty("os.name", ""));
|
||||
sb.append(System.getProperty("os.version", ""));
|
||||
sb.append(System.getProperty("user.name", ""));
|
||||
sb.append(System.getProperty("java.vm.vendor", ""));
|
||||
|
||||
String combined = sb.toString();
|
||||
String deviceId = "SYS_" + cn.hutool.crypto.SecureUtil.md5(combined).substring(0, 16).toUpperCase();
|
||||
|
||||
System.out.println("操作系统: " + System.getProperty("os.name"));
|
||||
System.out.println("系统版本: " + System.getProperty("os.version"));
|
||||
System.out.println("用户名: " + System.getProperty("user.name"));
|
||||
System.out.println("JVM厂商: " + System.getProperty("java.vm.vendor"));
|
||||
System.out.println("生成的设备ID: " + deviceId);
|
||||
} catch (Exception e) {
|
||||
System.out.println("获取系统信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class ClientDeviceController {
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public AjaxResult register(@RequestBody ClientDevice device, HttpServletRequest request) {
|
||||
String ip = IpUtils.getIpAddr(request);
|
||||
String ip = device.getIp();
|
||||
String username = device.getUsername();
|
||||
String deviceId = device.getDeviceId();
|
||||
String os = device.getOs();
|
||||
|
||||
Reference in New Issue
Block a user