鸿蒙开发5.0【基于自定义注解和代码生成实现路由框架】

news2024/12/28 3:50:30

场景描述

在应用开发中无论是出于工程组织效率还是开发体验的考虑,开发者都需要对项目进行模块间解耦,此时需要构建一套用于模块间组件跳转、数据通信的路由框架。

业界常见的实现方式是在编译期生成路由表。

1. 实现原理及流程

  • 在编译期通过扫描并解析ets文件中的自定义注解来生成路由表和组件注册类
  • Har中的rawfile文件在Hap编译时会打包在Hap中,通过这一机制来实现路由表的合并
  • 自定义组件通过wrapBuilder封装来实现动态获取
  • 通过NavDestination的Builder机制来获取wrapBuilder封装后的自定义组件

2. 使用ArkTS自定义装饰器来代替注解的定义

由于TS语言特性,当前只能使用自定义装饰器

使用@AppRouter装饰器来定义路由信息

// 定义空的装饰器
export function AppRouter(param:AppRouterParam) {
  return Object;
}

export interface AppRouterParam{
  uri:string;
}

自定义组件增加路由定义

@AppRouter({ uri: "app://login" })
@Component
export struct LoginView {
  build(){
    //...
  }
}

3. 实现动态路由模块

定义路由表(该文件为自动生成的路由表)

{
  "routerMap": [
    {
      "name": "app://login",   /* uri定义  */
      "pageModule": "loginModule",  /* 模块名  */
      "pageSourceFile": "src/main/ets/generated/RouterBuilder.ets",  /* Builder文件  */
      "registerFunction": "LoginViewRegister"  /* 组件注册函数  */
    }
  ]
}

应用启动时,在EntryAbility.onCreate中加载路由表

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  DynamicRouter.init({
    libPrefix: "@app", mapPath: "routerMap"
  }, this.context);
}
export class DynamicRouter {
  // 路由初始化配置
  static config: RouterConfig;
  // 路由表
  static routerMap: Map<string, RouterInfo> = new Map();
  // 管理需要动态导入的模块,key是模块名,value是WrappedBuilder对象,动态调用创建页面的接口
  static builderMap: Map<string, WrappedBuilder<Object[]>> = new Map();
  // 路由栈
  static navPathStack: NavPathStack = new NavPathStack();
  // 通过数组实现自定义栈的管理
  static routerStack: Array<RouterInfo> = new Array();
  static referrer: string[] = [];

  public static init(config: RouterConfig, context: Context) {
    DynamicRouter.config = config;
    DynamicRouter.routerStack.push(HOME_PAGE)
    RouterLoader.load(config.mapPath, DynamicRouter.routerMap, context)
  }
  //...
}

路由表存放在src/main/resources/rawfile目录中,通过ResourceManager进行读取

export namespace RouterLoader {

  export function load(dir: string, routerMap: Map<string, RouterInfo>, context: Context) {
    const rm: resourceManager.ResourceManager = context.resourceManager;
    try {
      rm.getRawFileList(dir)
        .then((value: Array<string>) => {
          let decoder: util.TextDecoder = util.TextDecoder.create('utf-8', {
            fatal: false, ignoreBOM: true
          })
          value.forEach(fileName => {
            let fileBytes: Uint8Array = rm.getRawFileContentSync(`${dir}/${fileName}`)
            let retStr = decoder.decodeWithStream(fileBytes)
            let routerMapModel: RouterMapModel = JSON.parse(retStr) as RouterMapModel
            loadRouterMap(routerMapModel, routerMap)
          })
        })
        .catch((error: BusinessError) => {
          //...
        });
    } catch (error) {
      //...
    }
  }
}

根据URI跳转页面时,通过动态import并执行路由表中定义的registerFunction方法来实现动态注册组件

Button("跳转")
  .onClick(()=>{
    DynamicRouter.pushUri("app://settings")
  })
