fix: 更新依赖项版本并优化远程应用连接逻辑

This commit is contained in:
2026-02-21 06:28:20 +08:00
parent 68c1976754
commit 13b0f45d3e
13 changed files with 184 additions and 76 deletions

View File

@@ -91,7 +91,6 @@ export type AssistantConfigData = {
*/
url?: string;
}
token?: string;
registry?: string; // https://kevisual.cn
/**
* 前端代理,比如/root/home 转到https://kevisual.cn/root/home

View File

@@ -7,12 +7,13 @@ import glob from 'fast-glob';
import type { App } from '@kevisual/router';
import { RemoteApp } from '@/module/remote-app/remote-app.ts';
import { logger } from '@/module/logger.ts';
import { getEnvToken } from '@/module/http-token.ts';
import { initApi } from '@kevisual/api/proxy'
import { Query } from '@kevisual/query';
import { initLightCode } from '@/module/light-code/index.ts';
import { ModuleResolver } from './assistant-app-resolve.ts';
import z from 'zod';
import { assistantQuery } from '@/app.ts';
import { useKey } from '@kevisual/context';
export class AssistantApp extends Manager {
config: AssistantConfig;
pagesPath: string;
@@ -23,8 +24,8 @@ export class AssistantApp extends Manager {
constructor(config: AssistantConfig, mainApp?: App) {
config.checkMounted();
const appsPath = config?.configPath?.appsDir || path.join(process.cwd(), 'apps');
const pagesPath = config?.configPath?.pagesDir || path.join(process.cwd(), 'pages');
const appsPath = config?.configPath?.appsDir
const pagesPath = config?.configPath?.pagesDir;
const appsConfigPath = config.configPath?.appsConfigPath;
const configFimename = path.basename(appsConfigPath || '');
super({
@@ -80,6 +81,12 @@ export class AssistantApp extends Manager {
return pagesParse;
}
/**
* 初始化远程应用连接,如果配置了远程应用且启用,则尝试连接远程应用服务器,并设置自动重连机制.
* 应用暴露
* @info 需要登录权限
* @param opts
*/
async initRemoteApp(opts?: { token?: string, enabled?: boolean }) {
const config = this.config.getConfig();
const share = config?.share;
@@ -90,8 +97,17 @@ export class AssistantApp extends Manager {
this.remoteApp = null;
this.remoteIsConnected = false;
}
const token = config?.token || opts?.token || getEnvToken() as string;
const url = new URL(share.url || 'https://kevisual.cn/ws/proxy');
let token = opts?.token;
if (!token) {
token = await assistantQuery.queryLogin.getToken();
}
let shareUrl = share?.url;
if (!shareUrl) {
const _url = new URL(config?.registry || 'https://kevisual.cn/');
_url.pathname = '/ws/proxy';
shareUrl = _url.toString();
}
const url = new URL(shareUrl);
const id = config?.app?.id;
if (token && url && id) {
const remoteApp = new RemoteApp({
@@ -129,11 +145,22 @@ export class AssistantApp extends Manager {
this.remoteApp = remoteApp;
} else {
if (!token) {
logger.error('Token是远程应用连接必须的参数');
logger.error('[remote-app] cli当前未登录无法连接远程app');
} else if (!id) {
logger.error('[remote-app] app id不存在无法连接远程app');
}
}
}
}
/**
* 本地路由初始化,根据配置加载应用的模块。
* 加载局域网或者某一个链接的远程路由, 或者代码仓库的动态加载的light-code模块实时同步远程文件执行
* @info 不需要登录
* 配置项说明:
* - router.proxy: 数组,包含需要代理的路由信息,每项可以是以下两种类型:
* - lightcode: 轻代码模块配置
* @returns
*/
async initRouterApp() {
const config = this.config.getConfig();
const routerProxy = config?.router?.proxy || [];
@@ -209,6 +236,10 @@ export class AssistantApp extends Manager {
}
}
}
/**
* 动态加载文件插件模块的应用模块
* @info 不需要登录
*/
async initRoutes() {
const routes = this.config.getConfig().routes || [];
for (const route of routes) {
@@ -222,4 +253,53 @@ export class AssistantApp extends Manager {
}
}
}
/**
* 检查本地用户登录状态,如果未登录且存在 CNB_TOKEN则尝试使用 CNB_TOKEN 登录并更新用户信息
*/
async checkLocalUser() {
const config = this.config.getConfig();
const auth = config?.auth;
let checkCNB = false;
if (!auth?.username) {
checkCNB = true;
} else {
let temp = await assistantQuery.queryLogin.getToken()
if (temp) {
const isExpired = await assistantQuery.queryLogin.checkTokenValid()
console.log('Token 是否过期', isExpired);
}
logger.info('[assistant] 当前登录用户', auth.username, 'token有效性检查结果', !!temp);
}
const cnbToken = useKey('CNB_TOKEN');
if (!checkCNB && cnbToken) {
const res = await assistantQuery.query.post({
path: 'user',
key: 'cnb-login',
payload: {
data: {
cnbToken: cnbToken,
}
}
});
if (res.code === 200) {
logger.info('CNB登录成功用户信息已更新');
const resUser = await assistantQuery.queryLogin.beforeSetLoginUser(res.data)
if (resUser.code === 200) {
const userInfo = resUser.data;
auth.username = userInfo.username;
auth.share = 'protected'
const app = config?.app || {};
if (!app?.id) {
app.id = 'dev-cnb'
}
this.config.setConfig({
auth,
app
});
} else {
console.error('CNB登录失败无法获取用户信息', resUser);
}
}
}
}
}

