MoreRSS

site iconEin Verne修改

软件工程师,开源爱好者,Linux用户和vimer开发者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Ein Verne的 RSS 预览

开源 Kotlin ORM 框架 Exposed 使用说明

2025-03-04 14:00:00

Exposed 是 JetBrains 在数年前推出的轻量级 ORM 框架,Kotlin 编写,已经在 JetBrains 内部多个关键产品使用。

Exposed 是基于 JDBC 实现,屏蔽了底层建立数据库连接,编写 SQL,操作数据,关闭数据库连接的操作,只需要关心数据操作。

Exposed 提供了两种形式 API,面向 DSL 的 API,面向对象的 API。

特点

  • 纯 Kotlin 实现
  • 类似 SQL 的静态类型化语言,可以轻松查询数据库
  • 减少样板代码
  • 支持非常多的数据库,H2,MySQL,MariaDB,Oracle,PostgreSQL,SQL Server,SQLite 等

使用

首先需要添加依赖

<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

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 }
    }
}

DAO

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)
}

Spring Boot 集成

<dependencies>
  <dependency>
    <groupId>org.jetbrains.exposed</groupId>
    <artifactId>exposed-spring-boot-starter</artifactId>
    <version>0.37.3</version>
  </dependency>
</dependencies>

Open WebUI 基于网页的大语言交互界面及联网搜索配置

2025-03-03 14:00:00

之前的视频也介绍过,ChatWise,Cherry Studio,Chatbox 等大语言模型调用的本地客户端,虽然这些客户端都还是跨平台的,但是总还是需要占用本地的空间,今天我就来介绍一款基于网页的 LLM 交互客户端 Open WebUI,以及再介绍一下如何配置 Open WebUI 让其支持联网搜索。

Open WebUI 是什么

Open WebUI 是一个功能丰富,用户友好的自托管大语言模型 Web 交互界面。Open WebUI 提供了一个类似 ChatGPT 的界面,但是允许用户与这种类型的 AI 交互

  • 完全离线
  • 本地环境可运行
  • 多种模型支持,兼容 Ollama,ChatGPT 等多种 LLM
  • 通过插件扩展能力
  • 提供富文本输出能力
  • 支持检索增强,本地 RAG 集成
Bilibili YouTube

Open WebUI 安装

可以直接通过 Docker compose 来安装,配置文件参考

本地大语言模型

本地大语言模型通过 Ollama 来支持,可以参考之前我关于如果利用 Ollama 本地安装 DeepSeek 的视频

配置联网搜索

在设置中,找到「联网搜索」配置,可以利用下方的在线服务,或者自己不熟 [[searxng]] 来实现联网搜索。

  • Tavily,1000 积分
  • Exa,80 USD,完成新手任务奖励
  • SearXNG self-hosted,免费

多版本管理工具 mise 使用详解

2025-03-02 14:00:00

我用了很多年的 asdf 作为管理各种语言版本的工具,但是最近一次更新,asdf 多了一些变化,我也写了一篇文章介绍怎么升级的。在文章下方有小伙伴(lonelyhentxi) 给我推荐了 mise 这样一款使用 Rust 编写的多版本管理工具 mise,我大致的看了一下 mise,觉得是一个非常不错的项目,在这里再次感谢。

所以今天我就来介绍一下 mise,替换 mise 倒不是因为 asdf 不能用了,而是 Rust 在执行效率上确实要更快一些,比如我之前介绍的 Meilisearchespanso 等等都是因为 Rust 编写,效率上都有所提升。

mise 是什么

mise 是 Rust 编写的一个多版本开发环境工具。

mise 可以无缝替换 asdf,具有 asdf 所有功能。asdf 会自动加载当前目录下的 .tool-versions 文件。mise 则使用稍微复杂一些的 toml ,命令为 .mise.toml

功能

mise 完全可以代替 asdf

  • 官方支持多语言,包括 Bun, Deno, Erlang, Go, Java, Python, Node, Rust 等等
  • 完全兼容 asdf 插件生态系统,通过插件支持更多开发工具
  • 每个项目可以使用独立的运行时版本
  • 自动切换对应的环境配置
  • 支持 latest, lts 等版本标识
  • 全局和项目级版本管理

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

搭建 Joplin 同步服务器

2025-03-01 14:00:00

在我的博客上很久之前我介绍过 Joplin,不过我自己只在 Linux,Android 上用过一段时间,后来出现了 Obsidian ,就切换到 Obsidian 了,但是最近看到一篇文章介绍了如何自建一个 Joplin 同步服务器,这样就可以直接无缝地进行同步数据了。之前 Joplin 只是开放了 [[WebDAV]] 协议的访问协议用可以用来同步。

介于由于部分小伙伴嫌弃 Obsidian 闭源,那不妨试试这一款完全开源的 Joplin。

什么是 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

  • 同步更快
  • 可以分享一个链接,任何人都可以访问
  • 可以直接和同一个 Server 上的人分享文档
  • 可以管理 Joplin Server 上的用户

从 Java 到 Kotlin

2025-03-01 14:00:00

Kotlin 和 Java 作为 JVM 生态中两大主流编程语言,在语法特性、开发效率和适用场景上存在显著差异。Kotlin 凭借现代化设计解决了 Java 的许多痛点,而 Java 凭借成熟生态和广泛支持仍是企业级开发的主力。

Java 转换到 Kotlin

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 类

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 直接是在函数前加上了接受者类型。

设计的目的都是为了扩展当前的类,为当前的类添加新功能,而无需修改原始类,遵循开闭原则。

一些区别

  • Kotlin 的扩展函数可以定义在任何地方,包括类内部。Dart 扩展函数智能定义在顶层。
  • Dart 和 Kotlin 都支持泛型扩展
  • Dart 不允许在 dynamic 类型上扩展,Kotlin 允许在动态类型上扩展
  • Dart 可以通过 as 关键字来解决命名冲突,Kotlin 通过导入时重命名解决冲突
  • Dart 可以创建未命名扩展,Kotlin 扩展函数可见性与普通函数相同

AI 时代我们是否还需要个人知识库

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 助手和搜索引擎都能快速提供大量信息,但这些信息绝大多数情况下都是碎片的,描述也是非个人化的,[[费曼学习法]] 说到,「学会」一个重要的特点就是能教给别人,而对于我来说,我没有那么多需要教的人,那我就把我所有的想法,经验记录在我的笔记里面,并且我认为构建个人知识库的过程本身就是一个学习和内化的过程,在组织信息的过程中,我们也可以更进一步的思考,分类,总结对于知识的理解。