HarmonyOS实战开发-如何实现一个简单的健康生活应用

功能概述

成就页面展示用户可以获取的所有勋章,当用户满足一定的条件时,将点亮本页面对应的勋章,没有得到的成就勋章处于熄灭状态。共有六种勋章,当用户连续完成任务打卡3天、7天、30天、50天、73天、99天时,可以获得对应的“连续xx天达成”勋章。

页面布局与 ArkTS 代码对应关系

效果如图所示:

标题部分TitleBar是一个横向容器Row里包含一个子组件Text。

// TitleBarComponent.ets
@Component
export struct TitleBar {build() {Row() {Text($r('app.string.achievement')).fontSize($r('app.float.default_24')).fontColor($r('app.color.white')).align(Alignment.Start).padding({left: Const.ACHIEVE_TITLE_BAR_LEFT,top: Const.ACHIEVE_TITLE_BAR_TOP})}.width(Const.FULL_WIDTH)}
}

每个勋章卡片BadgeCard是由一个竖向容器Column、一个图片子组件Image和一个文字子组件Text组成。

// BadgeCardComponent.ets
@Component
export struct BadgeCard {@Prop content: string = '';imgSrc: Resource = $r('app.string.empty');build() {Column({space: Const.DEFAULT_18}) {Image(this.imgSrc).width(Const.FULL_WIDTH).height(Const.ACHIEVE_CARD_IMG_HEIGHT).objectFit(ImageFit.Contain)Text($r('app.string.task_achievement_level', Number(this.content))).lineHeight($r('app.float.default_16')).fontSize($r('app.float.default_12')).fontColor($r('app.color.white'))}.width(ratio2percent(Const.ACHIEVE_SPLIT_RATIO)).padding({top: Const.ACHIEVE_CARD_TOP, bottom: Const.ACHIEVE_CARD_BOTTOM})}
}

整体的勋章面板使用Flex一个组件即可以实现均分和换行的功能。

// BadgePanelComponent.ets
@Component
export struct BadgePanel {@StorageProp(ACHIEVEMENT_LEVEL_KEY) successiveDays: number = 0;aboutToAppear() {Logger.debug('BadgePanel','aboutToAppear');getAchievementLevel();}build() {Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {ForEach(getBadgeCardItems(this.successiveDays), (item: CardInfo) => {BadgeCard({ content: item.titleContent, imgSrc: item.achievement})})}.width(Const.FULL_WIDTH)}
}

获取数据

进入界面第一次获取数据在aboutToAppear()声明周期中从数据库GlobalInfo表中获取存储的勋章数据, 通过@StorageProp装饰器刷新界面,其他的地方只要通过AppStorage更新勋章数据即可。

// BadgePanelComponent.ets
aboutToAppear() {Logger.debug('BadgePanel','aboutToAppear');getAchievementLevel();
}// AchieveModel.ets
export function getAchievementLevel() {GlobalInfoApi.query((res: GlobalInfo) => {let globalInfo: GlobalInfo = res;let achievementStr = globalInfo.achievements??'';let achievements = achievementStr.split(',');if (achievements.length > 0) {AppStorage.Set<Number>(ACHIEVEMENT_LEVEL_KEY, Number(achievements[achievements.length - 1]));}})
}// BadgePanelComponent.ets
@StorageProp(ACHIEVEMENT_LEVEL_KEY) successiveDays: number = 0;ForEach(getBadgeCardItems(this.successiveDays), (item: CardInfo) => {BadgeCard({ content: item.titleContent, imgSrc: item.achievement})
})// AchievementViewModel.ets
export function getBadgeCardItems(successiveDays: number): Array<CardInfo> {let badgeMileStones = ACHIEVEMENT_LEVEL_LIST;let cardItems: Array<CardInfo> = [];for (let i = 0; i < badgeMileStones.length; i++) {let onOrOff = successiveDays >= badgeMileStones[i] ? 'on' : 'off';let titleContent = String(badgeMileStones[i]);let cardInfo: CardInfo = new CardInfo();cardInfo.titleContent = titleContent;cardInfo.achievement = getAchievement(`${ onOrOff }_${ badgeMileStones[i] }`);cardItems.push(cardInfo);}return cardItems;
}

