kotlin kmp 跨平台环境使用sqldelight

欢迎访问我的主页: https://heeheeaii.github.io/

1. 项目结构

SQLDelightKMPDemo/
├── shared/
│   ├── src/
│   │   ├── commonMain/kotlin/
│   │   ├── androidMain/kotlin/
│   │   ├── desktopMain/kotlin/
│   │   └── commonMain/sqldelight/
│   └── build.gradle.kts
├── androidApp/
│   └── build.gradle.kts
├── desktopApp/
│   └── build.gradle.kts
└── build.gradle.kts

2. 根目录 build.gradle.kts

plugins {id("com.android.application") version "8.1.4" apply falseid("com.android.library") version "8.1.4" apply falseid("org.jetbrains.kotlin.multiplatform") version "1.9.20" apply falseid("org.jetbrains.kotlin.android") version "1.9.20" apply falseid("org.jetbrains.compose") version "1.5.4" apply falseid("app.cash.sqldelight") version "2.0.2" apply false
}

3. shared/build.gradle.kts

plugins {id("org.jetbrains.kotlin.multiplatform")id("com.android.library")id("app.cash.sqldelight")
}kotlin {androidTarget {compilations.all {kotlinOptions {jvmTarget = "1.8"}}}jvm("desktop")sourceSets {commonMain.dependencies {implementation("app.cash.sqldelight:runtime:2.0.2")implementation("app.cash.sqldelight:coroutines-extensions:2.0.2")implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")}androidMain.dependencies {implementation("app.cash.sqldelight:android-driver:2.0.2")implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")}val desktopMain by getting {dependencies {implementation("app.cash.sqldelight:sqlite-driver:2.0.2")}}commonTest.dependencies {implementation("org.jetbrains.kotlin:kotlin-test:1.9.20")implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")}}
}android {namespace = "com.example.sqldelightkmp.shared"compileSdk = 34defaultConfig {minSdk = 24}compileOptions {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8}
}sqldelight {databases {create("BeselfDatabase") {packageName.set("com.treevalue.beself.io")}}
}

SQL Schema定义

shared/src/commonMain/sqldelight/database/BeselfDatabase.sq

CREATE TABLE IF NOT EXISTS Task (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,title TEXT NOT NULL,description TEXT,completed  INTEGER NOT NULL DEFAULT 0,priority INTEGER NOT NULL DEFAULT 0,created_at INTEGER NOT NULL,updated_at INTEGER NOT NULL,due_date INTEGER
);-- 插入任务
insertTask:
INSERT INTO Task(title, description, completed, priority, created_at, updated_at, due_date)
VALUES(?, ?, ?, ?, ?, ?, ?);-- 获取所有任务
selectAllTasks:
SELECT * FROM Task
ORDER BY priority DESC, created_at DESC;-- 根据ID获取任务
selectTaskById:
SELECT * FROM Task WHERE id = ?;-- 根据完成状态获取任务
selectTasksByCompleted:
SELECT * FROM Task WHERE completed = ?
ORDER BY priority DESC, created_at DESC;-- 搜索任务
searchTasks:
SELECT * FROM Task
WHERE title LIKE '%' || ? || '%' OR description LIKE '%' || ? || '%'
ORDER BY priority DESC, created_at DESC;-- 更新任务
updateTask:
UPDATE Task
SET title = ?, description = ?, completed = ?, priority = ?, updated_at = ?, due_date = ?
WHERE id = ?;-- 标记任务完成
markTaskCompleted:
UPDATE Task SET completed = 1, updated_at = ? WHERE id = ?;-- 删除任务
deleteTask:
DELETE FROM Task WHERE id = ?;-- 删除所有已完成任务
deleteCompletedTasks:
DELETE FROM Task WHERE completed = 1;-- 获取任务统计
getTaskStats:
SELECTCOUNT(*) AS total,SUM(CASE WHEN completed = 1 THEN 1 ELSE 0 END) AS completed,SUM(CASE WHEN completed = 0 THEN 1 ELSE 0 END) AS pending
FROM Task;

通用代码实现

1. 数据模型

package com.treevalue.beself.ioimport kotlinx.datetime.Instantdata class Task(val id: Long = 0,val title: String,val description: String? = null,val completed: Boolean = false,val priority: Priority = Priority.MEDIUM,val createdAt: Instant,val updatedAt: Instant,val dueDate: Instant? = null
)enum class Priority(val value: Int, val displayName: String) {LOW(0, "Low"),MEDIUM(1, "Medium"),HIGH(2, "High"),URGENT(3, "Urgent");companion object {fun fromValue(value: Int): Priority = values().find { it.value == value } ?: MEDIUM}
}data class TaskStats(val total: Long,val completed: Long,val pending: Long
) {val completionRate: Double = if (total > 0) completed.toDouble() / total else 0.0
}

2. 数据库驱动工厂

package com.treevalue.beself.ioimport app.cash.sqldelight.db.SqlDriverexpect class DatabaseDriverFactory(context: Any? = null) {fun createDriver(): SqlDriver
}

BeselfDatabase 是由 SQLDelight 自动生成的类。要让它正常工作,需要确保以下几个步骤:

1. 确保 SQLDelight 配置正确

sqldelight {databases {create("BeselfDatabase") {packageName.set("com.treevalue.beself.io")}}
}

2. 确保 SQL 文件位置正确

SQL 文件应该位于:

shared/src/commonMain/sqldelight/database/BeselfDatabase.sq

注意:文件名 BeselfDatabase.sq 必须与 create("BeselfDatabase") 中的名称一致。

3. 构建项目生成代码

执行以下命令来生成 SQLDelight 代码:

./gradlew :shared:build

或者在 Android Studio/IntelliJ 中:

  • 点击 “Build” → “Rebuild Project”
  • 或者运行 “Sync Project with Gradle Files”

4. 验证生成的代码

构建成功后,SQLDelight 会在以下位置生成代码:

shared/build/generated/sqldelight/code/BeselfDatabase/commonMain/com/treevalue/beself/io

3. 数据库包装类

package com.treevalue.beself.ioimport app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import app.cash.sqldelight.coroutines.mapToOneOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.datetime.Clock
import kotlinx.datetime.Instantclass Database(databaseDriverFactory: DatabaseDriverFactory) {private val database = BeselfDatabase(databaseDriverFactory.createDriver())private val dbQuery = database.beselfDatabaseQueries// 扩展函数将数据库行映射为模型private fun database.Task.toModel(): Task = Task(id = id,title = title,description = description,completed = completed != 0L,priority = Priority.fromValue(priority.toInt()),createdAt = Instant.fromEpochMilliseconds(created_at),updatedAt = Instant.fromEpochMilliseconds(updated_at),dueDate = due_date?.let { Instant.fromEpochMilliseconds(it) })suspend fun insertTask(title: String,description: String? = null,priority: Priority = Priority.MEDIUM,dueDate: Instant? = null,): Long {val now = Clock.System.now()return dbQuery.transactionWithResult {dbQuery.insertTask(title = title,description = description,completed = 0L,priority = priority.value.toLong(),created_at = now.toEpochMilliseconds(),updated_at = now.toEpochMilliseconds(),due_date = dueDate?.toEpochMilliseconds())// 返回最后插入的IDdbQuery.selectAllTasks().executeAsList().lastOrNull()?.id ?: 0L}}fun getAllTasksFlow(): Flow<List<Task>> {return dbQuery.selectAllTasks().asFlow().mapToList(Dispatchers.IO).map { tasks -> tasks.map { it.toModel() } }}suspend fun getAllTasks(): List<Task> {return dbQuery.selectAllTasks().executeAsList().map { it.toModel() }}suspend fun getTaskById(id: Long): Task? {return dbQuery.selectTaskById(id).executeAsOneOrNull()?.toModel()}fun getTaskByIdFlow(id: Long): Flow<Task?> {return dbQuery.selectTaskById(id).asFlow().mapToOneOrNull(Dispatchers.IO).map { it?.toModel() }}fun getTasksByCompletedFlow(completed: Boolean): Flow<List<Task>> {return dbQuery.selectTasksByCompleted(if (completed) 1L else 0L).asFlow().mapToList(Dispatchers.IO).map { tasks -> tasks.map { it.toModel() } }}suspend fun searchTasks(query: String): List<Task> {return dbQuery.searchTasks(query, query).executeAsList().map { it.toModel() }}suspend fun updateTask(task: Task) {dbQuery.updateTask(title = task.title,description = task.description,completed = if (task.completed) 1L else 0L,priority = task.priority.value.toLong(),updated_at = Clock.System.now().toEpochMilliseconds(),due_date = task.dueDate?.toEpochMilliseconds(),id = task.id)}suspend fun markTaskCompleted(id: Long) {dbQuery.markTaskCompleted(updated_at = Clock.System.now().toEpochMilliseconds(),id = id)}suspend fun deleteTask(id: Long) {dbQuery.deleteTask(id)}suspend fun deleteCompletedTasks() {dbQuery.deleteCompletedTasks()}fun getTaskStatsFlow(): Flow<TaskStats> {return dbQuery.getTaskStats().asFlow().mapToOneOrNull(Dispatchers.IO).map { stats ->stats?.let {TaskStats(total = it.total,completed = it.completed ?: 0,pending = it.pending ?: 0)} ?: TaskStats(0, 0, 0)}}
}

4. Repository层

package com.treevalue.beself.ioimport kotlinx.coroutines.flow.Flow
import kotlinx.datetime.Instantclass TaskRepository(private val database: Database) {fun getAllTasks(): Flow<List<Task>> = database.getAllTasksFlow()fun getCompletedTasks(): Flow<List<Task>> = database.getTasksByCompletedFlow(true)fun getPendingTasks(): Flow<List<Task>> = database.getTasksByCompletedFlow(false)fun getTaskById(id: Long): Flow<Task?> = database.getTaskByIdFlow(id)fun getTaskStats(): Flow<TaskStats> = database.getTaskStatsFlow()suspend fun createTask(title: String,description: String? = null,priority: Priority = Priority.MEDIUM,dueDate: Instant? = null): Long {return database.insertTask(title, description, priority, dueDate)}suspend fun updateTask(task: Task) {database.updateTask(task)}suspend fun toggleTaskCompleted(task: Task) {val updatedTask = task.copy(completed = !task.completed)database.updateTask(updatedTask)}suspend fun deleteTask(id: Long) {database.deleteTask(id)}suspend fun deleteAllCompletedTasks() {database.deleteCompletedTasks()}suspend fun searchTasks(query: String): List<Task> {return database.searchTasks(query)}
}

平台特定实现

1. Android驱动实现

package com.treevalue.beself.ioimport android.content.Context
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.android.AndroidSqliteDriveractual class DatabaseDriverFactory actual constructor(context: Any?) {private val androidContext = context as Contextactual fun createDriver(): SqlDriver {return AndroidSqliteDriver(schema = BeselfDatabase.Schema,context = androidContext,name = "task_database.db")}
}

2. 桌面驱动实现

package com.treevalue.beself.ioimport app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import java.io.Fileactual class DatabaseDriverFactory actual constructor(context: Any?) {actual fun createDriver(): SqlDriver {val databasePath = File(System.getProperty("user.home"), ".taskapp/task_database.db")databasePath.parentFile?.mkdirs()val driver = JdbcSqliteDriver("jdbc:sqlite:${databasePath.absolutePath}")BeselfDatabase.Schema.create(driver)return driver}
}

桌面测试

1 依赖


val desktopTest by getting {dependencies {implementation(libs.testng)implementation(libs.kotlinx.coroutines.test)}
}

package com.beself.ioimport app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import com.treevalue.beself.io.*
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.Assert.*
import org.junit.Before
import org.junit.Testclass TestDatabaseDriverFactory : DatabaseDriverFactory() {companion object {private val instanceCounter = atomic(0L)}override fun createDriver(): SqlDriver {// 为每个实例创建唯一的内存数据库val instanceId = instanceCounter.incrementAndGet()val url = "jdbc:sqlite:file:test_db_$instanceId?mode=memory&cache=shared"val driver = JdbcSqliteDriver(url)BeselfDatabase.Schema.create(driver)return driver}
}class DatabaseTest {private lateinit var database: Databaseprivate lateinit var repository: TaskRepository@Beforefun setUp() {// 为每个测试创建新的内存数据库实例,确保完全的测试隔离database = Database(TestDatabaseDriverFactory())repository = TaskRepository(database)}@Testfun testInsertAndRetrieveTask() = runTest {// 插入测试任务val taskId = database.insertTask(title = "Test Task",description = "This is a test task",priority = Priority.HIGH)assertTrue("Task ID should be greater than 0", taskId > 0)// 检索任务val retrievedTask = database.getTaskById(taskId)assertNotNull("Retrieved task should not be null", retrievedTask)assertEquals("Test Task", retrievedTask?.title ?: "")assertEquals("This is a test task", retrievedTask?.description ?: "")assertEquals(Priority.HIGH, retrievedTask?.priority ?: Priority.URGENT)retrievedTask?.let { assertFalse(it.completed) }}@Testfun testGetAllTasks() = runTest {// 插入多个任务val taskIds = mutableListOf<Long>()repeat(3) { index ->val id = database.insertTask(title = "Task $index",description = "Description $index",priority = Priority.entries[index % Priority.entries.size])taskIds.add(id)}// 获取所有任务val allTasks = database.getAllTasks()assertEquals("Should have exactly 3 tasks", 3, allTasks.size)// 验证排序(按优先级降序,创建时间降序)val sortedTasks = allTasks.sortedWith(compareByDescending<Task> { it.priority.value }.thenByDescending { it.createdAt })assertEquals("Tasks should be sorted correctly", sortedTasks.map { it.id }, allTasks.map { it.id })}@Testfun testUpdateTask() = runTest {// 插入任务val taskId = database.insertTask(title = "Original Title",description = "Original Description",priority = Priority.LOW)val originalTask = database.getTaskById(taskId)assertNotNull("Original task should exist", originalTask)// 更新任务val updatedTask = originalTask!!.copy(title = "Updated Title",description = "Updated Description",priority = Priority.URGENT,completed = true)database.updateTask(updatedTask)// 验证更新val retrievedTask = database.getTaskById(taskId)assertNotNull("Updated task should exist", retrievedTask)assertEquals("Updated Title", retrievedTask!!.title)assertEquals("Updated Description", retrievedTask.description)assertEquals(Priority.URGENT, retrievedTask.priority)assertTrue(retrievedTask.completed)}@Testfun testMarkTaskCompleted() = runTest {// 插入未完成任务val taskId = database.insertTask(title = "Incomplete Task",priority = Priority.MEDIUM)val originalTask = database.getTaskById(taskId)assertNotNull("Original task should exist", originalTask)assertFalse("Task should be incomplete initially", originalTask!!.completed)// 标记为完成database.markTaskCompleted(taskId)// 验证已完成val completedTask = database.getTaskById(taskId)assertNotNull("Completed task should exist", completedTask)assertTrue("Task should be marked as completed", completedTask!!.completed)}@Testfun testDeleteTask() = runTest {// 插入任务val taskId = database.insertTask(title = "Task to Delete",priority = Priority.LOW)// 确认任务存在assertNotNull("Task should exist before deletion", database.getTaskById(taskId))// 删除任务database.deleteTask(taskId)// 确认任务已删除assertNull("Task should be deleted", database.getTaskById(taskId))}@Testfun testGetTasksByCompleted() = runTest {// 插入已完成和未完成的任务val completedId = database.insertTask("Completed Task", priority = Priority.LOW)val pendingId = database.insertTask("Pending Task", priority = Priority.HIGH)// 标记一个为完成database.markTaskCompleted(completedId)// 测试Flowval completedTasks = repository.getCompletedTasks().first()val pendingTasks = repository.getPendingTasks().first()assertEquals("Should have 1 completed task", 1, completedTasks.size)assertEquals("Should have 1 pending task", 1, pendingTasks.size)assertEquals("Completed Task", completedTasks.first().title)assertEquals("Pending Task", pendingTasks.first().title)}@Testfun testSearchTasks() = runTest {// 插入搜索测试任务database.insertTask("Learn Kotlin", "Study Kotlin multiplatform")database.insertTask("Learn Swift", "Study iOS development")database.insertTask("Build App", "Create amazing mobile app")// 搜索包含"Learn"的任务val learnTasks = database.searchTasks("Learn")assertEquals("Should find 2 tasks with 'Learn'", 2, learnTasks.size)assertTrue("All found tasks should contain 'Learn'",learnTasks.all { it.title.contains("Learn") })// 搜索描述中包含"mobile"的任务val mobileTasks = database.searchTasks("mobile")assertEquals("Should find 1 task with 'mobile'", 1, mobileTasks.size)assertEquals("Build App", mobileTasks.first().title)// 搜索不存在的内容val noResults = database.searchTasks("NonExistent")assertTrue("Should find no results for non-existent term", noResults.isEmpty())}@Testfun testDeleteCompletedTasks() = runTest {// 插入混合状态的任务val taskIds = (1..5).map { index ->database.insertTask("Task $index")}// 标记前3个为完成taskIds.take(3).forEach { id ->database.markTaskCompleted(id)}// 验证初始状态assertEquals("Should have 5 tasks initially", 5, database.getAllTasks().size)assertEquals("Should have 3 completed tasks", 3, repository.getCompletedTasks().first().size)// 删除已完成的任务database.deleteCompletedTasks()// 验证删除结果val remainingTasks = database.getAllTasks()assertEquals("Should have 2 tasks remaining", 2, remainingTasks.size)assertTrue("All remaining tasks should be incomplete",remainingTasks.none { it.completed })}@Testfun testTaskStats() = runTest {// 插入不同状态的任务repeat(5) { index ->val taskId = database.insertTask("Task $index")if (index < 2) {database.markTaskCompleted(taskId)}}// 获取统计信息val stats = repository.getTaskStats().first()assertEquals("Should have 5 total tasks", 5, stats.total)assertEquals("Should have 2 completed tasks", 2, stats.completed)assertEquals("Should have 3 pending tasks", 3, stats.pending)assertEquals("Completion rate should be 0.4", 0.4, stats.completionRate, 0.01)}@Testfun testRepositoryOperations() = runTest {// 测试Repository层的操作val taskId = repository.createTask(title = "Repository Test",description = "Testing repository functionality",priority = Priority.HIGH)assertTrue("Task ID should be positive", taskId > 0)// 测试获取任务val task = repository.getTaskById(taskId).first()assertNotNull("Task should exist", task)assertEquals("Repository Test", task?.title)// 测试切换完成状态repository.toggleTaskCompleted(task!!)val updatedTask = repository.getTaskById(taskId).first()assertTrue("Task should be completed after toggle", updatedTask?.completed == true)// 再次切换repository.toggleTaskCompleted(updatedTask!!)val toggledTask = repository.getTaskById(taskId).first()assertFalse("Task should be incomplete after second toggle", toggledTask?.completed == true)}@Testfun testTaskPriorityMapping() = runTest {// 测试所有优先级Priority.values().forEach { priority ->val taskId = database.insertTask(title = "Priority ${priority.displayName}",priority = priority)val task = database.getTaskById(taskId)assertNotNull("Task with priority ${priority.displayName} should exist", task)assertEquals("Priority should match", priority, task!!.priority)assertEquals("Priority display name should match",priority.displayName, task.priority.displayName)}// 测试fromValue方法assertEquals("Priority.LOW should map from value 0", Priority.LOW, Priority.fromValue(0))assertEquals("Priority.MEDIUM should map from value 1", Priority.MEDIUM, Priority.fromValue(1))assertEquals("Priority.HIGH should map from value 2", Priority.HIGH, Priority.fromValue(2))assertEquals("Priority.URGENT should map from value 3", Priority.URGENT, Priority.fromValue(3))assertEquals("Invalid value should default to MEDIUM", Priority.MEDIUM, Priority.fromValue(999))}@Testfun testEmptyDatabaseStats() = runTest {// 确保数据库为空(已在setUp中清理)val allTasks = database.getAllTasks()assertEquals("Database should be empty", 0, allTasks.size)// 测试空数据库的统计val stats = repository.getTaskStats().first()assertEquals("Total should be 0 for empty database", 0, stats.total)assertEquals("Completed should be 0 for empty database", 0, stats.completed)assertEquals("Pending should be 0 for empty database", 0, stats.pending)assertEquals("Completion rate should be 0.0 for empty database", 0.0, stats.completionRate, 0.01)}@Testfun testFlowUpdates() = runTest {val allTasksFlow = repository.getAllTasks()// 初始状态应该为空val initialTasks = allTasksFlow.first()assertEquals("Initial tasks should be empty", 0, initialTasks.size)// 添加任务后应该能在Flow中看到val taskId = repository.createTask("Flow Test Task")val tasksAfterInsert = allTasksFlow.first()assertEquals("Should have 1 task after insert", 1, tasksAfterInsert.size)assertEquals("Flow Test Task", tasksAfterInsert.first().title)// 删除任务后Flow应该更新repository.deleteTask(taskId)val tasksAfterDelete = allTasksFlow.first()assertEquals("Should be empty after delete", 0, tasksAfterDelete.size)}
}

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

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

相关文章

机器学习【五】decision_making tree

决策树是一种通过树形结构进行数据分类或回归的直观算法&#xff0c;其核心是通过层级决策路径模拟规则推理。主要算法包括&#xff1a;ID3算法基于信息熵和信息增益选择划分属性&#xff1b;C4.5算法改进ID3&#xff0c;引入增益率和剪枝技术解决多值特征偏差&#xff1b;CART…

简单记录一下VSCode中的一些学习记

在刚开始学习VSCode时&#xff0c;相信大家都会好奇VSCode底部区域那几个不同的状态栏具体有什么作用&#xff08;输出、调试控制台、终端、端口&#xff09;&#xff0c;貌似好像都是输出与代码相关的信息的&#xff1f;貌似代码运行结果既可以出现在输出中&#xff0c;也可以…

基于 Hadoop 生态圈的数据仓库实践 —— OLAP 与数据可视化(二)

目录 二、Hive、SparkSQL、Impala 比较 1. SparkSQL 简介 2. Hive、SparkSQL、Impala 比较 &#xff08;1&#xff09;功能 &#xff08;2&#xff09;架构 &#xff08;3&#xff09;场景 3. Hive、SparkSQL、Impala 性能对比 &#xff08;1&#xff09;cloudera 公司…

C++:std::array vs 原生数组 vs std::vector

&#x1f4cc; C&#xff1a;std::array vs 原生数组 vs std::vector 引用&#xff1a; C/C 标准库 std::vector、std::array、原生静态数组 的区别有哪些&#xff1f; 深度剖析&#xff1a;std::vector 内存机制与 push_back 扩容策略 今天过去了 还有许许多个明天 能和大…

Hyper-V + Centos stream 9 搭建K8s集群(二)

一、安装自动补全主节点安装就可以yum install -y bash-completion echo source <(kubectl completion bash) >>~/.bashrc kubectl completion bash >/etc/bash_completion.d/kubectl二、安装Calico网络插件&#xff08;主节点&#xff09;下载文件wget https://ca…

VBA代码解决方案第二十七讲:禁用EXCEL工作簿右上角的关闭按钮

《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程&#xff0c;目前已经是第三版修订了。这套教程定位于入门后的提高&#xff0c;在学习这套教程过程中&#xff0c;侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…

Spring AI 系列之三十一 - Spring AI Alibaba-基于Nacos的MCP

之前做个几个大模型的应用&#xff0c;都是使用Python语言&#xff0c;后来有一个项目使用了Java&#xff0c;并使用了Spring AI框架。随着Spring AI不断地完善&#xff0c;最近它发布了1.0正式版&#xff0c;意味着它已经能很好的作为企业级生产环境的使用。对于Java开发者来说…

sqli-labs:Less-12关卡详细解析

1. 思路&#x1f680; 本关的SQL语句为&#xff1a; $uname".$uname."; $passwd".$passwd."; $sql"SELECT username, password FROM users WHERE username($uname) and password($passwd) LIMIT 0,1";注入类型&#xff1a;字符串型&#xff0…

【SpringAI】8.通过json动态添加mcp服务

前言 官方示例的代码中&#xff0c;mcp一般是配置到yml中或者json文件中&#xff0c;使用自动装配的方式注入服务&#xff0c;这种方式不方便在程序启动后添加新的服务&#xff0c;这里参考cherry studio的方式动态添加mcp服务 1.确定方案 mcp服务的维护放到mysql业务数据库维…

【PDF + ZIP 合并器:把ZIP文件打包至PDF文件中】

B站链接 PDF ZIP 合并器&#xff1a;把ZIP文件打包至PDF文件中_哔哩哔哩_bilibiliz 加强作者的工具 https://wwgw.lanzn.com/i8h1C32k9bef 密码:30cv 新增c框架&#xff0c;加快运行速度

阿里云部署微调chatglm3

git Ifs install Git lfs 主要用于管理大型文件。在传统的Git仓库中&#xff0c;所有文件内容都会被完整记录在每一次提交中&#xff0c;这会导致仓库体积增大&#xff0c;克隆、拉取和推送操作变慢&#xff0c;甚至可能超出存储限额。Git LFS通过将大文件替换成文本指针&#…

Linux网络编程 ---五种IO模型

五种IO模型一、IO慢的原因二、五种IO模型三、如何设置非阻塞式IO&#xff1f;一、IO慢的原因 二、五种IO模型 阻塞式IO 非阻塞式IO 信号驱动IO 多路转接 异步IO 三、如何设置非阻塞式IO&#xff1f; &#xff08;一&#xff09;用法说明 &#xff08;二&#xff0…

Obsidian结合CI/CD实现自动发布

CI/CDQuickAddJS脚本bat脚本sh脚本实现自动发版Hugo文章 需求来源 每次手动执行Hugo的命令&#xff0c;手动把public文件夹上传到自己的服务器可以完成发版需求。 但是&#xff0c;作为一个内容创作者&#xff0c;我更希望的关注于自己的内容&#xff0c;而不是关注整个发版…

[硬件电路-141]:模拟电路 - 源电路,信号源与电源,能自己产生确定性波形的电路。

源电路&#xff08;Source Circuit&#xff09;是电子系统中为其他电路或负载提供特定信号或能量的基础电路模块&#xff0c;其核心功能是生成、调节或转换所需的物理量&#xff08;如电压、电流、波形、频率等&#xff09;。以下是源电路的详细解析&#xff1a;一、源电路的核…

Unity_数据持久化_PlayerPrefs基础

Unity数据持久化 一、数据持久化基础概念 1.1 什么是数据持久化 定义&#xff1a; 数据持久化就是将内存中的数据模型转换为存储模型&#xff0c;以及将存储模型转换为内存中的数据模型的统称。 通俗解释&#xff1a; 将游戏数据存储到硬盘&#xff0c;硬盘中数据读取到游戏中&…

什么是列存储(Columnar Storage)?深度解析其原理与应用场景

列存储的基本概念&#xff1a;颠覆传统的数据组织方式列存储&#xff08;Column Storage&#xff09;是一种革命性的数据库存储技术&#xff0c;它通过按列而非按行组织数据&#xff0c;从根本上改变了数据的物理存储结构。与传统行存储数据库不同&#xff0c;列式数据库将每一…

机器人抓取流程介绍与实现——机器人抓取系统基础系列(七)

机器人抓取系统基础系列文章目录 1. UR机械臂的ROS驱动安装官方教程详解——机器人抓取系统基础系列&#xff08;一&#xff09; 2. MoveIt控制机械臂的运动实现——机器人抓取系统基础系列&#xff08;二&#xff09; 3. 机器人&#xff08;机械臂&#xff09;的相机选型与安装…

【Qt】QObject::startTimer: Timers cannot be started from another thread

QTimer对象的 start 函数调用必须和创建QTimer对象是同一个线程。 #include "QtTimerTest.h" #include <QDebug>QtTimerTest::QtTimerTest(QWidget *parent): QMainWindow(parent),m_timer(nullptr),m_timerThread(nullptr), m_workingThread(nullptr) {ui.set…

社会治安满意度调查:为城市安全治理提供精准参考(满意度调查公司)

在社会治理不断深化的背景下&#xff0c;公众对社会治安的感知与评价已成为衡量城市治理水平的重要维度&#xff08;社会治安满意度调查&#xff09;&#xff08;公众满意度调查&#xff09;&#xff08;满意度调查&#xff09;。为全面掌握市民对治安状况的真实反馈&#xff0…

Python篇--- Python 的加载、缓存、覆盖机制

要理解 import 与 if __name__ "__main__": 的关系&#xff0c;以及 Python 的加载、缓存、覆盖机制&#xff0c;我们可以从 “模块的两种身份” 和 “导入的全过程” 入手&#xff0c;用通俗的例子一步步拆解。一、核心&#xff1a;模块的 “双重身份” 与 __name_…