feat: 重构更新流程 — 无更新不抛异常、用户决定下载、更新详情展示(md文件中的)
【核心重构】
- getLatestVersion() 不再 throw Error,无更新时返回 { version: APP_VERSION }
- checkForUpdates() 先自行预检 → 无更新直发 IPC,有更新才交 electron-updater
- 移除 NO_UPDATE_MESSAGE 常量及所有字符串匹配拦截代码
- HeiXiuProvider 增加 500ms TTL 缓存,避免预检+electron-updater 重复请求 API
【用户体验】
- 检查到新版本后不再自动下载,展示更新内容供用户决定
- 新增"下载更新"按钮(与"安装更新"分离为两步操作)
- 设置页新增"查看详情"弹窗,Markdown 格式化渲染更新日志
- available / downloading / downloaded 三个状态 UI 独立展示
【Bug 修复】
- 测试服务器版本排序:字符串序 → 语义版本序(_version_key),0.0.10 正确 > 0.0.9
- 版本比较:!= → <(_version_key),防止高版本误判为有更新
- get_best_release fallback 同样改为语义版本排序
- Windows cmd set 命令尾部空格 → Invalid URL(.trim() 修复)
- electron-updater 'error' 事件中拦截 NO_UPDATE_MESSAGE → UPDATE_NOT_AVAILABLE
【文档更新】
- CHANGELOG.md、PROJECT.md、UpdateA.md 同步更新架构图和流程说明
This commit is contained in:
@@ -11,7 +11,7 @@ import { safeIpcOn, safeIpcOff } from '@/utils/ipc';
|
||||
import { AppContext } from '@/contexts/app-context';
|
||||
import type { AppContextValue } from '@/contexts/app-context';
|
||||
import type { LoginResponseBody } from '@/services/modules';
|
||||
import { setToken, clearToken } from '@/services/request';
|
||||
import { setToken, clearToken, initTokenStore } from '@/services/request';
|
||||
import { emit, EVENTS } from '@/utils/event-bus';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { AuthAPI } from '@/services/modules';
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
getStoredRefreshToken,
|
||||
setStoredRefreshToken,
|
||||
clearStoredRefreshToken,
|
||||
initRefreshTokenStore,
|
||||
} from '@/services/auth-token';
|
||||
|
||||
interface AppProviderProps {
|
||||
@@ -37,9 +38,9 @@ export function AppProvider({ children }: AppProviderProps) {
|
||||
|
||||
// ---------- 登录 / 退出 ----------
|
||||
|
||||
const login = useCallback((auth: LoginResponseBody) => {
|
||||
setToken(auth.access_token);
|
||||
setStoredRefreshToken(auth.refresh_token);
|
||||
const login = useCallback(async (auth: LoginResponseBody) => {
|
||||
await setToken(auth.access_token);
|
||||
await setStoredRefreshToken(auth.refresh_token);
|
||||
setUser(auth);
|
||||
setIsLoggedIn(true);
|
||||
logger.info('auth', 'User logged in', {
|
||||
@@ -58,57 +59,58 @@ export function AppProvider({ children }: AppProviderProps) {
|
||||
emit(EVENTS.LOGOUT);
|
||||
}, []);
|
||||
|
||||
// ---------- 初始化 token 刷新回调(给 request.ts 401 拦截器用) ----------
|
||||
// ---------- 启动时初始化 token 存储 + 自动登录 ----------
|
||||
|
||||
useEffect(() => {
|
||||
initAuthRefresh();
|
||||
}, []);
|
||||
// 从加密存储恢复 token 到内存缓存
|
||||
Promise.all([initTokenStore(), initRefreshTokenStore()]).then(() => {
|
||||
// 初始化 401 刷新回调(token 缓存就绪后才注册)
|
||||
initAuthRefresh();
|
||||
|
||||
// ---------- 启动时自动登录(refresh_token 续期) ----------
|
||||
// 自动登录(refresh_token 续期)
|
||||
if (!getAutoLoginFlag()) return;
|
||||
|
||||
useEffect(() => {
|
||||
if (!getAutoLoginFlag()) return;
|
||||
const refreshToken = getStoredRefreshToken();
|
||||
if (!refreshToken) return;
|
||||
|
||||
const refreshToken = getStoredRefreshToken();
|
||||
if (!refreshToken) return;
|
||||
|
||||
AuthAPI.refreshToken({ refresh_token: refreshToken })
|
||||
.then((res) => {
|
||||
setToken(res.access_token);
|
||||
setStoredRefreshToken(res.refresh_token);
|
||||
setIsLoggedIn(true);
|
||||
logger.info('auth', 'Auto-login succeeded');
|
||||
// 补全用户信息
|
||||
AuthAPI.getUserInfo().then((info) => {
|
||||
const u: LoginResponseBody = {
|
||||
access_token: res.access_token,
|
||||
refresh_token: res.refresh_token,
|
||||
token_type: res.token_type,
|
||||
expires_in: res.expires_in,
|
||||
refresh_expires_in: res.refresh_expires_in,
|
||||
user_id: info.id,
|
||||
username: info.username,
|
||||
role: info.role,
|
||||
email: info.email,
|
||||
balance_cent: info.balance_cent,
|
||||
account_type: info.account_type,
|
||||
display_name: info.display_name,
|
||||
company_id: info.company_id,
|
||||
real_name: info.real_name,
|
||||
phone: info.phone,
|
||||
license_code: '',
|
||||
admin_permissions: info.admin_permissions,
|
||||
};
|
||||
setUser(u);
|
||||
emit(EVENTS.LOGIN_SUCCESS, u);
|
||||
AuthAPI.refreshToken({ refresh_token: refreshToken })
|
||||
.then(async (res) => {
|
||||
await setToken(res.access_token);
|
||||
await setStoredRefreshToken(res.refresh_token);
|
||||
setIsLoggedIn(true);
|
||||
logger.info('auth', 'Auto-login succeeded');
|
||||
// 补全用户信息
|
||||
AuthAPI.getUserInfo().then((info) => {
|
||||
const u: LoginResponseBody = {
|
||||
access_token: res.access_token,
|
||||
refresh_token: res.refresh_token,
|
||||
token_type: res.token_type,
|
||||
expires_in: res.expires_in,
|
||||
refresh_expires_in: res.refresh_expires_in,
|
||||
user_id: info.id,
|
||||
username: info.username,
|
||||
role: info.role,
|
||||
email: info.email,
|
||||
balance_cent: info.balance_cent,
|
||||
account_type: info.account_type,
|
||||
display_name: info.display_name,
|
||||
company_id: info.company_id,
|
||||
real_name: info.real_name,
|
||||
phone: info.phone,
|
||||
license_code: '',
|
||||
admin_permissions: info.admin_permissions,
|
||||
};
|
||||
setUser(u);
|
||||
emit(EVENTS.LOGIN_SUCCESS, u);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
logger.warn('auth', 'Auto-login failed, refresh_token expired');
|
||||
clearStoredRefreshToken();
|
||||
// 仅清除自动登录标记,保留用户名供下次表单回填
|
||||
clearAutoLoginFlag();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
logger.warn('auth', 'Auto-login failed, refresh_token expired');
|
||||
clearStoredRefreshToken();
|
||||
// 仅清除自动登录标记,保留用户名+密码供下次表单回填
|
||||
clearAutoLoginFlag();
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
// ---------- 监听主进程推送的平台/版本信息 ----------
|
||||
|
||||
Reference in New Issue
Block a user