搭建关系型数据库

本节将介绍如何调用关系型数据库接口在本地搭建数据库,并读写相应的用户数据。

创建数据库

要使用关系型数据库存储用户数据,首先要进行数据库的创建,并提供基本的增、删、查、改接口。

导入关系型数据库模块:

import data_rdb from '@ohos.data.rdb';

关系型数据库提供以下两个基本功能:

获取RdbStore

首先要获取一个RdbStore来操作关系型数据库,代码如下:

// RdbHelperImp.ets
getRdb(context: Context): Promise<RdbHelper> {this.storeConfig = {name: this.mDatabaseName, securityLevel: dataRdb.SecurityLevel.S1};return new Promise<RdbHelper>((success, error) => {dataRdb.getRdbStore(context, this.storeConfig).then(dbStore => {this.rdbStore = dbStore;success(this);}).catch((err: Error) => {Logger.error(`initRdb err : ${JSON.stringify(err)}`);error(err);})})
}

封装增、删、改、查接口

关系型数据库接口提供的增、删、改、查操作均有callback和Promise两种异步回调方式,本Codelab使用了callback异步回调,其中插入数据使用了insert()接口,实现代码如下:

// RdbHelperImp.ets
insert(tableName: string, values: dataRdb.ValuesBucket | Array<dataRdb.ValuesBucket>): Promise<number> {return new Promise<number>((success, error) => {Logger.info(`insert tableName : ${tableName}, values : ${JSON.stringify(values)}`);if (!values) {Logger.info(`insert failed, values is undefined`);error(0);return;}if (values instanceof Array) {Logger.info(`insert values isArray = ${values.length}`);this.rdbStore.beginTransaction();this.saveArray(tableName, values).then(data => {Logger.info(`insert success, data : ${JSON.stringify(data)}`);success(data);this.rdbStore.commit();}).catch((err: Error) => {Logger.error(`insert failed, err : ${err}`);error(err);this.rdbStore.commit();})} else {this.rdbStore.insert(tableName, values).then(data => {Logger.info(`insert success id : ${data}`);success(data);his.rdbStore.commit();}).catch((err: Error) => {Logger.error(`insert failed, err : ${JSON.stringify(err)}`);error(err);this.rdbStore.commit();})}})
}

删除数据使用了delete()接口,实现代码如下:

// RdbHelperImp.ets
delete(rdbPredicates: dataRdb.RdbPredicates): Promise<number> {Logger.info(`delete rdbPredicates : ${JSON.stringify(rdbPredicates)}`);return this.rdbStore.delete(rdbPredicates);
}

更新数据使用了update()接口,实现代码如下:

// RdbHelperImp.ets
update(values: dataRdb.ValuesBucket, rdbPredicates: dataRdb.RdbPredicates): Promise<number> {return this.rdbStore.update(values, rdbPredicates);
}

查找数据使用了query()接口,实现代码如下:

// RdbHelperImp.ets
query(rdbPredicates: dataRdb.RdbPredicates, columns?: Array<string>): Promise<dataRdb.ResultSet> {Logger.info(`query rdbPredicates : ${JSON.stringify(rdbPredicates)}`);return this.rdbStore.query(rdbPredicates, columns);
}

数据库表结构

根据健康生活APP的使用场景和业务逻辑,定义了三个数据对象,并使用三张数据表来存储,分别是健康任务信息表、每日信息表和全局信息表。

健康任务信息表

目前健康生活应用提供了6个基本的健康任务,分别是早起、喝水、吃苹果、每日微笑、睡前刷牙和早睡。用户可以选择开启或关闭某个任务,开启的任务可以选择是否开启提醒,在指定的时间段内提醒用户进行打卡。任务也可以选择开启的频率,如只在周一到周五开启等。需要记录每项任务的目标值和实际完成值,在用户打卡后判断任务是否已经完成,并记录在数据库中。因此,需要创建一张存储每天的健康任务信息的表,表头如下:

每日信息表

在主页面,用户可以查看当天健康任务的完成进度,需要创建一张表记录当天开启的任务个数和已经完成的任务个数,表头如下:

