OpenHarmony应用构建工具Hvigor的构建流程
前言
OpenHarmony 应用和服务使用 Hvigor 作为工程的构建工具。本篇文章将介绍 Hvigor 的构建流程,通过修改脚本配置使 Hvigor 执行自定义任务。
Hvigor 的构建流程
- 加载命令行参数和环境变量;
- 初始化项目结构,创建 Project 和 Module 实例;
- 配置项目插件和任务流;
- 执行任务流;
// hvigor/index.js
// LiftOff 命令行辅助工具
const cli = new LiftOff({name: 'hvigor',processTitle: make_title.makeTitle('hvigor', process.argv.slice(2)),moduleName: exports.hvigorPath,// @ohos/hvigor-baseconfigName: "hvigorFile",v8flags: v8flags,extensions: interpret.jsVariants
});
// cli options定义所有的可支持的命令行
const parser = yargs.usage("Usage", cli_options.cliOptions);
// 解析命令行
const opts = parser.argv;
function run() {cli.prepare({cwd: opts.cwd,require: opts.require,completion: opts.completion,}, function (env) {// help 指令if (opts.help) {yargs.usage('Usage: hvigor [options]').example('hvigor assembleApp', 'Do assembleApp task').help('h').alias('h', 'help').epilog('copyright 2021').argv;exit.exit(0);}// version 指令if (opts.version) {_log.info('CLI version:', cliVersion);_log.info('Local version:', env.modulePackage.version || 'Unknown');exit.exit(0);}// 设置日志等级evaluateLogLevel();
// 判断当前 nodejs工具版本信息const LOWEST_VERSION = "v14.18.3";if (process.version < LOWEST_VERSION) {_log.warn(`node version: ${process.version}`);_log.warn(`node version cannot be lower than ${LOWEST_VERSION}`);process.exit(-1);}// 1. 加载命令行参数和环境变量init_env_config_props.initEnvConfigProps(env, opts);cli.execute(env, env.nodeFlags, function () {return __awaiter(this, void 0, void 0, function* () {const taskBeginTime = process.hrtime();// 执行具体的 Hvigor 任务yield process_utils.processUtils(opts, env);const taskEndTime = process.hrtime(taskBeginTime);const realTime = pretty_hrtime.default)(taskEndTime);if (0 == profile_js.profile.executed) {_log.error(`No task found to execute in project!`);}_log.info(`BUILD SUCCESSFUL in ${realTime}`);});});});
}
执行具体的 Hvigor 的任务
// hvigor/src/process-utils.js
function processUtils(opts, env) {···// 2. 初始化项目结构,创建 Project 和 Module 实例;init.init(env);// 3. 配置项目插件和任务流;const project = configuration.configuration(env);// 4. 执行任务流;const executeMode = env.configProps.get(modeAlias);switch (executeMode) {···}
}
1. 加载命令行参数和环境变量
读取命令行参数,并把参数和配置文件的路径放入全局配置项中。
// hvigor/src/init-env-config-props.js
function initEnvConfigProps(env, opts) {···// 获取项目级别 build-profile.json5 文件路径,[项目路径]/build-profile.json5const configFilePath = path.resolve(process.cwd(), hvigor_base.HvigorCommonConst.PROJECT_CONFIG_FILE);···const properties = new Map();// 获取命令行 prop(p) 参数,放入 Map 中if (opts.prop !== undefined) {[].concat(opts.prop).forEach((value) => {const arr = value.split('=');properties.set(arr[0], arr[1]);});}// 把"项目级别 build-profile.json5 文件路径"、"命令行 prop(p) 参数集合"和"命令行 mode(m) 模式参数"配置进环境变量中env.configProps = new Map([[configFileName, configFilePath],[propertiesAlias, properties],[modeAlias, opts.mode]]);return configFilePath;
}
2. 初始化项目结构,创建 Project 和 Module 实例
工程结构实例化。
// hvigor/src/lifecycle/init.js
function init(env) {// env.modulePath 是在命令行辅助工具 LiftOff 中加载的模块函数,"modulePath": "[项目路径]/node_modules/@ohos/hvigor-base/index.js",const vigorConfigInst = require(env.modulePath).vigorConfigInst;// 把从命令行加载来的 prop(p) 参数放入 vigorConfigInst 实体的 ExtraConfig 中vigorConfigInst.setExtraConfig(env.configProps.get(propertiesAlias));// 初始化项目,参数:[项目路径]/build-profile.json5,[项目路径]vigorConfigInst.initRootProject(path.resolve(env.cwd, hvigor_base.HvigorCommonConst.PROJECT_CONFIG_FILE), env.cwd);
}
// hvigor-base/index.js
exports.vigorConfigInst = new VigorConfig();
class VigorConfig {constructor() {this._log = hvigor_log_js.HvigorLogger.getLogger(VigorConfig.name);this._project = undefined;this._projectDir = "";this._projectConfigFile = "";this._extraConfig = new Map();}···// 初始化项目,参数:[项目路径]/build-profile.json5,[项目路径]initRootProject(projectConfigFile, projectRootDir) {// 配置项目级别 build-profile.json5 文件路径this._projectConfigFile = projectConfigFile;// 配置项目根路径this._projectDir = projectRootDir;// 创建项目实例,参数:[项目名称],[项目路径]const projectImpl = new project_impl_js.ProjectImpl(path.basename(projectRootDir), projectRootDir);// 读取 [项目路径]/build-profile.json5 配置数据const projectStructureOpt = json5_reader_js.Json5Reader.getJson5Obj(this._projectConfigFile);···// 从配置文件中读取项目下的全部 Modules projectStructureOpt.modules?.forEach(module => {// 校验 Module 配置参数validate_util_js.ValidateUtil.validateModule(module);// 添加子项目,根据配置文件中 Modules 循环创建 Module 实例,参数:项目实例,Module 名称,Module 的路径projectImpl.addSubProject(new module_impl_js.ModuleImpl(projectImpl, module.name, module.srcPath));});this._project = projectImpl;return projectImpl;}
}
class ValidateUtil {static validateModule(module) {if (module.name === undefined) {this.logger.errorMessageExit(`Project level build-profile.json5 lose required property: module-name`);}if (module.srcPath === undefined) {this.logger.errorMessageExit(`Project level build-profile.json5 lose required property: module-srcPath`);}}
}
Project 实现类
// hvigor-base/src/impl/project-impl.js
class ProjectImpl extends default_module_impl.DefaultModuleImpl {constructor(moduleName, moduleDir) {super(moduleName, moduleDir);// 配置项目级别 build-profile.json5 文件路径this._projectStructureFile = path.resolve(moduleDir, common_const.HvigorCommonConst.PROJECT_CONFIG_FILE);this._subProjects = new Map();}···
}
Module 实现类
// hvigor-base/src/impl/module-impl.js
class ModuleImpl extends default_module_impl.DefaultModuleImpl {constructor(project, moduleName, moduleDir) {super(moduleName, moduleDir);this._project = project;}···
}
默认 Module 实现类
// hvigor-base/src/impl/default-module-impl.js
class DefaultModuleImpl {constructor(moduleName, modulePath) {this._moduleName = moduleName;this._modulePath = modulePath;// 获取项目和模块的 package.json 文件路径this._packageJsonPath = path.resolve(modulePath, "package.json");// 获取项目和模块的 hvigorfile.js 文件路径this._buildFilePath = path.resolve(modulePath, common_const.HvigorCommonConst.BUILD_FILE_NAME);}···
}
3. 配置项目插件和任务流
加载 hvigorfile.js 文件中配置的任务脚本。
// hvigor/src/lifecycle/configuration.js
function configuration(env) {// 整个项目的结构在 init 中初始化完成,读取 vigorConfigInstconst vigorConfigInst = require(env.modulePath).vigorConfigInst;// 通过 vigorConfigInst 获取项目实例const project = vigorConfigInst.getProject();// 获取项目全部的 Module 实例,遍历加载任务脚本for (const subModule of project.getAllSubProjects()) {// 获取 Module 的 hvigorfile.js 文件路径const subModuleVigorFileJs = subModule.getBuildFilePath();// 加载任务脚本configModule(subModule, subModuleVigorFileJs);}// 获取 Project 的 hvigorfile.js 文件路径const projectVigorFileJs = path.resolve(env.cwd, hvigor_base.HvigorCommonConst.BUILD_FILE_NAME);// 加载任务脚本configModule(project, projectVigorFileJs);return project;
}function configModule(module, hvigorFilePath) {// FA 模型// 工程级别// module.exports = require('@ohos/hvigor-ohos-plugin').legacyAppTasks// 模块级别// module.exports = require('@ohos/hvigor-ohos-plugin').legacyHapTasks// module.exports = require('@ohos/hvigor-ohos-plugin').legacyHarTasks// Stage 模型// 工程级别// module.exports = require('@ohos/hvigor-ohos-plugin').appTasks// 模块级别// module.exports = require('@ohos/hvigor-ohos-plugin').hapTasks// module.exports = require('@ohos/hvigor-ohos-plugin').harTasks// 加载 hvigorFile 任务脚本const moduleExported = require(hvigorFilePath);// 配置 Project 和 Module 的任务流const pluginNTasks = moduleExported(module);module.exported = pluginNTasks;// 绑定创建的 Plugin 对象到 Project 实例和 Module 实例上module.bindPlugin(pluginNTasks.plugin);
}
4. 执行任务流
根据命令行 mode 参数判断要执行任务流的级别。
// hvigor/src/process-utils.js
// 从环境变量中获取 mode(m) 参数
const executeMode = env.configProps.get(modeAlias);// 根据 mode 判断要执行任务流的级别
switch (executeMode) {case hvigor_base.HvigorBuildConst.PROJECT_MODE: // projectexecute_task.execute(project, true, opts).then(outputInfo);break;case hvigor_base.HvigorBuildConst.MODULE_MODE: // moduleexecute_task.executeModuleTask(project, opts).then(outputInfo);break;case undefined: // 未配置execute_task.execute(project, false, opts); execute_task.executeModuleTask(project, opts).then(outputInfo);break;default:_log.error(`Unknown mode '${executeMode}' specified in command line,Please check!`);
}
执行任务
//hvigor/src/lifecycle/execute-task.js
function executeModuleTask(project, opts) {// 获取 prop(p) 参数中 module 的取值const modules = hvigor_base.vigorConfigInst.getExtraConfig().get("module");// 如果不指定 module 则默认执行所有 Module 任务if (modules === undefined) {// 从项目实例中获取全部 Module 并执行任务yield Promise.all(project.getAllSubProjects().map((module) => {execute(module, false, opts);})).catch(error => {_log.error(error);});}else {// 如果指定 module 则从项目级别 build-profile.json5 配置文件中查找匹配的 Module ,如果存在则执行任务yield Promise.all(modules.split(",").map((value) => {const values = value.split("@");const module = project.findModuleByName(values[0]);if (module === undefined) {_log.errorMessageExit(`Unknown module '${values[0]}' in command lines,Please check!`);}execute(module, true, opts);})).catch(error => {_log.error(error);});}
}
// 执行任务流
function execute(model, shouldCheckTask, opts) {return __awaiter(this, void 0, void 0, function* () {// 从命令行获取需要执行的任务列表const tasks = opts._;// 如果没配置则执行 default 任务const toRun = tasks.length ? tasks : ['default'];···const moduleName = model.getName();// [项目路径]/node_modules/@ohos/hvigor-base/index.js.Hvigorconst HvigorClass = require(index.hvigorPath).Hvigor;// 项目打包工具的主入口const vigorInst = new HvigorClass();// 注册任务const registryTasks = register_exports.registerExports(vigorInst, model.exported);if (needSync) {profile_js.profile.executed += 1;yield sync.sync(model, registryTasks, vigorInst);return;}// 筛选最终要执行的操作const finallyToRun = toRun.filter((taskName) => {return registryTasks.has(taskName);});// 指定具体要执行的模块,但是找不到对应的任务if (!needSync && shouldCheckTask && finallyToRun.length === 0) {_log.errorMessageExit(`Task ${toRun} not found in module:${moduleName}.`);}// 未指定具体要执行的模块,没有可执行任务时直接返回if (finallyToRun.length === 0) {_log.debug(`No task found to execute in ${moduleName}!`);return;}profile_js.profile.executed += finallyToRun.length;// series:串行 parallel:并行const runMethod = opts.series ? 'series' : 'parallel';// 执行任务流vigorInst[runMethod](finallyToRun)(function (err) {if (err) {_log.errorExit(err.message);}});});
}
注册任务
// hvigor/src/register-exports.js
function registerExports(vigorInst, hvigorFileObj) {const registryTasks = new Map();const taskNames = Object.keys(hvigorFileObj);if (taskNames.length) {taskNames.forEach((taskName) => {const exportObj = hvigorFileObj[taskName];// plugin 不会被注册if (exportObj instanceof Task) {const task = exportObj.registry();vigorInst.task(taskName, task);registryTasks.set(taskName, exportObj instanceof DefaultSyncTask);}});}return registryTasks;
}
Hvigor 的插件和任务流
Build 任务脚本
// hvigor-ohos-plugin/index.js
// Stage 模型的 hap 任务流
const hapTasks = (module) => {const plugin = plugin_factory_js.PluginFactory.getHapPlugin(module);return {plugin,assembleHap: plugin.assembleHap,clean: plugin.clean,compileNative: plugin.compileNative,sync: plugin.sync,buildPreviewerResource: plugin.buildPreviewerRes};
};
// FA 模型的 hap 任务流
const legacyHapTasks = (module) => {const plugin = plugin_factory_js.PluginFactory.getHapPlugin(module, true);return {plugin,assembleHap: plugin.assembleHap,clean: plugin.clean,compileNative: plugin.compileNative,buildPreviewerResource: plugin.buildPreviewerRes,sync: plugin.sync};
};
// Stage 模型的 app 任务流
const appTasks = (module) => {const plugin = plugin_factory_js.PluginFactory.getAppPlugin(module);return {plugin,assembleApp: plugin.assembleApp,clean: plugin.clean,sync: plugin.sync};
};
// FA 模型的 app 任务流
const legacyAppTasks = (module) => {const plugin = plugin_factory_js.PluginFactory.getAppPlugin(module, true);return {plugin,assembleApp: plugin.assembleApp,clean: plugin.clean,sync: plugin.sync};
};
// Stage 模型的 Har 插件
const harTasks = (module) => {const plugin = plugin_factory_js.PluginFactory.getHarPlugin(module);return {plugin,clean: plugin.clean,assembleHar: plugin.assembleHar,assembleSubHar: plugin.assembleSubHar,buildPreviewerResource: plugin.buildHarPreviewerRes};
};
// FA 模型的 Har 插件
const legacyHarTasks = (module) => {const plugin = plugin_factory_js.PluginFactory.getHarPlugin(module, true);return {plugin,clean: plugin.clean,assembleHar: plugin.assembleHar,assembleSubHar: plugin.assembleSubHar,buildPreviewerResource: plugin.buildHarPreviewerRes};
};
Plugin 工厂类
// hvigor-ohos-plugin/src/plugin/factory/plugin-factory.js
class PluginFactory {static getAppPlugin(project, isFaMode = false) {// 创建 AppPlugin 实例return new app_plugin_js.AppPlugin(project, isFaMode);}static getHapPlugin(module, isFaMode = false) {// 创建 HppPlugin 实例return new hap_plugin_js.HapPlugin(module, isFaMode);}static getHarPlugin(module, isFaMode = false) {// 创建 HarPlugin 实例return new har_plugin_js.HarPlugin(module, isFaMode);}
}
HapPlugin & HarPlugin
// hvigor-ohos-plugin/src/plugin/hap-plugin.js
class HapPlugin extends abstract_module_plugin_js.AbstractModulePlugin {constructor(module, isFaMode) {super(module, isFaMode);}initTasks() {// Hap 打包的任务流this.assembleHap = new assemble_hap_js.AssembleHap(this._taskService, this.isFaMode);// Clean 任务this.clean = new clean_js.Clean(this._taskService);// Native 代码编译任务this.compileNative = new compile_native_js.CompileNative(this._taskService);// Module 级别的 Sync 任务this.sync = new sync_module_js.SyncModule(this._module);// Hap 在 Priviewer 模式构建的任务流this.buildPreviewerRes = new build_previewer_res_js.BuildPreviewerRes(this._taskService, this.isFaMode);}
}
// hvigor-ohos-plugin/src/plugin/har-plugin.js
class HarPlugin extends abstract_module_plugin_js.AbstractModulePlugin {constructor(module, isFaMode) {super(module, isFaMode);}initTasks() {// Har 打包的任务流this.assembleHar = new assemble_har_js.AssembleHar(this._taskService, this.isFaMode);// 子 Har 打包的任务流this.assembleSubHar = new assemble_sub_har_js.AssembleSubHar(this._taskService, this.isFaMode);// Clean 任务this.clean = new clean_js.Clean(this._taskService);// Har 在 Priviewer 模式构建的任务流this.buildHarPreviewerRes = new build_har_previewer_res_js.BuildHarPreviewerRes(this._taskService, this.isFaMode);}
}
初始化 ModulePlugin 结构实例
// hvigor-ohos-plugin/src/plugin/common/abstract-module-plugin.js
class AbstractModulePlugin {constructor(module, isFaMode) {this._log = ohos_logger_js.OhosLogger.getLogger(AbstractModulePlugin.name);this._module = module;const project = module.getProject();this.isFaMode = isFaMode;// FA 模型if (this.isFaMode) {// 创建 LegacyProjectModelImpl 实例,LegacyProjectModelImpl 为单例,同一个项目只会创建一次this._projectModel = legacy_project_model_impl_js.LegacyProjectModelImpl.getInstance(project.getModuleDir(), project.getName());// 创建 LegacyModuleModelImpl 实例this._moduleModel = new legacy_module_model_impl_js.LegacyModuleModelImpl(module.getModuleDir(), module.getName(), this._projectModel);// 检查任务脚本和模块级别 build-profile.json5 中 apiType 是否匹配this.checkModuleStatus(hap_extra_info_js.ApiType.FA);}// Stage 模型else {// 创建 ProjectModelImpl 实例,ProjectModelImpl 为单例,同一个项目只会创建一次this._projectModel = project_model_impl_js.ProjectModelImpl.getInstance(project.getModuleDir(), project.getName());// 创建 ModuleModelImpl 实例this._moduleModel = new module_model_impl_js.ModuleModelImpl(module.getModuleDir(), module.getName(), this._projectModel);// 检查任务脚本和模块级别 build-profile.json5 中 apiType 是否匹配this.checkModuleStatus(hap_extra_info_js.ApiType.STAGE);}// 创建 Module 任务服务实例this._taskService = new module_task_service_js.ModuleTaskService(this._projectModel, this._moduleModel);// 初始化任务this.initTasks();}checkModuleStatus(apiType) {// 获取模块级别 build-profile.json5 中 apiType 配置信息const curApiType = this._moduleModel.getProfileOpt().apiType === undefined ?hap_extra_info_js.ApiType.STAGE :this._moduleModel.getProfileOpt().apiType;if (curApiType !== apiType) {this._log._buildError("The 'hvigorfile.js' import is not match the apiType!")._solution(`Change the apiType to '${apiType}'.`)._file(this._moduleModel.getProfilePath())._printErrorAndExit(this._moduleModel.getName());}}···
}
创建 FA 模型 LegacyModuleModelImpl 实例
FA 模型的模块持久化数据模型,包含模块源码数据,配置数据等
// hvigor-ohos-plugin/src/model/module/legacy-module-model-impl.js
class LegacyModuleModelImpl extends core_module_model_impl_js.CoreModuleModelImpl {constructor(modulePath, moduleName, parentProject) {super(modulePath, moduleName, parentProject);this._legacyAbilitiesMap = new Map();this.initDefaultTargetSourceSet();}···initDefaultTargetSourceSet() {// 配置 [模块路径]/src/main 文件夹下配置文件 config.json 路径和 resources 路径实例const defaultTargetSourceSet = new legacy_target_source_set_impl_js.LegacyTargetSourceSetImpl(path.default.resolve(this._sourceRootDir, "main"));// defaultTargetName = 'default'this.targetSourceSetMap.set(defaultTargetName, defaultTargetSourceSet);this.initAbilityInfo(defaultTargetName, defaultTargetSourceSet);// 创建 [模块路径]/src/ohosTest 文件夹下配置文件 config.json 路径和 resources 路径const ohosTestTargetSourceSet = new legacy_target_source_set_impl_js.LegacyTargetSourceSetImpl(path.default.resolve(this._sourceRootDir, "ohosTest"));// ohosTestTargetName = 'ohosTest'this.targetSourceSetMap.set(ohosTestTargetName, ohosTestTargetSourceSet);// 初始化 Ability 信息this.initAbilityInfo(ohosTestTargetName, ohosTestTargetSourceSet);}···// 初始化 Module 中的 Ability 信息initAbilityInfo(targetName, targetSourceSet) {···// 读取 config.json 配置项const configJsonObj = project_file_reader_js.ProjectFileReader.getJson5Obj(configJsonPath);// 获取配置中 module.abilities 配置项const abilityObjs = configJsonObj.module.abilities ?configJsonObj.module.abilities : [];const legacyAbilities = [];for (let i = 0; i < abilityObjs.length; i++) {// 创建 FA模型 Ability 实例legacyAbilities.push(new legacy_ability_model_impl_js.LegacyAbilityModelImpl(configJsonPath, abilityObjs[i].name));}// 验证是否有重名的 Ability,如果重名则会报错并退出LegacyModuleModelImpl.validateSameNameAbility(legacyAbilities);// 读取 module.js 配置信息const jsObjs = configJsonObj.module.js ? configJsonObj.module.js : [];// 查找 js 配置项中 type 为 form 的配置项for (let i = 0; i < jsObjs.length; i++) {if ('form' !== jsObjs[i].type) {continue;}// 创建 FA模型 Form 实例legacyAbilities.push(new legacy_form_model_impl_js.LegacyFormModelImpl(configJsonPath, jsObjs[i]));}this._legacyAbilitiesMap.set(targetName, legacyAbilities);}
}
创建 Stage 模型 ModuleModelImpl 实例
Stage 模型的模块数据管理类
// hvigor-ohos-plugin/src/model/module/module-model-impl.js
class ModuleModelImpl extends core_module_model_impl_js.CoreModuleModelImpl {constructor(modulePath, moduleName, parentProject) {super(modulePath, moduleName, parentProject);this.initDefaultTargetSourceSet();}initDefaultTargetSourceSet() {// 创建 [模块路径]/src/main 文件夹下配置文件 module.json5 路径和 resources 路径实例const defaultTargetSourceSet = new target_source_set_impl_js.TargetSourceSetImpl(path.default.resolve(this._sourceRootDir, "main"));// defaultTargetName = 'default'this.targetSourceSetMap.set(defaultTargetName, defaultTargetSourceSet);// 创建 [模块路径]/src/ohosTest 文件夹下配置文件 module.json5 路径和 resources 路径实例const ohosTestTargetSourceSet = new target_source_set_impl_js.TargetSourceSetImpl(path.default.resolve(this._sourceRootDir, "ohosTest"));// OHOS_TEST_TARGET = 'ohosTest'this.targetSourceSetMap.set(common_const_js.DefaultTargetConst.OHOS_TEST_TARGET, ohosTestTargetSourceSet);}···
}
Module 模块的核心数据管理类
Hvigor 工程的 module 模块的核心数据管理类
// hvigor-ohos-plugin/src/model/module/core-module-model-impl.js
class CoreModuleModelImpl {constructor(modulePath, moduleName, parentProject) {this._log = ohos_logger_js.OhosLogger.getLogger(CoreModuleModelImpl.name);this.targetSourceSetMap = new Map();// 模块名称this.name = moduleName;// 模块路径this.modulePath = path.default.resolve(modulePath);// 项目 LegacyProjectModelImpl 实例this.parentProject = parentProject;// Module 的 hvigorfile.js 文件路径this.buildProfilePath = path.default.resolve(this.modulePath, this.getBuildProfileName());// Module 的 build-profile.json5 文件路径const moduleBuildProfilePath = path.default.resolve(this.modulePath, common_const_js.CommonConst.PROFILE_JSON5);// 使用 Schema 验证配置文件是否正确,错误则退出this.moduleBuildProfileCheck(moduleBuildProfilePath);// Module 的 src 路径this._sourceRootDir = path.default.resolve(modulePath, "src");}
}
创建 Module 任务服务实例
基于持久化 Module 的模型层提供的数据,经过处理后,提供给打包hap任务流需要使用的服务和数据
// hvigor-ohos-plugin/src/tasks/service/module-task-service.js
class ModuleTaskService extends task_service.TaskService {constructor(projectModel, moduleModel) {super(projectModel);this._log = ohos_logger_js.OhosLogger.getLogger(ModuleTaskService.name);this.computeTargets = () => {// 默认不配置target时,执行所有的targetconst targets = ["all"];// 从命令行 prop(p) 参数中获取 module 配置 const configModules = hvigor_base.vigorConfigInst.getExtraConfig().get("module");if (configModules === undefined) {return targets;}// 例:entry@phone@free 则 targets为 ['phone','free']const modules = configModules.split(",");for (let i = 0; i < modules.length; i++) {const module = modules[i];const values = module.split("@");if (this._moduleModel.getName() !== values[0]) {continue;}for (let j = 1; j < values.length; j++) {targets[j - 1] = values[j];}}return targets;};this.checkNeedPack = (targetProduct, targetName, curModuleConfigTargets) => {let needPack = false;// 如果在 prop(p) module 中配置则需要打包if (curModuleConfigTargets.indexOf(targetName) > -1) {needPack = true;}if ((curModuleConfigTargets.indexOf("all") > -1)) {// 默认不配置target时,不打包ohosTest的包needPack = targetName !== "ohosTest";}const checkApplyProduct = (targetProduct) => {// 获取模块级别 build-profile.json5 配置文件中对应的 target 的 applyToProductslet products = this._projectModel?.getTargetApplyProducts(this._moduleModel.getName(), targetName);// 如果没有配置applyToProducts则默认是defaultif (products === undefined) {products = ["default"];}// 如果存在则需要打包return products.includes(targetProduct);};return checkApplyProduct(targetProduct.name) && needPack;};// 初始化 Hap 模块打包流的 target 数据集合this.initTargetData = () => {// 根据命令行参数计算需要的 target 项const curModuleConfigTargets = this.computeTargets();// 获取命令行 prop(p) 中 buildRoot 参数let buildRoot = hvigor_base.vigorConfigInst.getExtraConfig().get("buildRoot");if (!buildRoot) {// 默认打包路径 buildbuildRoot = build_directory_const_js.BuildDirConst.BUILD_ROOT;}// 读取模块级别 build-profile.json5 的 targetslet targets = this._moduleModel.getProfileOpt().targets;if (targets === undefined) {targets = [{name: "default",}, {name: "ohosTest"}];}else {const targetNames = targets.map(target => {return target.name;});if (!targetNames.includes("default")) {targets.push({name: "default",});}if (!targetNames.includes("ohosTest")) {targets.push({name: "ohosTest"});}}// 获取命令行中配置的需要打包的 product 如果未配置则默认为 defaultconst targetProduct = find_target_product.findTargetProduct(this._projectModel);let hasTargetNeedPack = false;targets.forEach((target) => {const targetName = target.name;// 验证可打包的 targetconst needPack = this.checkNeedPack(targetProduct, targetName, curModuleConfigTargets);if (needPack) {hasTargetNeedPack = true;}// 创建路径信息实例const pathInfo = new module_path_info_iml.ModulePathInfoIml(this._moduleModel, targetName, targetProduct.name, buildRoot);this._targetDataMap.set(targetName, [new hap_task_target_data.ModuleTargetData(this._moduleModel, targetName, pathInfo, targetProduct), needPack]);});if (!hasTargetNeedPack) {this._log._buildError(`Current product is ${targetProduct.name},There is no executable target!`)._solution(`Please check the module targets ${util.format(targets)} applyToProducts field`)._file(this._projectModel.getProfilePath())._printErrorAndExit(this._moduleModel.getName());}};this._moduleModel = moduleModel;this._targetDataMap = new Map();// 获取额外信息, isSupportHos 和 isStageModethis._hapExtraInfo = project_extra_info_service_js.ProjectExtraInfoService.getProjectExtraInfoByPath(this._moduleModel);// 获取编译 SDK 版本信息this._compileSdkVersion = this._projectModel.getProfileOpt().app.compileSdkVersion;// 实例化 SDK 工具信息实例this._sdkInfo = new sdk_info.SdkInfo(this._compileSdkVersion, this.getModuleRequiredSDKs());// 初始化 Target 数据this.initTargetData();}getModuleRequiredSDKs() {// 默认加载 Toolchainsconst sdkComponents = [sdkmanager_common.ComponentPath.TOOLCHAINS];for (const key of this._moduleModel.getSourceSetByTargetName(common_const_js.DefaultTargetConst.DEFAULT_TARGET).getCodeMap().keys()) {// 按照目录存在就加载的逻辑,例如main目录下存在 ets目录则加载 ets SDK 实例// CodeType["JS"] = "js";// CodeType["ETS"] = "ets";// CodeType["CPP"] = "cpp";if (code_type_enum.CodeType.CPP === key) {if (this._moduleModel.getProfileOpt().buildOption.externalNativeOptions) {sdkComponents.push(code_type_enum.CodeType.getSDKComponentName(key));}}else {sdkComponents.push(code_type_enum.CodeType.getSDKComponentName(key));}}this._log.debug(`${this._moduleModel.getName()} require SDK: ${sdkComponents.join(" ").toLowerCase()}`);return sdkComponents;}···
}
根据命令行中的配置找到项目级别 build-profile.json5 中的 Product
// hvigor-ohos-plugin/src/common/find-target-product.js
function findTargetProduct(projectModuleModel) {// 从命令行 prop(p) 参数中获取 Product 配置 const configProduct = hvigor_base.vigorConfigInst.getExtraConfig().get("product");// 没有配置时默认是defaultconst targetProduct = configProduct ? configProduct : "default";_log.debug(`Find product from build-profile.json: %s`, targetProduct);// 检查项目级别 build-profile.json5 配置 app.products 项中是否有该 productconst mProduct = array_util_js.getElementFromArr(projectModuleModel.getProfileOpt().app.products, targetProduct);if (!mProduct) {_log._buildError(`Can not find product ${targetProduct}, please check!`)._solution('Please check attribute products from build-profile.json5.')._file(projectModuleModel.getProfilePath())._printErrorAndExit(projectModuleModel.getName());}return mProduct;
}
AppPlugin
// hvigor-ohos-plugin/src/plugin/app-plugin.js
class AppPlugin {constructor(project, isFaMode) {this._module = project;this._moduleName = project.getName();// 判断是否为 FA 模型,创建不同的 ProjectModel 实例,参数:项目路径,项目名称this._projectModel = isFaMode ?legacy_project_model_impl_js.LegacyProjectModelImpl.getInstance(project.getModuleDir(), project.getName()) :project_model_impl_js.ProjectModelImpl.getInstance(project.getModuleDir(), project.getName());// 创建项目任务服务实例this._taskService = new project_task_service_js.ProjectTaskService(project, this._projectModel);// 创建具体任务实例this.assembleApp = new assemble_app_js.AssembleApp(this._taskService, isFaMode);this.clean = new clean_js.Clean(this._taskService);this.sync = new sync_project_js.SyncProject(project); }getTaskService() {return this._taskService;}
}
创建 FA 模型 LegacyProjectModelImpl 实例
FA 模型的工程持久化数据模型,包含工程源码数据,配置数据等
// hvigor-ohos-plugin/src/model/project/legacy-project-model-impl.js
class LegacyProjectModelImpl extends core_project_model_impl_js.CoreProjectModelImpl {constructor(projectPath, name) {super(projectPath, name);}static getInstance(projectPath, name) {if (!LegacyProjectModelImpl.instance) {if (projectPath === null || projectPath === undefined) {throw new Error("工程模型还未初始化,请正确传递工程路径");}if (name === null || name === undefined) {throw new Error("工程模型还未初始化,请正确传递工程名称");}LegacyProjectModelImpl.instance = new LegacyProjectModelImpl(projectPath, name);}return LegacyProjectModelImpl.instance;}···
}
创建 Stage 模型 ProjectModelImpl 实例
Stage 模型的工程持久化数据模型,包含工程源码数据,配置数据等
// hvigor-ohos-plugin/src/model/project/project-model-impl.js
class ProjectModelImpl extends core_project_model_impl_js.CoreProjectModelImpl {constructor(projectPath, name) {super(projectPath, name);// 创建 App 级资源实例this.appRes = new app_res_model_impl_js.AppResModelImpl(path.default.resolve(projectPath, "AppScope"));}static getInstance(projectPath, name) {if (!ProjectModelImpl.instance) {if (projectPath === null || projectPath === undefined) {throw new Error("工程模型还未初始化,请正确传递工程路径");}if (name === null || name === undefined) {throw new Error("工程模型还未初始化,请正确传递工程名称");}ProjectModelImpl.instance = new ProjectModelImpl(projectPath, name);}return ProjectModelImpl.instance;}···
}
Project 模块的核心数据管理类
// hvigor-ohos-plugin/src/model/project/project-model-impl.js
class CoreProjectModelImpl {constructor(projectPath, name) {this._log = ohos_logger_js.OhosLogger.getLogger(CoreProjectModelImpl.name);this.subModels = new Map();// 项目路径this.projectPath = projectPath;// 项目名称this.name = name;// 设置 [项目路径]/build-profile.json5 文件路径this._profilePath = path.default.resolve(this.projectPath, common_const_js.CommonConst.PROFILE_JSON5);// 验证文件是否存在,是否符合规范this.projectBuildProfileCheck(this._profilePath);// 读取 [项目路径]/build-profile.json5 文件数据this._profileOptions = project_file_reader_js.ProjectFileReader.getJson5Obj(this.getProfilePath());// 检查配置文件中 SDK 版本信息,compileApiVersion 大于 8,compatibleApiVersion 小于等于 compileApiVersionthis.projectStatusCheck();// 加载 [项目路径]/build-profile.json5 文件中 Modules 配置信息,创建 ModuleModelImpl 实例并设置进 subModels 中this.initSubProject();}···
}
创建 Project 任务服务实例
基于持久化 project 的模型层提供的数据,经过处理后,提供给打包app任务流需要使用的服务和数据
// hvigor-ohos-plugin/src/tasks/service/project-task-service.js
class ProjectTaskService extends task_service.TaskService {constructor(project, projectModel) {super(projectModel);// 初始化 Project 中 Module 需要打包的目标项集合this.initProductData = () => {// 遍历所有模块this._project?.getSubProjects().forEach((value) => {const moduleName = value.getName();const plugin = value.getPlugin();if (plugin === undefined) {throw new Error(`Cannot find build file 'hvigorfile.js' in module ${moduleName}`);}const moduleTargetDataArr = [];if (plugin instanceof hap_plugin.HapPlugin) {// 获取项目级别 build-profile.json5 中 modules 里配置的 name 匹配的配置项const appModuleOpt = this._projectModel?.getModuleProfileOpt(moduleName);// 获取 module 配置中 targets 配置项const appModuleConfigTargets = appModuleOpt?.targets;// 获取 module 的 taskService 中 targetData 信息const targetDataMap = plugin.getTaskService().getTargetDataMap();// 遍历 Module 中需要打包的目标项targetDataMap.forEach((targetData, targetName, targetMap) => {// 该target需要打包,并且在项目级别 build-profile.json5 的 targets 中配置if (targetData[1] && array_util.getElementFromArr(appModuleConfigTargets, targetName) !== undefined) {moduleTargetDataArr.push(targetData[0]);}});this._productDataMap.set(moduleName, moduleTargetDataArr);}});};this._project = project;// 获取命令行中配置的需要打包的 product 如果未配置则默认为 defaultthis._targetProduct = find_target_product.findTargetProduct(projectModel);// 创建 SDK 工具实例,传入编译版本和 toolchains 工具名称this._sdkInfo = new sdk_info_js.SdkInfo(projectModel.getCompileApiVersion(), [sdkmanager_common.ComponentPath.TOOLCHAINS]);// 创建路径信息实例this._pathInfo = new project_path_info_iml.ProjectPathInfoIml(projectModel, this._targetProduct.name);this._productDataMap = new Map();this.initProductData();}···
}
Hvigor 自定义任务
创建任务
继承 Task 类实现自定义任务。创建自定义任务 command.js 文件,输出工程名称。
// command.js
const hvigor_base = require("@ohos/hvigor-base");class Command extends hvigor_base.Task {_taskService = null_logger = hvigor_base.HvigorLogger.getLogger(Command.name);constructor(taskService) {super();this._taskService = taskService;}registry = () => {return this.doTaskAction;}doTaskAction = (cb) => {this._logger.info(`CustomCommand`);this._logger.info(`ProjectName : ${this._taskService.getProjectModel().getName()}`);cb();}
}
exports.Command = Command;
修改任务脚本
修改任务脚本把自定义任务加入到任务流中。
// hvigorfile
const command_js = require('./command');
const hapTasks = require('@ohos/hvigor-ohos-plugin').hapTasks;module.exports = (module) => {const tasks = hapTasks(module);const taskService = tasks['plugin'].getTaskService();tasks.command = new command_js.Command(taskService);return tasks;
}
执行任务
执行命令 node node_modules\@ohos\hvigor\bin\hvigor.js command
效果如下:
为了能让大家更好的学习鸿蒙 (Harmony OS) 开发技术,这边特意整理了《鸿蒙 (Harmony OS)开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05
《鸿蒙 (Harmony OS)开发学习手册》
入门必看:https://qr21.cn/FV7h05
- 应用开发导读(ArkTS)
- ……
HarmonyOS 概念:https://qr21.cn/FV7h05
- 系统定义
- 技术架构
- 技术特性
- 系统安全
如何快速入门?:https://qr21.cn/FV7h05
- 基本概念
- 构建第一个ArkTS应用
- 构建第一个JS应用
- ……
开发基础知识:https://qr21.cn/FV7h05
- 应用基础知识
- 配置文件
- 应用数据管理
- 应用安全管理
- 应用隐私保护
- 三方应用调用管控机制
- 资源分类与访问
- 学习ArkTS语言
- ……
基于ArkTS 开发:https://qr21.cn/FV7h05
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……
相关文章:
OpenHarmony应用构建工具Hvigor的构建流程
前言 OpenHarmony 应用和服务使用 Hvigor 作为工程的构建工具。本篇文章将介绍 Hvigor 的构建流程,通过修改脚本配置使 Hvigor 执行自定义任务。 Hvigor 的构建流程 加载命令行参数和环境变量;初始化项目结构,创建 Project 和 Module 实例…...
ChatGPT在金融财务领域的10种应用方法
1.生成报告 在金融领域中,最耗时的任务之一是报告生成。通过ChatGPT,您可以在一定程度上自动化这个过程。这款人工智能工具可以获取关于公司财务表现的结构化数据,并生成一份书面摘要,详细说明关键点、趋势和观察结果。这个功能在…...
全程云OA ajax.ashx SQL注入漏洞复现
0x01 产品简介 全程云OA为企业提供日常办公管理、公文管理、工作请示、汇报、档案、知识体系、预算控制等26个功能,超过100多个子模块。为企业内部提供高效、畅通的信息渠道,同时也能大力推动公司信息系统发展,提高企业的办公自动化程度和综合管理水平,加快企业信息的流通…...
VMware 安装 macOS虚拟机(附工具包)
VMware 安装 macOS虚拟机,在Windows上体验苹果macOS系统! 安装教程:VMware 安装 macOS虚拟机VMware Workstation Pro 是一款强大的虚拟机软件,可让您在 Windows 电脑上运行 macOS 系统。只需简单几步操作,即可轻松安装…...
Tomcat与Servlet是什么关系
Tomcat与Servlet是什么关系 Apache Tomcat和Servlet之间存在密切的关系,可以说它们是一对密切合作的组件。下面是它们的关系: Tomcat是Servlet容器: Tomcat是一个开源的、轻量级的Servlet容器。Servlet容器是一个Web服务器扩展,用…...
C++11_右值引用
文章目录 前言一、右值引用是什么?那么,什么又是右值?右值引用 二、使用步骤和意义1.1.11.2 2.右值引用的最大意义2.1 完美转发2.2 万能折叠 前言 C11 是2011年对C这门语言发布的新标准,并且此次标准引入了十分多的新特性&#x…...
C#使用条件语句判断用户登录身份
目录 一、示例 二、生成 利用条件语句判断用户登录身份,根据用户登录身份的不同,给予相应的操作权限。 一、示例 主要用if语句及ComboBox控件。其中,ComboBox是窗体中的下拉列表控件,在使用ComboBox控件前,可以先向…...
在VM下使用Composer完成快照方式的软件制作
Composer允许您构建软件、应用程序、偏好设置文件或是文档的安装包,安装包可以部署到远程电脑或是作为镜像流程的一部分。构建软件包的第一步就是创建包源,根据要打包的软件,Composer允许您监视软件的安装和使用驱动器上已存在的文件来创建包…...
YOLOv5改进 | Neck篇 | 利用Damo-YOLO的RepGFPN改进特征融合层
一、本文介绍 本文给大家带来的改进机制是Damo-YOLO的RepGFPN(重参数化泛化特征金字塔网络),利用其优化YOLOv5的Neck部分,可以在不影响计算量的同时大幅度涨点(亲测在小目标和大目标检测的数据集上效果均表现良好涨点幅度超级高!)。RepGFPN不同于以往提出的改进模块,其…...
设计模式——最全梳理,最好理解
新年献礼! 设计模式呕心梳理 创建型模式 单例模式(Singleton Pattern)https://blog.csdn.net/qq_34869143/article/details/134874044 整理中... 结构型模式 代理模式(Proxy Pattern)https://blog.csdn.net/qq_34…...
外包干了4个月,技术退步明显了...
先说一下自己的情况,大专生,18年通过校招进入武汉某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四…...
rust 注释文档生成 cargo doc
rust的cargo文档生成 只需要在每个函数写清楚注释,就可以自动生成文档,很方便 即不用写文档,又可以快速查看,是开发rust的必备技能 rust安装和开发环境配置,可以参考:链接 1.写注释的方法 连续三个 \ 即…...
大语言模型(LLM)框架及微调 (Fine Tuning)
大语言模型(LLM) 技术作为人工智能领域的一项重要创 新在今年引起了广泛的关注。 LLM 是利用深度学习和大数据训练的人工智能系统,专门 设计来理解、生成和回应自然语言。这些模型通过分析大量 的文本数据来学习语言的结构和用法,…...
速盾高防ip:专业防御ddos
速盾高防IP是速盾网络为企业提供的专业DDoS攻击防御解决方案之一。作为一种先进的网络安全服务,速盾高防IP致力于保护客户的网络资源免受分布式拒绝服务(DDoS)攻击的威胁。以下是速盾高防IP的一些关键特点和优势: 实时攻击监测&am…...
第5章-第8节-Java面向对象中的内部类
1、内部类:属于类的成员之一,类的内部又定义类,外层的class称为外部类,内部的class称为内部类。 设计了某个类,根据需求发现其内部又需要定义一个独立的内部结构,此时就考虑将其定义为内部类,内…...
首次引入大模型!Bert-vits2-Extra中文特化版40秒素材复刻巫师3叶奈法
Bert-vits2项目又更新了,更新了一个新的分支:中文特化,所谓中文特化,即针对中文音色的特殊优化版本,纯中文底模效果百尺竿头更进一步,同时首次引入了大模型,使用国产IDEA-CCNL/Erlangshen-Megat…...
从零学Java - 接口
Java 接口 文章目录 Java 接口1.接口的语法1.1 与抽象类的区别 2.如何使用接口?2.1 接口的使用规范 3.什么是接口?3.1 常见关系 4.接口的多态性5.面向接口编程5.1 接口回调 6.特殊接口6.1 常量接口6.2 标记接口 7.接口的好处 补充面向对象 七大设计原则 1.接口的语法 接口&a…...
安全防御之身份鉴别技术
身份认证技术用于在计算机网络中确认操作者的身份。在计算机网络世界中,用户的身份信息是用一组特定的数据来表示的,计算机也只能识别用户的数字身份。身份认证技术能够作为系统安全的第一道防线,主要用于确认网络用户的身份,防止…...
axios post YII2无法接收post参数问题解决
axios post YII2无法接收post参数问题解决 在yii 配置文件中增加 ‘parsers’ > [“application/json” > “yii\web\JsonParser”] 如下所示: $config [id > basic,language > zh-CN,timeZone > env(TIME_ZONE, PRC),basePath > $basePath,bo…...
性能优化-OpenMP基础教程(三)
本文主要介绍OpenMP并行编程的环境变量和实战、主要对比理解嵌套并行的效果。 🎬个人简介:一个全栈工程师的升级之路! 📋个人专栏:高性能(HPC)开发基础教程 🎀CSDN主页 发狂的小花 &…...
[足式机器人]Part2 Dr. CAN学习笔记-动态系统建模与分析 Ch02-1+2课程介绍+电路系统建模、基尔霍夫定律
本文仅供学习使用 本文参考: B站:DR_CAN Dr. CAN学习笔记-动态系统建模与分析 Ch02-12课程介绍电路系统建模、基尔霍夫定律 1. 课程介绍2. 电路系统建模、基尔霍夫定律 1. 课程介绍 2. 电路系统建模、基尔霍夫定律 基本元件: 电量 库伦&…...
VSCode配置C/C++环境
文章目录 1. 安装配置 C 编译器1.1 下载 MinGW1.2 Mingw添加到系统变量1.3 验证 2. 安装和配置VSCode2.1 安装VSCode2.2 VSCode配置C环境2.3. 优化 3.参考文章 本文主要记录在VSCode中配置C环境,非常感谢参考文章中的博主。 1. 安装配置 C 编译器 首先需要安装 C 编…...
ChatGPT绘制全球植被类型分布图、生物量图、土壤概念图、处理遥感数据并绘图、病毒、植物、动物细胞结构图
以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮,可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…...
vmware workstation的三种网络模式通俗理解
一、前言 workstations想必很多童鞋都在用,经常会用来在本机创建不同的虚拟机来做各种测试,那么对于它支持的网络模式,在不同的测试场景下应该用哪种网络模式,你需要做下了解,以便可以愉快的继续测(搬&…...
C++程序设计兼谈对象模型(侯捷)笔记
C程序设计兼谈对象模型(侯捷) 这是C面向对象程序设计的续集笔记,仅供个人学习使用。如有侵权,请联系删除。 主要内容:涉及到模板中的类模板、函数模板、成员模板以及模板模板参数,后面包含对象模型中虚函数调用&…...
selenium实现UI自动化
1.selenium简介 selenium是支持web浏览器自动化的一系列工具和库的综合项目。具有支持linux、windows等多个平台,支持Firefox、chrome等多种主流浏览器;支持Java、Python等多种语言。 主要包括的三大工具有: WebDriver(rc 1.0)、…...
【DevOps-03】Build阶段-Maven安装配置
一、简要说明 下载安装JDK8下载安装Maven二、复制准备一台虚拟机 1、VM虚拟复制克隆一台机器 2、启动刚克隆的虚拟机,修改IP地址 刚刚克隆的虚拟机 ,IP地址和原虚拟的IP地址是一样的,需要修改克隆后的虚拟机IP地址,以免IP地址冲突。 # 编辑修改IP地址 $ vi /etc/sysconfig…...
已解决java.lang.ArrayIndexOutOfBoundsException异常的正确解决方法,亲测有效!!!
已解决java.lang.ArrayIndexOutOfBoundsException异常的正确解决方法,亲测有效!!! 目录 报错问题 解决思路 解决方法 总结 Q1 - 报错问题 java.long.ArrayIndexOutOfBoundsException 是Java中的一个运行时异常,…...
Pycharm打包程序为exe文件
Pycharm打包程序为exe文件 【一】导入模块pyinstaller 【1】图片说明 【2】文字说明 根据图片顺序执行 首先点击file进入settings界面,在setting界面找到Project下面的Python Interpretor,点击号进行模块的添加在搜索框中输入pyinstaller,…...
地理空间分析3——数据可视化与地理空间
写在开头 数据可视化是将数据以图形形式呈现,使其更易于理解和分析的过程。在地理空间分析中,数据可视化不仅能够展示地理位置信息,还能够有效地传达地理空间数据的模式、趋势和关联。本文将探讨数据可视化在地理空间分析中的作用,介绍Python中常用的数据可视化工具,并深…...
英文二手汽车网站建设/潍坊网站建设咨询
织梦500内部服务器错误一般是iis环境的小伙伴才会遇到,织梦的500错误一般是因为这2个原因引起的 第一个500错误原因:你网站织梦程序版本太低而你主机PHP版本过高 1、先查看你网站程序是织梦什么版本的 打开 /data/admin/ver.txt 文件查看,例…...
wordpress css样式/百度首页优化排名
功能介绍 【后台管理员功能】 会员列表:查看所有注册会员信息,支持删除 录入资讯:录入资讯标题、内容等信息 管理资讯:查看已录入资讯列表,支持删除和修改 广告设置:上传图片和设置小程序首页轮播图广告地…...
网站产品后台界面怎么做/seo专员的工作内容
1题目描述2题目要求3额外的信息 1、题目描述 问题C:“合作和导航” 由于道路的数量,美国许多地区的交通容量有限。例如,在大西雅图地区,司机在交通高峰时段遇到长时间的延误,因为交通流量超过了道路网络的设计容量。…...
崇州市微信端网站建/怎样下载优化大师
excel如何读取B列的数据类型和b列里的数据sum(b:b,"工具",a:a)EXCEL如何将表格的数据类型分类提取出来(提取筛选的数据类型)把C列的数据复制,到右边找一列空的列,粘贴进去。然后选中右边粘贴过来的这列、点顶部的:数据---删除重复项…...
十大采购平台/seo代理
MyBatis中mapper.xml中foreach的使用 Author:kak MySql的动态语句foreach,当传入参数为数组或者集合时需要通过foreach标签进行遍历,其主要是在in条件中,可以在SQL语句中迭代一个集合; 综述 <foreach collection&…...
大学 英文网站建设/湖南关键词优化快速
在页面上显示SWF是要等到目标完全被加载完才会渲染出来。当加载的SWF文件比较大时,页面会处于空白状态,用户体验不好。较好的做法是做一个文件大小较小的loading动画来加载目标SWF,但对于单独的项目来说并不是最好的做法,因为这样…...