export class DynamicRouter {
  //...
  public static pushUri(uri: string, param?: Object, onPop?: (data: PopInfo) => void): void {
    if (!DynamicRouter.routerMap.has(uri)) {
      return;
    }
    let routerInfo: RouterInfo = DynamicRouter.routerMap.get(uri)!;
    if (!DynamicRouter.builderMap.has(uri)) {
      // 动态加载模块
      import(`${DynamicRouter.config.libPrefix}/${routerInfo.pageModule}`)
        .then((module: ESObject) => {
          module[routerInfo.registerFunction!](routerInfo)   // 进行组件注册,实际执行了下文中的LoginViewRegister方法
          DynamicRouter.navPathStack.pushDestination({ name: uri, onPop: onPop, param: param });
          DynamicRouter.pushRouterStack(routerInfo);
        })
        .catch((error: BusinessError) => {
          console.error(`promise import module failed, error code: ${error.code}, message: ${error.message}.`);
        });
    } else {
      DynamicRouter.navPathStack.pushDestination({ name: uri, onPop: onPop, param: param });
      DynamicRouter.pushRouterStack(routerInfo);
    }
  }
}

组件注册实际执行的方法为LoginViewRegister(该文件为自动生成的模版代码)

// auto-generated RouterBuilder.ets
import { DynamicRouter, RouterInfo } from '@app/dynamicRouter/Index'
import { LoginView } from '../components/LoginView'

@Builder
function LoginViewBuilder() {
  LoginView()
}

export function LoginViewRegister(routerInfo: RouterInfo) {
  DynamicRouter.registerRouterPage(routerInfo, wrapBuilder(LoginViewBuilder))
}

通过wrapBuilder将自定义组件保存在组件表

export class DynamicRouter {
  //...
  // 通过URI注册builder
  public static registerRouterPage(routerInfo: RouterInfo, wrapBuilder: WrappedBuilder<Object[]>): void {
     const builderName: string = routerInfo.name;
     if (!DynamicRouter.builderMap.has(builderName)) {
       DynamicRouter.registerBuilder(builderName, wrapBuilder);
     }
   }

   private static registerBuilder(builderName: string, builder: WrappedBuilder<Object[]>): void {
     DynamicRouter.builderMap.set(builderName, builder);
   }

  // 通过URI获取builder
  public static getBuilder(builderName: string): WrappedBuilder<Object[]> {
    const builder = DynamicRouter.builderMap.get(builderName);
    return builder as WrappedBuilder<Object[]>;
  }
}

首页Navigation通过组件表获取自定义组件Builder

@Entry
@Component
struct Index {
  build() {
    Navigation(DynamicRouter.getNavPathStack()) {
      //...
    }
    .navDestination(this.PageMap)
    .hideTitleBar(true)
  }

  @Builder
  PageMap(name: string, param?: ESObject) {
    NavDestination() {
      DynamicRouter.getBuilder(name).builder(param);
    }
  }

}

4. 实现路由表生成插件

新建插件目录etsPlugin,建议创建在HarmonyOS工程目录之外

mkdir etsPlugin
cd etsPlugin

创建npm项目

npm init

安装依赖

npm i --save-dev @types/node @ohos/hvigor @ohos/hvigor-ohos-plugin
npm i typescript handlebars

初始化typescript配置

./node_modules/.bin/tsc --init

修改tsconfig.json

{
  "compilerOptions": {
    "target": "es2021",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    "module": "commonjs",                                /* Specify what module code is generated. */
    "strict": true,                                      /* Enable all strict type-checking options. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
    "skipLibCheck": true,                                 /* Skip type checking all .d.ts files. */
    "sourceMap": true,
    "outDir": "./lib",
  },
  "include": [".eslintrc.js", "src/**/*"],
  "exclude": ["node_modules", "lib/**/*"],
}

创建插件文件src/index.ts

export function etsGeneratorPlugin(pluginConfig: PluginConfig): HvigorPlugin {
  return {
    pluginId: PLUGIN_ID,
    apply(node: HvigorNode) {
      pluginConfig.moduleName = node.getNodeName();
      pluginConfig.modulePath = node.getNodePath();
      pluginExec(pluginConfig);
    },
  };
}

修改package.json

