2025-03-04 14:00:00
Exposed 是 JetBrains 在数年前推出的轻量级 ORM 框架,Kotlin 编写,已经在 JetBrains 内部多个关键产品使用。
Exposed 是基于 JDBC 实现,屏蔽了底层建立数据库连接,编写 SQL,操作数据,关闭数据库连接的操作,只需要关心数据操作。
Exposed 提供了两种形式 API,面向 DSL 的 API,面向对象的 API。
首先需要添加依赖
<dependency>
<groupId>org.jetbrains.exposed</groupId>
<artifactId>exposed-core</artifactId>
<version>0.37.3</version>
</dependency>
<dependency>
<groupId>org.jetbrains.exposed</groupId>
<artifactId>exposed-dao</artifactId>
<version>0.37.3</version>
</dependency>
<dependency>
<groupId>org.jetbrains.exposed</groupId>
<artifactId>exposed-jdbc</artifactId>
<version>0.37.3</version>
</dependency>
数据库连接
Database.connect("", driver = "")
DSL 和 DAO 都需要在 transaction 中执行
transaction {
// do
commit()
}
DSL 是领域特定语言。
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction
object Member : Table("member") {
val id = integer("id").autoIncrement()
val name = varchar("name", 32)
val createdAt = timestamp("created_at")
}
fun main() {
Database.connect(
url = "jdbc:mysql://127.0.0.1:3306/exposed_example",
driver = "com.mysql.cj.jdbc.Driver",
user = "root",
password = "mysql"
)
transaction {
addLogger(StdOutSqlLogger)
val id = Member.insert {
it[name] = "Kotlin"
it[createdAt] = Instant.now()
} get Member.id
println("Inserted id: $id")
val member: ResultRow = Member.select(Member.id eq id).single()
println("id: ${member[Member.id]}")
println("name: ${member[Member.name]}")
Member.update {
it[createdAt] = Instant.now()
}
Member.deleteWhere { Member.id eq id }
}
}
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.transactions.transaction
// 创建名为 member 的表,添加 name 字段
object MemberTable : IntIdTable("member") {
val name = varchar("name", 32)
val createdAt = timestamp("created_at)
}
// 创建实体类
class MemberEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<MemberEntity>(MemberTable)
var name by MemberTable.name
var createdAt by MemberTable.createdAt
}
fun main() {
Database.connect(
url = "jdbc:mysql://127.0.0.1:3306/exposed_example",
driver = "com.mysql.cj.jdbc.Driver",
user = "root",
password = "mysql"
)
transaction {
addLogger(StdOutSqlLogger)
val member = MemberEntity.new {
name = "Kotlin"
createdAt = Instant.now()
}
println("Inserted id: ${member.id}")
MemberEntity.findById(member.id)?.let {
println("id: ${it.id}")
println("name: ${it.name}")
}
val result = MemberEntity.findById(member.id)
result?.createAt = Instant.now()
result?.delete()
}
}
单列或者多列索引
object Members : IntIdTable("members") {
val index = index("idx_name_other", true, name, other)
}
<dependencies>
<dependency>
<groupId>org.jetbrains.exposed</groupId>
<artifactId>exposed-spring-boot-starter</artifactId>
<version>0.37.3</version>
</dependency>
</dependencies>
2025-03-03 14:00:00
之前的视频也介绍过,ChatWise,Cherry Studio,Chatbox 等大语言模型调用的本地客户端,虽然这些客户端都还是跨平台的,但是总还是需要占用本地的空间,今天我就来介绍一款基于网页的 LLM 交互客户端 Open WebUI,以及再介绍一下如何配置 Open WebUI 让其支持联网搜索。
Open WebUI 是一个功能丰富,用户友好的自托管大语言模型 Web 交互界面。Open WebUI 提供了一个类似 ChatGPT 的界面,但是允许用户与这种类型的 AI 交互
Bilibili | YouTube |
可以直接通过 Docker compose 来安装,配置文件参考。
本地大语言模型通过 Ollama 来支持,可以参考之前我关于如果利用 Ollama 本地安装 DeepSeek 的视频。
在设置中,找到「联网搜索」配置,可以利用下方的在线服务,或者自己不熟 [[searxng]] 来实现联网搜索。
2025-03-02 14:00:00
我用了很多年的 asdf 作为管理各种语言版本的工具,但是最近一次更新,asdf 多了一些变化,我也写了一篇文章介绍怎么升级的。在文章下方有小伙伴(lonelyhentxi) 给我推荐了 mise 这样一款使用 Rust 编写的多版本管理工具 mise,我大致的看了一下 mise,觉得是一个非常不错的项目,在这里再次感谢。
所以今天我就来介绍一下 mise,替换 mise 倒不是因为 asdf 不能用了,而是 Rust 在执行效率上确实要更快一些,比如我之前介绍的 Meilisearch,espanso 等等都是因为 Rust 编写,效率上都有所提升。
mise 是 Rust 编写的一个多版本开发环境工具。
mise 可以无缝替换 asdf,具有 asdf 所有功能。asdf 会自动加载当前目录下的 .tool-versions
文件。mise 则使用稍微复杂一些的 toml
,命令为 .mise.toml
。
mise 完全可以代替 asdf
mise 完全兼容 asdf 的 .tool-version
文件,也会默认加载。如果想要实现 mise 特有的功能,则可以切换成 .mise.toml
配置
在 asdf 下,如果用户切换到目录,发现没有安装对应的版本,asdf 需要用户手动通过 asdf install
来安装,而 mise 会自动进行安装。
mise 可以通过 .mise.toml
配置文件工具传递选项。例如给 Python 传递虚拟环境。
[tools]
python = { version = '3.10', virtualenv = '.venv' }
配置文件
# .mise.toml 示例
[tools]
node = '18.12.0'
python = '3.10.0'
[env]
NODE_ENV = 'development'
安装 mise
curl https://mise.run | sh
如果是在 macOS 下,可以使用
brew install mise
将 mise 添加到 Shell 中
echo 'eval "$(~/local/bin/mise activate zsh)"' >> ~/.zshrc
source ~/.zshrc
插件管理
node, python 等都是内置插件(core plugin),不需要额外添加。
mise plugins list-all
mise plugins add flutter
mise plugins ls
mise plugins update
版本管理以及安装对应版本
# 列举所有版本
mise ls-remote node
mise ls-remote python
# 列举安装版本
mise ls node
mise ls python
# 安装版本
mose install [email protected]
mise use --global [email protected]
mise use [email protected]
mise use node@lts
环境变量
mise set NODE_ENV=development
mise settings
2025-03-01 14:00:00
在我的博客上很久之前我介绍过 Joplin,不过我自己只在 Linux,Android 上用过一段时间,后来出现了 Obsidian ,就切换到 Obsidian 了,但是最近看到一篇文章介绍了如何自建一个 Joplin 同步服务器,这样就可以直接无缝地进行同步数据了。之前 Joplin 只是开放了 [[WebDAV]] 协议的访问协议用可以用来同步。
介于由于部分小伙伴嫌弃 Obsidian 闭源,那不妨试试这一款完全开源的 Joplin。
Joplin 是一个开源的笔记,以及 Todo 管理应用,可以在 Windows,macOS,Linux ,Android 和 iOS 上使用。
本次要介绍的 Joplin Server 就是用来给 Joplin 提供同步服务,并且实现加密传输的一个方案。
一旦自己托管了 Joplin Server 之后,就可以直接一键分享笔记到网页上。
可以使用 Docker Compose 来安装,可以根据我的配置。
version: '3'
services:
db:
image: postgres:15
volumes:
- /docker/joplindb:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_USER=postgres
- POSTGRES_DB=joplin
app:
image: joplin/server:latest
depends_on:
- db
ports:
- "22300:22300"
restart: unless-stopped
environment:
- APP_PORT=22300
- APP_BASE_URL=https://websiteurl.com
- DB_CLIENT=pg
- POSTGRES_PASSWORD=postgres
- POSTGRES_DATABASE=joplin
- POSTGRES_USER=postgres
- POSTGRES_PORT=5432
- POSTGRES_HOST=db
- MAILER_ENABLED=1
- MAILER_HOST=smtp.gmail.com
- MAILER_PORT=465
- MAILER_SECURE=1
- [email protected]
- MAILER_AUTH_PASSWORD=Y0urP@ssw0rd
- MAILER_NOREPLY_NAME=Joplin
- [email protected]
volumes:
joplindb:
我之前也提到过我使用 NextCloud 搭配 Joplin 使用,也可以同步笔记,但是相较于 Joplin Server,还是有一些缺陷。
Joplin Server
2025-03-01 14:00:00
Kotlin 和 Java 作为 JVM 生态中两大主流编程语言,在语法特性、开发效率和适用场景上存在显著差异。Kotlin 凭借现代化设计解决了 Java 的许多痛点,而 Java 凭借成熟生态和广泛支持仍是企业级开发的主力。
Kotlin 采用极简语法设计,相比 Java 减少约 40% 的样板代码量:
String name = "Kotlin";
final int age = 30;
在 Kotlin 中
val name: String = "Kotlin"
val name = "Kotlin" // 不可变变量 相当于 final
var age = 30; // 可变变量
fun max(a: Int, b: Int) = if (a > b) a else b
fun sum(a: Int, b: Int): Int {
return a + b
}
会自动生成 get set 方法
var nonNull: String = "value" // 编译时强制非空
var name: String? = null // 可空类型
val length = nonNull.length // 无需判空
// 安全调用
val nameLen = name?.length
// Elvis 操作符
val l = name?.length ?: -1
user?.address?.street?.length ?: 0
val javaList: List<String!> = JavaClass.getList()
val readOnlyList = listOf(1, 2, 3)
val mutableMap = mutableMapOf("a" to 1)
list.filter { it > 5}
.map { it * 2 }
.take(10)
data class User(val name: String, val age: Int)
数据类自动生成 equals() 和 hashCode() 方法
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val msg: String) : Result()
}
val intArray = IntArray(10)
简化了异步编程
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
println("world!")
}
println("hello")
}
通过扩展函数,为现有的类添加新方法
fun String.addExclamation() = this + "!"
val str = "Hello".addExclamation() // Hello!
Kotlin 中的扩展函数的设计和我之前学习的 Dart 函数有一些相似。Dart 使用 extension 关键字定义扩展,Kotlin 直接是在函数前加上了接受者类型。
设计的目的都是为了扩展当前的类,为当前的类添加新功能,而无需修改原始类,遵循开闭原则。
一些区别
2025-02-26 14:00:00
这两天不管是在我的视频评论下方,还是在 X(Twitter)上,都有人提出了一个问题「AI 时代还是否需要个人知识库」,我觉得在回答这个问题之前,我们首先要回答两个问题,什么是「知识」?,而什么又是「个人知识库」?
首先我们来聊一聊,「知识」的概念。
在牛津高阶字典中对知识的解释是:
the information, understanding and skills that you gain through education or experience
我们从教育和经验中获得的信息,理解和技能。
《汉典》中对知识的解释
人们在实践中获得的认识和经验。
这里我们可以看到,知识通常被理解为个体通过学习、经验、交流等方式获得的信息和技能的集合。它不仅仅包括事实和数据,还包括对这些信息的理解、分析和应用能力。知识可以分为显性知识和隐性知识。显性知识是可以被清晰表达、记录和传输的信息,例如书本上的内容、文档中的数据等。而隐性知识则是难以形式化的,比如个人经验、直觉以及技能。
很显然,说到这里,我们肯定是需要「知识」的,并且这个知识不仅包括了我们从教育中获取的信息和技能,也包括我们从经验中获取到的隐形知识。
在进入下一个话题之前,我还想聊一聊,知识是如何产生的?从古至今,已经有非常多的哲学家思考过这一个问题了。
古希腊亚里士多德认为,知识源自人类对周围世界的观察和感官体验。柏拉图认为存在三种类型的知识,被证实的(justified),被相信的 (believed)以及真的。笛卡儿等理性主义者认为,可靠的知识源自理性思维,而非感官经验,认为「直观」和「演绎」是获取知识最可靠的方法。而经验主义者的[[洛克]] 在《人类理解论》中批评了「天赋观念」的理论,并提出了人类所有的知识都是源自后天经验。康德则试图调解理性主义和经验主义,他认为知识既不单纯源于经验,也不仅由理性构成,他认为知识由先天的形式(概念和范畴)和后天的质料(感觉表象)构成。
如果无法理解哲学家们纯理论的观点的话,我们不放再来看看近代以来,人类通过不断的科学实验进一步拓展了知识的边界。
知识形成的过程是一个动态的长期过程
很久以前看到过这样一句名言,「想象力比知识重要」,那是因为如果要产生新的知识,假设想象的重要程度远远高于其他。
在结束关于「知识」的探讨之前,最后想再聊一下「波普尔」,在《通过知识获得解放》一书中,波普尔认为我们的知识都是建立在暂时性和尝试性解决办法之上,这就是他著名的 [[可证伪理论]]。
接下来我们再聊一聊个人知识库,在了解到知识库之前,我很长一段时间都只有笔记库,并且还散落在各个地方,曾经用过一段时间 OneNote,用过一段时间 EverNote,还用过一段时间 WizNote,笔记和笔记之间相互不关联,每一个笔记都有一个或多个内容。但是前两年我获知 Zettelkasten 之后,就逐渐将我的所有笔记放到了 Obsidian 中,并建立了一个笔记间相互链接的个人笔记库。
Zettelkasten 中我认为对我最重要的一点启发就是,每一张卡片都只有一个主题,并且需要用我自己的话重新对这个概念进行编写。洛克认为语言是观念的标记,是人们用来交流思想的载体,但是语言却也容易产生歧义,所以对于每一个写入我笔记的内容,我都会使用我自己的语言进行重塑。
那再回到我们的主题,「AI 时代我们还是否需要个人知识库」,我的回答是「当然」,这个问题对我而言就等同于,AI 时代我们是否还需要教育和书籍一样,我之前做的几期视频里面已经完全证明了目前的 AI 在完成高考语文数学物理化学题目上没有任何困难了,更甚至一些 LLM 已经在认知水平上达到了博士后,更不用说在数学,编程等等需要依靠逻辑思维的地方,那我们难道就可以不再学习基础学科吗?
另外一点,就是个人知识库的重点,我认为是个人,知识库在哪里都有,Wikipedia 容纳了人类多少年的百科知识,互联网上哪里都可以找到专业领域的知识库,更不用说还有非常庞大的论文,文献,语料库等等。但是个人知识库,它是一个个人经验,认识的积累,它可能对于其他人来说没有那么重要,尤其是对于哪些跨行业的其他人来说,但是当我们去维护一个属于自己的知识网络的时候,我认为是一个 1+1 > 2 的积累。AI 尽管可以快速给我们分析讲解某一个概念,但问题在于我们不能全盘接受 AI 的回复,AI 的回答可能会是错误的,也可能是引用了互联网上错误的认知。而如果有一份个人知识库,我们每一次学习工作累积下来的知识,都可以快速地被我们找到并且记忆,一旦当我们认识到我们知识库中的知识「过期」时,我们也可以借助 AI 以及专业的书籍来给我们纠正。而当我们想要去快速复习某一些概念时,个人知识库也可以快速给我们一个复习清单,节省我们和 AI 对话,从无数多信息中过滤出有效信息的过程。
在 AI 技术日益发展的今天,诸多的 AI 助手和搜索引擎都能快速提供大量信息,但这些信息绝大多数情况下都是碎片的,描述也是非个人化的,[[费曼学习法]] 说到,「学会」一个重要的特点就是能教给别人,而对于我来说,我没有那么多需要教的人,那我就把我所有的想法,经验记录在我的笔记里面,并且我认为构建个人知识库的过程本身就是一个学习和内化的过程,在组织信息的过程中,我们也可以更进一步的思考,分类,总结对于知识的理解。