鸿蒙应用开发—数据持久化之SQLite
文章目录
- SQLite简介
- 创建数据库
- 添加数据
- 查询数据
- 更新数据
- 删除数据
- 升级数据库
- 使用事务
- 参考
SQLite简介
SQLite是一个轻量级关系数据库,占用资源很少,只有几百KB的大小,无需服务器支撑,是一个零配置、事务性的SQL数据库引擎。
相对于首选项Preferences,SQLite更适合存储大量复杂的关系型数据,首选项则适合于保存一些简单的键值对数据;比如IM应用的聊天会话信息的本地存储,用首选项存储是明显是不合适,因为其数据量是极大的,数据关系结构也很复杂,在这方面首选项是明显是不合适的,SQLite则可以很轻松存储操作这些数据。那么SQLite在鸿蒙中是如何使用的,下面会一一讲解。
创建数据库
在@kit.ArkData方舟数据管理模块中,为开发者提供数据存储、数据管理和数据同步能力,而SQLite的服务则在这个模块中,专门提供了一个relationalStore来辅助创建数据库。我们以一个用户信息为例,创建一个名称是user.db的数据库,首先创建DBUtils类来管理数据库的行为操作。
import { relationalStore } from '@kit.ArkData'
import AppUtils from './AppUtils'
export default class DBUtils {private static rdbStore?: relationalStore.RdbStoreprivate constructor() {}static init(callback: Function = (state: boolean, msg?: string) => {}) {const context = AppUtils.getContext()// 数据库配置const STORE_CONFIG: relationalStore.StoreConfig = {name: 'user.db', // 数据库名称securityLevel: relationalStore.SecurityLevel.S1, // 数据库安全级别encrypt: false, // 可选参数,指定数据库是否加密,默认不加密} as relationalStore.StoreConfig// 数据库文件的默认存储路径,可通过 customDir修改路径console.log(`${TAG} db dir: `, context.databaseDir)// 1、获取RdbStore实例,用于操作数据库relationalStore.getRdbStore(context, STORE_CONFIG).then((r) => {DBUtils.rdbStore = rconsole.log(TAG, 'db create success')callback(true)}).catch((err: Error) => {console.error(`${TAG} db create error: `, err.message)callback(false, err.message)})}
}
上面代码是配置和初始化数据库相关的配置,主要步骤:
- 创建一个STORE_CONFIG的对象,包含了数据库配置的信息,有数据库名称、安全级别和加密状态。name是数据库文件名称,值是user.db,安全级别是relationalStore.SecurityLevel.S1,表示数据库的安全级别为低级别,当数据泄露时会产生较低影响,是不加密的状态。
- 通过relationalStore获取RdbStore实例,这是操作数据库的接口,通过调用relationalStore.getRdbStore 函数并传入上下文和配置对象来实现。在创建数据库成功后,会执行then代码块,接着将RdbStore实例赋值给DBUtils.rdbStore,这样使得这个实例可以被 DBUtils 类的其他方法使用。
- 最后在外部触发调用DBUitls的init()方法就完成了数据库的创建。当在控制台有打印
db create success
日志则表示数据库文件创建成功了。
数据库的安全级别除了S1,还有S2、S3、S4,如下:
属性 | 值 | 概述 |
---|---|---|
S1 | 1 | 表示数据库的安全级别为低级别,当数据泄露时会产生较低影响。例如,包含壁纸等系统数据的数据库。 |
S2 | 2 | 表示数据库的安全级别为中级别,当数据泄露时会产生较大影响。例如,包含录音、视频等用户生成数据或通话记录等信息的数据库。 |
S3 | 3 | 表示数据库的安全级别为高级别,当数据泄露时会产生重大影响。例如,包含用户运动、健康、位置等信息的数据库。 |
S4 | 4 | 表示数据库的安全级别为关键级别,当数据泄露时会产生严重影响。例如,包含认证凭据、财务数据等信息的数据库。 |
上面AppUtils是一个简单的工具类,用于存储全局context实例,代码如下所示:
import { common } from '@kit.AbilityKit'export default class AppUtils {private constructor() {}private static context: common.UIAbilityContextstatic init(context: common.UIAbilityContext) {AppUtils.context = context}static getContext(): common.UIAbilityContext {if (!AppUtils.context) {throw new Error('在EntryAbility类的onCreate()方法中调用init()方法完成初始化')}return AppUtils.context}
}
通常会在EntryAbility的onCreate()方法中初始化,代码如下所示:
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {AppUtils.init(this.context)
}
如果我们需要查看数据库文件在设备中的位置,可以通过context上下文获取数据库文件目录,代码如下所示:
// 数据库文件的默认存储路径,可通过 customDir修改路径
const context = AppUtils.getContext()
console.log(`${TAG} db dir: `, context.databaseDir)
应用创建的数据库与其上下文(Context)有关,即使使用同样的数据库名称,但不同的应用上下文,会产生多个数据库,例如每个UIAbility都有各自的上下文。
在控制台我们可以看到打印数据库文件的默认存储路径,使用 console.log 来展示数据库目录的路径。如下:
DBUtils db dir: /data/storage/el2/database/entry
从日志可知数据库文件默认位置是/data/storage/el2/database/entry,但在DevEco Studio 5.0.3.400 API12 上,发现没有data目录下没有storage目录,反而在app目录下可以找到对应的数据库文件,完整的文件路径是/data/app/el2/database/entry。
我们可以在DevEco Studio编译器中的Device File Browser工具栏中可以查看到数据库文件。如下图所示:
如果希望移动数据库文件到其它地方使用查看,则需要同时移动这些以-wal和-shm结尾的临时文件。
在创建数据库文件后,此时就要创建数据表来描述数据,这里以创建一个USER表为例,user表包括了id,name、age、sex 、height、weight属性,然后通过RdbStore实例的executeSql()方法执行创建数据表的SQL语句,代码如下所示:
static createTable() {// 创建USER表的SQL语句const CREATE_TABLE_USER ='CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, weight REAL)'// 2、创建表DBUtils.rdbStore?.executeSql(CREATE_TABLE_USER).then(() => {console.log(TAG, 'create table done')}).catch((err: BusinessError) => {console.error(TAG, err.message)})}
DBUtils.rdbStore对象实例还记得是如何获取的吗,在创建数据库文件时通过relationalStore.getRdbStore()来获取的,这里把RdbStore实例赋值给了DBUtils类的rdbStore属性。如果数据表创建异常则会执行catch代码块,创建成功则执行then代码块。
在完成创建数据USER表后,如何可视化查看其内容呢,除了将user.db数据库文件导出通过SQLite Studio查看外,我们还可以借助于IDE的Database Navigator插件,目前在DevEco Studio的Setting -> Plugins 是无法搜索到该插件,但在其他IDE(Android Studio、intellij idea)是正常能搜索安装的,既然在线无法安装,我们可以去JetBrains 插件应用市场手动下载后,离线安装。
打开网址https://plugins.jetbrains.com/idea 搜索下载
下载后是一个压缩包无需解压,然后在DevEco Studio -> Settings -> Pulgins 安装离线包,选择 Install plugin from Disk选项,选择下载的压缩包进行安装,完成安装后需要重启DevEco Studio才会生效,如下图所示:
安装成功后会在DevEco Studio的左侧边栏,现在应该多出了一个DB Browser工具,然后接着回到Device File Browser工具栏打开数据库user.db、user.db-shm和user.db-wal三个文件,右击→Save As,将它从移动设备导出到你的计算机的任意位置。在DB Browser中选择SQLite,如下图所示:
最后在弹出窗中选择刚才导出的user.db数据库文件,然后点击OK完成配置。
完成配置后,在IDE左侧的DB Browser工具栏可以USER表的信息,如下图所示:
添加数据
对数据库的操作无非就是CRUD,C代表添加(create),R代表查询(retrieve),U代表更新(update),D代表删除(delete);每个操作都有对应的SQL语句,其中添加数据是insert,查询数据是select,更新数据是update,删除数据是delete。
使用RdbStore的insert()方法添加参数,第一个参数是表名,第二参数是要插入到表中的数据,是ValuesBucket对象,需要将表中每一列设置对应的值,是一个异步方法,插入成功则返回的数据在表中的行数,插入失败则返回-1,代码所下所示:
static insert() {let item: relationalStore.ValuesBucket = {name: 'lili',age: 18,sex: 0,height: 160,weight: 45,};let item2: relationalStore.ValuesBucket = {name: 'hzw',age: 28,sex: 1,height: 180,weight: 60,};// 插入数据DBUtils.rdbStore?.insert(DBUtils.tableName, item).then((r) => {console.log(TAG, 'insert success: ', r);DBUtils.rdbStore?.commit()}).catch((e: Error) => {console.log(TAG, 'insert err: ', e.message);});DBUtils.rdbStore?.insert(DBUtils.tableName, item2).then((r) => {console.log(TAG, 'insert success: ', r);DBUtils.rdbStore?.commit()}).catch((e: Error) => {console.log(TAG, 'insert err: ', e.message);});}
在上面代码添加了两条数据,首先创建ValuesBucket对象,对表中的每一列赋值,你会发现我们并没有给id赋值,这是因为在创建USER表时我们将id设置了自增,然后通过DBUtils.rdbStore对象的insert方法,用来将数据item插入到名为DBUtils.tableName的表中,DBUtils.rdbStore对象是前面已提及到了,是rdbStore的实例,而DBUtils.tableName的值是USER。
接下来测试添加数据的insert()方法,给布局添加插入数据的按钮,如下图所示:
@Entry
@Component
struct Index {build() {Column() {Scroll() {Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceEvenly }) {Button('创建数据库').btnStyle(OperateType.CREATE_DB)Button('创建User表').btnStyle(OperateType.CREATE_TABLE)Button('插入数据').btnStyle(OperateType.INSERT)}.width('100%').height('30%')}}.height('100%').width('100%')}
}@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {.margin({ top: '12vp' }).onClick(() => {switch (type) {case OperateType.CREATE_DB:DBUtils.init()breakcase OperateType.CREATE_TABLE:DBUtils.createTable()breakcase OperateType.INSERT:// 添加数据DBUtils.insert()break}})
}class OperateType {static readonly CREATE_DB: number = 0static readonly CREATE_TABLE: number = 1static readonly INSERT: number = 2static readonly DELETE: number = 3static readonly UPDATE: number = 4static readonly QUERY: number = 5
}
上面底代码运行程序后,如下图所示:
点击“插入数据”按钮,会调用 DBUtils.insert()方法,将两条数据便会添加到USER数据表中,在insert()异步方法中的then代码块中会返回数据在表中行数,则表示数据添加成功。另外我们也通过DB Browser查看,将user.db等三个数据库文件重新导出到指定目录,由于之前已连接数据库,重新导出覆盖后,点击数据USER表自动刷新重载。如下图所示:
从上图可知,我们已成功添加了两条数据到USER表中。
查询数据
在添加数据案例中,我们是通过DB Browser工具查看数据的,在实际开发中通常会通过SQL语句来查询数据,在鸿蒙中RdbStore实例提供相关query()查询数据的方法。代码如下所示:
static query() {// 创建RdbPredicates实例let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);//equalTo 方法的第一个参数是列名,第二个参数是列值// 查询name=lili的数据predicates.equalTo("name", "lili")DBUtils.rdbStore?.query(predicates).then((r) => {let items: Array<relationalStore.ValuesBucket> = []// 遍历查询结果while (r.goToNextRow()) {// 获取当前行的数据const row = r.getRow()items.push(row)}console.log(TAG, 'query success: ', JSON.stringify(items, null, 2));// 关闭查询结果集r.close()}).catch((e: Error) => {console.log(TAG, 'query err: ', e.message);})// 执行SQL语句 查询USER表的所有数据DBUtils.rdbStore?.querySql(`SELECT * FROM ${DBUtils.tableName}`).then((r) => {let items: Array<relationalStore.ValuesBucket> = []// 遍历查询结果while (r.goToNextRow()) {// 获取当前行的数据const row = r.getRow()items.push(row)}console.log(TAG, 'querySql success: ', JSON.stringify(items, null, 2));// 关闭查询结果集r.close()}).catch((e: Error) => {console.log(TAG, 'querySql err: ', e.message);})}
上面代码中query()和querySql()两个不同方法来查询数据,query()方法需要接收RdbPredicates实例,在RdbPredicates的构造函数设置表名USER,通过predicates对象来设置查询的条件,equalTo()方法的第一个参数是列名,第二个参数是列值,则查询name是lili值的数据。在then代码块中通过ResultSet遍历查询每个行的数据,当查询完毕后,ResultSet会调用close()方法释放所有的资源。querySql()方法的参数则是SQL语句,上面是查询所有的数据,then代码块的逻辑与query()方法的then是类似的。
下面我们在布局添加一个“查询数据”的按钮,点击按钮时通过DBUtils调用静态query()方法,代码如下所示:
Button('查询数据').btnStyle(OperateType.QUERY)@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {.margin({ top: '12vp' }).onClick(() => {switch (type) {case OperateType.CREATE_DB:DBUtils.init()breakcase OperateType.CREATE_TABLE:DBUtils.createTable()breakcase OperateType.INSERT:DBUtils.insert()breakcase OperateType.QUERY:// 查询数据DBUtils.query()break}})
}
运行上面的程序后,如下图所示:
点击“查询数据”按钮后,便会执行查询,在控制台会输出查询结果,如下所示:
查询是一个相对复杂的操作,equalTo()方法只是RdbPredicates其中一个,系统还提供的其他查询条件的API,如下所示:
上面的例子只是一个简单的案例,在实际开发中要靠自己去慢慢摸索。
更新数据
在学习完添加和查询数据后,更新和删除的数据变更就可以通过查询方式来观察变化,就不需要将数据库文件导出这样繁琐操作了。RdbStore提供了update()方法来更新数据。代码如下所示:
static update() {// 设置更新的列值,这里设置了age列的值为25let valueBucket: relationalStore.ValuesBucket = {age: 25}// 创建RdbPredicates实例let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);// 设置查询的条件,name列的值为lili的数据predicates.equalTo("name", "lili")// 执行更新操作DBUtils.rdbStore?.update(valueBucket, predicates).then((r: number) => {DBUtils.rdbStore?.commit() // 打印更新的行数console.log(TAG, 'update success: ', r)}).catch((e: Error) => {console.log(TAG, 'update err: ', e.message)})}
上面代码是将lili名称的age值由原来的18改成25,接着我们在布局添加一个“更新数据”的按钮,点击按钮时通过DBUtils调用静态update()方法,代码如下所示:
Button('更新数据')
.btnStyle(OperateType.UPDATE)@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {.margin({ top: '12vp' }).onClick(() => {switch (type) {case OperateType.UPDATE:DBUtils.update()break}})
}
运行上面的程序后,如下图所示:
点击“更新数据”按钮后,便会执行更新对应的数据,然后点击查询更新后的数据,在控制台会输出更新后的结果,如下所示:
从日志可知,在执行update()更新操作后,我们更新值是生效了,age值由原来的18变成了25.
删除数据
在知道了查询数据后,删除数据则相对很简单了,RdbStore提供了delete()方法来删除数据,只接收一个参数RdbPredicates,前面已经使用过多次了,已经熟能生巧了就不多讲了,直接上代码了。
static delete() {// 创建RdbPredicates实例,用于设置查询条件 ,指定查询的表名let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);// 设置删除的条件,name列的值为hzw的数据predicates.equalTo("name", "hzw")DBUtils.rdbStore?.delete(predicates).then((r: number) => {DBUtils.rdbStore?.commit() // 打印删除的行数console.log(TAG, 'delete success: ', r)}).catch((e: Error) => {console.log(TAG, 'delete err: ', e.message)})}
上面代码是删除name值为hzw的数据,接着我们在布局添加一个“删除数据”的按钮,点击按钮时通过DBUtils调用静态delete()方法,代码如下所示:
Button('删除数据').btnStyle(OperateType.DELETE)
@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {.margin({ top: '12vp' }).onClick(() => {switch (type) {case OperateType.DELETE:DBUtils.delete()break}})
}
运行上面的程序后,如下图所示:
点击“删除数据”按钮后,便会删除对应的数据,然后点击查询删除后的数据,在控制台会输出删除后的结果,如下所示:
从日志可知,在执行delete()方法执行删除操作后,name为hzw的这条数据记录已经被删除了,不存在USER表中了。
升级数据库
什么情况下需要升级升级库呢?比如我们的应用1.0版本已成功上线了,产品在规划2.0版本时,用户信息新增一个staffId字段,接着在3.0版本时又删除一个weight字段,此时数据库就要升级,确保在应用版本升级的过程中本地数据库的数据不会丢失。
在初始化数据库配置的getRdbStore()方法的then代码块中进行数据库版本升级。当数据库创建时,数据库默认版本是0,此时通常会创建需要的表,同时将数据库版本设为1,相当于从0升级到1,代码如下:
// 数据库版本号是0时,创建数据表语句的SQL语句
const CREATE_TABLE_USER ='CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, weight REAL)'relationalStore.getRdbStore(context, STORE_CONFIG).then((store: relationalStore.RdbStore) => {DBUtils.rdbStore = storeconsole.log(TAG, 'db ver: ',store.version)// 升级数据库// 当数据库创建时,数据库默认版本为0if (store.version == 0) {store.executeSql(CREATE_TABLE_USER)// 将版本设置为1 相当于版本号从0升级到1store.version = 1}}).catch((err: Error) => {console.error(`${TAG} db create error: `, err.message)
})
此时随着应用版本迭代升级,USER表新增了一个staffId字段,创建USER表的SQL语句则需要增加一个staffId字段,代码如下所示:
// 新增staffId字段,创建数据表语句的SQL语句
const CREATE_TABLE_USER_1 ='CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, weight REAL, staffId INTEGER)'relationalStore.getRdbStore(context, STORE_CONFIG).then((store: relationalStore.RdbStore) => {DBUtils.rdbStore = storeconsole.log(TAG, 'db ver: ',store.version)// 升级数据库const oldVersion = store.version// 当数据库创建时,数据库默认版本为0if (store.version == 0) {store.executeSql(CREATE_TABLE_USER_1)// 设置数据库最高版本2 ,这里始终设置成最高版本号store.version = 2}// 如果是数据库版本从1 升级到 2,则需要新增staffId字段 if (store.version == 1) {store.executeSql('alter table USER add column staffId integer')// 数据库版本升级为2store.version = 2 }}).catch((err: Error) => {console.error(`${TAG} db create error: `, err.message)
})
如果是一个新用户初次安装应用,数据库默认版本号是0,会调用executeSql()方法按照最新的SQL语句(CREATE_TABLE_USER_1
)创建USER表,同时将数据库版本号设置成最高版本2,这样就不会执行后面if升级逻辑,如果这个用户的数据库版本是1,则会通过executeSql()方法执行alter table USER add column staffId integer
SQL语句新增staffId字段,同时将数据库版本号升级为2。
当数据库由版本1升级成2时,我们去查询数据,会发现数据表中有staffId字段了,如下:
DBUtils query success: [{"ID": 1,"age": 18,"height": 160,"name": "lili","sex": 0,"staffId": null,"weight": null
}]
接着后面产品又说,USER表中不需要weight字段了,开发者此时从中表中去除,就需要修改创建USER表的语句,这样数据库又要升级,由版本2升级为3,代码如下:
// 去除weight字段,创建数据表语句的SQL语句
const CREATE_TABLE_USER_2 ='CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, staffId INTEGER)'relationalStore.getRdbStore(context, STORE_CONFIG).then((store: relationalStore.RdbStore) => {DBUtils.rdbStore = storeconsole.log(TAG, 'db ver: ',store.version)// 升级数据库const oldVersion = store.version// 当数据库创建时,数据库默认版本为0if (store.version == 0) {store.executeSql(CREATE_TABLE_USER_2)// 这里始终设置为最高版本 3store.version = 3}// 如果是数据库版本从1 升级到 2,则需要新增staffId字段if (store.version == 1) {store.executeSql('alter table USER add column staffId integer')store.version = 2}// 如果是数据库版本从2 升级到 3,则需要去除weight字段if (store.version == 2) {store.executeSql('alter table USER drop column weight')store.version = 3}}).catch((err: Error) => {console.error(`${TAG} db create error: `, err.message)
})
这里的升级逻辑与升级到版本2的逻辑是类似的,就不多述了。当版本升级为3时我们去查询数据,weight字段则不存在了,如下所示:
DBUtils query success: [{"ID": 1,"age": 18,"height": 160,"name": "lili","sex": 0,"staffId": null,
}]
使用这种if方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据完全不会丢失。
使用事务
SQLite数据库是支持事务的,事务是指一系列操作,要么全部完成,那么全部不完成,是原子性操作。比如我们常用的转账功能,A账户向B账户转账,可以分为两个步骤,从A账户扣钱,然后再往B账户打入等量的金额,这两个动作是独立的操作,可能存在一个成功,一个失败,比如A账户扣钱成功了,B账户没有收到钱,出现这种情况是很危险的,如何确保两个独立操作要么全部失败,要么全部成功,当某个失败时,就回滚到初始状态,此时事务就派上用场了。
在鸿蒙中如何使用事务,rdbStore提供了beginTransaction() 和rollBack()方法来保证事务,确保操作时原子性。下面以一个简单案例为例,代码如下:
static async transaction(isError: boolean = true){// 开启事务DBUtils.rdbStore?.beginTransaction()try {// 删除hzw的数据let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);predicates.equalTo("name", "hzw")let rowNum = await DBUtils.rdbStore?.delete(predicates)DBUtils.rdbStore?.commit()console.log(TAG, 'delete success: ', rowNum)if (isError) {// 制造一个异常,让事务失败throw new Error('error')}let xml: relationalStore.ValuesBucket = {name: 'xml',age: 28,sex: 0,height: 165,};const num = await DBUtils.rdbStore?.insert(DBUtils.tableName, xml)DBUtils.rdbStore?.commit()console.log(TAG, 'insert success: ', num)} catch (e) {// 回滚console.log(TAG, '回滚');DBUtils.rdbStore?.rollBack()}
}
上面代码的原子性逻辑是先删除hzw的数据,然后添加xml名称的数据。
首先在执行SQL前通过beginTransaction()方法开启事务,接着删除hzw的数据,此时已经执行删除的SQL语句,但当isError为true时,这人为制造一个异常中断整个流程,导致事务的失败,但添加数据的操作还未执行。不过由于在catch代码块中,调用了rollBack()方法回滚到开启事务处,因此hzw数据是删除不了的。
参考
- https://blog.csdn.net/K346K346/article/details/114085663
相关文章:

鸿蒙应用开发—数据持久化之SQLite
文章目录 SQLite简介创建数据库添加数据查询数据更新数据删除数据升级数据库使用事务参考 SQLite简介 SQLite是一个轻量级关系数据库,占用资源很少,只有几百KB的大小,无需服务器支撑,是一个零配置、事务性的SQL数据库引擎。 相对…...

JSON对象处理工具类
目录 1. 工具类的功能设计 2. 工具类的实现 依赖配置 工具类代码 3. 工具类的使用示例 示例1:美化JSON打印 示例2:从JSON中提取数据 示例3:修改JSON数据 示例4:合并JSON对象 4. 总结 在现代软件开发中,JSON&…...

通义万相 2.1 + 蓝耘算力,AI 视频生成的梦幻组合
在这个科技日新月异的时代,人工智能不断刷新着我们对世界的认知。一次偶然的机会,我借助北京蓝耘科技股份有限公司提供的算力支持,踏上了使用通义万相 2.1 进行 AI 视频生成的奇妙之旅。 目录 1.1初遇蓝耘科技: 1.2通义万相 2.1…...

汽车一键启动按钮更换注意事项
汽车一键启动开关更换教程 一键启动开关是现代汽车中常见的便捷配置,但随着时间的推移,这个部件可能会出现失灵的情况。当一键启动开关发生故障时,许多车主选择自行更换。以下是整理的一键启动开关更换教程: 更换前的准备 选择匹…...

AI系统架构
在AI系统架构中,通常可以分为基础设施层、模型层和应用层。它们分别对应不同的技术和应用场景,具体如下: 1. 基础设施层(Infrastructure Layer) 这是AI系统的底层支持,主要涉及计算资源、存储、网络等基础…...

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加列宽调整功能,示例Table14_01基础固定表头示例
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加列宽调整功能,示例Table14_01基础固定表头…...

spring boot3.4.3+MybatisPlus3.5.5+swagger-ui2.7.0
使用 MyBatis-Plus 操作 books 表。我们将实现以下功能: 创建实体类 Book。 创建 Mapper 接口 BookMapper。 创建 Service 层 BookService 和 BookServiceImpl。 创建 Controller 层 BookController。 配置 MyBatis-Plus 和数据库连接。 1. 项目结构 src ├─…...

解决CentOS 8.5被恶意扫描的问题
CentOS 8 官方仓库已停止维护(EOL),导致一些常用依赖包如fail2ban 无法正常安装。 完整解决方案: 一、问题根源 CentOS 8 官方仓库已停更:2021 年底 CentOS 8 停止维护,默认仓库的包可能无法满足依赖关系。EPEL 仓库兼容性:EPEL 仓库可能未适配 CentOS 8.5 的旧版本依赖…...

laravel中 添加公共/通用 方法/函数
一,现在app 下面创建Common目录,然后在创建Common.php 文件 二,修改composer.json文件 添加这个到autoload 中 "files": ["app/Common/Common.php"]"autoload": {"psr-4": {"App\\": &quo…...

在vs中无法用QtDesigner打开ui文件的解决方法
解决方法 右键ui文件,选择打开方式,弹出如下界面。 点击添加,弹出如下界面 点击程序后边的三个点,去电脑查找designer.exe,我的位置为D:\Qt\Qt5.9.9\5.9.9\msvc2015_64\bin\designer.exe。 名称可以自己起一个名字,…...

springboot 文件下载
在springboot中,执行如下代码实现文件下载 GetMapping("/file/download/test")public void Download(HttpServletResponse response){try {String path "XXXXXXXXXXXX";//文件路径File file new File(path);// 读到流中InputStream inputStre…...

Nest.js全栈开发终极实践:TypeORM+微服务+Docker构建高可用企业级应用
文章目录 **第一部分:认识Nest.js与基础环境搭建****1.1 什么是Nest.js?****1.2 环境准备****1.3 创建第一个项目****1.4 启动开发服务器****1.5 核心文件解读** **第二部分:基础控制器与路由****2.1 控制器的作用****2.2 创建自定义控制器**…...

Go语言集成DeepSeek API和GoFly框架文本编辑器实现流式输出和对话(GoFly快速开发框架)
说明 本文是GoFly快速开发框架集成Go语言调用 DeepSeek API 插件,实现流式输出和对话功能。为了方便实现更多业务功能我们在Go服务端调用AI即DeepSeek接口,处理好业务后再用Gin框架实现流失流式输出到前端,前端使用fetch请求接收到流式的mar…...

Hexo博客Icarus主题不蒜子 UV、PV 统计数据初始化配置
文章首发于 不蒜子 UV、PV 统计数据初始化配置 适用场景 如果你有个运行的网站域名,采用了不蒜子统计 UV、PV等访客和阅读数据,但是有一天,你觉得想要换一个新的域名。当你将网站绑定到新的域名后,突然发现,所有的文章…...

在资源有限中逆势突围:从抗战智谋到寒门高考的破局智慧
目录 引言 一、历史中的非对称作战:从李牧到八路军的智谋传承 李牧戍边:古代军事博弈中的资源重构 八路军的游击战:现代战争中的智慧延续 二、创业界的逆袭之道:小米与拼多多的资源重构 从MVP到杠杆解 社交裂变与资源错配 …...

SQLAlchemy系列教程:如何执行原生SQL
Python中的数据库交互提供了高级API。但是,有时您可能需要执行原始SQL以提高效率或利用数据库特定的特性。本指南介绍在SQLAlchemy框架内执行原始SQL。 在SQLAlchemy中执行原生SQL SQLAlchemy虽然以其对象-关系映射(ORM)功能而闻名ÿ…...

绪论数据结构基本概念(刷题笔记)
(一)单选题 1.与数据元素本身的形式、相对位置和个数无关的是(B)【广东工业大学2019年829数据结构】 A.数据存储结构 B.数据逻辑结构 C.算法 D.操作 2.在数据结构的讨论中把数据结构从逻辑上分为(C)【中国…...

delphi 正则提取html中的内容
function ExtractTextFromHTML(const HTML: string): string; var RegEx: TRegEx; begin Result := HTML; // 移除<script>标签及其内容 Result := TRegEx.Replace(Result, <script.*?>.*?</script>, , [roIgnoreCase, roSingleLine]); // 移除<s…...

18天 - 常见的 HTTP 状态码有哪些?HTTP 请求包含哪些内容,请求头和请求体有哪些类型?HTTP 中 GET 和 POST 的区别是什么?
常见的 HTTP 状态码有哪些? HTTP 状态码用于指示服务器对客户端请求的响应结果,常见的 HTTP 状态码可以分为以下几类: 1. 信息类(1xx) 100 Continue:客户端应继续发送请求。101 Switching Protocols&…...
从0开始的操作系统手搓教程45——实现exec
目录 建立抽象 实现加载 实现sys_execv !!!提示:因为实现问题没有测试。所以更像是笔记! exec 函数的作用是用新的可执行文件替换当前进程的程序体。具体来说,exec 会将当前正在运行的用户进程的进程体&…...

Android TCP封装工具类
TCP通信的封装,我们可以从以下几个方面进行改进: 线程池优化:使用更高效的线程池配置,避免频繁创建和销毁线程。 连接重试机制:在网络不稳定时,自动重试连接。 心跳机制:保持长连接ÿ…...

解决火绒启动时,报安全服务异常,无法保障计算机安全
1.找到控制面板-安全和维护-更改用户账户控制设置 重启启动电脑解决。...

Spring Boot框架总结(超级详细)
前言 本篇文章包含Springboot配置文件解释、热部署、自动装配原理源码级剖析、内嵌tomcat源码级剖析、缓存深入、多环境部署等等,如果能耐心看完,想必会有不少收获。 一、Spring Boot基础应用 Spring Boot特征 概念: 约定优于配置&#…...

为什么要使用前缀索引,以及建立前缀索引:sql示例
背景: 你想啊,数据库里有些字段,它老长了,就像那种 varchar(255) 的字段,这玩意儿要是整个字段都拿来建索引,那可太占地方了。打个比方,这就好比你要在一个超级大的笔记本上记东西,每…...

Nuxt3 ssr build/dev时区分不同的环境
package.json "scripts": {"build": "nuxt build --dotenv .env.prod","build:dev": "nuxt build --dotenv .env.dev","postbuild": "mv -f .output ./dist/.output", //支持自定义文件名"dev&quo…...

嵌入式学习第二十四天--网络 服务器
服务器模型 tcp服务器: socket bind listen accept recv/send close 1.支持多客户端访问 //单循环服务器 socket bind listen while(1) { accept while(1) { recv/send } } close 2.支持多客户端同时访问 (并发能力) 并发服务器 socket bind …...

tcp/ip协议配置参数有哪些?tcp/ip协议需要设置的参数有哪些
TCP/IP协议的配置参数是确保网络设备能够正确接入互联网并与其他设备进行通信的关键设置。这些参数主要包括以下几个方面: 1. IP地址 定义:IP地址是网络中设备的唯一标识符,用于标识和定位设备。它由32位二进制数组成,通常采用点…...

我有点担心开始AI中台了
有个特点历史教训是很难吸取的 从大数据开始就是一窝蜂的去搞,不管有没有什么数据量。反正要来个Hadoop。其实有些企业数据一块硬盘都放得下。 微服务来了,也不管自己的系统是不是适合微服务。我个人经验得出,to B和to G的业务场景…...

《用Python+PyGame开发双人生存游戏!源码解析+完整开发思路分享》
导语 "你是否想过用Python开发一款可玩性高的双人合作游戏?本文将分享如何从零开始实现一款类《吸血鬼幸存者》的生存射击游戏!包含完整源码解析、角色系统设计、敌人AI逻辑等核心技术点,文末提供完整代码包下载!" 哈…...

优选算法系列(1. 双指针_上)
目录 双指针 一:移动零(easy) 题目链接:移动零 解法: 代码: 二:复写零(easy) 题目链接:复写零 编辑 解法: 代码: 三:快乐…...