{
  //...
  "main": "lib/index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "dev": "tsc && node lib/index.js",
    "build": "tsc"
  },
  //...
}

插件实现流程

1

  • 通过扫描自定义组件的ets文件,解析语法树,拿到注解里定义的路由信息
  • 生成路由表、组件注册类,同时更新Index.ets

定义插件配置

const config: PluginConfig = {
  builderFileName: "RouterBuilder.ets",  // 生成的组件注册类文件名
  builderDir: "src/main/ets/generated",  // 代码生成路径
  routerMapDir: "src/main/resources/rawfile/routerMap",  // 路由表生成路径
  scanDir: "src/main/ets/components",  // 自定义组件扫描路径
  annotation: "AppRouter",  // 路由注解
  viewKeyword: "struct",  // 自定义组件关键字
  builderTpl: "viewBuilder.tpl",  // 组件注册类模版文件
};

插件核心代码:

function pluginExec(config: PluginConfig) {
  // 读取指定自定义组件目录下的文件
  const scanPath = `${config.modulePath}/${config.scanDir}`;
  const files: string[] = readdirSync(scanPath);
  files.forEach((fileName) => {
    // 对每个文件进行解析
    const sourcePath = `${scanPath}/${fileName}`;
    const importPath = path
      .relative(`${config.modulePath}/${config.builderDir}`, sourcePath)
      .replaceAll("\", "/")
      .replaceAll(".ets", "");

    // 执行语法树解析器
    const analyzer = new EtsAnalyzer(config, sourcePath);
    analyzer.start();

    // 保存解析结果
    console.log(JSON.stringify(analyzer.analyzeResult));
    console.log(importPath);
    templateModel.viewList.push({
      viewName: analyzer.analyzeResult.viewName,
      importPath: importPath,
    });
    routerMap.routerMap.push({
      name: analyzer.analyzeResult.uri,
      pageModule: config.moduleName,
      pageSourceFile: `${config.builderDir}/${config.builderFileName}`,
      registerFunction: `${analyzer.analyzeResult.viewName}Register`,
    });
  });

  // 生成组件注册类
  generateBuilder(templateModel, config);
  // 生成路由表
  generateRouterMap(routerMap, config);
  // 更新Index文件
  generateIndex(config);
}

语法树解析流程

  • 遍历语法树节点,找到自定义注解@AppRouter
  • 读取URI的值
  • 通过识别struct关键字来读取自定义组件类名
  • 其他节点可以忽略

核心代码:

export class EtsAnalyzer {
  sourcePath: string;
  pluginConfig: PluginConfig;
  analyzeResult: AnalyzeResult = new AnalyzeResult();
  keywordPos: number = 0;

  constructor(pluginConfig: PluginConfig, sourcePath: string) {
    this.pluginConfig = pluginConfig;
    this.sourcePath = sourcePath;
  }

  start() {
    const sourceCode = readFileSync(this.sourcePath, "utf-8");
    // 创建ts语法解析器
    const sourceFile = ts.createSourceFile(
      this.sourcePath,
      sourceCode,
      ts.ScriptTarget.ES2021,
      false
    );
    // 遍历语法节点
    ts.forEachChild(sourceFile, (node: ts.Node) => {
      this.resolveNode(node);
    });
  }

  // 根据节点类型进行解析
  resolveNode(node: ts.Node): NodeInfo | undefined {
    switch (node.kind) {
      case ts.SyntaxKind.ImportDeclaration: {
        this.resolveImportDeclaration(node);
        break;
      }
      case ts.SyntaxKind.MissingDeclaration: {
        this.resolveMissDeclaration(node);
        break;
      }
      case ts.SyntaxKind.Decorator: {
        this.resolveDecorator(node);
        break;
      }
      case ts.SyntaxKind.CallExpression: {
        this.resolveCallExpression(node);
        break;
      }
      case ts.SyntaxKind.ExpressionStatement: {
        this.resolveExpression(node);
        break;
      }
      case ts.SyntaxKind.Identifier: {
        return this.resolveIdentifier(node);
      }
      case ts.SyntaxKind.StringLiteral: {
        return this.resolveStringLiteral(node);
      }
      case ts.SyntaxKind.PropertyAssignment: {
        return this.resolvePropertyAssignment(node);
      }
    }
  }

  resolveImportDeclaration(node: ts.Node) {
    let ImportDeclaration = node as ts.ImportDeclaration;
  }

  resolveMissDeclaration(node: ts.Node) {
    node.forEachChild((cnode) => {
      this.resolveNode(cnode);
    });
  }

  resolveDecorator(node: ts.Node) {
    let decorator = node as ts.Decorator;
    this.resolveNode(decorator.expression);
  }

  resolveIdentifier(node: ts.Node): NodeInfo {
    let identifier = node as ts.Identifier;
    let info = new NodeInfo();
    info.value = identifier.escapedText.toString();
    return info;
  }

  resolveCallExpression(node: ts.Node) {
    let args = node as ts.CallExpression;
    let identifier = this.resolveNode(args.expression);
    this.parseRouterConfig(args.arguments, identifier);
  }

  resolveExpression(node: ts.Node) {
    let args = node as ts.ExpressionStatement;
    let identifier = this.resolveNode(args.expression);
    if (identifier?.value === this.pluginConfig.viewKeyword) {
      this.keywordPos = args.end;
    }
    if (this.keywordPos === args.pos) {
      this.analyzeResult.viewName = identifier?.value;
    }
  }

  resolveStringLiteral(node: ts.Node): NodeInfo {
    let stringLiteral = node as ts.StringLiteral;
    let info = new NodeInfo();
    info.value = stringLiteral.text;
    return info;
  }

  resolvePropertyAssignment(node: ts.Node): NodeInfo {
    let propertyAssignment = node as ts.PropertyAssignment;
    let propertyName = this.resolveNode(propertyAssignment.name)?.value;
    let propertyValue = this.resolveNode(propertyAssignment.initializer)?.value;
    let info = new NodeInfo();
    info.value = { key: propertyName, value: propertyValue };
    return info;
  }

}

使用模版引擎生成组件注册类

使用Handlebars生成组件注册类

const template = Handlebars.compile(tpl);
const output = template({ viewList: templateModel.viewList });复制

模版文件viewBuilder.tpl示例:

// auto-generated RouterBuilder.ets
import { DynamicRouter, RouterInfo } from '@app/dynamicRouter/Index'
{{#each viewList}}
import { {{viewName}} } from '{{importPath}}'
{{/each}}

{{#each viewList}}
@Builder
function {{viewName}}Builder() {
  {{viewName}}()
}

export function {{viewName}}Register(routerInfo: RouterInfo) {
  DynamicRouter.registerRouterPage(routerInfo, wrapBuilder({{viewName}}Builder))
}

{{/each}}

生成的RouterBuilder.ets代码示例:

// auto-generated RouterBuilder.ets
import { DynamicRouter, RouterInfo } from '@app/dynamicRouter/Index'
import { LoginView } from '../components/LoginView'

@Builder
function LoginViewBuilder() {
  LoginView()
}

export function LoginViewRegister(routerInfo: RouterInfo) {
  DynamicRouter.registerRouterPage(routerInfo, wrapBuilder(LoginViewBuilder))
}

将路由表和组件注册类写入文件

  • 路由表保存在rawfile目录
  • 组件注册类保存在ets代码目录
  • 更新模块导出文件Index.ets

核心代码:

function generateBuilder(templateModel: TemplateModel, config: PluginConfig) {
  console.log(JSON.stringify(templateModel));
  const builderPath = path.resolve(__dirname, `../${config.builderTpl}`);
  const tpl = readFileSync(builderPath, { encoding: "utf8" });
  const template = Handlebars.compile(tpl);
  const output = template({ viewList: templateModel.viewList });
  console.log(output);
  const routerBuilderDir = `${config.modulePath}/${config.builderDir}`;
  if (!existsSync(routerBuilderDir)) {
    mkdirSync(routerBuilderDir, { recursive: true });
  }
  writeFileSync(`${routerBuilderDir}/${config.builderFileName}`, output, {
    encoding: "utf8",
  });
}

function generateRouterMap(routerMap: RouterMap, config: PluginConfig) {
  const jsonOutput = JSON.stringify(routerMap, null, 2);
  console.log(jsonOutput);
  const routerMapDir = `${config.modulePath}/${config.routerMapDir}`;
  if (!existsSync(routerMapDir)) {
    mkdirSync(routerMapDir, { recursive: true });
  }
  writeFileSync(`${routerMapDir}/${config.moduleName}.json`, jsonOutput, {
    encoding: "utf8",
  });
}

function generateIndex(config: PluginConfig) {
  const indexPath = `${config.modulePath}/Index.ets`;
  const indexContent = readFileSync(indexPath, { encoding: "utf8" });
  const indexArr = indexContent
    .split("\n")
    .filter((value) => !value.includes(config.builderDir!));
  indexArr.push(
    `export * from './${config.builderDir}/${config.builderFileName?.replace(
      ".ets",
      ""
    )}'`
  );
  writeFileSync(indexPath, indexArr.join("\n"), {
    encoding: "utf8",
  });
}

5. 在应用中使用

修改项目的hvigor/hvigor-config.json文件,导入路由表插件

{
  "hvigorVersion": "4.2.0",
  "dependencies": {
    "@ohos/hvigor-ohos-plugin": "4.2.0",
    "@app/ets-generator" : "file:../../etsPlugin"   // 插件目录的本地相对路径,或者使用npm仓版本号
  },
  //...
}

修改loginModule模块的hvigorfile.ts文件(loginModule/hvigorfile.ts),加载插件

import { harTasks } from '@ohos/hvigor-ohos-plugin';
import {PluginConfig,etsGeneratorPlugin} from '@app/ets-generator'

const config: PluginConfig = {
    builderFileName: "RouterBuilder.ets",
    builderDir: "src/main/ets/generated",
    routerMapDir: "src/main/resources/rawfile/routerMap",
    scanDir: "src/main/ets/components",
    annotation: "AppRouter",
    viewKeyword: "struct",
    builderTpl: "viewBuilder.tpl",
}

export default {
    system: harTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
    plugins:[etsGeneratorPlugin(config)]         /* Custom plugin to extend the functionality of Hvigor. */
}

在loginModule模块的oh-package.json5中引入动态路由模块依赖

{
  "name": "loginmodule",
  "version": "1.0.0",
  "description": "Please describe the basic information.",
  "main": "Index.ets",
  "author": "",
  "license": "Apache-2.0",
  "dependencies": {
    "@app/dynamicRouter": "file:../routerModule"
  }
}

在loginModule模块的自定义组件中使用@AppRouter定义路由信息

@AppRouter({ uri: "app://login" })
@Component
export struct LoginView {
  build(){
    //...
  }
}

在entry中的oh-package.json5中引入依赖

{
  "name": "entry",
  "version": "1.0.0",
  "description": "Please describe the basic information.",
  "main": "",
  "author": "",
  "license": "",
  "dependencies": {
    "@app/loginModule": "file:../loginModule",
    "@app/commonModule": "file:../commonModule",
    "@app/dynamicRouter": "file:../routerModule"
  }
}

在entry中的build-profile.json5中配置动态import

{
  "apiType": "stageMode",
  "buildOption": {
    "arkOptions": {
      "runtimeOnly": {
        "packages": [
          "@app/loginModule",  // 仅用于使用变量动态import其他模块名场景,静态import或常量动态import无需配置。
          "@app/commonModule"  // 仅用于使用变量动态import其他模块名场景,静态import或常量动态import无需配置。
        ]
      }
    }
  },
  //...
}

在entry中的EntryAbility.onCreate中初始化路由组件

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  DynamicRouter.init({
    libPrefix: "@app", mapPath: "routerMap"
  }, this.context);
}

组件内使用pushUri进行跳转

Button("立即登录", { buttonStyle: ButtonStyleMode.TEXTUAL })
  .onClick(() => {
    DynamicRouter.pushUri("app://login")
  })
  .id("button")

在entry模块执行Run/Debug,即可在编译时自动生成路由表配置并打包运行。
以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
1

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

2

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!
3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2055840.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

吴恩达机器学习-C2W3-应用机器学习的建议

在本实验中&#xff0c;您将探索评估和改进机器学习模型的技术。 1-调包 首先&#xff0c;让我们运行下面的单元格来导入在此任务中需要的所有包。 numpymatplotlibscikitlearntensorflow import numpy as np %matplotlib widget import matplotlib.pyplot as plt from skle…

模拟实现queue适配器【队列】【C++】

P. S.&#xff1a;以下代码均在VS2022环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;LiUEEEEE                        …

私域经济的挖掘:多元渠道下的流量引爆方法

近年来&#xff0c;私域经济越来越受到品牌企业的青睐。基于个性化需求的私域市场&#xff0c;既有精准定位的成本优势&#xff0c;又有巨大的潜力空间。然而&#xff0c;想要把私域做成&#xff0c;其实也是充满了挑战&#xff0c;其中&#xff0c;怎样有效吸引流量&#xff0…

01、Redis入门:数据类型、基本操作、SpringDataRedis

Redis快速入门 Redis的常见命令和客户端使用 1.初识Redis Redis是一种键值型的NoSql数据库&#xff0c;这里有两个关键字&#xff1a; 键值型 NoSql 其中键值型&#xff0c;是指Redis中存储的数据都是以key、value对的形式存储&#xff0c;而value的形式多种多样&#xf…

错误信息“缺少msvcr120.dll”或“找不到msvcr120.dll”应该如何修复?几种方法快速修复

由于这个msvcr120.dll文件与应用程序的运行密切相关&#xff0c;任何与之相关的问题都可能导致应用程序无法正常运行。错误信息如“缺少msvcr120.dll”或“找不到msvcr120.dll”&#xff0c;通常出现在软件安装不正确或系统更新后。接下俩就教大家几种方法快速修复msvcr120.dll…

7 周岁自闭症儿童可以去普校上学吗?

对于许多自闭症儿童的家长来说&#xff0c;孩子能否去普通学校上学是一个至关重要的问题。而星贝育园给出了充满希望的答案。 星贝育园向家长郑重承诺&#xff0c;4 周岁之前开始干预可以 100%摘帽&#xff0c;即消除自闭症症状。在这里&#xff0c;为自闭症儿童提供个性化教学…

Spring源码解析(34)之Spring事务回滚流程

一、前言 在上一个篇章我们主要介绍了Spring事务的运行流程&#xff0c;也带着一步步debug看了整个事务的运行流程&#xff0c;但是还是欠缺了Spring事务的回滚的流程。 在上篇也主要介绍了Spring事务的传播特性&#xff0c;这里还是要看一下Spring事务的传播特性&#xff0c;因…

思博伦测试每秒最大新建、并发、吞吐

详细方法查看本文资源链接 一、最大新建测试说明 1、新建测试的主要目标是测试被测设备&#xff08;DUT&#xff09;的处理器能力。在单位时间内能够建立的连接数越多&#xff0c;说明被测设备的处理器的能力越强。 2、由于在测试过程中&#xff0c;我们只关心成功的建立TCP…

流动会场:定义新一代灵活空间的全新选择—轻空间

在当今快节奏的世界里&#xff0c;活动和会议的需求正变得越来越多样化和复杂化。无论是公司年会、大型宴会、还是各类演出和会议&#xff0c;场地的选择不仅需要满足功能需求&#xff0c;更要灵活、易于部署。正是在这样的背景下&#xff0c;“流动会场”这一创新概念应运而生…

反向沙箱是什么?如何使用反向沙箱保障上网安全

反向沙箱是什么&#xff1f; 反向沙箱是深信达的一种沙箱技术&#xff0c;又称SPN&#xff08;Sandbox Proxy Network&#xff09;沙箱。主要用于解决企业在安全上网过程中的风险问题。它通过在企业内部部署一个隔离的沙盒环境&#xff0c;实现安全的互联网访问&#xff0c;从而…

注意力机制(课程笔记)

一&#xff1a; 针对的问题 解决在循环卷积网络RNN模型中存在的信息瓶颈问题。 信息瓶颈&#xff1a; 举的是机器翻译的例子。在RNN中&#xff0c;Decoder的第一个输出取决于Encoder中的上一个输出&#xff0c;然后Decoder的其余输出都取决于上一个Decoder输出&#xff08;也就…

使用Nexus3为containerd和docker配置镜像代理

1.Nexus3介绍&#xff1a; Nexus3&#xff08;Nexus Repository Manager3&#xff09;是一个用于存储、组织和管理软件组件&#xff08;如 JAR文件、npm包、Docker镜像等&#xff09;的仓库管理系统。它由Sonatype开发并维护。Nexus Repository Manger支持许多流行的包管理工具…

免费仿微信聊天工具盒子IM

盒子IM是一个仿微信实现的网页版聊天软件&#xff0c;不依赖任何第三方收费组件。后端采用springbootnetty实现&#xff0c;web端使用vue&#xff0c;移动端使用uniapp&#xff0c;支持私聊、群聊、离线消息、发送图片、文件、语音、emoji表情、视频聊天等功能。包含pc端和移动…

Redis 技术详解

一、Redis 基础 &#xff08;一&#xff09;为什么使用 Redis 速度快&#xff0c;因为数据存在内存中&#xff0c;类似于 HashMap&#xff0c;查找和操作的时间复杂度都是 O(1)。支持丰富数据类型&#xff0c;支持 string、list、set、Zset、hash 等。支持事务&#xff0c;操…

Go —— 反射

反射 反射是什么&#xff1f; 反射是运行时检查自身结构的机制反射是困惑的源泉。 反射特性与 interface 紧密相关。 接口 1. 类型 Go是静态类型语言&#xff0c;比如int、float32、[]byte&#xff0c;等等。每个变量都有一个静态类型&#xff0c;而且在编译时就确定了。…

自主智能体的未来:LangChain Agents如何实现复杂任务自动化

一、AI Agents 基础&#xff1a;ReAct 范式 在AI领域&#xff0c;智能体&#xff08;Agents&#xff09;指的是能够自主感知环境并采取行动以实现特定目标的系统。ReAct&#xff08;Reasoning and Acting&#xff09;范式是理解智能体的基础&#xff0c;它强调智能体在执行任务…

【YashanDB知识库】共享集群YAC换IP

【标题】共享集群YAC换IP 【需求分类】安装部署&#xff0c;配置变更 【关键字】安装部署&#xff0c;更换IP&#xff0c;运维&#xff0c;配置变更&#xff0c;高可用&#xff0c;YAC 【需求描述】客户需要将已经部署的YAC集群更换IP&#xff0c;从测试网段切换生产网段 【…

2024年AI艺术生成器精选榜单,抢先体验!

选择合适的AI艺术生成器对于设计项目的成功至关重要。无论是从设计线框到复杂的交互原型&#xff0c;合适的工具都能帮助顺利实现目标。本文将分享2024年最受欢迎的AI艺术生成器&#xff0c;让我们一起来看看&#xff01; 即时设计 在2024年好用的AI艺术生成器中&#xff0c;…

基于STM32开发的智能家居照明系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化光线检测与自动调节手动控制与状态指示Wi-Fi通信与远程控制应用场景 家庭智能照明办公室自动化照明常见问题及解决方案 常见问题解决方案结论 1. 引言 智能家居照明系统通过集成光…

宠物空气净化器是智商税吗吗?哪款最好用?

在当今社会&#xff0c;随着生活节奏不断加快&#xff0c;许多人会感到孤独。因此养猫已成为许多家庭的生活方式之一。他们期待着家里有欢声笑语的出现&#xff0c;希望家里一推开门都是有猫咪等着自己&#xff0c;在自己无人诉说心事的时候&#xff0c;猫咪能给自己一份陪伴。…