鸿蒙5.0实战案例:基于自定义注解和代码生成实现路由框架
往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录)
✏️ 鸿蒙(HarmonyOS)北向开发知识点记录~
✏️ 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
✏️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
✏️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
✏️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
✏️ 记录一场鸿蒙开发岗位面试经历~
✏️ 持续更新中……
场景描述
在应用开发中无论是出于工程组织效率还是开发体验的考虑,开发者都需要对项目进行模块间解耦,此时需要构建一套用于模块间组件跳转、数据通信的路由框架。
业界常见的实现方式是在编译期生成路由表。
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 RouterMapModelloadRouterMap(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注册builderpublic 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获取builderpublic 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)}@BuilderPageMap(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"},//...
}
插件实现流程
- 通过扫描自定义组件的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,即可在编译时自动生成路由表配置并打包运行。
相关文章:
鸿蒙5.0实战案例:基于自定义注解和代码生成实现路由框架
往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录) ✏️ 鸿蒙(HarmonyOS)北向开发知识点记录~ ✏️ 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~ ✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景&#…...
项目设置内网 IP 访问实现方案
在我们平常的开发工作中,项目开发、测试完成后进行部署上线。比如电商网站、新闻网站、社交网站等,通常对访问不会进行限制。但是像企业内部网站、内部管理系统等,这种系统一般都需要限制访问,比如内网才能访问等。那么一个网站应…...
单片机 Bootloade与二进制文件的生成
单片机的 Bootloader 是一种特殊的程序,负责在单片机上电后初始化硬件、更新用户应用程序(固件),并将控制权移交给用户程序。以下是其运行机制和关键流程的详细说明: 1、单片机 Bootloader 的核心作用 固件更新&…...
open webui 部署 以及解决,首屏加载缓慢,nginx反向代理访问404,WebSocket后端服务器链接失败等问题
项目地址:GitHub - open-webui/open-webui: User-friendly AI Interface (Supports Ollama, OpenAI API, ...) 选择了docker部署 如果 Ollama 在您的计算机上,请使用以下命令 docker run -d -p 3000:8080 --add-hosthost.docker.internal:host-gatewa…...
深度学习每周学习总结Y1(Yolov5 调用官方权重进行检测 )
🍨 本文为🔗365天深度学习训练营 中的学习记录博客Y1中的内容 🍖 原作者:K同学啊 | 接辅导、项目定制 ** 注意该训练营出现故意不退押金,恶意揣测偷懒用假的结果冒充真实打卡记录,在提出能够拿到视频录像…...
HTML项目一键打包工具:HTML2EXE 最新版
HTML2EXE 工具可以一键打包生成EXE可执行文件。可以打包任意HTML项目或者是一个网址为单个EXE文件,直接打开即可运行。支持KRPano全景VR项目、WebGL游戏项目、视频播放、,课件打包、网址打包等。 一、功能特点 类别序号功能标题1支持程序图标自定义(支持…...
网络工程师 (43)IP数据报
前言 IP数据报是互联网传输控制协议(Internet Protocol,IP)的数据报格式,由首部和数据两部分组成。 一、首部 IP数据报的首部是控制部分,包含了数据报传输和处理所需的各种信息。首部可以分为固定部分和可变部分。 固定…...
springboot-自定义注解
1.注解的概念 注解是一种能被添加到java代码中的【元数据,类、方法、变量、参数和包】都可以用注解来修饰。用来定义一个类、属性或一些方法,以便程序能被捕译处理。 相当于一个说明文件,告诉应用程序某个被注解的类或属性是什么,…...
Pytorch实现之特征损失与残差结构稳定GAN训练,并训练自己的数据集
简介 简介:生成器和鉴别器分别采用了4个新颖设计的残差结构实现,同时在损失中结合了鉴别器层的特征损失来提高模型性能。 论文题目:Image Generation by Residual Block Based Generative Adversarial Networks(基于残留块的生成对抗网络产生图像) 会议:2022 IEEE Int…...
微信小程序模仿快播标签云滚动特效
说到快播,故事肯定就不少。用过的人都知道快播首页有个标签云的特效效果,就是渐隐渐显外加上下滚动,其实还挺好看的。至于其他故事嘛,因为没有酒,所以,还是来说说代码吧~ 一开始不是做这个特效需求…...
XUnity.AutoTranslator-deepseek——调用腾讯的DeepSeek V3 API,实现Unity游戏中日文文本的自动翻译
XUnity.AutoTranslator-deepseek 本项目通过调用腾讯的DeepSeek V3 API,实现Unity游戏中日文文本的自动翻译。 准备工作 1. 获取API密钥 访问腾讯云API控制台申请DeepSeek的API密钥(限时免费)。也可以使用其他平台提供的DeepSeek API。 …...
对比机器学习揭示了跨物种共享与特异性的脑功能结构|文献速递-医学影像人工智能进展
Title 题目 Contrastive machine learning reveals species -shared and -specific brainfunctional architecture 对比机器学习揭示了跨物种共享与特异性的脑功能结构 01 文献速递介绍 猕猴作为人类的动物模型,广泛用于研究大脑和行为的关键方面(G…...
Vue 和 React 响应式的区别
React 和 Vue 在响应式机制上的核心区别主要体现在数据变化侦测方式、更新触发逻辑和设计理念上,具体如下: 一、数据变化侦测方式 Vue 的响应式 原理:通过 Proxy(Vue3)或 Object.defineProperty(Vue2&#…...
MySQL主从架构
MySQL主从架构 MySQL REPLICATION 在实际生产环境中,如果对数据库的读和写都在一个数据库服务器中操作。无论是在安全性、高可用性,还是高并发等各个方面都是完全不能满足实际需求的,因此,一般来说都是通过主从复制(…...
基于ros2与gazebo的导航仿真案例
文章目录 前言操作1、创建docker容器2、安装ROS23、Gazebo安装4、Nav2安装5、测试 前言 导航的入门小案例 参考: Ubuntu24.04 ROS2 Jazzy Gazebo Harmonic安装教程Docs / Gazebo Harmonic 注意选择版本 ROS 2 documentation 操作 1、创建docker容器 sudo docke…...
《Python实战进阶》专栏 No.3:Django 项目结构解析与入门DEMO
《Python实战进阶》专栏 第3集:Django 项目结构解析与入门DEMO 在本集中,我们将深入探讨 Django 的项目结构,并实际配置并运行一个入门DEMO博客网站,帮助你在 Web 开发中更高效地使用 Django。Django 是一个功能强大的 Python Web…...
基于WebGIS技术的校园地图导航系统架构与核心功能设计
本文专为IT技术人员、地理信息系统(GIS)开发者、智慧校园解决方案架构师及相关领域的专业人士撰写。本文提出了一套基于WebGIS技术的校园地图导航系统构建与优化方案,旨在为用户提供高效、智能、个性化的导航体验。如需获取校园地图导航系统技…...
开源且免费的CMS系统有哪几个可以放心用?
既开源又免费的两全其美的CMS不多见,不过总会存在一些个例,给用户们带来更具有建设性的选择,以下是一些开源免费且值得信赖的CMS系统,可以根据你的需求选择合适的平台: 1、WordPress ▷ 特点:全球最流行的…...
逻辑架构与软件架构在PREEvision中的设计关系
1 Introduction 在如今汽车电子系统的开发过程中,系统架构设计是至关重要的环节。无论是汽车控制系统、信息娱乐系统,还是电动驱动系统,架构设计都决定了整个系统的功能、性能以及后期的可维护性和可扩展性。 在往期文章中,我们…...
DeepSeek vs ChatGPT:AI 领域的华山论剑,谁主沉浮?
一、引言 在当今科技飞速发展的时代,人工智能(AI)已然成为推动各领域变革的核心力量。而在人工智能的众多分支中,自然语言处理(NLP)因其与人类日常交流和信息处理的紧密联系,成为了最受瞩目的领…...
现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能
现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能,每个人都可以通过手机实时拍照上传到大屏幕上,同时还可以发布留言内容,屏幕上会同步滚动播放展示所有人的照片和留言。相比校传统的照片直播功能更加灵活方便,而…...
AF3 _process_single_hit 函数解读
AlphaFold3 中templates模块的_process_single_hit函数处理单个 HHsearch 比对的模板 TemplateHit,并从相应的 mmCIF 文件中提取模板特征,返回包含模板位置信息、比对质量等特征的 SingleHitResult 对象。它是 AlphaFold3 在模板模块中生成模板特征结构输入的重要步骤。 源代…...
go 模块管理
go version 查看版本 go version go1.21.12 windows/amd64 需要保证:go的版本升级为1.11以上,go mod依赖的最底版本 go env 查看go的环境变量 go env 开启go mod # 标识开启go的模块管理 set GO111MODULE=on GO111MODULE有三个值:off, on和auto(默认值)。 GO111M…...
23种设计模式 - 命令模式
模式定义 命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为独立对象,使请求的发送者与接收者解耦。通过将操作抽象为命令对象,支持命令的存储、传递、撤销和重做,增强系统的灵活性和可扩展性…...
php-fpm
摘要 php-fpm(fastcgi process manager)是PHP 的FastCGI管理器,管理PHP的FastCGI进程,提升PHP应用的性能和稳定性 php-fpm是一个高性能的php FastCGI管理器,提供了更好的php进程管理方式,可以有效的控制内存和进程,支…...
Visual Studio 2022配置网址参考
代码格式化和清理冗余代码选项的配置: 代码样式选项和代码清理 - Visual Studio (Windows) | Microsoft Learn 调试时传递参数: 调试时传递命令行参数(C) - Visual Studio (Windows) | Microsoft Learn...
【含文档+PPT+源码】基于Django的新闻推荐系统的设计与实现
项目介绍 本课程演示的是一款基于Django的新闻推荐系统的设计与实现,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Python学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3.…...
2025年02月21日Github流行趋势
项目名称:source-sdk-2013 项目地址url:https://github.com/ValveSoftware/source-sdk-2013项目语言:C历史star数:7343今日star数:929项目维护者:JoeLudwig, jorgenpt, narendraumate, sortie, alanedwarde…...
后端开发:开启技术世界的新大门
在互联网的广阔天地中,后端开发宛如一座大厦的基石,虽不直接与用户 “面对面” 交流,却默默地支撑着整个互联网产品的稳定运行。它是服务器端编程的核心领域,负责处理数据、执行业务逻辑以及与数据库和其他后端服务进行交互。在当…...
Apache Doris 实现毫秒级查询响应
1. 引言 1.1 数据分析的重要性 随着大数据时代的到来,企业对实时数据分析的需求日益增长。快速、准确地获取数据洞察成为企业在竞争中脱颖而出的关键。传统的数据库系统在处理大规模数据时往往面临性能瓶颈,难以满足实时分析的需求。例如,一个电商公司需要实时监控销售数据…...
【Python项目】基于Python的语音数据及标注核对审核系统
【Python项目】基于Python的语音数据及标注核对审核系统 技术简介: 采用Python技术、MySQL数据库、Django框架等实现。 系统简介: 语音数据及标注核对审核系统是一个基于B/S架构的语音数据处理平台,旨在通过自动化的方式对语音数据进行标…...
路由基本配置
学习目标 • 根据拓扑图进行网络布线。 • 清除启动配置并将路由器重新加载为默认状态。 • 在路由器上执行基本配置任务。 • 配置并激活以太网接口。 • 测试并检验配置。 • 思考网络实施方案并整理成文档。 任务 1:网络布线 使用适当的电缆类型连接网络设备。…...
从WebRTC到EasyRTC:嵌入式适配的视频通话SDK实现低延迟、高稳定性音视频通信
WebRTC最初是为浏览器之间的实时通信设计的,其资源需求和复杂性可能对嵌入式设备的性能提出较高要求,因此在嵌入式系统中应用时面临一些挑战: 1)资源消耗较高 CPU和内存占用:WebRTC是一个功能强大的实时通信框架&…...
【Blender】二、建模篇--05,阵列修改器与晶格形变
阵列修改器是bender里面一个比较常用的修改器,所以我们单独开口来讲,我们会先从几片树叶出发,然后我们用阵列修改器把这几片树叶变成这样的造型和这样的造型。这两个造型分别就代表着阵列修改器最常用的两种偏移方法,我们现在就开始我们先来做几个树叶。 1.树叶建模 首先…...
Python爬虫实战:获取12306特定日期、城市车票信息,并做数据分析以供出行参考
注意:以下内容仅供技术研究,请遵守目标网站的robots.txt规定,控制请求频率避免对目标服务器造成过大压力! 1. 核心思路 需求:获取明天(2025 年 2 月 21 日)从北京到上海的车次、票价、出发时间、硬卧二等卧信息,并保存到 CSV 文件,然后分析出价格最低的 10 趟车次。目…...
C++ 设计模式-策略模式
支付策略 #include <iostream> #include <memory> #include <unordered_map> #include <vector> #include <ctime>// 基础策略接口 class PaymentStrategy { public:virtual ~PaymentStrategy() default;virtual std::string name() const 0;…...
数据结构:哈希表(unordered_map)
unordered_map 是 C 标准库中的一种哈希表实现,它提供了基于键值对(key-value)的存储,提供了常数时间复杂度的查找、插入和删除键值对的操作。 初始化代码示例: #include <unordered_map> using namespace std…...
鸿蒙-自定义布局-实现一个可限制行数的-Flex
文章目录 前提onMeasureSizeselfLayoutInfoconstraintchildren onPlaceChildren 实现思路属性准备测量组件布局小结 刷新 千呼万唤始出来的自定义布局功能终于可以用了,这就给了我们更多自由发挥创造的空间,不再局限于使用已有组件做组合。当然ÿ…...
安装可视化jar包部署平台JarManage
一、下载 下载地址:JarManage 发行版 - Gitee.com 🚒 下载 最新发行版 下载zip的里面linux和windows版本都有 二、运行 上传到服务器,解压进入目录 🚚 执行java -jar jarmanage-depoly.jar 命令运行 java -jar jarmanage-dep…...
1、Window Android 13模拟器 将编译的映像文件导入Android Studio
1、环境准备 编译环境:Ubuntu-18.04.5编译版本:android13-release下载地址:清华大学开源软件镜像站AOSP # 下载repo # 同步代码:repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android13-r…...
力扣27. 移除元素(快慢指针)
Problem: 27. 移除元素 文章目录 题目描述思路Code 题目描述 思路 定义快慢指针均指向数组起始位置,当fast指针指向的元素不等于val时将fast指针指向的元素赋值给slow并让slow指针向前移动,fast指针一直向前移动 时间复杂度: O ( n ) O(n) O(n); 空间复杂…...
Unity学习part4
1、ui界面的基础使用 ui可以在2d和矩形工具界面下操作,更方便,画布与游戏窗口的比例一般默认相同 如图所示,图片在画布上显示的位置和在游戏窗口上显示的位置是相同的 渲染模式:屏幕空间--覆盖,指画布覆盖在游戏物体渲…...
前端面试之Flex布局:核心机制与高频考点全解析
目录 引言:弹性布局的降维打击 一、Flex布局的本质认知 1. 两大核心维度 2. 容器与项目的权力边界 二、容器属性深度剖析 1. 主轴控制三剑客 2. 交叉轴对齐黑科技 三、项目属性关键要点 1. flex复合属性解密 2. 项目排序魔法 四、六大高频面试场景 1. 经…...
关系数据理论
一、函数依赖 若t1(X)t2(X),必有t1(Y)t2(Y),那么我们称属性组X函数确定属性组Y,或者说Y函数依赖于X。记为X->Y,其中X叫决定因素,Y叫依赖因素。 平凡函数依赖与非平凡函数依赖: 二、1-BCNF 评价关系模式“好坏”的理论标准就…...
低代码与开发框架的一些整合[2]
1.分析的项目资源说明 经过近期的的不断分析与运行对比,最终把注意力集中在了以下几个框架: 01.dibootdiboot.diboot: 写的更少, 性能更好 -> 为开发人员打造的低代码开发平台。Mybatis-plus关联查询,关联无SQL,性能高10倍&a…...
网络空间安全(1)web应用程序的发展历程
前言 Web应用程序的发展历程是一部技术创新与社会变革交织的长卷,从简单的文档共享系统到如今复杂、交互式、数据驱动的平台,经历了多个重要阶段。 一、起源与初期发展(1989-1995年) Web的诞生: 1989年,欧洲…...
Android 之 AIDL for HAL
Android AIDL for HAL 的作用与实现 作用: Android AIDL for HAL(Android Interface Definition Language for Hardware Abstraction Layer)旨在统一 HAL 开发接口,替代 HIDL(Hardware Interface Definition Language…...
Python爬虫基础文件操作
文件操作 引言 爬虫爬取的一切内容都是在内存进行的,这样会有什么问题吗?如果一旦短电或着发生意外电脑关机了那么你的工作成果将瞬间消失。所以,我们还缺少数据在本地文件系统进行持久化的能力,简单的来说就是文件读写操作。文…...
OpenGauss MySQL兼容库迁移
OpenGauss 提供了从MySQL到OG的迁移工具,虽然安装在起来及其繁琐,也不怎么好用,不过我现在需要的是,从MySQL到OG的MySQL兼容库,可以理解成从MySQL到MySQL的迁移。 但是很不幸的是,OG的MySQL的兼容模式&…...
SOME/IP--协议英文原文讲解10
前言 SOME/IP协议越来越多的用于汽车电子行业中,关于协议详细完全的中文资料却没有,所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块: 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 4.2.2 Req…...