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
英文地址,請注明出處:http://en.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_…