View File

@@ -38,4 +38,4 @@ export class AssistantQuery {
getToken() {
return this.queryLogin.getToken();
}
}
}

View File

@@ -1,5 +1,4 @@
import { useKey } from '@kevisual/use-config';
import http from 'node:http';
import { IncomingMessage } from 'node:http';
export const error = (msg: string, code = 500) => {
return JSON.stringify({ code, message: msg });
};
@@ -16,7 +15,12 @@ const cookie = {
return cookies;
}
}
export const getToken = async (req: http.IncomingMessage) => {
/**
* 从请求中获取token优先级Authorization header > query parameter > cookie
* @param req
* @returns
*/
export const getToken = async (req: IncomingMessage) => {
let token = (req.headers?.['authorization'] as string) || (req.headers?.['Authorization'] as string) || '';
const url = new URL(req.url || '', 'http://localhost');
if (!token) {
@@ -31,9 +35,4 @@ export const getToken = async (req: http.IncomingMessage) => {
}
return { token };
};
export const getEnvToken = () => {
const envTokne = useKey('KEVISUAL_TOKEN') || '';
return envTokne;
}
};

View File

@@ -8,6 +8,7 @@ const codeDemoId = '0e700dc8-90dd-41b7-91dd-336ea51de3d2'
import { filter } from "@kevisual/js-filter";
import { getHash, getStringHash } from '../file-hash.ts';
import { AssistantConfig } from '@/lib.ts';
import { assistantQuery } from '@/app.ts';
const codeDemo = `// 这是一个示例代码文件
import {App} from '@kevisual/router';
@@ -48,11 +49,11 @@ export const initLightCode = async (opts: Opts) => {
console.log('初始化 light-code 路由');
const config = opts.config as AssistantInit;
const app = opts.router;
const token = config.getConfig()?.token || '';
const token = await assistantQuery.getToken();
const query = config.query;
const sync = opts.sync ?? 'remote';
if (!config || !app) {
console.error('initLightCode 缺少必要参数, config 或 app');
console.error('[light-code] initLightCode 缺少必要参数, config 或 app');
return;
}
const lightcodeDir = opts.rootPath;
@@ -126,7 +127,7 @@ export const initLightCode = async (opts: Opts) => {
}
diffList = findGlob({ cwd: lightcodeDir });
} else {
console.error('light-code 同步失败', queryRes.message);
console.error('[light-code] 同步失败', queryRes.message);
diffList = codeFiles;
}
} else if (sync === 'local') {
@@ -191,22 +192,22 @@ export const initLightCode = async (opts: Opts) => {
}
}
} else {
console.error('light-code 路由执行失败', runRes.error);
console.error('[light-code] 路由执行失败', runRes.error);
}
}
console.log(`light-code 路由注册成功`, `注册${diffList.length}个路由`);
console.log(`[light-code] 路由注册成功`, `注册${diffList.length}个路由`);
}
export const clearLightCodeRoutes = (opts: Pick<Opts, 'router'>) => {
const app = opts.router;
if (!app) {
console.error('clearLightCodeRoutes 缺少必要参数, app');
console.error('[light-code] clearLightCodeRoutes 缺少必要参数, app');
return;
}
const routes = app.getList();
for (const route of routes) {
if (route.metadata?.source === 'light-code') {
// console.log(`删除 light-code 路由: ${route.path} ${route.id}`);
// console.log(`[light-code] 删除 light-code 路由: ${route.path} ${route.id}`);
app.removeById(route.id);
}
}