diff --git a/.gitignore b/.gitignore index 58e7138..09ba7d3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.temp .DS_Store Thumbs.db +nul # IDE 文件 ## IntelliJ IDEA @@ -14,6 +15,25 @@ Thumbs.db *.ipr out/ .idea_modules/ + +## VSCode +.vscode/ +.history/ + +## Eclipse +.classpath +.project +.settings/ + +## AI 助手配置 +.claude/ +.cursor/ +.starFactory/ +.windsurf/ +.aider/ +.copilot/ + +# Maven 构建目录 /ruoyi-common/target/ /ruoyi-system/target/ /ruoyi-quartz/target/ @@ -21,4 +41,54 @@ out/ /ruoyi-framework/target/ /ruoyi-admin/target/ /erp_client_sb/target/ -target / \ No newline at end of file +target/ +*.class + +# Node.js 前端项目 +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +.npm +.yarn/ +dist/ +dist-ssr/ +*.local + +# Vite 构建缓存 +.vite/ +.vite-inspect/ + +# Electron 构建产物 +build/ +release/ +out/ +*.exe +*.dmg +*.AppImage + +# Java 打包文件 +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# JRE 运行时环境 +jre/ +jdk/ + +# 数据库文件 +*.db +*.db-shm +*.db-wal +*.sqlite +*.sqlite3 + +# 缓存和临时文件 +.cache/ +*.swp +*.swo +*~ \ No newline at end of file diff --git a/electron-vue-template/package.json b/electron-vue-template/package.json index e0c2b7f..dda5e5e 100644 --- a/electron-vue-template/package.json +++ b/electron-vue-template/package.json @@ -19,7 +19,7 @@ "@vitejs/plugin-vue": "^4.4.1", "chalk": "^4.1.2", "chokidar": "^3.5.3", - "electron": "^28.2.0", + "electron": "^28.3.3", "electron-builder": "^25.1.6", "express": "^5.1.0", "fs-extra": "^11.3.2", diff --git a/electron-vue-template/src/renderer/components/common/AccountManager.vue b/electron-vue-template/src/renderer/components/common/AccountManager.vue index 4dcff18..476d1f5 100644 --- a/electron-vue-template/src/renderer/components/common/AccountManager.vue +++ b/electron-vue-template/src/renderer/components/common/AccountManager.vue @@ -18,7 +18,6 @@ const PLATFORM_LABEL: Record = { const accounts = ref([]) async function load() { - // 目前后端只有斑马接口,其它平台先共用此接口占位 const res = await zebraApi.getAccounts() const list = (res as any)?.data ?? res accounts.value = Array.isArray(list) ? list : [] diff --git a/electron-vue-template/update-helper.bat b/electron-vue-template/update-helper.bat new file mode 100644 index 0000000..4210b8e --- /dev/null +++ b/electron-vue-template/update-helper.bat @@ -0,0 +1,88 @@ +@echo off +setlocal enabledelayedexpansion + +set APP_ASAR=%~1 +set UPDATE_FILE=%~2 +set EXE_PATH=%~3 +set LOG_FILE=%TEMP%\electron-update.log + +echo ======================================== > "%LOG_FILE%" +echo Electron App Auto-Update Helper >> "%LOG_FILE%" +echo Started: %date% %time% >> "%LOG_FILE%" +echo ======================================== >> "%LOG_FILE%" +echo. >> "%LOG_FILE%" + +if not exist "%UPDATE_FILE%" ( + echo [ERROR] Update file not found: %UPDATE_FILE% >> "%LOG_FILE%" + goto :start_app +) + +echo [INFO] Update file found: %UPDATE_FILE% >> "%LOG_FILE%" +echo [INFO] Target app.asar: %APP_ASAR% >> "%LOG_FILE%" +echo [INFO] Application exe: %EXE_PATH% >> "%LOG_FILE%" +echo [INFO] Waiting for application to close... >> "%LOG_FILE%" + +REM 获取应用进程名 +for /f "tokens=*" %%a in ("%EXE_PATH%") do set EXE_NAME=%%~nxa +echo [INFO] Waiting for %EXE_NAME% to close... >> "%LOG_FILE%" + +REM 等待应用进程完全关闭(最多等待10秒) +set COUNT=0 +:wait_loop +tasklist /FI "IMAGENAME eq %EXE_NAME%" 2>nul | find /I "%EXE_NAME%" >nul +if errorlevel 1 goto process_closed +set /a COUNT+=1 +if %COUNT% GEQ 20 ( + echo [WARN] Application still running after 10 seconds >> "%LOG_FILE%" + goto process_closed +) +timeout /t 1 /nobreak >nul +goto wait_loop + +:process_closed +echo [INFO] Application process closed >> "%LOG_FILE%" +timeout /t 1 /nobreak >nul + +echo [INFO] Backing up current app.asar... >> "%LOG_FILE%" +if exist "%APP_ASAR%.backup" ( + del /f /q "%APP_ASAR%.backup" >nul 2>&1 +) + +if exist "%APP_ASAR%" ( + move /y "%APP_ASAR%" "%APP_ASAR%.backup" >nul 2>&1 + if errorlevel 1 ( + echo [WARN] First move attempt failed, retrying... >> "%LOG_FILE%" + timeout /t 2 /nobreak >nul + move /y "%APP_ASAR%" "%APP_ASAR%.backup" >nul 2>&1 + if errorlevel 1 ( + echo [ERROR] Failed to backup app.asar, file is locked >> "%LOG_FILE%" + goto :start_app + ) + ) + echo [SUCCESS] Backup completed >> "%LOG_FILE%" +) + +echo [INFO] Applying update... >> "%LOG_FILE%" +move /y "%UPDATE_FILE%" "%APP_ASAR%" >nul 2>&1 + +if errorlevel 1 ( + echo [ERROR] Failed to apply update >> "%LOG_FILE%" + if exist "%APP_ASAR%.backup" ( + echo [INFO] Restoring backup... >> "%LOG_FILE%" + move /y "%APP_ASAR%.backup" "%APP_ASAR%" >nul 2>&1 + ) + goto :start_app +) + +echo [SUCCESS] Update applied successfully! >> "%LOG_FILE%" + +if exist "%UPDATE_FILE%" ( + del /f /q "%UPDATE_FILE%" >nul 2>&1 +) + +:start_app +echo [INFO] Restarting application... >> "%LOG_FILE%" +echo ======================================== >> "%LOG_FILE%" + +start "" "%EXE_PATH%" +exit /b 0 diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/AuthController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/AuthController.java index da24e5b..8e91b5a 100644 --- a/erp_client_sb/src/main/java/com/tashow/erp/controller/AuthController.java +++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/AuthController.java @@ -43,14 +43,11 @@ public class AuthController { public JsonData saveAuth(@RequestBody Map data) { String serviceName = (String) data.get("serviceName"); String authKey = (String) data.get("authKey"); - if (serviceName == null || authKey == null) return JsonData.buildError("serviceName和authKey不能为空"); - AuthTokenEntity entity = authTokenRepository.findByServiceName(serviceName).orElse(new AuthTokenEntity()); entity.setServiceName(serviceName); entity.setToken(authKey); authTokenRepository.save(entity); - return JsonData.buildSuccess("认证信息保存成功"); } @@ -104,7 +101,7 @@ public class AuthController { } /** - * 删除缓存数据 - POST方式 + * 删除缓存数据 */ @PostMapping("/cache/delete") public JsonData deleteCacheByPost(@RequestParam String key) { @@ -133,7 +130,7 @@ public class AuthController { } /** - * 获取设备ID(硬件UUID) + * 获取设备ID */ @GetMapping("/device-id") public JsonData getDeviceId() { diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/BanmaAccountMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/BanmaAccountMapper.java new file mode 100644 index 0000000..5d2bb82 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/BanmaAccountMapper.java @@ -0,0 +1,18 @@ +package com.ruoyi.system.mapper; + +import java.util.List; +import com.ruoyi.system.domain.BanmaAccount; + +/** + * 斑马账号 Mapper + */ +public interface BanmaAccountMapper { + BanmaAccount selectById(Long id); + List selectList(BanmaAccount query); + int insert(BanmaAccount entity); + int update(BanmaAccount entity); + int deleteById(Long id); + int clearDefault(); +} + + diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IBanmaAccountService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IBanmaAccountService.java new file mode 100644 index 0000000..68ec459 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IBanmaAccountService.java @@ -0,0 +1,17 @@ +package com.ruoyi.system.service; + +import java.util.List; +import com.ruoyi.system.domain.BanmaAccount; + +/** + * 斑马账号 Service 接口 + */ +public interface IBanmaAccountService { + List listSimple(); + Long saveOrUpdate(BanmaAccount entity); + void remove(Long id); + boolean refreshToken(Long id); + void refreshAllTokens(); +} + + diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BanmaAccountServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BanmaAccountServiceImpl.java new file mode 100644 index 0000000..e09ee10 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BanmaAccountServiceImpl.java @@ -0,0 +1,109 @@ +package com.ruoyi.system.service.impl; + +import java.util.Date; +import java.util.List; +import java.util.Objects; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; +import com.ruoyi.system.domain.BanmaAccount; +import com.ruoyi.system.mapper.BanmaAccountMapper; +import com.ruoyi.system.service.IBanmaAccountService; + +/** + * 斑马账号 Service 实现 + */ +@Service +public class BanmaAccountServiceImpl implements IBanmaAccountService { + + @Autowired + private BanmaAccountMapper mapper; + private final RestTemplate restTemplate = new RestTemplate(); + private static final String LOGIN_URL = "https://banma365.cn/api/login"; + + @Override + public List listSimple() { + List list = mapper.selectList(new BanmaAccount()); + // 隐藏密码 + for (BanmaAccount a : list) { a.setPassword(null); } + return list; + } + + @Override + public Long saveOrUpdate(BanmaAccount entity) { + if (entity.getId() == null) { + mapper.insert(entity); + } else { + mapper.update(entity); + } + if (Objects.equals(entity.getIsDefault(), 1)) { + mapper.clearDefault(); + BanmaAccount only = new BanmaAccount(); + only.setId(entity.getId()); + only.setIsDefault(1); + mapper.update(only); + } + return entity.getId(); + } + + @Override + public void remove(Long id) { mapper.deleteById(id); } + + @Override + public boolean refreshToken(Long id) { + BanmaAccount one = mapper.selectById(id); + if (one == null || one.getStatus() == null || one.getStatus() == 0) return false; + if (one.getUsername() == null || one.getPassword() == null) return false; + String token = validateAndGetToken(one.getUsername(), one.getPassword()); + if (token != null) { + BanmaAccount upd = new BanmaAccount(); + upd.setId(one.getId()); + upd.setToken("Bearer " + token); + upd.setTokenExpireAt(new Date(System.currentTimeMillis() + 2L * 24 * 60 * 60 * 1000)); + mapper.update(upd); + return true; + } + return false; + } + + /** + * 验证账号密码并获取Token(不保存到数据库) + */ + public String validateAndGetToken(String username, String password) { + if (username == null || password == null) return null; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + java.util.Map body = new java.util.HashMap<>(); + body.put("username", username); + body.put("password", password); + @SuppressWarnings("unchecked") + java.util.Map resp = restTemplate.postForObject(LOGIN_URL, new HttpEntity<>(body, headers), java.util.Map.class); + if (resp == null) return null; + Object code = resp.get("code"); + if (code instanceof Number && ((Number) code).intValue() == 0) { + Object data = resp.get("data"); + if (data instanceof java.util.Map) { + Object token = ((java.util.Map) data).get("token"); + if (token instanceof String && !((String) token).isEmpty()) { + return (String) token; + } + } + } + return null; + } + + @Override + public void refreshAllTokens() { + BanmaAccount q = new BanmaAccount(); + q.setStatus(1); + List list = mapper.selectList(q); + for (BanmaAccount a : list) { + try { refreshToken(a.getId()); } catch (Exception ignore) {} + } + } +} + + diff --git a/ruoyi-system/src/main/resources/mapper/system/BanmaAccountMapper.xml b/ruoyi-system/src/main/resources/mapper/system/BanmaAccountMapper.xml new file mode 100644 index 0000000..ae47e69 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/BanmaAccountMapper.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + id, name, username, password, token, token_expire_at, is_default, status, remark, create_by, create_time, update_by, update_time + + + + + + + + insert into banma_account + + name, + username, + password, + token, + token_expire_at, + is_default, + status, + remark, + create_by, + create_time + + + #{name}, + #{username}, + #{password}, + #{token}, + #{tokenExpireAt}, + #{isDefault}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + + + + + update banma_account + + name=#{name}, + username=#{username}, + password=#{password}, + token=#{token}, + token_expire_at=#{tokenExpireAt}, + is_default=#{isDefault}, + status=#{status}, + remark=#{remark}, + update_by=#{updateBy}, + update_time = sysdate() + + where id = #{id} + + + + update banma_account set is_default = 0 where is_default = 1 + + + + delete from banma_account where id = #{id} + + + + + diff --git a/sql/banma_account.sql b/sql/banma_account.sql new file mode 100644 index 0000000..7579376 --- /dev/null +++ b/sql/banma_account.sql @@ -0,0 +1,20 @@ +-- 斑马账号表(与 BanmaAccount 实体、BanmaAccountMapper.xml 一致) +DROP TABLE IF EXISTS `banma_account`; +CREATE TABLE `banma_account` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(64) DEFAULT NULL COMMENT '显示名', + `username` VARCHAR(128) DEFAULT NULL COMMENT '登录用户名', + `token` VARCHAR(512) DEFAULT NULL COMMENT '访问Token(客户端刷新后回写)', + `token_expire_at` DATETIME DEFAULT NULL COMMENT 'Token过期时间', + `is_default` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否默认 1是 0否', + `status` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '状态 1启用 0停用', + `remark` VARCHAR(255) DEFAULT NULL COMMENT '备注', + `create_by` VARCHAR(64) DEFAULT NULL, + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, + `update_by` VARCHAR(64) DEFAULT NULL, + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_banma_is_default` (`is_default`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='斑马账号表'; + +