全局信息表

用户连续多日打卡完成所有创建的任务可以获得相应的成就,因此,需要有一张表记录连续打卡天数和已达成的成就项。另外,考虑应用多日未打开的情况,需要记录应用第一次打开的日期和最后一次打开的日期以向数据库回填数据,表头如下:

创建数据表

根据6.2中设计的表结构,创建对应的数据表,实现对相应数据的读写操作。

健康任务信息数据表

在获取RdbStore后,需要使用executeSql接口执行SQL语句来创建相应的表结构和初始化数据,SQL语句如下:

CREATE TABLE IF NOT EXISTS taskInfo(id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, taskID INTEGER, targetValue TEXT NOT NULL, isAlarm BOOLEAN, startTime TEXT NOT NULL, endTime TEXT NOT NULL, frequency TEXT NOT NULL, isDone BOOLEAN, finValue TEXT NOT NULL, isOpen BOOLEAN
)

健康任务信息数据表需要提供插入数据的接口,以在用户当天第一次打开应用时创建当天的健康任务信息,实现代码如下:

// TaskInfoApi.ets
insertData(taskInfo: TaskInfo, callback: Function): void {// 根据输入数据创建待插入的数据行const valueBucket = generateBucket(taskInfo);RdbUtils.insert('taskInfo', valueBucket).then(result => {callback(result);});Logger.info('TaskInfoTable', `Insert taskInfo {${taskInfo.date}:${taskInfo.taskID}} finished.`);
}

其中generateBucket()代码如下:

// TaskInfoApi.ets
function generateBucket(taskInfo: TaskInfo): dataRdb.ValuesBucket {let valueBucket = {} as dataRdb.ValuesBucket;Const.TASK_INFO.columns?.forEach((item: string) => {if (item !== 'id') {switch (item) {case 'date':valueBucket[item] = taskInfo.date;break;case 'taskID':valueBucket[item] = taskInfo.taskID;break;case 'targetValue':valueBucket[item] = taskInfo.targetValue;break;case 'isAlarm':valueBucket[item] = taskInfo.isAlarm;break;case 'startTime':valueBucket[item] = taskInfo.startTime;break;case 'endTime':valueBucket[item] = taskInfo.endTime;break;case 'frequency':valueBucket[item] = taskInfo.frequency;break;case 'isDone':valueBucket[item] = taskInfo.isDone;break;case 'finValue':valueBucket[item] = taskInfo.finValue;break;case 'isOpen':valueBucket[item] = taskInfo.isOpen;break;default:break;}}});return valueBucket;
}

用户开启和关闭任务,改变任务的目标值、提醒时间、频率等,用户打卡后修改任务的实际完成值都是通过更新数据接口来实现的,代码如下:

// TaskInfoApi.ets
updateDataByDate(taskInfo: TaskInfo, callback: Function): void {const valueBucket = generateBucket(taskInfo);let tableName = Const.TASK_INFO.tableName;if (!tableName) {return;}let predicates = new dataRdb.RdbPredicates(tableName);// 根据date和taskID匹配要更新的数据行predicates.equalTo('date', taskInfo.date).and().equalTo('taskID', taskInfo.taskID);RdbUtils.update(valueBucket, predicates).then((result: number) => {callback(result);});Logger.info('TaskInfoTable', `Update data {${taskInfo.date}:${taskInfo.taskID}} finished.`);
}

用户可以查看当天和以前某日的健康任务信息,需要提供查找数据接口,实现代码如下:

// TaskInfoApi.ets
query(date: string, isOpen: boolean = true, callback: Function): void {let tableName = Const.TASK_INFO.tableName;if (!tableName) {return;}let predicates = new dataRdb.RdbPredicates(tableName);predicates.equalTo('date', date);// 如果isOpen为true,则只查找开启的任务 if (isOpen) {predicates.equalTo('isOpen', true);}predicates.orderByAsc('taskID');  // 查找结果按taskID排序RdbUtils.query(predicates).then(resultSet => {let count = resultSet.rowCount;// 查找结果为空则返回空数组,否则返回查找结果数组if (count === 0 || typeof count === 'string') {Logger.error('TaskInfoTable', `${date} query no results!`);const result: TaskInfo[] = [];callback(result);} else {resultSet.goToFirstRow();const result: TaskInfo[] = [];for (let i = 0; i < count; i++) {let tmp = new TaskInfo(0, '', 0, '', false, '', '', '', false, '');tmp.isOpen = resultSet.getDouble(resultSet.getColumnIndex('isOpen')) ? true : false;tmp.id = resultSet.getDouble(resultSet.getColumnIndex('id'));tmp.date = resultSet.getString(resultSet.getColumnIndex('date'));tmp.taskID = resultSet.getDouble(resultSet.getColumnIndex('taskID'));tmp.targetValue = resultSet.getString(resultSet.getColumnIndex('targetValue'));tmp.isAlarm = resultSet.getDouble(resultSet.getColumnIndex('isAlarm')) ? true : false;tmp.startTime = resultSet.getString(resultSet.getColumnIndex('startTime'));tmp.endTime = resultSet.getString(resultSet.getColumnIndex('endTime'));tmp.frequency = resultSet.getString(resultSet.getColumnIndex('frequency'));tmp.isDone = resultSet.getDouble(resultSet.getColumnIndex('isDone')) ? true : false;tmp.finValue = resultSet.getString(resultSet.getColumnIndex('finValue'));result[i] = tmp;resultSet.goToNextRow();}callback(result);}});
}

每日信息数据表

创建每日信息数据表的SQL语句如下:

CREATE TABLE IF NOT EXISTS dayInfo(date TEXT NOT NULL PRIMARY KEY, targetTaskNum INTEGER, finTaskNum INTEGER
)

在当天第一次打开应用时需要初始化每日信息数据,页面需要根据用户编辑任务和打卡的情况来更新当天目标任务个数和完成任务个数,所以需要提供插入数据和更新数据的接口,写法与上一条中相应接口类似,不再赘述。

页面需要查找对应日期的目标任务个数和完成任务个数用以在页面显示任务进度,因此需要查找数据的接口。且页面在打开时需要显示当周每天任务的完成情况,因此需要允许一次调用查找一周的每日任务信息。实现代码如下:

// DayInfoApi.ets
queryList(dates: string[], callback: Function): void {let predicates: dataRdb.RdbPredicates = new dataRdb.RdbPredicates(Const.DAY_INFO.tableName ? Const.DAY_INFO.tableName : '');predicates.in('date', dates);  // 匹配日期数组内的所有日期RdbUtils.query(predicates).then(resultSet => {let count = resultSet.rowCount;if (count === 0) {Logger.info('DayInfoTable', 'query no results.');let result: DayInfo[] = [];callback(result);} else {resultSet.goToFirstRow();let result: DayInfo[] = [];for (let i = 0; i < count; i++) {let tmp = new DayInfo('', 0, 0);tmp.date = resultSet.getString(resultSet.getColumnIndex('date'));tmp.targetTaskNum = resultSet.getDouble(resultSet.getColumnIndex('targetTaskNum'));tmp.finTaskNum = resultSet.getDouble(resultSet.getColumnIndex('finTaskNum'));result[i] = tmp;resultSet.goToNextRow();}callback(result);}});
}

全局信息数据表

创建全局信息数据表的SQL语句如下:

CREATE TABLE IF NOT EXISTS globalInfo(id INTEGER PRIMARY KEY, firstDate TEXT NOT NULL, lastDate TEXT NOT NULL, checkInDays INTEGER, achievements TEXT NOT NULL
)

全局信息数据表同样需要提供插入数据、更新数据和查找数据的接口,写法与本节前两条中相应接口类似,不再赘述。

数据库初始化

应用首次打开时,数据库中没有数据,要做数据库的初始化,写入一组空数据。另外,如果用户连续几天没有打开APP,再次打开时需要将数据回写至数据库。因此需要实现一个数据库接口,在应用打开时调用,进行上述操作。代码如下:

// DatabaseModel.ets
query(date: string, callback: Function) {let result: TaskInfo[] = [];let self = this;GlobalInfoApi.query((globalResult: GlobalInfo) => {if (!globalResult.firstDate) { // 如果找不到全局信息,则写入let globalInfo: GlobalInfo = new GlobalInfo(date, date, 0, '');GlobalInfoApi.insertData(globalInfo, (isDone: number) => {if (isDone) {Logger.info('AppStart', 'Insert globalInfo success: ' + JSON.stringify(globalInfo));}});self.insertGlobalTask();let dayInfo: DayInfo = new DayInfo(date, 0, 0);DayInfoApi.insertData(dayInfo, (isDone: number) => {if (isDone) {Logger.info('AppStart', 'Insert dayInfo success: ' + JSON.stringify(dayInfo));}})self.insertTask(date);callback(result, dayInfo);} else { // 如果找到全局信息,则查询当天的任务信息let newGlobalInfo = globalResult;let preDate = globalResult.lastDate;newGlobalInfo.lastDate = date;GlobalInfoApi.updateData(newGlobalInfo, (isDone: number) => {if (isDone) {Logger.info('AppStart', 'update globalInfo success: ' + JSON.stringify(newGlobalInfo));}});self.queryPreInfo(date, preDate, result, callback);}});
}

编写通用工具类

本节将介绍日志打印、时间换算等通用工具类的编写和使用,工具类可以简化应用代码编写和业务流程处理。

日志类

日志类Logger旨在提供一个全局的日志打印、日志管理的地方,既可以规范整个应用的日志打印,也方便日后对日志工具类进行修改,而不需要去改动代码中每一个调用日志的地方,如切换具体的日志实现类(比如不使用Console而是HiLog),将日志记录到本地文件等。

Logger对外的日志API全部使用静态方法,方便调用者使用,目前分verbose,debug,info,warn,error五个级别。

使用方法如下:

  1. import Logger日志类:
import { Logger } from '../utils/log/Logger';

2.调用对应级别的静态方法:

Logger.debug('MyAbilityStage', 'onCreate');

3.Logger类中包括debug、info、warn、error,具体内容如下:

// Logger.ets  
import hilog from '@ohos.hilog';const LOGGER_PREFIX: string = 'Healthy_life';class Logger {private domain: number;private prefix: string;...constructor(prefix: string = '', domain: number = 0xFF00) {this.prefix = prefix;this.domain = domain;}debug(...args: string[]): void {hilog.debug(this.domain, this.prefix, this.format, args);}info(...args: string[]): void {hilog.info(this.domain, this.prefix, this.format, args);}warn(...args: string[]): void {hilog.warn(this.domain, this.prefix, this.format, args);}error(...args: string[]): void {hilog.error(this.domain, this.prefix, this.format, args);}
}export default new Logger(LOGGER_PREFIX, 0xFF02);

时间工具

为全局提供时间工具,避免重复定义。

  1. 常用时间相关常量:
  // Utils.etsconst CHINESE_OF_WEEK: string[] = ['一', '二', '三', '四', '五', '六', '日'];const YEAR: string = '年';const MONTH: string = '月';const DAY: string = '日';const WEEK: string = '星期';DAYS_OF_WEEK: number = 7;const SUNDAY_FIRST_SHIFT: number = 6;
  1. 时间函数示例(由时间常量衍生出星期一到星期日和数字 1-7 的字典映射):
// Utils.ets
export const oneWeekDictFunc = () => {const oneWeekDict: Array<string> = [];for (let index = 0;index < CHINESE_OF_WEEK.length; index++) {oneWeekDict[index] = `${WEEK}${CHINESE_OF_WEEK[index]}`;}return oneWeekDict;
}

单位转换工具

把比例等分浮点数转换为百分比字符串。

例如成就页面,每一行平均分布三个徽章,可以先定义一个浮点数代表等分比例,再转换为百分比字符串。

// Utils.ets
export function ratio2percent(ratio: number): string {return `${ratio * 100}%`;
}

使用方法如下:

  1. import 工具方法:
import { ratio2percent } from '../common/utils/Utils'

2.引用工具方法 ( 例如成就页面,每个徽章占据屏幕宽度的三分之一 ) :

// BadgeCardComponent.ets
Column({space: commonConst.DEFAULT_18}) { ...  // 省略徽章卡片的 UI 布局细节
}
.width(ratio2percent(Const.ACHIEVE_SPLIT_RATIO)) // achieveConst.ACHIEVE_SPLIT_RATIO = 1 / 3

事件分发类

事件分发类提供应用全局的事件注册,分发,接受,可以实现组件之间的解耦。

事件分发类全局共享一个实例, 将事件处理统一管理(HealthDataSrcMgr是单例):

获取事件分发实例

// HomeComponent.ets
@Provide broadCast: BroadCast = HealthDataSrcMgr.getInstance().getBroadCast();// HealthDataSrcMgr.ets
public getBroadCast(): BroadCast {return this.broadCast;
}

事件注册:

// CustomDialogView.ets
aboutToAppear() {Logger.debug('CustomDialogView', 'aboutToAppear'); // 成就对话this.broadCast.on(BroadCastType.SHOW_ACHIEVEMENT_DIALOG, (achievementLevel: number) => {Logger.debug('CustomDialogView', 'SHOW_ACHIEVEMENT_DIALOG');this.achievementLevel = achievementLevel;this.achievementDialog.open();});// 任务时钟对话框this.broadCast.on(BroadCastType.SHOW_TASK_DETAIL_DIALOG,(currentTask: TaskInfo, dialogCallBack: CustomDialogCallback) => {Logger.debug('CustomDialogView', 'SHOW_TASK_DETAIL_DIALOG');this.currentTask = currentTask || TaskItem;this.dialogCallBack = dialogCallBack;this.taskDialog.open();});
}// BroadCast.ets
public on(event: string, callback: Function) {Logger.info(FILE_TAG, 'register broadcast with type '+ event);switch (event) {case BroadCastType.SHOW_ACHIEVEMENT_DIALOG:this.callBackArray.showAchievementDialog = callback;break;case BroadCastType.SHOW_TASK_DETAIL_DIALOG:this.callBackArray.showTaskDetailDialog = callback;break;case BroadCastType.SHOW_TARGET_SETTING_DIALOG:this.callBackArray.showTargetSettingDialog = callback;break;case BroadCastType.SHOW_REMIND_TIME_DIALOG:this.callBackArray.showRemindTimeDialog = callback;break;case BroadCastType.SHOW_FREQUENCY_DIALOG:this.callBackArray.showFrequencyDialog = callback;break;default:break;}
}

取消事件注册:

// TaskDetailComponent.ets
aboutToAppear() {this.broadCast.off(BroadCastType.SHOW_TARGET_SETTING_DIALOG, () => {});this.broadCast.off(BroadCastType.SHOW_REMIND_TIME_DIALOG, () => {});this.broadCast.off(BroadCastType.SHOW_FREQUENCY_DIALOG, () => {});
}// BroadCast.ets
public off(event: string, callback: Function) {if (event === null) {Logger.info(FILE_TAG, 'cancel all broadcast');this.callBackArray = callBackArrayTemp;}Logger.info(FILE_TAG, 'cancel broadcast with type '+ event);const cbs = this.callBackArray;if (!cbs) {return;}if (callback === null) {switch (event) {case BroadCastType.SHOW_ACHIEVEMENT_DIALOG:this.callBackArray.showAchievementDialog = () => {};break;case BroadCastType.SHOW_TASK_DETAIL_DIALOG:this.callBackArray.showTaskDetailDialog = () => {}; break;case BroadCastType.SHOW_TARGET_SETTING_DIALOG:this.callBackArray.showTargetSettingDialog = () => {};break;case BroadCastType.SHOW_REMIND_TIME_DIALOG: this.callBackArray.showRemindTimeDialog = () => {};break;case BroadCastType.SHOW_FREQUENCY_DIALOG:this.callBackArray.showFrequencyDialog = () => {};break;default:break;}}
}

发送事件:

// HomeComponent.ets
taskItemAction(item: TaskInfo, isClick: boolean): void {if (!this.homeStore.checkCurrentDay()) {return;}if (isClick) {// 点击时钟let callback: CustomDialogCallback = { confirmCallback: (taskTemp: TaskInfo) => {this.onConfirm(taskTemp)}, cancelCallback: () => {} };this.broadCast.emit(BroadCastType.SHOW_TASK_DETAIL_DIALOG, [item, callback]);} else {// 编辑任务let editTaskStr: string = JSON.stringify(TaskMapById[item.taskID - 1]);let editTask: ITaskItem = JSON.parse(editTaskStr);editTask.targetValue = item?.targetValue;editTask.isAlarm = item.isAlarm;editTask.startTime = item.startTime;editTask.frequency = item.frequency;editTask.isOpen = item.isOpen;router.pushUrl({ url: 'pages/TaskEditPage', params: { params: JSON.stringify(editTask) } });}
}

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

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

相关文章

SpringBoot框架——8.MybatisPlus常见用法(常用注解+内置方法+分页查询)

1.MybatisPlus常用注解&#xff1a; 1.1 当数据库、表名和字段名和实体类完全一致时无需加注解&#xff0c;不一致时&#xff1a; TableName指定库名 TableId指定表名 TableField指定字段名 1.2 自增主键&#xff1a; TableId(typeIdType.AUTO) private Long id; 1.3 实体类中属…

2000-2022年各省人力资本水平数据(含原始数据+计算过程+计算结果)(无缺失)

2000-2022年各省人力资本水平数据&#xff08;含原始数据计算过程计算结果&#xff09; 1、时间&#xff1a;2000-2022年 2、来源&#xff1a;国家统计局 3、指标&#xff1a;普通高等学校在校学生数(万人)、年末常住人口&#xff08;万人&#xff09;、人力资本水平 4、范…

CTFshow-PWN-前置基础(pwn20)

提交ctfshow{【.got表与.got.plt是否可写(可写为1&#xff0c;不可写为0)】,【.got的地址】,【.got.plt的地址】 前置基础知识&#xff1a; .got 和 .got.plt 是 ELF&#xff08;Executable and Linkable Format&#xff0c;可执行和可链接格式&#xff09;二进制文件中的两个…

(四)qt中使用ffmpeg播放视频,可暂停恢复

一、在qt中添加ffmpeg库及头文件 INCLUDEPATH /usr/local/ffmpeg/include LIBS -L/usr/local/lib -lavutil -lavcodec -lavformat -lswscale 二、详细代码 FFempegVideoDecode 视频解码类&#xff08;放入线程中&#xff09; ffmpegvideodecode.h #ifndef FFMPEGVIDEODE…

RHCE作业二

一.配置server主机要求如下&#xff1a; 1.server主机的主机名称为 ntp_server.example.com 2.server主机的IP为&#xff1a; 172.25.254.100 3.server主机的时间为1984-11-11 11&#xff1a;11&#xff1a;11 4.配置server主机的时间同步服务要求可以被所有人使用 二.设定cli…

Http 请求偶发400错误

1. 背景 生产环境偶发400请求错误&#xff0c;发生概率万分之一&#xff0c;异常信息如下&#xff1a; 1&#xff09; 从异常信息可以看到&#xff0c;skywalking的sw8 header解析失效导致异常信息。 2&#xff09; 0x0d0x0a 作为回车换行符号&#xff0c;没有被正确处理&#…

OpenGL:图元

OpenGL的图元 点 GL_POINTS: 将顶点绘制成单个的点 线 GL_LINES:将顶点用于创建线段,2个点成为一条单独的线段。如果顶点个数是奇数,则忽略最后一个。 顶点:v0, v1, v2, v3, … , vn,线段:v0-v1, v2-v3, v4-v5, … , vn-1 - vn GL_LINE_STRIP:将顶点用于创建线段,…

学习笔记(4月18日)vector底层模拟实现(1)

1.迭代器 vector实际上是由迭代器进行维护的&#xff0c;关于迭代器是什么&#xff0c;为什么要叫这个名字&#xff0c;后面的学习会逐渐了解&#xff0c;现在先将迭代器是作为指针即可。 vector底层有三个迭代器&#xff0c;用来起到容量、数组头、元素个数的作用。 同时为…

基于XML配置bean(一)

文章目录 1.获取bean的两种方式1.通过id获取bean&#xff08;前面用过&#xff09;2.通过类型获取bean&#xff08;单例时使用&#xff09;1.案例2.代码1.beans.xml2.SpringBeanTest.java3.结果 3.注意事项 2.三种基本依赖注入方式1.通过属性配置bean&#xff08;前面用过&…

DDoS攻击趋势分析及防御建议:网络安全新挑战与应对策略

在数字化日益普及的今天&#xff0c;网络安全问题日益凸显。其中&#xff0c;分布式拒绝服务&#xff08;DDoS&#xff09;攻击以其巨大的破坏力和难以防范的特性&#xff0c;发起简单、效果显著、难以追踪等特点&#xff0c;因此被黑客广泛使用&#xff0c;已经成为网络安全领…

Python(九十四)变量的作用域

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

CSS 设置空格原样显示 white-space:pre-wrap;

CSS 设置空格原样显示 问题描述 html 渲染内容时&#xff0c;对于 空格、回车、Tab 键的 默认处理方式是 &#xff1a; 无论存在多少个连续的空格&#xff0c;都只会保留一个。 结论 由于以上的特性&#xff0c;导致了我们无法直接渲染出原格式的文本。pre 标签 了解一下 &…

element-plus中的图标和文字水平对齐

<span><el-icon size"14px"><Delete /></el-icon> <span>删除</span> </span>解决方法&#xff1a;加上vertical-align: middle样式就可以了 <span><el-icon size"14px" style"vertical-align: …

【STM32CubeIDE 1.15.0】汉化包带路径配置过程

一、IDE软件下载 二、汉化版包路径 三、IDE软件板载汉化包 一、IDE软件下载 ST官网IDE下载链接 二、汉化版包路径 https://mirrors.ustc.edu.cn/eclipse/technology/babel/update-site/ 找不到就到.cn后面一级一级进 三、IDE软件板载汉化包 https://mirrors.ustc.edu…

数据库工具解析之 OceanBase 数据库导出工具

背景 大多数的数据库都配备了自己研发的导入导出工具&#xff0c;对于不同的使用者来说&#xff0c;这些工具能够发挥不一样的作用。例如&#xff1a;DBA可以使用导数工具进行逻辑备份恢复&#xff0c;开发者可以使用导数工具完成系统间的数据交换。这篇文章主要是为OceanBase…

​波士顿动力发布全新人形机器人:Atlas

4月16日&#xff0c;波士顿动力&#xff08;Boston Dynamics&#xff09;发布了《再见&#xff0c;液压Atlas》视频&#xff0c;正式宣告其研发的液压驱动双足人形机器人Atlas退役。 在视频的结尾&#xff0c;Atlas深深鞠躬&#xff0c;之后还有一句话“直到我们再次相遇&…

B1098 岩洞施工

solution #include<iostream> using namespace std; int main(){int n, x, top 1000, down 0;//管道水平放入>顶部最低点和底部最高点之间的距离就是能够承担的最大宽度scanf("%d", &n);for(int i 0; i < n; i){scanf("%d", &x);i…

3D模型处理的多进程并行【Python】

今天我们将讨论如何使用 Python 多进程来处理大量3D数据。 我将讲述一些可能在手册中找到的一般信息&#xff0c;并分享我发现的一些小技巧&#xff0c;例如将 tqdm 与多处理 imap 结合使用以及并行处理存档。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生…

AI论文速读 | 2024[VLDB]TFB:全面与公正的时间序列预测方法基准测试研究

论文标题&#xff1a;TFB: Towards Comprehensive and Fair Benchmarking of Time Series Forecasting Methods 作者&#xff1a;Xiangfei Qiu ; Jilin Hu&#xff08;胡吉林&#xff09; ; Lekui Zhou ; Xingjian Wu ; Junyang Du ; Buang Zhang ; Chenjuan Guo&#xff08;郭…

【软件】如何下载谷歌安装包?

1、访问谷歌浏览器官网&#xff1a;https://www.google.cn/chrome/index.html 2、在浏览器地址栏最后添加?standalone1&#xff0c;按回车&#xff0c;重新加载页面。页面和之前的一样&#xff0c;点击下载 完整地址&#xff1a;https://www.google.cn/chrome/index.html?…