chore: remove blog, open-source and news sections

- Delete the blogs, opensource-projects and notifications trees
- Drop their nav entries and i18n keys
- Point the nav at the new open-source site instead
- Drop the opensource-projects sitemap priority
This commit is contained in:
zihluwang committed 2026-09-29 12:21:53 +08:00
1 parent e149401346
commit 7a63074a65
92 files changed
+20 -8112

No files matched your search

+1 -12
View File
@@ -14,19 +14,8 @@
"link": "/services/",
"activeMatch": "/services/"
},
{
"text": "nav.insights",
"link": "/blogs/",
"activeMatch": "/blogs/"
},
{
"text": "nav.news",
"link": "/notifications/",
"activeMatch": "/notifications/"
},
{
"text": "nav.opensource",
"link": "/opensource-projects/",
"activeMatch": "/opensource-projects/"
"link": "https://opensource.onixbyte.com"
}
]
-33
View File
@@ -1,33 +0,0 @@
[
"index",
{ "type": "section-header", "label": "insights.group.frontend" },
{ "type": "file", "name": "ecmascript-2025-syntax-sugar" },
{ "type": "file", "name": "frontend-development-standards" },
{ "type": "file", "name": "frontend-tips-and-solutions" },
{ "type": "file", "name": "font-size-conversion-table" },
{ "type": "section-header", "label": "insights.group.backend" },
{ "type": "file", "name": "java-development-standards" },
{ "type": "file", "name": "java-development-cheatsheet" },
{ "type": "file", "name": "mybatis-flex" },
{ "type": "file", "name": "postgresql-zhparser-fuzzy-search" },
{ "type": "file", "name": "use-json-in-mysql" },
{ "type": "section-header", "label": "insights.group.devops" },
{ "type": "file", "name": "docker-deployment-standards" },
{ "type": "file", "name": "gitlab-ops" },
{ "type": "file", "name": "setup-ldap-service" },
{ "type": "file", "name": "minio-admin-guide" },
{ "type": "section-header", "label": "insights.group.practice" },
{ "type": "file", "name": "general-development-standards" },
{ "type": "file", "name": "google-code-review-standards" },
{ "type": "file", "name": "version-control-and-code-review" },
{ "type": "file", "name": "git-cheatsheet" },
{ "type": "section-header", "label": "insights.group.tooling" },
{ "type": "file", "name": "fix-idea-application-yaml-schema-validation" },
{ "type": "file", "name": "windows-driver-backup-restore" },
{ "type": "file", "name": "fix-macos-monterey-sleep-wake" },
{ "type": "file", "name": "macos-private-dns-hostname" },
{ "type": "file", "name": "quartile-method" },
{ "type": "file", "name": "use-of-gravatar" },
{ "type": "file", "name": "email-like-a-boss" },
{ "type": "file", "name": "cooking-seasoning-guide" }
]
@@ -1,31 +0,0 @@
---
title: 炒菜调料指南
tags:
- cooking
- cheatsheet
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 调料投放时机
| 调料类型 | 放调料时机 | 适用菜品 | 作用 |
| -------------------- | ---------------- | ---------------------------- | -------------------------------------- |
| **盐** | 最后放 | 菜叶子 | 防止菜叶变软出水,保证口感干爽脆嫩 |
| | 中间放 | 土豆丝、豆角、蒜苔等 | 更好入味 |
| **味精、生抽、蚝油** | 偏后放 | 各类需要增鲜提香的菜品 | 增鲜提香,避免高温破坏鲜味和香味 |
| **料酒** | 和食材一起放 | 需要去腥的食材 | 焯水时去腥效果更好 |
| | 锅温最高的时候放 | 需要炒菜去腥的食材 | 炒菜时高温促进酒气挥发,炝锅去腥效果好 |
| **醋** | 提前放 | 酸辣土豆丝、酸辣大白菜 | 使土豆丝变脆更入味 |
| | 出锅前锅边醋 | 辣椒炒肉等需要猛火爆炒类菜品 | 大火炝锅增香 |
| **老抽** | 炒半熟后放 | 需要上色的菜品 | 达到最佳上色效果 |
## 快速参考
| 调料 | 投放时机 | 作用 | 示例 |
| ----------------------- | ------------------- | -------------------------------------------- | ------------------------------------- |
| **盐** | 出锅前 10 - 15 秒放 | 避免提前放盐导致菜叶出水、变软,保持脆嫩口感 | 炒生菜、菠菜时,关火前撒盐翻炒均匀 |
| **蒜末 / 姜末** | 热油后先放 | 爆香提味,激发香味 | 油热后下蒜末炝锅,再放青菜翻炒 |
| **生抽 / 蚝油**(可选) | 盐之后放 | 增鲜提味,少量即可,避免盖过蔬菜原味 | 炒上海青时,加盐后滴 1 - 2 滴生抽翻炒 |
| **食用油** | 热锅热油 | 油热后快速炒,锁住水分 | 大火快炒时,油稍多一点,油温要高 |
@@ -1,16 +0,0 @@
---
title: Docker 部署规范
tags:
- docker
- deployment
- standards
- best-practice
author:
name: Zihlu Wang
email: real@zihluwang.me
---
- **Dockerfiles**: 为应用程序提供 `Dockerfile`,实现容器化部署。
- **轻量级镜像**: 通过使用适当的基础镜像和多阶段构建,力求实现轻量级 Docker 镜像。
- **配置**: 确保环境特定配置(例如,数据库连接字符串、外部服务 URL)通过注入到 Docker 容器中的环境变量进行管理。
- **日志记录**: 配置容器化日志记录,将输出发送到 `stdout` 和 `stderr`,以便日志聚合系统轻松收集。
@@ -1,55 +0,0 @@
---
title: ECMAScript 2025 语法糖大全
tags:
- javascript
- ecmascript
- pattern-matching
- frontend
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 模式匹配
### 传统写法
```javascript
function processResponse(response) {
if (response.status === 200 && response.data) {
return { success: true, data: response.data }
} else if (response.status === 404) {
return { success: false, error: "Not found" }
} else if (response.status >= 500) {
return { success: false, error: "Server error" }
} else {
return { success: false, error: "Unknown error" }
}
}
```
### 模式匹配写法
```javascript
function processResponse(response) {
return match (response) {
when ({ status: 200, data }) -> ({ success: true, data })
when ({ status: 404 }) -> ({ success: false, error: 'Not found' })
when ({ status: status if status >= 500 }) -> ({ success: false, error: 'Server error' })
default -> ({ success: false, error: 'Unknown error' })
};
}
```
### 数组长度分支也能优雅处理
```javascript
function handleArray(arr) {
return match (arr) {
when ([]) -> "Empty array"
when ([first]) -> `Only one element: ${first}`
when ([first, second]) -> `Two elements: ${first}, ${second}`
when ([first, ...rest]) -> `First element: ${first}, others: ${rest.length} items`
};
}
```
-40
View File
@@ -1,40 +0,0 @@
---
title: 像老板一样写邮件
tags:
- communication
- email
- soft-skill
- career
- productivity
author:
name: Zihlu Wang
email: real@zihluwang.me
---
工作中的邮件沟通不仅仅是传递信息,更是建立信任、塑造专业形象的过程。同样一件事情,换一种表达方式,给人的印象可能是天壤之别。
以下整理了 9 个常见邮件场景中「低姿态表达」与「高阶表达」的对比,让你的邮件读起来更自信、更专业。
## 场景速查表
| 场景 | ❌ 别用(听起来……) | ✅ 改用(听起来……) | 为什么有效 |
| ---------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| 回复晚了 | **Sorry for the delay**<br/>(抱歉回复晚了) | **Thanks for your patience**<br/>(感谢你的耐心等待) | 前者在道歉,后者在感谢——从「我做错了」转变为「你太好了」,既表达了歉意又让对方感到被尊重 |
| 约时间 | **What works best for you?**<br/>(你什么时候方便?) | **Could you do …?**<br/>(你可以在某某时间吗?) | 前者把决定权完全推给对方,后者主动给出选项,减少来回沟通的成本 |
| 帮助了别人之后 | **No problem / No worries**<br/>(没问题 / 别担心) | **Always happy to help**<br/>(乐于效劳) | 前者暗示这件事"本来可能是个问题",后者传递出你乐在其中、下次还愿意帮 |
| 提出建议 | **I think maybe we should …**<br/>(我觉得也许我们应该……) | **It'd be best if we …**<br/>(最好是……) | 前者透着犹豫和不自信,后者直接给出判断——像有经验的人在做决策 |
| 文字沟通费劲 | **_花 30 分钟反复修改一封邮件_** | **It'd be easier to discuss in person**<br/>(当面/电话聊会更轻松) | 意识到沟通渠道本身就是问题,果断切换方式反而是效率最高的选择 |
| 确认对方是否理解 | **Hopefully that makes sense?**<br/>(希望我说清楚了?) | **Let me know if you have questions**<br/>(有问题随时问我) | 前者在怀疑自己的表达能力,后者把责任合理地分担给对方,姿态更稳 |
| 催进度 | **Just wanted to check in**<br/>(只是想确认一下) | **When can I expect an update?**<br/>(我什么时候可以收到更新?) | 前者显得小心翼翼不敢打扰,后者直接问时间节点——清楚、礼貌、专业 |
| 犯了小错 | **Ahh sorry my bad totally missed that**<br/>(啊啊抱歉我的错,完全漏掉了) | **Thanks for letting me know**<br/>(感谢提醒) | 前者过度道歉反而让对方尴尬,后者承认了问题但把焦点放在了"解决"上 |
| 需要早退 | **Could I possibly leave early?**<br/>(我能提前走吗?) | **I will need to leave at …**<br/>(我需要在某某时间离开) | 前者在请求批准,后者在陈述安排——你是专业人士,不需要为合理需求道歉 |
## 核心原则
写好邮件的关键不在于词汇量,而在于**姿态**。时刻提醒自己三条规则:
1. **用陈述代替请求** —— 「我需要……」比「我能不能……」更有分量
2. **用感谢代替道歉** —— 把焦点从「我的过失」转移到「对方的支持」
3. **用明确代替模糊** —— 给出具体的时间、选项、行动项,而不是把球踢回给对方
下次打开邮箱,花 5 秒想一想:这句话能不能说得更像一个做决定的人?
@@ -1,58 +0,0 @@
---
title: 修复 IntelliJ IDEA 中 Spring Boot 的 application.yaml 智能提示消失问题
tags:
- intellij-idea
- spring-boot
- yaml
- schema-validation
- troubleshooting
author:
name: Zihlu Wang
email: real@zihluwang.me
---
import { Kbd } from "@rspress/core/theme"
最近在使用 IDEA 开发 Spring Boot 项目时,我发现 `application.yaml` 和 `application.yml` 的智能代码提示突然消失了。更糟糕的是,编辑器中充满了如下警告:
> `Schema validation: Missing required property 'kind' = 'Application'`
## 症状
打开 `src/main/resources/` 下的 `application.yaml` 或 `application.yml` 文件时,IDEA 会将其视为 Enonic XP 应用描述文件,而非 Spring Boot 配置文件。所有 Spring 特有的属性提示全部消失,取而代之的是一堆要求填写 `kind` 属性的 Schema 校验错误——这个字段与 Spring Boot 毫无关系。
重启 IDEA、清除缓存、重新导入项目都无济于事,问题依旧存在。
## 快速修复
经过一番排查,我找到了罪魁祸首。IDEA 会从远程源(具体来说是 [SchemaStore](https://www.schemastore.org/) 目录)下载 JSON Schema,并用它们来校验配置文件。最近的一次 Schema 更新引入了一个匹配范围过大的 `fileMatch` 模式。
要立即禁用此行为:
1. 打开 **Settings**(Windows:<Kbd>Ctrl</Kbd>+<Kbd>Alt</Kbd>+<Kbd>S</Kbd> / macOS:<Kbd mac="cmd" /> + <Kbd>,</Kbd>)
2. 导航至 **Languages & Frameworks → Schemas and DTDs → Remote JSON Schemas**
3. 取消勾选 **Allow downloading JSON Schemas from remote sources**
4. 点击 **OK**
Spring Boot 的配置提示会立刻恢复。
## 根本原因
事情的根源要追溯到 **2026 年 5 月 19 日**,一位来自 [Enonic](https://www.enonic.com/) 的开发者向 SchemaStore 仓库提交了一个 PR([PR #5704](https://github.com/SchemaStore/schemastore/pull/5704)),添加了 Enonic XP 应用描述文件的 Schema。该 Schema 的 `fileMatch` 属性被设置为:
```json
"fileMatch": [
"**/src/main/resources/application.yaml",
"**/src/main/resources/application.yml"
]
```
这个匹配模式过于宽泛——它匹配了 **所有** 位于 `src/main/resources/` 下的 `application.yaml` 和 `application.yml` 文件,而这正是 Spring Boot 项目存放配置文件的位置。一旦该 Schema 被合并到 SchemaStore 中,任何启用了远程 JSON Schema 下载功能的 IntelliJ IDEA 都会拉取它,并开始用 Enonic XP 的 Schema 来校验 Spring Boot 的配置。
该开发者已意识到问题,并提交了后续修复 PR([PR #5711](https://github.com/SchemaStore/schemastore/pull/5711))来收紧 `fileMatch` 匹配规则。截至本文撰写时,该修复尚未被 SchemaStore 合并。
## 为什么这很重要
SchemaStore 是一个由社区维护的 JSON Schema 目录,被许多编辑器(VS Code、IntelliJ IDEA、Neovim 等)用来为常见配置文件提供校验和自动补全功能。当该目录中的某个 Schema 声明了过于宽泛的匹配规则时,会影响到所有平台、所有编辑器的用户——不仅仅是 IDEA。
对于 Spring Boot 开发者来说,结论很简单:如果你的 `application.yaml` 提示一夜之间消失了,先检查一下你的 IDE 认为该文件属于哪个 Schema,它可能根本不是 Spring Boot。
@@ -1,26 +0,0 @@
---
title: 解决 macOS Monterey+ 设备休眠频繁自动唤醒的问题
tags:
- macos
- sleep
- power-management
- bug
---
> 本文由 **落格博客** 原创撰写:[落格博客](https://www.logcg.com/) » [升级 macOS Monterey 后设备休眠半夜频繁唤醒问题](https://www.logcg.com/archives/3528.html)
更新到了 macOS Monterey,半夜总会被屏幕照醒,就觉得很诡异,以前也有过,但都是有通知的时候才会点亮屏幕,现在是没有任何理由的自己点亮,硬件还是那个硬件,那就应该是软件的锅了。
在网上查了一圈,先是找到了[苹果官方的教程](https://support.apple.com/zh-cn/guide/mac-help/mchlp2995/mac)。写的很详细,但显然是没有任何用处的。
于是继续深挖,还真找到了原因,使用命令 **`pmset -g log | grep DarkWake`** 就可以看到,在你睡觉的过程中,你的 Mac 并没有歇着……
看看输出,这几个比较典型,大部分都是这样的,一个 DarkWake, 一个 Wake, 两个仅仅相连,那么问题就出来了, DarkWake 是给电脑在后台唤醒来更新数据的,但不知怎么的,外设被触发,从而导致了全局唤醒。
总之,我并不想要这个功能, 就那个 PowerNap,对我来说,我更希望它能给我多省点电,于是大概解决思路就这么多:关掉网络访问唤醒,关掉 PowerNap……不过问题来了,在 m1 设备上,其实是没有 PowerNap 选项的……(显然,苹果对自己的续航很有信心,但他们忽略了Bug的威力)
于是对于 PowerNap 这个功能来说,我们只能从命令行下手,首先使用 **`pmset -g`** 命令查看当前状态,找到 **`powernap`** 的值,如果不是 0 ,说明是启用的状态,使用命令 **`sudo pmset -a powernap 0`** 关掉它。
同时,还有另外一个 **`tcpkeepalive`** ,这个默认应该也不是 0 ,也要关掉,它决定了你的 rmbp 在休眠时是否要保持 tcp 连接。 **`sudo pmset -a tcpkeepalive 0`** ,执行这条命令会导致终端提示:**_Warning: This option disables TCP Keep Alive mechanism when system is sleeping. This will result in some critical features like 'Find My Mac' not to function properly._**
大概是说关了的话某些功能会受到限制,其实就是系统功能不能休眠时联网了,我相信真有人偷了你 Mac,它也连不上网的。
@@ -1,30 +0,0 @@
---
title: 字号榜值转换表
tags:
- typography
- font
- cheatsheet
- design
author:
name: Zihlu Wang
email: real@zihluwang.me
---
| 中文字号 | 英文字号(磅)/pt | 毫米/mm | 像素/px |
| -------- | ----------------- | ------- | ------- |
| 初号 | 42 | 14.82 | 56 |
| 小初 | 36 | 12.7 | 48 |
| 一号 | 26 | 9.17 | 34.7 |
| 小一 | 24 | 8.47 | 32 |
| 二号 | 22 | 7.76 | 29.3 |
| 小二 | 18 | 6.35 | 24 |
| 三号 | 16 | 5.64 | 21.3 |
| 小三 | 15 | 5.29 | 20 |
| 四号 | 14 | 4.94 | 18.7 |
| 小四 | 12 | 4.23 | 16 |
| 五号 | 10.5 | 3.7 | 14 |
| 小五 | 9 | 3.18 | 12 |
| 六号 | 7.5 | 2.56 | 10 |
| 小六 | 6.5 | 2.29 | 8.7 |
| 七号 | 5.5 | 1.94 | 7.3 |
| 八号 | 5 | 1.76 | 6.7 |
@@ -1,42 +0,0 @@
---
title: 前端开发规范
tags:
- frontend
- react
- standards
- best-practice
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 依赖管理 (pnpm)
- **严格性**: 利用 pnpm 的严格依赖管理,以确保更确定性的 `node_modules` 结构和高效的磁盘空间使用。
- **工作区**: 如果使用 monorepo 方法,配置 pnpm 工作区以简化多个前端包的依赖管理。
- **审计**: 定期使用 `pnpm audit` 审计前端依赖项是否存在已知漏洞。
## API 通信 (Axios)
- **Axios 实例**: 创建一个集中的 Axios 实例用于 API 调用,以应用通用配置(基本 URL、请求头、拦截器)。
- **拦截器**: 使用 Axios 拦截器进行以下操作:
- 向传出请求添加认证令牌。
- 处理全局错误响应(例如,显示 `401 Unauthorized` 的通知)。
- 在开发环境中记录请求/响应。
- **错误处理**: 在 Axios 拦截器或自定义工具函数中集中处理 API 错误,以提供一致的用户反馈。
## React 与组件标准
- **函数组件与 Hooks**: 对于新开发,优先使用带有 React Hooks 的函数组件,而非类组件。
- **Props**:
- 为组件 props 定义 `interface` 或 `type` 以确保类型安全。
- 在组件入口处解构 props 以提高清晰度。
- **状态管理 (Redux)**:
- 使用 Redux Toolkit 进行高效且减少样板代码的 Redux 开发。
- 使用 `createSlice` 将 Redux 逻辑组织成"切片"(特定功能的 reducer、action 和 selector)。
- 遵循"ducks"模式或"slices"方法来协同定位 Redux 逻辑。
- **组件组合**: 将复杂的 UI 分解为更小、可重用且职责单一的组件。
- **Ant Design**:
- 利用 Ant Design 组件以实现一致的 UI/UX。
- 如果需要,使用 CSS-in-JS 解决方案在整个应用程序中一致地定制 Ant Design 主题和样式。
- **可访问性**: 从一开始就设计和实现考虑到 Web 可访问性 (a11y) 的组件。
@@ -1,75 +0,0 @@
---
title: 前端开发技巧与方案速查手册
tags:
- frontend
- react
- ant-design
- tailwind
- best-practice
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 表单组件的解耦
将表单组件拆分为 UI 组件和逻辑组件,并在逻辑组件中使用 UI 组件渲染样式。
## React 入口组件顺序
```tsx
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { Provider as ReduxProvider } from "react-redux"
import { PersistGate } from "redux-persist/integration/react"
import { RouterProvider } from "react-router"
import { App as AntApp, ConfigProvider as AntConfigProvider } from "antd"
import { StyleProvider } from "@ant-design/cssinjs"
import AntSimplifiedChinese from "antd/locale/zh_CN"
import "./index.css"
import "@/config/dayjs-config"
import store, { persistor } from "@/store"
import router from "@/router"
createRoot(document.getElementById("root")!).render(
<StrictMode>
<ReduxProvider store={store}>
<PersistGate loading={null} persistor={persistor}>
{/* 注意:StyleProvider 必须是 ConfigProvider 的父组件!!! */}
<StyleProvider layer>
<AntConfigProvider
locale={AntSimplifiedChinese}
button={{
autoInsertSpace: false,
}}>
<AntApp className="h-full w-full">
<RouterProvider router={router} />
</AntApp>
</AntConfigProvider>
</StyleProvider>
</PersistGate>
</ReduxProvider>
</StrictMode>
)
```
## React 中 Ant Design 与 Tailwind 整合
> 参考 [https://github.com/ant-design/ant-design/discussions/56152](https://github.com/ant-design/ant-design/discussions/56152)
```css
/* index.css */
@layer theme, base, antd, components, utilities;
@import "tailwindcss";
```
```tsx
<StyleProvider>
<ConfigProvider>
<App>
<RouterProvider />
</App>
</ConfigProvider>
</StyleProvider>
```
@@ -1,23 +0,0 @@
---
title: 应用开发通用规范
tags:
- standards
- best-practice
- engineering
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 引言
本文件旨在概述应用程序开发的编码标准和最佳实践。遵守这些指导方针可确保所有项目的代码质量、可维护性和一致性。
## 通用原则
- **清晰性与可读性**: 代码必须易于阅读和理解。优先选择清晰、自文档化的代码,而非巧妙、简洁但晦涩的解决方案。
- **一致性**: 在整个项目中保持一致的编码风格、命名约定和架构模式。
- **模块化**: 设计组件时应实现松耦合和高内聚,以促进重用性和更简单的测试。
- **可测试性**: 编写本质上可测试的代码。
- **安全先行设计**: 在开发的每个阶段都将安全考虑融入其中。
- **性能意识**: 对关键代码段和 API 端点考虑其性能影响。
-161
View File
@@ -1,161 +0,0 @@
---
title: Git 使用小贴士
tags:
- git
- tips
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 忽略已跟踪文件的本地改动:`git update-index --skip-worktree`
当你需要在本机修改某个已被 Git 跟踪的文件,但又不希望这些改动出现在 `git status` 或提交里时(比如本地的 `application-local.yml`、`.env`、IDE 配置等),可以使用 `--skip-worktree` 让 Git 忽略该文件的本地改动。
### 标记与取消标记
```bash
# 让 Git 忽略该文件的本地改动
git update-index --skip-worktree <file>
# 取消忽略,恢复对该文件的跟踪
git update-index --no-skip-worktree <file>
```
### 查看所有被忽略的文件
被标记的文件会在 `git ls-files -v` 的输出中以 `S` 标识:
```bash
git ls-files -v | grep '^S'
```
### 提交对已标记文件的改动
如果需要向仓库提交该文件的新改动,需要先取消标记、提交、再重新标记:
```bash
git update-index --no-skip-worktree <file>
git add <file>
git commit -m "chore: update config"
git update-index --skip-worktree <file>
```
### `--skip-worktree` 与 `--assume-unchanged` 的区别
两个选项都能让 Git 不再关注已跟踪文件的改动,但用途不同:
| 选项 | 用途 |
| -------------------- | ---------------------------------------------------------------------------- |
| `--assume-unchanged` | 性能优化,适用于几乎不会改变的大型文件;Git 检测到文件变化后可能自动重新跟踪 |
| `--skip-worktree` | 有意忽略本地改动,适用于「保持本地修改但不上传」的场景,更持久 |
因此,「忽略本地改动」应该使用 `--skip-worktree`。
### 注意事项
- `--skip-worktree` 只影响工作区层面,并不是一个「永久忽略」。在执行 `git pull`、`git checkout`、`git stash` 等可能改写该文件的操作时,本地改动可能会被覆盖或引发冲突。如果该文件在远端也会变化,建议在拉取前先取消标记。
- 它只对**已跟踪**的文件生效;对于未跟踪的文件,请使用 `.gitignore`。
## 暂存未完成的改动:`git stash`
需要临时切换分支时,可以用 `git stash` 把工作区改动保存起来:
```bash
git stash push -m "work in progress" # 保存并附带备注
git stash -u # 连同未跟踪的文件一起保存
git stash list # 查看已保存的改动
git stash pop # 恢复并删除该条记录
git stash apply # 恢复但保留该条记录
git stash drop # 删除某条记录
```
## 修正最近一次提交:`git commit --amend`
当你提交后发现忘了加入某些文件、或想修改提交信息时:
```bash
git add <forgotten-file>
git commit --amend # 保持原提交信息,合并进新改动
git commit --amend -m "新提交信息" # 同时修改提交信息
```
注意:如果该提交已经被推送到远端,请改用新增一个提交的方式,避免改写公共历史。
## 撤销改动:`git restore` 与 `git reset`
```bash
git restore <file> # 丢弃工作区(未暂存)的改动
git restore --staged <file> # 取消暂存,保留改动(相当于 git reset HEAD <file>)
git restore --source=HEAD <file> # 将文件恢复到 HEAD 版本
```
想彻底丢弃未提交的全部改动(包括暂存区),可以:
```bash
git reset --hard HEAD
```
`--hard` 会连同工作区一并重置,使用前请确认没有需要保留的改动。
## 找回丢失的提交:`git reflog`
误用了 `git reset --hard` 或 `git rebase` 导致提交「消失」时,`reflog` 记录了 HEAD 的所有历史位置:
```bash
git reflog
git reset --hard HEAD@{2} # 回到 reflog 中记录的某个位置
```
只要提交还在对象库中,就能通过 reflog 找回。
## 二分查找回归:`git bisect`
当某个 bug 不知道从哪个提交引入时,`git bisect` 会通过二分法帮你定位:
```bash
git bisect start
git bisect bad # 当前提交是有问题的
git bisect good <sha> # 标记一个已知正常的提交
```
Git 会不断切换到一个中间提交,你只需要根据当前状态标记 `good` 或 `bad`,最终会定位到引入 bug 的那个提交:
```bash
git bisect good # 或 git bisect bad
git bisect reset # 结束并回到原分支
```
## 更友好的历史记录:`git log`
```bash
git log --oneline --graph --decorate --all # 以图形方式查看全部分支
git log -S "some-string" --all # 查找新增或删除某字符串的提交
git blame <file> # 查看每行代码的最后修改者
git blame -L 10,20 <file> # 只查看指定行范围
```
## 清理未跟踪文件:`git clean`
```bash
git clean -n -fd # 先预览会被删除的文件(安全起见务必先跑)
git clean -fd # 删除未跟踪的文件和目录
```
`-n` 表示只展示而不实际删除,建议先预览再执行。
## 精挑细选:`git cherry-pick`
把其他分支上的某个提交应用到当前分支:
```bash
git cherry-pick <sha>
```
## 清理已合并的本地分支
```bash
git branch --merged | grep -v '\*' | xargs -n 1 git branch -d
```
该命令会删除所有已合并进当前分支的本地分支(当前分支除外)。执行前建议先用 `git branch --merged` 预览。
-56
View File
@@ -1,56 +0,0 @@
---
title: GitLab 运维
tags:
- gitlab
- devops
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 在国际版 GitLab 中设置默认语言为中文
GitLab 在代码中通过 `SWITCHER_MINIMUM_TRANSLATION_LEVEL` 变量将翻译程度低于 90% 的语言过滤了,简体中文为 84%,繁体中文为 83%
可以前往 `/path/to/gitlab/embedded/service/gitlab-rails/app/helpers/preferred_language_switcher_helper.rb` 中将代码修改如下:
```diff title="/path/to/gitlab/embedded/service/gitlab-rails/app/helpers/preferred_language_switcher_helper.rb"
- SWITCHER_MINIMUM_TRANSLATION_LEVEL = 90
+ SWITCHER_MINIMUM_TRANSLATION_LEVEL = 80
```
随后通过指令 `gitlab-ctl restart` 重启 GitLab 即可。
## 在首页添加备案信息
前往 `/path/to/gitlab/embedded/service/gitlab-rails/app/views/devise/shared/_footer.html.haml` 中,添加如下代码:
```diff
+ = link_to _("你的备案号"), "https://beian.miit.gov.cn/", target: '_blank', class: 'text-nowrap', rel: 'noopener noreferrer'
```
## 升级 GitLab
### 下载 GitLab Global CE 版安装包
从 [GitLab Package 下载页](https://packages.gitlab.com/gitlab/gitlab-ce) 下载 deb 工具包,并使用 scp 等工具将其上传到服务器中。
### 停用内存大户
```shell
gitlab-ctl stop puma
gitlab-ctl stop sidekiq
```
### 执行备份
```shell
# 示例:跳过构建附件和容器镜像仓库,仅备份数据库和代码仓库
sudo gitlab-backup create SKIP=artifacts,registry
```
### 进行安装
```shell
sudo dpkg -i gitlab-package.deb
```
@@ -1,98 +0,0 @@
---
title: Google 代码审查标准
tags:
- code-review
- best-practice
- engineering
- google
author:
name: Zihlu Wang
email: real@zihluwang.me
---
代码审查是开发过程中的一个环节,顾名思义,代码审查需要一位或多位开发人员审查另一位开发人员(即代码的作者)编写的代码,以确保:
- 代码没有任何错误,没有 bug,也没有问题;
- 代码符合质量与样式指南的要求和标准;
- 代码完成了所有预期功能;
- 合并代码后,代码库仍然能够正常运行,且达到更好的状态。
这就是为什么代码审查是软件开发的重要环节的原因。代码审查者担当着把关者的职责,负责决定这些代码是否能够成为代码库的一部分并进入生产环境。
## 这些代码能够提升系统整体的运行状况
每次代码变更(拉取请求)都能够提升系统整体的运行状况。重点在于,即便是很小的改进,合并代码后都会提升软件或代码库的运行状况。
## 快速审查代码,并给出积极地响应和反馈
首先也是最重要的一点,不可延误代码的合并。世上没有完美的代码。如果代码可以提升系统的整体运行状况,则应该立即交付这些代码。
> 关键在于,世上没有完美的代码,只有更好的代码。—— Google 工程实践文档
如果手头没有紧急任务,那么请在代码提交上来后立即进行审查。响应拉取请求的时间最长不得超过一个工作日。一天之内,应针对一次拉取请求完成多轮的部分或完整的代码审查。
## 在代码审查的过程中开展教育和启发
在代码审查的过程中,应尽可能通过共享知识和经验提供指导。
## 审查代码应遵循标准
请始终牢记,样式指南、编程标准以及相关的文档应该作为代码审查的绝对权威。例如,制表符与空格的使用应保持一致,此时你可以引用编程约定。
> 如果你选用的是 Java,那么以下文章可能会有所帮助,文中总结了大型科技公司 Java 编程的最佳实践:《[Java 编程最佳实践摘要](https://rhamedy.medium.com/a-short-summary-of-java-coding-best-practices-31283d0167d3)》
## 解决代码审查冲突
解决代码审查冲突时,应遵循样式指南以及编程标准中商定的最佳实践,并征求其他拥有更多产品领域知识和经验的人的建议。
如果你的意见是可选或不怎么重要的,请在注释中说明,然后由作者来决定是解决还是略过。
作为代码审查者,在没有样式指南或编程标准的情况下,你至少可以建议此次代码变更与其余代码库保持一致。
## 演示 UI 变更是代码审查的一部分
如果代码变更涉及用户界面变化,则除了代码审查外,还需要提供演示,确保界面符合预期且与界面设计一致。
对于前端代码变更,你需要进行演示,或确保代码变更包括必要的UI自动化测试,以验证添加或更新的功能。
## 确保代码审查中包含了所有测试
除非遇到紧急情况,否则拉取请求应包含所有必要的测试,例如单元测试、集成测试以及端到端测试等。
这里所说的紧急情况指的是,某个需要尽快修复的 bug 或安全漏洞,而测试可以等到以后再添加。在这种情况下,请确保创建了适当的票证/问题,并确保有人负责在完成热修复或部署后立即完成测试。
我们绝对不可以跳过测试。如果时间有限,某些目标有无法实现的风险,那么解决方案不是跳过测试,而是限定可交付成果的范围。
## 不要为了代码审查打断手头的工作
如果你正在专心致志地工作,那么请不要打断自己,因为你需要花费很长时间才能重新投入工作。换句话说,打断专心工作的开发人员所付出的代价远远超过了让开发人员等待代码审查。你可以在休息(午餐或咖啡等)过后,进行代码审查。
大多数时候,整个代码审查以及代码的合并无法在一天内完成。重要的是迅速给作者一些反馈。例如,虽然可能无法完成完整的审查,但你可以快速指出一些有待探讨的地方。这可以极大地降低代码审查期间的挫败感。
## 审查所有代码,不要做任何假设
你需要审查提交上来的每一行代码。不要对人工编写的类和方法做任何假设,而且应该确保你理解代码在做什么。
确保你理解正在审核的代码。如果不理解,则请作者澄清或提供代码演示和解释。如果你不具备审核部分代码的资格,则请其他有资格的开发人员代为审查。
## 审查代码时需要保持大局观
从更广阔的视野来看待代码变更会更有帮助。例如,某个文件被修改,并添加了4行新代码。请不要只看这4行代码,你应该考虑审查整个文件,并检查新添加的内容。它们是否会降低现有代码的质量?它们是否会导致现有功能成为重构的候选对象?
如果不在函数/方法或类的背景下审查添加的代码,则随着时间的流逝,你将会得到一个面临无法维护、纠缠不清、不易于测试等问题的类,而且这个类很难扩展或重构。
请记住,即便是微不足道的改进,随着时间的推移,也可能导致产品出现缺陷,同样,即便是轻微的代码降级或技术负债也可能在日积月累下导致产品难以维护和扩展。
## 在代码审查期间认同和鼓励出色的工作
如果看到出色的代码变更,请别忘了大力表扬和鼓励作者。代码审查的目的不仅仅是发现错误,而且还应该鼓励和指导开发人员出色的工作。
## 在代码审查期间应保持谨慎、尊重、友善和思路清晰
在代码审查期间,你应该保持友善、思路清晰、有礼貌和尊重别人,这一点至关重要,同时也要给予作者清晰的反馈和积极的帮助。在审查代码时,你需要做到对事不对人,即对代码做出评论,而非开发人员。
## 详细解释代码审查的意见,并注意尺度
每当代码审查意见提出替代方案或指出某些问题时,重要的是你需要解释其中的原因,并根据个人的知识和经验提供示例,以帮助开发人员理解为何你的建议能够提升代码质量。
在建议修改或变更代码时,你需要在如何指导作者修改代码方面找到适当的平衡。例如,我更喜欢指导、解释、提示或建议,而不是整个解决方案。
-17
View File
@@ -1,17 +0,0 @@
---
title: 技术洞察
---
# 技术洞察
这里汇集了日常工程实践中沉淀的实用指南、速查手册和开发规范。
## 话题分类
- **前端** — React 模式、TailwindCSS 技巧、ECMAScript 语法指南、浏览器疑难杂症
- **后端与数据库** — Java 开发、MyBatis、PostgreSQL、MySQL JSON
- **DevOps 与基础设施** — Docker、GitLab CI/CD、LDAP、MinIO、macOS 故障排查
- **工程规范** — 代码评审、版本控制、通用与语言专项编码标准
- **工具与效率** — 邮件礼仪、字号换算、数据分析方法
侧边栏浏览或直接翻阅下方文章——每篇内容独立成文,即读即用。
@@ -1,277 +0,0 @@
---
title: Java 开发小技巧
tags:
- java
- tips
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## BigDecimal 对比
在 Java 中对 `BigDecimal` 进行比较需要格外注意,因为 `equals` 和 `compareTo` 的行为并不相同。
### `equals` 与 `compareTo` 的区别
`BigDecimal#equals` 会同时比较**数值**和**标度(scale)**,而 `BigDecimal#compareTo` 仅比较**数值**(忽略标度)。例如:
```java
var a = new BigDecimal("100"); // scale = 0
var b = new BigDecimal("100.00"); // scale = 2
a.equals(b); // false — 标度不同
a.compareTo(b); // 0 — 数学数值相同
var c = new BigDecimal("200");
a.compareTo(c); // -1(负数)— a 小于 c
c.compareTo(a); // 1(正数)— c 大于 a
```
### 为什么这很重要
当值来自不同来源时,标度不匹配的情况很常见——例如解析用户输入、从数据库(`DECIMAL(10,2)` 列)读取、或接收 JSON 数据。你可能会认为两个值相等,但 `equals` 却说它们不相等。
### 使用建议
- 使用 **`compareTo`** 进行数值相等判断:`a.compareTo(b) == 0`
- 仅在需要「完全相同的表示」(数值和标度都相同)时使用 **`equals`**
- 如果需要在 `equals` 前统一标度,可使用 **`stripTrailingZeros()`**:
```java
a.stripTrailingZeros().equals(b.stripTrailingZeros()); // true
```
### 与零比较
避免使用 `==` 或 `.equals(BigDecimal.ZERO)` 来判断是否为零——推荐使用 `compareTo`:
```java
if (value.compareTo(BigDecimal.ZERO) == 0) { ... }
```
## 如何从 BlockingQueue 中获得数据?
- 使用 `take()` 方法可以拿取数据,如果队列中没有数据会阻塞线程,一直等待;
- 使用 `poll()` 方法拿取数据,如果没有数据会返回 `null` ;
- 使用 `poll(long timeout, TimeUnit unit)` 方法拿去数据,将会在 `poll()` 的基础上等待特定的时间,如果时间到了还没有拿到数据则会返回 `null` ;
- 使用 `peek()` 拿取数据,但是这种方式不会将已经拿到的数据从队列中移除。
## Spring Cloud Alibaba 常见问题
### Nacos 在个人文件夹中创建 `nacos` 文件夹怎么办?
添加下面两个 Configuration properties 即可指定 nacos 的存储路径:
- `JM.LOG.PATH`
- `JM.SNAPSHOT.PATH`
### Sentinel 乱拉屎怎么办?
添加 Configuration property `csp.sentinel.log.dir` 即可修改 sentinel 的日志路径。
### 怎么在 JetBrains IntelliJ IDEA 中添加 Configuration Properties 配置?
在 JetBrains IntelliJ IDEA 中,在右上角的运行配置中,点击 **`Edit Configurations…`** ,即可看到配置页面。
点击页面中的 **`Modify options`** 按钮,并在下方附加上的 **`Override configuration properties`** 表格中添加需要重置的配置属性。
## Spring Data JPA 常见问题
### 使用 JPA 分页查询时提示 "Serializing `PageImpl` instances as-is not supported, meaning that there is no guarantee about the stability of the resulting JSON structure" 怎么办?
在应用程序主启动类上添加下面的代码:
```java
@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO)
```
### 使用 JPA 分页查询时查询第一页都没数据是为什么?
JPA 的分页页码是从 0 开始的,请求中的页码数据 = 你实际想查询页码数 - 1。
### 如何避免 N+1 查询问题?
N+1 问题是指 JPA 先执行 1 条查询获取父实体,再为每条父实体的关联执行 N 条额外的子查询。
**检测方法** — 观察日志中是否有大量重复的 SQL 查询,或者配置 `spring.jpa.properties.hibernate.generate_statistics=true` 来发现。
**修复方案:**
| 方式 | 适用场景 |
| ---------------------------- | ---------------------------------- |
| `@EntityGraph` | 声明式,适合为特定实体定制抓取计划 |
| `@Query` 中使用 `JOIN FETCH` | 按查询精细控制 |
| `@BatchSize` | 将 N+1 降低为 N/k+1,批量加载 |
```java
// 方式一:EntityGraph
@EntityGraph(attributePaths = {"roles", "permissions"})
Optional<User> findById(long id);
// 方式二:JOIN FETCH
@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.id = :id")
Optional<User> findByIdWithRoles(@Param("id") long id);
```
### `findById` 与 `getReferenceById` 该用哪个?
- **`findById`** — 立即查询数据库,返回实体或 `Optional.empty()`。需要实际数据时使用。
- **`getReferenceById`** — 返回一个惰性代理(proxy),**不查询数据库**。只有当访问不存在的代理属性时才会抛出 `EntityNotFoundException`。只需要 ID 来设置外键关系时使用。
```java
// 推荐:只需要引用 User 来设置外键
Post post = new Post();
post.setAuthor(userRepository.getReferenceById(userId));
```
### 如何解决 `LazyInitializationException`?
当你在持久化上下文之外(例如 Controller 或序列化器中,事务已关闭后)访问懒加载的关联属性时,就会抛出此异常。
**解决方案:**
1. **使用 `JOIN FETCH` 或 `@EntityGraph`** 提前加载所需的关联数据。
2. **使用 DTO 投影** — 只返回需要的字段,而不是整个实体:
```java
@Query("SELECT new com.example.UserDto(u.id, u.name) FROM User u WHERE u.id = :id")
UserDto findUserDtoById(@Param("id") long id);
```
3. **在 Service 方法上使用 `@Transactional(readOnly = true)`** — 使 Session 在整个方法作用域内保持打开。
### 什么时候该用 `@Transactional(readOnly = true)`?
在**只读**的 Service 方法上使用 `@Transactional(readOnly = true)` 有三个好处:
- Hibernate 会跳过脏检查(无需快照,占用内存更少)。
- JDBC 驱动可能会将请求路由到只读副本。
- 清晰地表达了方法的意图。
```java
@Service
public class UserService {
@Transactional(readOnly = true)
public UserDto getUser(long id) { ... }
@Transactional
public UserDto createUser(CreateUserRequest request) { ... }
}
```
### `save()` 与 `saveAll()` — 批量插入哪个更快?
`saveAll()` 在单个事务中执行,可以受益于 JDBC 批处理。需要配置批处理大小:
```yaml
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 20
order_inserts: true
order_updates: true
```
对于大量数据的批量插入(数千行),建议改用 `JdbcTemplate` 的批处理操作——在这种规模下 Hibernate 的实体管理开销会非常大。
### 如何用 `Specification` 实现动态查询?
对于带有多个可选筛选条件的复杂搜索表单,使用 `JpaSpecificationExecutor`:
```java
public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User> {
}
// 使用方式
Specification<User> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (name != null) {
predicates.add(cb.like(root.get("name"), "%" + name + "%"));
}
if (status != null) {
predicates.add(cb.equal(root.get("status"), status));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
Page<User> page = userRepository.findAll(spec, pageable);
```
## 使用 Jackson 将对象或 Map 平铺到 JSON 中
在序列化 JSON 时,有时需要将嵌套对象的属性"平铺"到父级 JSON 中,而不是作为一个嵌套字段出现。Jackson 提供了两个注解来分别处理对象和 `Map` 的平铺场景。
### `@JsonUnwrapped` — 平铺已知结构的对象
`@JsonUnwrapped` 会将一个嵌套对象的属性直接展开到父级 JSON 中:
```java
public class Address {
private String street;
private String city;
// getters and setters
}
public class User {
private String name;
@JsonUnwrapped
private Address address;
// getters and setters
}
```
序列化结果:
```json
{
"name": "陳大文",
"street": "彌敦道",
"city": "香港"
}
```
如果不使用 `@JsonUnwrapped`,`address` 会作为一个嵌套 JSON 对象出现:`"address": { "street": "...", "city": "..." }`。
### `@JsonAnyGetter` — 平铺键值不固定的 Map
`@JsonAnyGetter` 可以将 `Map` 中的键值对直接平铺到父级 JSON 中,适合 key 不固定或动态扩展的场景:
```java
public class Product {
private String name;
private Map<String, Object> attributes = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> getAttributes() {
return attributes;
}
// other getters and setters
}
```
当 `attributes` 包含 `{"colour": "red", "size": "XL"}` 时,序列化结果:
```json
{
"name": "Example Product",
"colour": "red",
"size": "XL"
}
```
注意 `@JsonAnyGetter` 需要加在 **getter 方法**上,而不是字段上;同时需要搭配 `@JsonAnySetter` 来支持反序列化。
### 如何选择
| 场景 | 注解 |
| -------------------------------------- | ---------------- |
| 平铺字段固定的嵌套对象(如地址、配置) | `@JsonUnwrapped` |
| 平铺 key 不固定的动态键值对 | `@JsonAnyGetter` |
@@ -1,238 +0,0 @@
---
title: Java 开发规范
tags:
- java
- spring-boot
- standards
- best-practice
- backend
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## Java 语言与编码风格
- **Java 版本**: 项目所使用的 JDK 应当在可能的情况下,使用最新的 LTS 版本。
- **命名约定**:
- 类: 帕斯卡命名法 (`PascalCase`) (例如,`UserService`, `OrderController`)。
- 方法: 驼峰命名法 (`camelCase`) (例如,`getUserById`, `saveOrder`)。
- 变量: 驼峰命名法 (`camelCase`) (例如,`username`, `statusCode`)。
- 常量: 全大写下划线命名法 (SCREAMING_SNAKE_CASE) (例如,`DEFAULT_PAGE_SIZE`)。
- **不变性**: 在可能的情况下,优先选择领域对象和 DTOs 的不变性,使用 `records` 或不可变类来减少副作用并提高线程安全性。
- **Optional**: 使用 `Optional<T>` 显式处理可能缺失的值,避免 `NullPointerException`。
- **Streams API**: 优先使用 Java Streams API 进行集合处理,提倡函数式和声明式编程。
- **异常处理**:
- 使用特定异常。避免捕获通用 `Exception`。
- 对于不可恢复的错误,抛出运行时异常。
- 对于可恢复的错误,如果业务逻辑需要,定义自定义受检异常。
- 利用 Spring 的 `@ControllerAdvice` 和 `@ExceptionHandler` 进行集中式全局异常处理和一致的 API 错误响应。
- **代码审查**: 所有后端代码在合并前必须经过彻底的人工代码审查,特别是遵循 GitFlow 原则。IntelliJ IDEA 的集成代码分析工具应作为初次审查使用。
## 文档与注释
- 后端 Java 代码中的所有**公共**类、方法和重要字段必须包含全面的 Javadoc 注释。
- Javadoc 应解释其目的、参数 (`@param`)、返回值 (`@return`) 和抛出的异常 (`@throws`)。
- Javadoc 的格式如下:
- Javadoc 需要遵循每行最多 100 个字符(包括用于调整格式的空白字符)。若内容超出了 100 字符,则应当在小于 100 字符的最后一个单词结尾处用换行符换行。但是如果在换行后,下一行句子的开头只有一个单词后就结束,则应当提前一个单词换行。
```java
/**
* Enables configuration properties for S3 file storage services. Individual service beans are
* created by their respective service classes to better support conditional configuration.
*/
```
- 每个段落之间使用一个 `<p>` 分割。
```java
/**
* This is the first paragraph of the Javadoc.
* <p>
* This is the second paragraph of the Javadoc.
*/
```
- 每个段落的内容必须是语法正确且含义完整的段落,遵循英文句子规范。
- 所有参数(`@param`)、返回值(`@return`)、异常抛出(`@throws`)及参考(`@see`)的解释需要遵循下面的规则:
- 无需使用大写字母开头
- 若描述的最后为陈述句,则无需使用标点符号结尾
```java
/**
* Returns the greater of two {@code int} values. That is, the
* result is the argument closer to the value of
* {@link Integer#MAX_VALUE}. If the arguments have the same value,
* the result is that same value.
*
* @param a an argument
* @param b another argument
* @return the larger of {@code a} and {@code b}
*/
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
```
## 依赖管理 (Gradle)
- **构建文件**: `build.gradle.kts` 必须组织良好,对插件、依赖项和任务有清晰的划分。
- **依赖版本**: 必须在 `gradle/libs.versions.toml` 文件中集中管理依赖项版本,以确保一致性。
- **插件管理**: 显式声明 Gradle 插件及其版本。
- **避免不必要的依赖项**: 仅包含项目实际使用的依赖项。定期审查并清理未使用的依赖项。
## API 设计 (RESTful)
- **RESTful 原则**: 遵循 RESTful 原则:
- **资源**: 将数据建模为可通过 URI 标识的资源。
- **HTTP 方法**: 适当地使用标准 HTTP 方法(GET 用于检索,POST 用于创建,PUT 用于完全更新,PATCH 用于部分更新,DELETE 用于删除)。
- **无状态性**: API 应是无状态的;客户端向服务器发送的每个请求都必须包含理解请求所需的所有信息。
- **URI**:
- 集合资源使用复数名词(例如,`/users`,`/products`)。
- URI 中使用连字符以提高可读性(例如,`/user-accounts`)。
- 避免在 URI 中使用动词(例如,使用 `/users` 而不是 `/getAllUsers`)。
- **状态码**: 使用适当的 HTTP 状态码来指示 API 请求的结果(例如,`200 OK`、`201 Created`、`204 No Content`、`400 Bad Request`、`401 Unauthorized`、`403 Forbidden`、`404 Not Found`、`500 Internal Server Error`)。
- **响应格式**: JSON 是首选的响应格式。
- **版本控制**: 建议在需要版本控制的接口中使用 Header 参数 `X-Endpoint-Version` 控制接口版本。
## Spring Boot 最佳实践与分层架构
- **分层架构 (MVC 配合 Manager 层)**: 我们的后端应用程序遵循严格的多层架构,确保职责清晰分离,并提高可维护性和可测试性。各层及其职责如下:
- **控制器层 (Controller Layer)**: 位于 `controller` 包中。负责暴露 RESTful API,处理 HTTP 请求,并将请求参数/主体映射到服务层调用。控制器应保持轻量,主要关注输入验证(使用 DTOs)和协调对 `Service` 层的调用。
- **服务层 (Service Layer)**: 位于 `service` 包中。此层封装了核心业务逻辑。服务层向 `Controller` 层提供可以直接使用的 API,抽象业务流程和事务管理。服务层协调对 `Manager` 层的调用以执行业务操作。
- **管理器层 (Manager Layer)**: 位于 `manager` 包中。此层提供原子化的业务操作,可由 `Service` 层组合使用。管理器通常处理更复杂的业务逻辑,可能涉及在更细粒度级别上与多个仓库或其他外部系统进行交互。
- **仓库层 (Repository Layer) (MyBatis)**: 位于 `repository` 包和 `src/main/resources/repository`(用于 XML 映射文件)中。此层负责提供原子化的数据库交互操作。
**层间通信策略**:
- **每一层内的组件仅可调用直接位于其下方的层中的组件。**
- **严格禁止跨层调用(例如,Controller 直接调用 Manager,Service 直接调用 Repository)。**
- **严格禁止向上调用(例如,Service 调用 Controller)。**
- **横向调用(例如,Service A 调用 Service B 处理不同领域)应仔细考虑,通常表明需要将共享逻辑重构到 `Manager` 或为该共享关注点重构一个专用 `Service` 中。**
- **配置**: 优先选择 `application.yml` 进行配置属性,而不是 `application.properties`,以获得更好的可读性和分层结构。使用 `@ConfigurationProperties` 进行类型安全配置。
- **依赖注入**: 所有依赖项都使用构造函数(强制依赖)或 Setter(非强制依赖)注入。避免在字段上使用 `@Autowired`,因为它会使测试更困难并隐藏依赖项。
- **服务**: 业务逻辑类使用 `@Service` 进行注解。服务应保持精简,并专注于协调领域操作,这些操作通常涉及通过 `Manager` 层组件进行业务逻辑处理,以及通过 `Manager` 或直接与 MyBatis 仓库进行交互(如果该特定操作不需要中间 Manager 逻辑,尽管对于所有仓库交互,Manager 层是首选,符合分层架构定义)。
- **仓库 (MyBatis 与 JPA)**:
- **项目使用 MyBatis 及 JPA 进行数据库操作**。`repository` 包及 `mapper` 包中的仓库接口定义了数据访问契约。
- 相应的 SQL 定义在位于 `src/main/resources/mapper` 的 XML 映射文件中管理。
- 数据操作约定:
- 对于简单数据库操作,使用 JPA。
- 对于复杂数据库操作,使用 MyBatis。
- 当进行分页查询时,页码应从 0 开始(_兼容 Spring Data JPA_)。
- **数据操作方法命名约定**:
- 对于**查询数据列表**:方法**必须**以 `selectListBy` 开头,紧随其后是筛选条件(例如,`selectListByUserId`、`selectListByDepartmentIdAndStatus`)。这些方法也必须包含一个 `PageRequest` 参数用于分页。
- 对于查询**单个数据记录**:方法**必须**以 `selectOne` 开头(例如,`selectOneById`、`selectOneByUsername`)。
- 对于**保存新数据**:方法**必须**命名为 `save`,Mapper 方法的返回值必须是 `int`(受影响的行数)。
- 对于**更新现有数据**:方法**必须**命名为 `update`,Mapper 方法的返回值必须是 `int`(受影响的行数)。
- 对于**删除数据**:方法**必须**以 `deleteBy` 开头,清晰地指明删除的依据(例如,`deleteById`)。Mapper 方法的返回值必须是 `int`(受影响的行数)。
- **控制器**:
- 使用 `@RestController` 注解 REST 控制器。
- 使用 `@GetMapping`、`@PostMapping` 等将 HTTP 方法(GET、POST、PUT、DELETE)映射到适当的控制器方法。
- 确保请求和响应负载定义明确(DTOs)并有文档说明。
- 控制器应主要处理 HTTP 请求/响应映射,并将实际业务逻辑委托给服务层。
- **DTOs (数据传输对象)**: 定义单独的 DTOs 用于请求和响应主体,以将内部领域模型与 API 契约解耦。在 DTOs 上使用验证注解(例如,`@Valid`、`@NotNull`、`@Size`)。
- **日志 (Slf4j & Logback)**:
- 应用程序中的所有日志记录都使用 `org.slf4j.Logger`。
- 配置 `application-dev.yml` 以设置日志级别、Appender 和输出格式。
- 日志消息应具有描述性并提供足够的上下文。避免记录敏感信息。
- 使用参数化日志记录以提高性能并防止字符串拼接开销(例如,`log.debug("Processing user: {}", userId);`)。
- 标准日志级别:`ERROR`、`WARN`、`INFO`、`DEBUG`、`TRACE`。
## 项目结构
后端应用程序遵循结构化的 Gradle 项目布局。核心应用程序应清晰地划分为各种子包。
```text
backend-application
├── build.gradle.kts // 项目的主 Gradle 构建脚本
├── config // 外部配置目录
│ ├── application-dev.yml // 开发环境的应用程序属性
│ └── application-prod.yml.example // 生产环境属性示例(待复制和配置)
├── database // 数据库相关文件
│ └── init.d // 数据库初始化脚本
│ └── init-en_GB.sql // 使用指定语言的数据库模式和初始数据的 SQL 脚本(英式英语区域设置)
├── gradle // Gradle Wrapper 和配置文件
│ ├── libs.versions.toml // 集中管理依赖版本(Gradle 版本目录)
│ └── wrapper // Gradle Wrapper 文件
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties // 项目特定的 Gradle 属性
├── gradlew // Gradle Wrapper 可执行文件 (Linux/macOS)
├── gradlew.bat // Gradle Wrapper 可执行文件 (Windows)
├── settings.gradle.kts // 多项目构建的 Gradle 设置(如果适用)
└── src
├── main
│ ├── java
│ │ └── com/onixbyte/application // 应用程序的根包
│ │ ├── Application.java // Spring Boot 应用程序主入口点
│ │ ├── config // Spring 配置类
│ │ ├── constant // 定义应用程序范围常量的类
│ │ ├── controller // REST API 端点
│ │ ├── domain // 核心领域模型和相关类型
│ │ │ ├── common // 通用领域对象/工具类
│ │ │ ├── entity // 表示数据库表的 JPA/MyBatis 实体
│ │ │ ├── model // 通用的实体类模型
│ │ │ ├── view // 专门用于只读操作的数据传输对象(例如,查询结果、报告结构)
│ │ │ └── web // 专门用于 Web 请求/响应体的数据传输对象
│ │ │ ├── request // 请求 DTO
│ │ │ └── response // 响应 DTO
│ │ ├── exception // 自定义应用程序特定异常
│ │ ├── extension // 扩展点或自定义功能
│ │ │ ├── jackson // Jackson 序列化/反序列化扩展
│ │ │ └── redis // Redis 相关扩展
│ │ │ └── serializer
│ │ ├── filter // Servlet 过滤器或 Spring Security 过滤器
│ │ ├── manager // 业务逻辑协调器,通常协调多个服务或仓库
│ │ ├── mapper // 数据访问层 (MyBatis 接口)
│ │ ├── processor // 通用处理组件或业务工作流
│ │ ├── properties // 用于类型安全配置属性的类 (`@ConfigurationProperties`)
│ │ ├── repository // 数据访问层 (Spring Data JPA 接口)
│ │ ├── security // Spring Security 特定组件
│ │ │ ├── authentication // 自定义认证机制
│ │ │ └── provider // 自定义认证提供者
│ │ ├── service // 核心业务逻辑(事务层)
│ │ ├── utils // 通用工具类
│ │ └── validation // 自定义验证逻辑
│ │ └── group // 针对不同上下文(例如,创建、更新)的验证分组
│ └── resources
│ ├── application.yml // 默认应用程序属性
│ └── mapper // MyBatis XML 映射文件
└── test
└── java
└── com/onixbyte/helix
└── HelixApplicationTests.java // Spring Boot 集成测试
```
**关键观察与具体指令:**
- **外部 `config` 目录**
- 环境配置 (`application-dev.yml`、`application-prod.yml.example`) 在顶层 `config` 目录中管理,与 `src/main/resources` 分开。这促进了特定于环境的属性管理,允许在部署时挂载或链接不同的配置。
- **禁止上传除 `src/main/resources/application.yml` 之外的任何配置文件到 Git 仓库。**
- **数据库初始化**: `database/init.d` 目录保留用于 SQL 脚本,特别是数据库模式初始化 (`init-en_GB.sql`),这对于环境设置和 CI/CD 流水线至关重要。这种结构暗示了"模式优先"或"代码驱动的模式演进"方法。
- **`client` 包**: 该包用于向应用提供所有中间件的服务,如 HTTP、S3 存储、Redis 调用等。对于需要添加到 Spring 上下文中的,自行编码实现的功能(如 JSON Web Token 生成与解析)也推荐放到该包中。
- **MyBatis 与 Spring Data JPA 集成**
- `src/main/java/.../mapper` 包包含 MyBatis mapper 接口,而实际的 SQL 定义位于 `src/main/resources/mapper/*.xml` 文件中。这种分离是保持代码整洁同时利用 MyBatis 强大 XML 映射能力的关键。
- `src/main/java/.../repository` 包包含 Spring Data JPA 接口。
- **`domain` 包的粒度**:
- `domain.entity`: 保留用于直接映射到数据库表的类(MyBatis 的 POJO)。
- `domain.model`: 用于不直接与单个表一对一映射的更通用领域对象或聚合根。
- `domain.view`: 专门用于在只读场景(例如,查询结果、报告结构)中呈现数据的数据传输对象 (DTO)。
- `domain.web.request` / `domain.web.response`: 明确分离的用于传入 API 请求和传出 API 响应的 DTO,严格遵守 API 契约并与内部领域实体解耦。
- **`manager` 和 `processor` 包**: 这些包暗示了一个分层架构,其中"管理器"协调涉及多个服务或仓库的操作,而"处理器"可能处理业务流程的特定方面。强制明确定义这些包中类的职责,以防止出现"贫血领域模型"或"上帝对象"等反模式。
- **`security` 包**: 此子包包含除初始配置之外的自定义 Spring Security 组件,例如自定义认证类型和提供者,表明了定制的安全实现。
- **`properties` 包**: 此包用于自定义 `@ConfigurationProperties` 类,促进对 YML 文件中定义的应用程序设置进行类型安全访问,位置得当。
- **`extension` 包**: 这是一个灵活的区域,用于应用程序特定的扩展,例如自定义 Jackson 序列化器或 Redis 定制。应谨慎使用,以防止成为"杂项"的堆放地。
## 安全性 (Spring Security)
- **强制使用**: Spring Security 在所有 Spring Boot Web 应用程序中都是强制的。
- **认证与授权**: 在 `SecurityConfig.java` 中配置认证机制(例如,OAuth 2.0、JWT、基于会话)和授权规则。
- **CSRF 保护**: 确保对修改状态的操作启用 CSRF 保护,除非有充分的理由禁用它(例如,已存在其他机制的无状态 API)。
- **CORS**: 根据前端部署要求正确配置跨域资源共享 (CORS)。
- **第三方身份提供者**: 在与第三方身份提供者(例如 Microsoft Entra ID)集成时,遵循安全令牌处理和用户配置的最佳实践。敏感凭据必须安全管理(例如,环境变量、Vault)。
- **输入验证**: 始终在服务器端验证所有用户输入,以防止常见的漏洞,如 SQL 注入、XSS 等。
- **内容安全策略 (CSP)**: 考虑为前端实施强大的 CSP 以减轻 XSS 攻击。
@@ -1,55 +0,0 @@
---
title: 修复 macOS 终端在私网 DNS 下 Host Name 显示为 IP 段的问题
tags:
- macos
- dns
- terminal
author:
name: Zihlu Wang
email: real@zihluwang.me
---
在部分企业或家庭私网环境中,DNS 反向解析会将设备的私网 IP 映射为一个以 `192`、`172` 或 `10` 开头的主机名。此时 macOS 终端提示符会从正常的 `user@MacBook-Pro` 变为 `user@192-168-1-100` 这样的形式,影响日常使用体验。
## 原因
macOS 在启动终端会话时会通过反向 DNS 查询当前 IP 对应的主机名。当私网 DNS 服务器返回了一个基于 IP 段拼凑的主机名(例如 `192-168-1-100.example.com`)时,系统便会采纳该名称作为 Host Name,终端提示符随之改变。
## 修复方法
使用 macOS 自带的 `scutil`(System Configuration Utility)即可将 Host Name 固定为你期望的值。
### 查看当前状态
```shell
# 查看当前 Host Name(可能为空或已被 DNS 改写)
scutil --get HostName
# 查看本地 Bonjour 名称
scutil --get LocalHostName
# 查看 Finder 中显示的计算机名
scutil --get ComputerName
```
### 设置 Host Name
```shell
sudo scutil --set HostName "MacBook-Pro"
```
建议使用不含空格和特殊字符的名称,例如 `MacBook-Pro`、`My-Mac` 或你的设备序列号。
### 验证修改
重新打开一个终端窗口,提示符中的 `@` 后面应恢复为你设置的主机名。
```shell
scutil --get HostName
# 输出: MacBook-Pro
```
## 补充说明
- `HostName` 仅影响网络层面的主机名标识,不会影响 `LocalHostName`(Bonjour 本地名称)和 `ComputerName`(Finder 显示名),三者独立管理。
- 若问题在重启后复现,可以检查 `/etc/hosts` 中是否有相关条目,或确认 DHCP/DNS 服务器是否持续下发不期望的主机名。
-200
View File
@@ -1,200 +0,0 @@
---
title: MinIO 新版本管理中心
tags:
- minio
- storage
- s3
- devops
- cheatsheet
author:
name: Zihlu Wang
email: real@zihluwang.me
---
MinIO 在新版本中将管理功能从 Web UI 中移除了。现在需要使用 **MinIO Client (mc)** 命令行工具来进行管理操作。
## 安装 MinIO Client (mc)
### Windows:
```powershell
# 下载 mc.exe
Invoke-WebRequest -Uri "https://dl.min.io/client/mc/release/windows-amd64/mc.exe" -OutFile "mc.exe"
# 或使用 Chocolatey
choco install minio-client
```
### Linux/macOS:
```bash
# Linux
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
# macOS
brew install minio/stable/mc
```
## 配置 MinIO Client
```bash
# 添加 MinIO 服务器别名
mc alias set myminio http://localhost:9000 minioadmin minioadmin
# 验证连接
mc admin info myminio
```
## 用户管理操作
### 创建用户
```bash
# 创建新用户
mc admin user add myminio newuser newpassword
# 查看所有用户
mc admin user list myminio
```
### 创建 Access Key 和 Secret Key
```bash
# 为用户创建服务账户(生成 AccessKey/SecretKey)
mc admin user svcacct add myminio newuser
# 或者指定自定义的 AccessKey
mc admin user svcacct add myminio newuser --access-key "MYACCESSKEY123" --secret-key "MYSECRETKEY456"
# 查看用户的服务账户
mc admin user svcacct list myminio newuser
```
## 权限管理
### 创建策略
```bash
# 创建策略文件 policy.json
cat > policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::mybucket/*"
]
}
]
}
EOF
# 添加策略
mc admin policy add myminio mypolicy policy.json
# 将策略分配给用户
mc admin policy set myminio mypolicy user=newuser
```
## 存储桶管理
```bash
# 创建存储桶
mc mb myminio/mybucket
# 列出存储桶
mc ls myminio
# 设置存储桶策略
mc policy set public myminio/mybucket
```
## 常用管理命令
```bash
# 查看服务器信息
mc admin info myminio
# 查看服务器配置
mc admin config get myminio
# 重启服务器
mc admin service restart myminio
# 查看日志
mc admin logs myminio
# 查看统计信息
mc admin prometheus metrics myminio
```
## 实用脚本示例
创建一个管理脚本 `setup-minio.sh`:
```bash
#!/bin/bash
MINIO_ALIAS="myminio"
MINIO_URL="http://localhost:9000"
ADMIN_USER="minioadmin"
ADMIN_PASS="minioadmin"
# 配置 MinIO 客户端
mc alias set $MINIO_ALIAS $MINIO_URL $ADMIN_USER $ADMIN_PASS
# 创建应用用户
APP_USER="appuser"
APP_PASS="apppassword"
mc admin user add $MINIO_ALIAS $APP_USER $APP_PASS
# 创建服务账户并获取 AccessKey/SecretKey
echo "Creating service account for $APP_USER..."
CREDENTIALS=$(mc admin user svcacct add $MINIO_ALIAS $APP_USER --json)
ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.accessKey')
SECRET_KEY=$(echo $CREDENTIALS | jq -r '.secretKey')
echo "Generated credentials:"
echo "Access Key: $ACCESS_KEY"
echo "Secret Key: $SECRET_KEY"
# 创建存储桶
mc mb $MINIO_ALIAS/app-bucket
# 设置只读策略
mc policy set download $MINIO_ALIAS/app-bucket
```
## Web Console 访问
虽然管理功能被移除,但您仍然可以通过以下方式访问 MinIO Console:
```bash
# 启动 MinIO Console(如果单独安装)
mc admin console myminio
```
或者在启动 MinIO 服务器时指定 Console 地址:
```bash
minio server /data --console-address ":9001"
```
## 总结
新版 MinIO 的管理完全依赖 `mc` 命令行工具:
1. **安装 mc 客户端**
2. **配置服务器别名**
3. **使用 `mc admin` 命令进行用户、权限、存储桶管理**
4. **通过 `mc admin user svcacct` 生成 AccessKey/SecretKey**
这种方式虽然需要命令行操作,但提供了更强大和灵活的管理能力,特别适合自动化部署和脚本化管理。
-31
View File
@@ -1,31 +0,0 @@
---
title: MyBatis Flex
tags:
- java
- mybatis
- spring-boot
- orm
- framework
author:
name: Zihlu Wang
email: real@zihluwang.me
---
> 本文仅针对在 Spring Boot 3 中使用 `MyBatis Flex`
## 安装与配置
### 安装
在 `gradle` 目录下的 `libs.versions.toml` 中添加如下代码:
```toml
[versions]
mybatisFlexVersion = "X.Y.Z"
hikariVersion = "X.Y.Z"
[libraries]
hikari = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikariVersion" }
mybatisFlex-processor = { group = "com.mybatis-flex", name = "mybatis-flex-processor", version.ref = "mybatisFlexVersion" }
mybatisFlex-starter = { group = "com.mybatis-flex", name = "mybatis-flex-spring-boot3-starter", version.ref = "mybatisFlexVersion" }
```
@@ -1,218 +0,0 @@
---
title: 使用分词在 PostgreSQL 中加速模糊查询
tags:
- postgresql
- zhparser
- full-text-search
- performance
author:
name: Siu Jam Oh
email: jamo.siu@gmail.com
---
## 背景与挑战
随着业务数据量突破 **200 万行**,传统的 `LIKE '%关键词%'` 模糊查询导致数据库 I/O 频繁告警,查询响应时间从毫秒级退化至秒级。为提升检索效率并支持中文语义,我们决定引入 `zhparser` 插件实现全文检索。
## 演进路线与环境适配
本次技术落地经历了四个关键阶段,每个阶段解决了不同的技术核心:
### CentOS 7.9 虚拟机(可行性验证)
- **目标**:验证 `SCWS` + `zhparser` 在旧版系统下的兼容性。
- **核心操作**:在 CentOS 7.9 环境下手动编译 `postgresql-16.2` 源码,完成插件初步跑通。
**结论**:确认了分词方案对中文检索性能的显著提升。
### 本地 Docker 容器(容器化探索)
- **目标**:在本地的完整系统进行初步测试。
- **核心操作**:通过 `docker cp` 注入二进制 `.so` 文件,解决 `ldconfig` 动态链接库路径可见性问题。
- **发现**:识别出 **"词典文件缺失"** 会导致分词退化为单字(助词)的故障点。
### EulerOS 2.0 测试服(自编译环境适配)
- **目标**:适配生产环境的 OS 架构(x86_64)与自编译安装的 PostgreSQL。
- **核心问题**:解决了 `libscws.so.1` 无法加载的报错。
- **关键方案**:
- 明确了 `postgres` 运行用户对 `/usr/local/scws/lib` 的访问权限需求。
- 通过修改 `systemd` 服务环境变量或创建 `/usr/lib64` 软链接强制刷新库搜索路径。
### 生产部署预备(最终调优)
- **目标**:确保 200w+ 数据量下的查询稳定性。
- **优化点**:针对"非语义片段(如:古唐合)"查询不到的问题,制定了"全文检索优先 + `pg_trgm` 索引辅助"的降级查询策略。
## 核心安装与配置步骤 (自编译环境)
### 安装 `SCWS` 分词引擎
SCWS 是 `zhparser` 依赖的底层核心,必须先行安装。
1. **下载并解压**:下载源码包(如 `scws-1.2.3`)。
2. **编译安装**:
```bash
./configure --prefix=/usr/local/scws
make && make install
```
3. **验证库文件**:确保 `/usr/local/scws/lib/libscws.so.1` 存在。
### 编译与安装 `zhparser`
这一步需要用到 PostgreSQL 自编译产生的 `pg_config`。
1. **获取源码**:从 GitHub 克隆 `zhparser` 项目。
2. **指定路径编译**:
```bash
# 确保 pg_config 在 PATH 中,或手动指定
make USE_PGXS=1 PG_CONFIG=/usr/local/pgsql/bin/pg_config
make USE_PGXS=1 PG_CONFIG=/usr/local/pgsql/bin/pg_config install
```
_注:`install` 步骤会自动将 `zhparser.so` 放入 PG 的 `pkglibdir`,将脚本放入 `extension` 目录。_
### 解决动态链接库依赖
1. **刷新系统缓存**:
```bash
echo "/usr/local/scws/lib" > /etc/ld.so.conf.d/scws.conf
ldconfig
```
2. **权限检查**:确保运行 `postgres` 的 OS 用户对 `/usr/local/scws/lib` 有 `rx` 权限。
3. **强制软链 (备选)**:若 `ldconfig` 失效,可将库文件软链至 `/usr/lib64`。
### 重启数据库
修改完系统共享库配置后,必须重启 PostgreSQL 进程以重新加载环境变量和链接库。
```bash
## 使用 pg_ctl 重启(自编译路径需根据实际情况调整)
/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data restart
## 或通过 systemd 重启(如果已注册服务)
systemctl restart postgresql
```
### 数据库内初始化
连接到 `psql`,执行逻辑配置:
```sql
-- 创建扩展
CREATE EXTENSION zhparser;
-- 创建全文检索配置并绑定分词器
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);
-- 添加 Token 映射
ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l,t,b WITH simple;
-- 【可选】指定自编译路径下的词典位置
-- ALTER DATABASE postgres SET zhparser.dict_path = '/usr/local/scws/etc/dict.utf8.xdb';
```
## 性能分析与避坑指南
### 索引性能瓶颈分析
在测试中发现,即使是精确查询,由于使用了不当的联合索引(`create_time` 在首列),导致产生 `Bitmap Index Scan`,耗时高达 **482ms**。
- **改进**:对检索高频列建立单列 **B-tree** 索引,利用 `Index Scan` 将响应降至 **10ms** 以内。
### GIN 索引与非语义匹配
- **跨词截断问题**:分词引擎基于语义,类似"古唐合"这种截断词可能因为分词边界导致 `@@` 无法命中。
- **应对方案**:采用"瀑布式搜索"。全文检索(FTS)优先;若结果为空,自动降级为 `LIKE` 模糊查询,并配合 `pg_trgm` 索引加速。
## 终极部署方案:双轨并行检索
经过对数据的分析,发现对于账户名称中出现了约 1.24% 的非标准简体中文的数据,并且数据库中也存在部分因为公司名称怪异而导致数据查询不出来的问题,决定采取"分步降级"策略:
- **第一步:全文检索(Fast Track):** 利用 GIN 索引进行 `@@` 匹配。
- **第二步:结果评估:** 若返回结果为空,且搜索词包含字母或疑似繁体字符。
- **第三步:模糊兜底(Safe Fallback):** 执行 `LIKE '%关键词%'`。虽然速度较慢,但由于其作为"补漏"逻辑,触发频率仅为 1% 左右,不会对数据库造成整体压力。
## 搜索优化:集成自定义业务词库
为了解决全文检索中公司品牌名被误切(如"元一"被切分为数词/量词)的问题,需构建一套从数据提取到索引重建的自动化维护链路。
### 词库提取与预处理
利用 **`companynameparser`** 的结构化解析能力剥离地名与行业后缀,并结合 **`jieba`** 进行语义复核,确保核心品牌词的完整性:
- **提取逻辑**:通过 Python 脚本遍历全量 `buyer_unique_name`,提取核心 `brand` 字段。
- **权重补偿**:针对包含"元"、"一"、"三"等易碎词条,手动将 TF(词频权重)提升至 **50.0 - 60.0**,确保其优先级高于内置量词规则。
- **输出规范**:输出符合 SCWS 标准的 4 字段 `UTF-8` 文本(WORD, TF, IDF, ATTR),建议使用制表符 `\t` 分隔以规避解析异常。
### 词库编译与部署
<!-- TODO(link): 该提示原本指向 OnixByte 预编译的 Windows 版 scws,但两个链接
都是死链 —— GitHub 那个引用的账号并不存在,Gitea 那个正在下线。暂时指向上游;
若日后重新发布下载地址,在此恢复。 -->
:::tip
适用于 Windows 的原生 scws 命令行工具使用 MingW 编译。源码与编译说明见上游
[SCWS 项目](https://github.com/hightman/scws)。
:::
将文本词典转换为 SCWS 高效二进制格式(XDB):
1. **编译二进制词典**:
```bash
# 使用 scws-gen-dict 工具生成加密二进制词库
/usr/local/scws/bin/scws-gen-dict -i custom_company.txt -o /usr/local/scws/etc/custom_company.xdb -c utf8
```
2. **文件分发与权限**:将生成的 `.xdb` 文件移动至分词数据目录,并确保 `postgres` 用户具备读取权限:
```bash
cp custom_company.xdb /usr/local/pgsql/share/tsearch_data/
chown postgres:postgres /usr/local/pgsql/share/tsearch_data/custom_company.xdb
```
### 数据库参数固化
修改 `postgresql.conf` 配置文件,强制加载 `zhparser` 及其自定义扩展词库:
```plain text
## 预加载插件库(必须重启生效)
shared_preload_libraries = 'zhparser'
## 加载自定义外部词典(需使用相对 tsearch_data 的路径)
zhparser.extra_dicts = 'custom_company.xdb'
```
### 索引热更新与验证
由于分词规则变动,存量数据必须通过重建索引实现语义同步:
**物理重启服务**:
```bash
su - postgres -c "/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data restart"
```
**在线重建索引**:利用 `CONCURRENTLY` 关键字,在不阻塞 40 万条数据 DML 操作的前提下刷新 GIN 索引:
```bash
REINDEX INDEX CONCURRENTLY index_name;
```
**分词效能验证**:
```sql
-- 预期结果词性应显示为 n (noun),而非 x (unknown)
SELECT * FROM ts_debug('chinese', '元一能源');
```
**优化建议:**
- **显式提及权重补偿**:这是解决"元一"分词失败(显示为 `x`)的关键技术点。
- **区分重启与重载**:明确 `shared_preload_libraries` 必须通过 `restart` 激活。
-22
View File
@@ -1,22 +0,0 @@
---
title: 四分位法
tags:
- statistics
- algorithm
- data-analysis
- math
author:
name: Zihlu Wang
email: real@zihluwang.me
---
四分位法是统计学中的一种常用方法,主要用于数据的分析和表示。这种方法涉及将一组数据分为四个等分,每份包含四分之一的数据量。主要的统计量包括第一四分位数(Q1)、第二四分位数(Q2),即中位数和第三四分位数(Q3)。
- 第一四分位数(`Q1`),也称为下四分位数,是指数据集中所有数值按大小排列后位于第 25% 位置的数值。
- 第二四分位数(`Q2`),即中位数,是指数据集中所有数值按大小排列后位于第 50% 位置的数值。
- 第三四分位数(`Q3`),也称为上四分位数,是指数据集中所有数值按大小排列后位于第 75% 位置的数值。
- 四分位距(`IQR`),即第三四分位数与第一四分位数之间的差值,用于表示数据集中间50%数据的离散程度。四分位距的计算公式为 IQR = Q3 - Q1。四分位距常用于构建箱形图,是一种描述数据分布的有效方式,特别适用于识别数据中的异常值。
上界值 = Q3 + 1.5 IQR。
下界值 = Q1 - 1.5 IQR。
-140
View File
@@ -1,140 +0,0 @@
---
title: 搭建 LDAP 服务
tags:
- ldap
- openldap
- authentication
- linux
- devops
author:
name: Zihlu Wang
email: real@zihluwang.me
---
搭建 LDAP(轻型目录访问协议)服务器是实现企业级**集中身份认证**和**权限管理**的核心步骤。最常用的开源实现是 **OpenLDAP**。
以下是在基于 Debian/Ubuntu 的 Linux 系统上搭建 OpenLDAP 服务器的详细步骤。
## 环境准备与安装
在开始之前,请确保你的系统软件包是最新的,并设置好主机名。
```bash
## 更新系统
sudo apt update && sudo apt upgrade -y
## 安装 OpenLDAP 及管理工具
## slapd 是守护进程,ldap-utils 是命令行客户端工具
sudo apt install slapd ldap-utils -y
```
> 在安装过程中,系统会提示你设置 **LDAP 管理员密码**。请务必记住这个密码,稍后配置时会频繁使用。
## 配置 OpenLDAP 服务器
虽然安装时已经初始化了部分设置,但通常我们需要根据具体的域名(如 `example.com`)进行自定义配置。
### 重新配置 slapd
执行以下命令进入交互式配置界面:
```bash
sudo dpkg-reconfigure slapd
```
**配置建议**:
1. **Omit OpenLDAP server configuration?** 选择 **No**。
2. **DNS domain name:** 输入你的域名(例如 `centre.example.com`)。这将决定你的基准识别名(Base DN),如 `dc=centre,dc=example,dc=com`。
3. **Organization name:** 输入你的组织名称。
4. **Administrator password:** 输入之前设置的管理员密码。
5. **Database backend:** 建议选择 **MDB**。
6. **Remove database when slapd is purged?** 选择 **No**。
7. **Move old database?** 选择 **Yes**。
## 理解 LDAP 层级结构
LDAP 的数据是以**树状结构**存储的。为了方便管理,我们通常会创建两个"组织单元"(Organizational Units, OU):一个存放用户(Users),一个存放组(Groups)。
## 创建组织单元 (OU)
在 LDAP 中,我们使用 **LDIF** (LDAP Data Interchange Format) 文件来添加或修改目录。
创建一个名为 `base.ldif` 的文件:
```text
## 创建用户组 OU
dn: ou=people,dc=centre,dc=example,dc=com
objectClass: organizationalUnit
ou: people
## 创建用户组别 OU
dn: ou=groups,dc=centre,dc=example,dc=com
objectClass: organizationalUnit
ou: groups
```
**导入数据:**
```bash
## 使用管理员身份将 LDIF 文件导入数据库
ldapadd -x -D "cn=admin,dc=centre,dc=example,dc=com" -W -f base.ldif
```
## 添加用户和组
创建一个名为 `groups.ldif` 的文件来定义一个新组:
```text
## 定义一个新组
dn: cn=developers,ou=groups,dc=centre,dc=example,dc=com
objectClass: inetOrgGroup
cn: developers
```
创建一个名为 `users.ldif` 的文件来定义一个新用户:
```text
## 定义一个新用户
dn: uid=jbloggs,ou=people,dc=centre,dc=example,dc=com # 该条目的身份证号,即 Distinguished Name (DN)。
objectClass: inetOrgPerson # 对象类,inetOrgPerson 代表这是一个互联网机构人员。它允许你使用 email、displayName、telephoneNumber 等常见的联系人属性
objectClass: posixAccount # 它使该条目兼容 Unix/Linux 系统账号。有了它,用户才能登录 Linux 服务器,并拥有 UID、GID 和家目录。
objectClass: shadowAccount # 用于管理密码老化(如过期、更改警告等),对应 Linux 中的 /etc/shadow 文件功能。
uid: jbloggs # 用户的登录名(User ID)。在 Linux 登录界面输入的通常就是这个。
sn: Bloggs # 姓(Surname)。inetOrgPerson 类要求必须填写。
givenName: Joe # 名(First Name)。
cn: Joe Bloggs # 全名(Common Name)。这是 LDAP 条目的标准显示名称。
displayName: Joe Bloggs # 在图形界面或邮件客户端显示的友好名称。
uidNumber: 10000 # Linux 系统中的用户 ID 数字
gidNumber: 5000 # 用户主组的 ID 数字。
userPassword: {SSHA}密码哈希值或明文 # 存储用户的加密密码。
homeDirectory: /home/jbloggs # 用户登录 Linux 后的家目录路径。
loginShell: /bin/bash # 用户登录后使用的 Shell 环境。
```
> 如果您将来要集成 **GitLab**、**Jenkins** 或 **VPN**,它们通常会搜索 `uid` 属性来验证登录名。
### FAQ
#### 如果不需要为该用户授予服务器的登录权限,是不是可以移除 objectClass: posixAccount?
**是的,完全可以移除**,但你需要注意属性之间的"绑定关系"。
如果你只需要这个用户用于 Web 应用登录(如 GitLab, Jenkins, Wiki)或作为电子邮件联系人,而不需要他通过 SSH 或控制台登录 Linux 服务器,移除 `posixAccount` 是更规范的做法。
当你移除 `objectClass: posixAccount` 时,以下属性**必须一并删除**,因为它们属于该类定义的"强制或可选属性":
- `uidNumber`
- `gidNumber`
- `homeDirectory`
- `loginShell`
此外, `shadowAccount` 通常也与系统登录挂钩,如果不需要管理 Linux 密码过期策略,也可以一并移除。
## 后续建议与管理工具
命令行管理 LDAP 较为繁琐,建议使用以下工具进行可视化操作:
- **phpLDAPAdmin**: 基于 Web 的管理界面,适合快速上手;
- **Apache Directory Studio**: 强大的跨平台桌面客户端,适合复杂的架构设计。
- **安全加固**: 默认情况下 LDAP 是明文传输的,建议配置 **LDAPS (LDAP over SSL/TLS)** 来加密 636 端口的通信。
-89
View File
@@ -1,89 +0,0 @@
---
title: 在 MySQL 中使用 JSON
tags:
- mysql
- json
- database
author:
name: Zihlu Wang
email: real@zihluwang.me
---
MySQL (自 5.7 版本开始) **不直接支持名为 `jsonb` 的数据类型**。`jsonb` 是 PostgreSQL 数据库特有的数据类型,它以二进制格式存储 JSON 数据,并对其进行预解析,以便在查询时更快地访问和操作。
然而,MySQL 提供的 `JSON` 数据类型在功能和内部实现上与 PostgreSQL 的 `jsonb` 有很多相似之处,尤其是在查询数据方面。
MySQL 的 `JSON` 数据类型于 MySQL 5.7 版本引入,其特点如下:
1. **二进制存储**:与 PostgreSQL 的 `jsonb` 类似,MySQL 的 `JSON` 类型数据也是以**内部二进制格式**存储的,而不是简单的文本字符串。这使得 JSON 数据的读取和操作效率更高,因为数据库不必在每次查询时都解析文本格式的 JSON 字符串。
2. **自动验证**:当您插入或更新 `JSON` 列时,MySQL 会自动验证其内容是否是有效的 JSON 文档。如果不是,则会抛出错误。
3. **优化存储**:二进制格式的存储也对空间进行了优化,通常比存储原始文本格式的 JSON 更加紧凑。
MySQL 提供了一系列强大的函数和操作符来查询和操作 `JSON` 数据,这些功能与您对 `jsonb` 的期望非常相似:
1. **`->` (JSON Extract Operator)**:用于从 JSON 文档中提取值。它返回一个 JSON 值。
```sql
SELECT my_json_column->'$.key' FROM my_table;
-- 示例:提取 user 对象的 name 属性
-- 假设 my_json_column 存储 {'user': {'name': 'Alice'}}
SELECT json_data->'$.user.name' FROM my_table;
```
2. **`->>` (JSON Unquote Operator)**:用于从 JSON 文档中提取值并**自动取消引用 (unquote)**,通常返回一个标量值(如字符串、数字)。这等同于 `JSON_UNQUOTE(JSON_EXTRACT(...))`。
```sql
SELECT my_json_column->>'$.key' FROM my_table;
-- 示例:提取 user 对象的 name 属性(直接返回字符串 'Alice')
SELECT json_data->>'$.user.name' FROM my_table;
```
3. **`JSON_EXTRACT(json_doc, path, ...)`**:显式地从 JSON 文档中提取数据。
```sql
SELECT JSON_EXTRACT(my_json_column, '$.key') FROM my_table;
```
4. **`JSON_CONTAINS(json_doc, candidate, path)`**:检查 JSON 文档中是否包含指定的值。
```sql
-- 检查tags数组是否包含 'backend'
-- 假设 my_json_column 存储 {'tags': ['frontend', 'backend']}
SELECT * FROM my_table WHERE JSON_CONTAINS(json_data->'$.tags', '"backend"');
```
5. **`JSON_SEARCH(json_doc, one_or_all, search_str, escape_char, path, ...)`**:返回指定字符串在 JSON 文档中的路径。
```sql
-- 查找值为 'test' 的路径
SELECT JSON_SEARCH(my_json_column, 'one', 'test') FROM my_table;
```
6. **`JSON_TABLE(json_doc, path COLUMNS ...) ` (MySQL 8.0 及更高版本)**:这是一个非常强大的函数,可以将 JSON 数据 "展开" 成关系型的行和列,非常适合进行复杂查询和报表。
```sql
-- 假设 json_data 存储 {'items': [{'id': 1, 'name': 'A'}, {'id': 2, 'name': 'B'}]}
SELECT *
FROM my_table,
JSON_TABLE(json_data, '$.items[*]' COLUMNS(
itemId INT PATH '$.id',
itemName VARCHAR(50) PATH '$.name'
)) AS jt;
```
像 PostgreSQL 的 `jsonb` 一样,为了对 JSON 字段进行高效查询,您通常需要创建索引。由于 JSON 字段的内容是动态的,MySQL 不直接支持在 JSON 字段的某个内部路径上直接创建传统 B-tree 索引。但是,可以通过 **虚拟列 (Virtual Generated Columns)** 来实现:
1. **创建虚拟列**:定义一个虚拟列,其值是从 JSON 字段中提取的特定路径。
```sql
ALTER TABLE my_table
ADD COLUMN user_name VARCHAR(255) AS (json_data->>'$.user.name') VIRTUAL;
```
2. **在虚拟列上创建索引**:这样,当您查询 `WHERE json_data->>'$.user.name' = 'Alice'` 时,MySQL 优化器可以选择使用 `idx_user_name` 索引,从而大大提高查询性能。
```sql
CREATE INDEX idx_user_name ON my_table (user_name);
```
尽管 MySQL 没有 `jsonb` 这个名字,但其 `JSON` 数据类型提供了高度相似的功能:二进制存储优化、自动验证和丰富的查询操作符及函数。通过结合虚拟列和索引,MySQL 可以在处理 JSON 数据时提供与 PostgreSQL 的 `jsonb` 相似的查询性能和灵活性。
-82
View File
@@ -1,82 +0,0 @@
---
title: 在第一方系统中使用 Gravatar
tags:
- gravatar
- avatar
author:
name: Zihlu Wang
email: real@zihluwang.me
---
[Gravatar](https://gravatar.com)(Globally Recognised Avatar)是一项将头像图片与邮箱地址关联的服务。当用户使用邮箱在你的平台上注册时,你可以直接展示其
Gravatar 作为默认头像,而无需自行搭建图片上传和存储服务。
## 工作原理
Gravatar 提供了一个简单的 HTTP 端点。你只需计算用户**小写并去除首尾空格后**的邮箱地址的 SHA256 哈希值,然后将其嵌入图片
URL:
```
https://gravatar.com/avatar/<hash>
```
## 生成哈希
### Node.js
```js
import { createHash } from "node:crypto"
const email = "user@example.com".trim().toLowerCase()
const hash = createHash("sha256").update(email).digest("hex")
const url = `https://gravatar.com/avatar/${hash}`
```
### Python
```python
import hashlib
email = "user@example.com".strip().lower()
hash = hashlib.sha256(email.encode()).hexdigest()
url = f"https://gravatar.com/avatar/{hash}"
```
### Shell
```shell
echo -n "user@example.com" | tr '[:upper:]' '[:lower:]' | sha256sum | cut -d ' ' -f1
```
## URL 参数
`/avatar/` 端点接受以下查询参数来定制结果:
| 参数 | 描述 | 示例 |
| ---- | ------------------------------- | -------------- |
| `s` | 图片尺寸(像素,默认 80) | `?s=200` |
| `d` | 未找到 Gravatar 时的默认图片 | `?d=identicon` |
| `r` | 内容分级(`g`、`pg`、`r`、`x`) | `?r=g` |
### 默认图片选项(`d`)
- `identicon` — 基于哈希值的几何图案
- `robohash` — 自动生成的机器人图片
- `retro` — 8-bit 风格的像素化人脸
- `monsterid` — 自动生成的怪物卡通
- `wavatar` — 自动生成的人脸
- `mp` — 通用剪影(Mystery Person)
- `blank` — 透明 PNG
- 自定义 URL(需经过 URL 编码)
### 完整示例
```html
<img
src="https://gravatar.com/avatar/a3b4c5d6e7f8?s=160&d=robohash&r=g"
alt="用户头像"
width="160"
height="160" />
```
始终传入 `d` 参数以避免未设置 Gravatar 的用户出现裂图。`identicon` 和 `robohash` 是常用的选择,因为它们能为每个哈希值生成唯一且易于辨识的图片。
@@ -1,36 +0,0 @@
---
title: 版本控制与代码审查
tags:
- git
- code-review
- best-practice
- workflow
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## GitFlow 工作流
版本控制将使用 GitFlow 分支模型,包括 `main`、`develop`、`feature`、`release` 和 `hotfix` 分支。
- `main`: 生产就绪代码。只有 `release` 和 `hotfix` 分支会合并到 `main`。
- `develop`: 即将开发的功能集成分支。
- `feature/*`: 用于新功能的分支,从 `develop` 分支出来。
- `release/*`: 用于准备新生产版本的分支,从 `develop` 分支出来。
- `hotfix/*`: 用于紧急生产错误修复的分支,从 `main` 分支出来。
## 拉取请求 (PRs)
所有代码更改(直接推送到功能分支除外)都必须通过拉取请求提交。
## 代码审查
- 每个拉取请求必须由至少一名其他开发人员进行审查。
- 审查者负责检查是否符合本代码标准、代码质量、逻辑正确性和测试覆盖率。
- 在创建 PR 之前,应在本地运行 IntelliJ IDEA 的集成代码分析工具。
## 提交消息
编写清晰、简洁、描述性的提交消息,解释更改了什么以及为什么进行更改。如果可能,遵循约定式提交格式(例如,
`feat: add user registration endpoint`)。
@@ -1,85 +0,0 @@
---
title: 通过备份驱动的方式重装系统后一键恢复驱动
tags:
- windows
- driver
- backup
- system-reinstall
- dism
author:
name: Zihlu Wang
email: real@zihluwang.me
---
import { Kbd } from "@rspress/core/theme"
重装 Windows 系统后,最让人头疼的环节之一就是安装驱动程序——显卡、网卡、声卡、蓝牙、触摸板……缺少任何一个都会影响正常使用。Windows Update 能自动拉取一部分,但往往版本老旧或根本找不到某些小众硬件的驱动。
好在 Windows 内置的 DISM 工具提供了驱动导出和恢复功能,只需在重装前花一分钟备份,重装后就能一键恢复所有驱动。
## 重装前:备份所有驱动
以**管理员身份**打开终端(PowerShell 或命令提示符均可),执行以下命令:
```powershell
dism /online /export-driver /destination:D:\DriverBackup
```
将 `D:\DriverBackup` 替换为你的备份目标路径。建议放到外置硬盘、U 盘或非系统盘,确保重装时不会被格式化。
命令执行后会遍历系统中所有已安装的第三方驱动,逐个导出到目标目录。整个过程通常在一分钟左右完成。导出完成后,你会看到类似如下的输出:
```
The operation completed successfully.
```
将该备份目录妥善保存即可进行系统重装。
### 备份了什么?
该命令导出的是**第三方驱动程序**(即非 Windows 内置驱动),包括显卡、声卡、网卡、芯片组、蓝牙、读卡器等所有你在系统使用过程中安装过的驱动。Windows 自带的通用驱动不会被导出,这部分系统重装后会自动回归。
## 重装后:一键恢复所有驱动
### 方法一:命令行一键恢复(推荐)
这是最省心的方式。同样以**管理员身份**打开终端,执行:
```powershell
dism /online /add-driver /driver:D:\DriverBackup /recurse
```
`/recurse` 参数会递归搜索备份目录下的所有子文件夹,将每一个驱动都安装到新系统中。执行完成后**必须重启电脑**使其生效。
### 方法二:通过设备管理器逐个安装
如果你更偏好图形界面,或者只需要恢复特定驱动:
1. 按 <Kbd>Win</Kbd> + <Kbd>X</Kbd>,选择**设备管理器**
2. 找到带有黄色感叹号的设备(即缺失驱动的设备)
3. 右键点击该设备,选择**更新驱动程序**
4. 选择**浏览我的电脑以查找驱动程序**
5. 将路径指向你的驱动备份目录,勾选**包括子文件夹**
6. 点击下一步,系统会自动搜索并安装匹配的驱动
重复以上步骤直到所有设备都恢复正常。
## 常见问题
### 显示 "The driver package could not be installed"
少数情况下,某些驱动可能因为签名问题或版本不兼容而无法通过 DISM 安装。这时可以尝试手动安装:在备份目录中找到对应的 `.inf` 文件,右键选择**安装**。
### 恢复后仍有设备缺失驱动
DISM 备份的仅是当前系统上已安装的驱动。如果重装前就有设备未被正确驱动,这些设备的驱动自然不会存在于备份中。建议在备份前先用设备管理器确认所有设备都已正常工作。
### 能否跨 Windows 版本恢复?
同一台电脑从 Windows 10 升级到 Windows 11 的驱动恢复通常是可行的,但不建议在不同硬件配置的电脑间迁移驱动备份。每台机器的驱动应独立备份。
## 小结
这套流程的核心优势在于**零第三方工具依赖**——DISM 是 Windows 内建组件,无需安装任何额外软件。重装前花一分钟导出,重装后一条命令恢复,比手动逐个下载安装驱动高效得多。养成重装前先备份驱动的习惯,能让每次系统迁移都轻松不少。
> 本文内容参考自抖音用户 [@科技碎碎念](https://v.douyin.com/Cbf0ruVdbUM/) 的视频教程。
-18
View File
@@ -1,18 +0,0 @@
---
title: 企业动态
---
## Microsoft 邮件投递问题(@outlook、@hotmail、@live、@msn 以及托管在 Microsoft 的自定义域名邮箱)
> 更新于 _2026 年 5 月 19 日_
**使用 Microsoft 托管邮箱的用户需立即采取行动。**
特此通知,我们的自动邮件服务目前向所有由 Microsoft 托管的邮箱地址投递时遇到严重问题。由于 Microsoft Exchange Online Protection (EOP) 近期采取了激进的过滤策略,我们的邮件投递服务器已被 Microsoft 屏蔽。因此,我们的系统**无法向以下邮箱地址发送任何通知、密码重置或激活邮件**:**@outlook.com**、**@hotmail.com**、**@live.com**、**@msn.com**,以及**任何由 Microsoft 托管自定义域名的邮箱**。
为确保服务不受影响,请根据您的账户状态参考以下指引:
- **新用户(注册前)**:请**不要**使用 Microsoft 托管的邮箱地址注册账户。您将无法收到必要的激活链接。请使用其他邮件服务商,例如您的企业邮箱、QQ 邮箱或网易 163 邮箱完成注册。
- **已注册用户**:如果您的账户当前绑定了 Outlook 或 Hotmail 邮箱地址,请立即登录并更新您的主邮箱。您可以通过 **[用户设置 -> 偏好设置 -> 邮箱](https://github.com/settings/emails)** 页面进行更改。未能及时更新将导致您无法收到重要的仓库更新、CI/CD 流水线失败日志以及合并请求通知。
我们正在积极与 Microsoft Postmaster Centre 沟通,以申请解除此屏蔽。对于由此带来的任何不便,我们深表歉意,并感谢您及时配合更新联系信息。
@@ -1,14 +0,0 @@
[
"index",
{ "type": "section-header", "label": "opensource.group.libraries" },
{ "type": "dir", "name": "onixbyte-toolbox" },
{ "type": "dir", "name": "regions4j" },
{ "type": "dir", "name": "captcha" },
{ "type": "dir", "name": "calendar-toolbox" },
{ "type": "section-header", "label": "opensource.group.applications" },
{ "type": "dir", "name": "helix" },
{ "type": "dir", "name": "delta-force-guide" },
{ "type": "section-header", "label": "opensource.group.tools" },
{ "type": "dir", "name": "claude-code-skill-manager" },
{ "type": "dir", "name": "vite-plugins" }
]
@@ -1,390 +0,0 @@
---
title: Calendar Toolbox API
---
## 包结构概览
库的代码组织在基础包 `com.onixbyte.calendar` 下,结构如下:
| 包 | 描述 |
| ------------------------------------------ | ---------------------------------- |
| `com.onixbyte.calendar` | 根日历对象 |
| `com.onixbyte.calendar.component` | iCalendar 组件(VEVENT、VTODO 等) |
| `com.onixbyte.calendar.component.property` | 组件级属性 |
| `com.onixbyte.calendar.property` | 日历级属性 |
| `com.onixbyte.calendar.parameter` | iCalendar 属性参数 |
| `com.onixbyte.calendar.recurrence` | 重复规则类型 |
| `com.onixbyte.calendar.value` | 值类型 |
| `com.onixbyte.calendar.util` | 格式化工具类 |
---
## Calendar
表示 iCalendar(RFC 5545)VCALENDAR 容器的顶层对象。
```java
Calendar calendar = Calendar.builder()
.withCalendarScale(...)
.withMethod(...)
.withProductIdentifier(...)
.withVersion(...)
.withOwner(...)
.withPrimaryCalendar(...)
.withPublishedTTL(...)
.withCalendarDescription(...)
.withCalendarName(...)
.withCalendarId(...)
.withCustomProperties(...)
.withComponents(event, todo, ...)
.build();
String icsContent = calendar.formatted();
```
### 日历属性
所有日历级属性位于 `com.onixbyte.calendar.property`,实现 `CalendarProperty` 接口。
| 类 | iCalendar 属性 | 描述 |
| ------------------------ | -------------------- | --------------------------------------- |
| `CalendarScale` | `CALSCALE` | 日历系统(例如 GREGORIAN) |
| `Method` | `METHOD` | iCalendar 方法(例如 PUBLISH、REQUEST) |
| `ProductIdentifier` | `PRODID` | 日历的产品标识符 |
| `Version` | `VERSION` | iCalendar 版本(例如 2.0) |
| `Owner` | `X-OWNER` | 日历所有者 |
| `PrimaryCalendar` | `X-PRIMARY-CALENDAR` | 是否为主日历 |
| `PublishedTTL` | `X-PUBLISHED-TTL` | 已发布日历的生存时间 |
| `CalendarDescription` | `X-CALENDAR-DESC` | 日历描述 |
| `CalendarName` | `X-CALENDAR-NAME` | 日历名称 |
| `CalendarId` | `X-CALENDAR-ID` | 日历标识符 |
| `CustomCalendarProperty` | `X-*` | 自定义日历属性 |
自定义属性使用 Builder 创建:
```java
var customProp = CustomCalendarProperty.builder()
.withParameters(...)
.build("X-MY-PROPERTY", "my-value");
```
> 注意:自定义属性名称**必须**以 `X-` 开头。
---
## 组件
组件是 iCalendar 对象的核心实体。每个组件实现 `CalendarComponent` 接口,并在 `BEGIN:TYPE` 和 `END:TYPE` 定界符之间渲染。
### Event(VEVENT)
表示一个预定的活动/事件。
```java
Event event = Event.builder()
.withDateTimeStamp(dtstamp) // 必需
.withUniqueIdentifier(uid) // 必需
.withDateTimeStart(dtstart)
.withDateTimeEnd(dtend) // 与 duration 互斥
.withDuration(duration) // 与 dtend 互斥
.withSummary(summary)
.withDescription(description)
.withClassification(classification)
.withDateTimeCreated(dateTimeCreated)
.withGeographicPosition(geoPos)
.withLastModified(lastModified)
.withLocation(location)
.withOrganiser(organiser)
.withPriority(priority)
.withSequenceNumber(seqNumber)
.withStatus(status)
.withTimeTransparency(transparency)
.withUniformResourceLocator(url)
.withRecurrenceId(recurrenceId)
.withRecurrenceRule(rrule)
.withAttachments(attachments)
.withAttendees(attendees)
.withCategories(categories)
.withComments(comments)
.withContacts(contacts)
.withExceptionDateTimes(exDates)
.withRequestStatuses(statuses)
.withRelated(related)
.withResources(resources)
.withRecurrenceDateTimes(rDates)
.build();
```
### Todo(VTODO)
表示待办事项或任务。
```java
Todo todo = Todo.builder()
.withDateTimeStamp(dtstamp) // 必需
.withUniqueIdentifier(uid) // 必需
.withSummary(summary)
.withDateTimeStart(dtstart)
.withDateTimeDue(due) // 与 duration 互斥
.withDuration(duration) // 与 due 互斥
.withDateTimeCompleted(completed)
.withPercentComplete(percent)
.withClassification(classification)
.withDateTimeCreated(created)
.withDescription(description)
.withGeographicPosition(geoPos)
.withLastModified(lastModified)
.withLocation(location)
.withOrganiser(organiser)
.withPriority(priority)
.withSequenceNumber(seq)
.withStatus(status)
.withRecurrenceId(recurId)
.withRecurrenceRule(rrule)
.withUniformResourceLocator(url)
.withAttachments(attachments)
.withAttendees(attendees)
.withCategories(categories)
.withComments(comments)
.withContacts(contacts)
.withExceptionDateTimes(exDates)
.withRequestStatuses(statuses)
.withRelatedToList(related)
.withResources(resources)
.withRecurrenceDateTimes(rDates)
.build();
```
### Journal(VJOURNAL)
表示日记条目或笔记。
```java
Journal journal = Journal.builder()
.withDateTimeStamp(dtstamp) // 必需
.withUniqueIdentifier(uid) // 必需
.withSummary(summary)
.withClassification(classification)
.withDateTimeCreated(created)
.withDateTimeStart(dtstart)
.withLastModified(lastModified)
.withOrganiser(organiser)
.withRecurrenceId(recurId)
.withSequenceNumber(seq)
.withStatus(status)
.withUniformResourceLocator(url)
.withRecurrenceRule(rrule)
.withAttachments(attachments)
.withAttendees(attendees)
.withCategories(categories)
.withComments(comments)
.withContacts(contacts)
.withDescriptions(descriptions)
.withExceptionDateTimes(exDates)
.withRelatedToList(related)
.withRecurrenceDate(rDates)
.withRequestStatuses(statuses)
.build();
```
### FreeBusy(VFREEBUSY)
表示空闲/忙碌时间信息。
```java
FreeBusy freeBusy = FreeBusy.builder()
.withDateTimeStamp(dtstamp) // 必需
.withUniqueIdentifier(uid) // 必需
.withContact(contact)
.withDateTimeStart(dtstart)
.withDateTimeEnd(dtend)
.withOrganiser(organiser)
.withUniformResourceLocator(url)
.withAttendees(attendees)
.withComments(comments)
.withFreeBusyTimes(freeBusyTimes)
.withRequestStatuses(statuses)
.build();
```
### TimeZone(VTIMEZONE)
定义时区规则,包括标准时间和夏令时转换。
```java
TimeZone timezone = TimeZone.builder()
.withTimeZoneIdentifier(tzId)
.withLastModified(lastModified)
.withTimeZoneUrl(tzUrl)
.withTimeZoneProperties(standardProp, daylightProp)
.build();
```
时区属性定义实际的转换规则:
```java
TimeZoneProperty standard = TimeZoneProperty.builder()
.withDateTimeStart(dtstart)
.withTimeZoneOffsetTo(offsetTo)
.withTimeZoneOffsetFrom(offsetFrom)
.withRecurrenceRule(rrule)
.withTimeZoneNames(tzName)
.buildStandard(); // 或 buildDaylight()
TimeZoneProperty daylight = TimeZoneProperty.builder()
.withDateTimeStart(dtstart)
.withTimeZoneOffsetTo(offsetTo)
.withTimeZoneOffsetFrom(offsetFrom)
.buildDaylight();
```
### Alarm(VALARM)
定义闹钟/提醒通知。支持三种闹钟类型:
```java
// 音频闹钟
Alarm audioAlarm = Alarm.builder()
.withTrigger(trigger)
.withDuration(duration)
.withRepeatCount(repeatCount)
.withAttachments(audioAttachment)
.buildAudio();
// 显示闹钟
Alarm displayAlarm = Alarm.builder()
.withTrigger(trigger)
.withDescription(description) // 必需
.withDuration(duration)
.withRepeatCount(repeatCount)
.buildDisplay();
// 邮件闹钟
Alarm emailAlarm = Alarm.builder()
.withTrigger(trigger)
.withDescription(description) // 必需
.withSummary(summary) // 必需
.withAttendees(attendees) // 必需
.withDuration(duration)
.withRepeatCount(repeatCount)
.buildEmail();
```
---
## 组件属性
组件属性位于 `com.onixbyte.calendar.component.property`,实现 `ComponentProperty` 接口。
| 类 | iCalendar 属性 | 描述 |
| ------------------------- | ------------------ | ---------------------------------------------- |
| `Action` | `ACTION` | 闹钟操作类型(AUDIO、DISPLAY、EMAIL) |
| `Attachment` | `ATTACH` | 文档附件 |
| `Attendee` | `ATTENDEE` | 事件/任务参与者 |
| `Categories` | `CATEGORIES` | 类别或标签 |
| `Classification` | `CLASS` | 访问分类(PUBLIC、PRIVATE、CONFIDENTIAL) |
| `Comment` | `COMMENT` | 评论 |
| `Contact` | `CONTACT` | 联系信息 |
| `DateTimeCompleted` | `COMPLETED` | 完成日期时间 |
| `DateTimeCreated` | `CREATED` | 创建日期时间 |
| `DateTimeDue` | `DUE` | 截止日期时间 |
| `DateTimeEnd` | `DTEND` | 结束日期时间 |
| `DateTimeStamp` | `DTSTAMP` | 日期时间戳 |
| `DateTimeStart` | `DTSTART` | 开始日期时间 |
| `Description` | `DESCRIPTION` | 描述 |
| `ExceptionDateTimes` | `EXDATE` | 例外日期时间 |
| `FreeBusyTime` | `FREEBUSY` | 空闲/忙碌时间段 |
| `GeographicPosition` | `GEO` | 地理位置(纬度/经度) |
| `LastModified` | `LAST-MODIFIED` | 最后修改日期时间 |
| `Location` | `LOCATION` | 地点 |
| `Organiser` | `ORGANIZER` | 组织者 |
| `PercentComplete` | `PERCENT-COMPLETE` | 完成百分比 |
| `Priority` | `PRIORITY` | 优先级 |
| `RecurrenceDateTimes` | `RDATE` | 重复日期时间 |
| `RecurrenceId` | `RECURRENCE-ID` | 重复实例标识符 |
| `RecurrenceRule` | `RRULE` | 重复规则 |
| `RelatedTo` | `RELATED-TO` | 相关组件引用 |
| `RepeatCount` | `REPEAT` | 闹钟重复次数 |
| `RequestStatus` | `REQUEST-STATUS` | 请求状态 |
| `Resources` | `RESOURCES` | 资源 |
| `SequenceNumber` | `SEQUENCE` | 序列号(修订版本) |
| `Status` | `STATUS` | 组件状态(TENTATIVE、CONFIRMED、CANCELLED 等) |
| `Summary` | `SUMMARY` | 标题或摘要 |
| `TimeTransparency` | `TRANSP` | 时间透明度(OPAQUE、TRANSPARENT) |
| `TimeZoneIdentifier` | `TZID` | 时区标识符 |
| `TimeZoneName` | `TZNAME` | 时区名称 |
| `TimeZoneOffsetFrom` | `TZOFFSETFROM` | 相对于 UTC 的时区偏移(原) |
| `TimeZoneOffsetTo` | `TZOFFSETTO` | 相对于 UTC 的时区偏移(目标) |
| `TimeZoneUrl` | `TZURL` | 时区 URL |
| `Trigger` | `TRIGGER` | 闹钟触发器 |
| `UniformResourceLocator` | `URL` | 关联 URL |
| `UniqueIdentifier` | `UID` | 唯一标识符 |
| `CustomComponentProperty` | `X-*` | 自定义组件属性 |
---
## 参数
参数位于 `com.onixbyte.calendar.parameter`,为组件属性提供额外限定。
| 类 | iCalendar 参数 | 描述 |
| ----------------------------- | ---------------- | ---------------------------------------------- |
| `AlarmTriggerRelationship` | `RELATED` | 触发器关联(START、END) |
| `AlternateTextRepresentation` | `ALTREP` | 替代文本 URI |
| `CalendarUserType` | `CUTYPE` | 日历用户类型(INDIVIDUAL、GROUP、RESOURCE 等) |
| `CommonName` | `CN` | 通用名称 |
| `Delegatees` | `DELEGATED-TO` | 受托人 |
| `Delegators` | `DELEGATED-FROM` | 委托人 |
| `DirectoryEntryReference` | `DIR` | 目录条目 URI |
| `FormatType` | `FMTTYPE` | 格式类型(MIME 类型) |
| `FreeBusyTimeType` | `FBTYPE` | 空闲/忙碌时间类型 |
| `InlineEncoding` | `ENCODING` | 内联编码(BASE64) |
| `Language` | `LANGUAGE` | 语言 |
| `Membership` | `MEMBER` | 组成员 |
| `ParticipationRole` | `ROLE` | 参与角色(CHAIR、REQ-PARTICIPANT 等) |
| `ParticipationStatus` | `PARTSTAT` | 参与状态(ACCEPTED、DECLINED 等) |
| `RecurrenceIdentifierRange` | `RANGE` | 重复范围(THISANDPRIOR、THISANDFUTURE) |
| `RelationshipType` | `RELTYPE` | 关系类型(PARENT、CHILD、SIBLING) |
| `RsvpExpectation` | `RSVP` | RSVP 期望(TRUE、FALSE) |
| `SentBy` | `SENT-BY` | 发送者 |
| `TimeZoneIdentifier` | `TZID` | 时区标识符 |
| `ValueDataType` | `VALUE` | 值数据类型(DATE、DATE-TIME 等) |
---
## 递归类型
位于 `com.onixbyte.calendar.recurrence`。
| 类 | 描述 |
| ------------ | ------------------------------------------------------------ |
| `Frequency` | 重复频率常量(DAILY、WEEKLY、MONTHLY、YEARLY),用于重复规则 |
| `WeekdayNum` | 重复规则的星期序号(例如第 2 个星期一) |
---
## 值类型
位于 `com.onixbyte.calendar.value`。
| 类 | 描述 |
| ------------------- | ------------------------------------- |
| `FreeBusyTimeValue` | 表示空闲/忙碌时间值 |
| `PeriodOfTime` | 表示一个时间段,包含开始和结束 |
| `PropertyValue` | 属性的基础值类型 |
| `UtcOffset` | UTC 偏移值(例如 `-05:00`、`+01:00`) |
---
## 格式化输出
所有组件和属性都提供 `formatted()` 方法,返回符合 iCalendar 规范的字符串。`Calendar.formatted()` 方法生成完整的 `.ics` 输出,包含 `BEGIN:VCALENDAR`/`END:VCALENDAR`。
```java
String ics = calendar.formatted();
// BEGIN:VCALENDAR
// CALSCALE:GREGORIAN
// METHOD:PUBLISH
// ...
// END:VCALENDAR
```
@@ -1,100 +0,0 @@
---
title: Calendar Toolbox
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
Calendar Toolbox 是一个 Java 库,旨在简化遵循 iCalendar 规范(RFC 5545)的 `.ics` 文件的创建。该库为开发者提供了简洁的 API,可生成能够导入到 Microsoft Outlook、Google Calendar 和 Apple Calendar 等主流日历应用程序中的日历事件。
通过 Calendar Toolbox,您可以以编程方式轻松创建、定制和导出日历事件文件,从而以最少的代价将日历功能集成到您的 Java 应用中。
## 特性
- **完整 RFC 5545 兼容** — 支持 VEVENT、VTODO、VJOURNAL、VFREEBUSY、VTIMEZONE 和 VALARM 等标准 iCalendar 组件。
- **流式 Builder 模式** — 所有组件和属性均使用 Builder 模式,构建过程可读性强且直观。
- **类型安全属性** — 强类型的属性和参数确保编译期正确性。
- **自定义扩展** — 支持日历级和组件级的自定义 `X-` 属性。
- **轻量级** — 核心功能零外部依赖。
## 安装
<Tabs>
<Tab label="Gradle with Kotlin DSL">
```kotlin title="build.gradle.kts" dependencies{" "}
{implementation("com.onixbyte:calendar-toolbox:1.1.0")}
```
</Tab>
<Tab label="Maven">
```xml title="pom.xml"
<dependencies>
<dependency>
<groupId>com.onixbyte</groupId>
<artifactId>calendar-toolbox</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
```
</Tab>
</Tabs>
## 快速开始
```java
import com.onixbyte.calendar.Calendar;
import com.onixbyte.calendar.component.Event;
import com.onixbyte.calendar.component.property.*;
import com.onixbyte.calendar.property.*;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
public class CalendarExample {
public static void main(String[] args) {
// 创建所需的日期时间属性
var now = ZonedDateTime.now(ZoneOffset.UTC);
var dtstamp = DateTimeStamp.of(now);
var uid = UniqueIdentifier.of("meeting-001@example.com");
var dtstart = DateTimeStart.of(
LocalDateTime.of(2025, 8, 15, 9, 0),
"America/New_York"
);
var dtend = DateTimeEnd.of(
LocalDateTime.of(2025, 8, 15, 10, 0),
"America/New_York"
);
var summary = Summary.of("Team Standup");
var description = Description.of("Daily team standup meeting.");
// 构建事件
var event = Event.builder()
.withDateTimeStamp(dtstamp)
.withUniqueIdentifier(uid)
.withDateTimeStart(dtstart)
.withDateTimeEnd(dtend)
.withSummary(summary)
.withDescription(description)
.build();
// 构建日历并输出 .ics 内容
var calendar = Calendar.builder()
.withCalendarScale(CalendarScale.of("GREGORIAN"))
.withMethod(Method.of("PUBLISH"))
.withProductIdentifier(ProductIdentifier.of("-//OnixByte//Calendar Toolbox//EN"))
.withVersion(Version.of("2.0"))
.withComponents(event)
.build();
System.out.println(calendar.formatted());
}
}
```
上述代码会生成一个 `.ics` 格式的字符串,可以保存为文件或通过邮件发送。
## 许可证
Calendar Toolbox 是采用 MIT 许可证发布的开源软件。
@@ -1,248 +0,0 @@
---
title: Captcha API
---
## 包结构概览
库的代码组织在基础包 `com.onixbyte.captcha` 下,结构如下:
| 包 | 描述 |
| -------------------------------------- | ---------------------- |
| `com.onixbyte.captcha` | 核心接口 |
| `com.onixbyte.captcha.impl` | 默认实现 |
| `com.onixbyte.captcha.text` | 文本生成器和渲染器接口 |
| `com.onixbyte.captcha.text.impl` | 文本生成器和渲染器实现 |
| `com.onixbyte.captcha.text.enums` | 文本相关枚举 |
| `com.onixbyte.captcha.background` | 背景生成器接口 |
| `com.onixbyte.captcha.background.impl` | 背景生成器实现 |
| `com.onixbyte.captcha.noise` | 噪点生成器接口 |
| `com.onixbyte.captcha.noise.impl` | 噪点生成器实现 |
| `com.onixbyte.captcha.gimpy` | 扭曲引擎接口 |
| `com.onixbyte.captcha.gimpy.impl` | 扭曲引擎实现 |
---
## Producer
负责创建 CAPTCHA 图片并在其上绘制文字的顶层接口。
```java
Producer captcha = DefaultCaptchaProducer.builder()
.textProducer(textProducer)
.wordRenderer(wordRenderer)
.gimpyEngine(gimpyEngine)
.backgroundProducer(backgroundProducer)
.width(200)
.height(50)
.borderDrawn(true)
.borderColour(Color.BLACK)
.borderThickness(1)
.build();
String text = captcha.createText();
BufferedImage image = captcha.createImage(text);
```
### DefaultCaptchaProducer Builder
| 方法 | 默认值 | 描述 |
| -------------------- | --------------------------------------------- | -------------- |
| `textProducer` | `DefaultTextProducer.builder().build()` | 文本生成器 |
| `wordRenderer` | `DefaultWordRenderer.builder().build()` | 文字渲染器 |
| `gimpyEngine` | `WaterRipple.builder().build()` | 扭曲引擎 |
| `backgroundProducer` | `DefaultBackgroundProducer.builder().build()` | 背景生成器 |
| `width` | `200` | 验证码图片宽度 |
| `height` | `50` | 验证码图片高度 |
| `borderDrawn` | `true` | 是否绘制边框 |
| `borderColour` | `Color.BLACK` | 边框颜色 |
| `borderThickness` | `1` | 边框厚度 |
---
## Text
### TextProducer
用于创建 CAPTCHA 文本字符串的接口。
```java
public interface TextProducer {
String getText();
}
```
#### DefaultTextProducer
生成可配置长度和字符集的随机文本。
```java
DefaultTextProducer textProducer = DefaultTextProducer.builder()
.length(6) // 默认: 6
.chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray()) // 默认: a-z, A-Z, 0-9
.build();
```
| 方法 | 默认值 | 描述 |
| -------- | ----------------------------------- | -------------------- |
| `length` | `6` | 生成文本的长度 |
| `chars` | `a-z`、`A-Z`、`0-9`(共 62 个字符) | 生成文本使用的字符集 |
### WordRenderer
将文字渲染到图片上的接口。
```java
public interface WordRenderer {
BufferedImage renderWord(String word, int width, int height);
}
```
#### DefaultWordRenderer
使用可配置的字体、颜色和间距渲染文字。
```java
DefaultWordRenderer wordRenderer = DefaultWordRenderer.builder()
.fontSize(40) // 默认: 40
.fonts("Arial", "Courier") // 默认: Arial, Courier
.fontColour(Color.BLACK) // 默认: Color.BLACK
.charSpace(2) // 默认: 2
.fontStyle(FontStyle.BOLD) // 默认: FontStyle.BOLD
.build();
```
| 方法 | 默认值 | 描述 |
| ------------ | ---------------------- | ------------------ |
| `fontSize` | `40` | 字号(像素) |
| `fonts` | `["Arial", "Courier"]` | 字体族(随机选择) |
| `fontColour` | `Color.BLACK` | 字体颜色 |
| `charSpace` | `2` | 字符间距(像素) |
| `fontStyle` | `FontStyle.BOLD` | 字体样式 |
### FontStyle
定义 CAPTCHA 文字渲染支持的字体样式。
| 值 | 对应的 AWT 常量 | 描述 |
| ------------- | ---------------------- | ------------ | ------ |
| `PLAIN` | `java.awt.Font.PLAIN` | 普通样式 |
| `BOLD` | `java.awt.Font.BOLD` | 粗体 |
| `ITALIC` | `java.awt.Font.ITALIC` | 斜体 |
| `BOLD_ITALIC` | `java.awt.Font.BOLD | Font.ITALIC` | 粗斜体 |
---
## GimpyEngine
用于对图片应用扭曲效果的接口。
```java
public interface GimpyEngine {
BufferedImage getDistortedImage(BufferedImage baseImage);
}
```
### WaterRipple
应用水波纹扭曲效果。继承 `AbstractGimpyEngine`,自动添加噪点。
```java
WaterRipple gimpy = WaterRipple.builder()
.noiseProducer(DefaultNoiseProducer.builder().build()) // 默认: DefaultNoiseProducer
.build();
```
| 方法 | 默认值 | 描述 |
| --------------- | ---------------------------------------- | ---------- |
| `noiseProducer` | `DefaultNoiseProducer.builder().build()` | 噪点生成器 |
### FishEyeGimpy
应用鱼眼扭曲效果,在图片上绘制水平和垂直线条。
```java
FishEyeGimpy gimpy = FishEyeGimpy.builder()
.build();
```
### ShadowGimpy
应用阴影和波纹效果。继承 `AbstractGimpyEngine`,自动添加噪点。
```java
ShadowGimpy gimpy = ShadowGimpy.builder()
.noiseProducer(DefaultNoiseProducer.builder().build()) // 默认: DefaultNoiseProducer
.build();
```
| 方法 | 默认值 | 描述 |
| --------------- | ---------------------------------------- | ---------- |
| `noiseProducer` | `DefaultNoiseProducer.builder().build()` | 噪点生成器 |
---
## Noise
### NoiseProducer
向图片添加噪点的接口。
```java
public interface NoiseProducer {
void makeNoise(BufferedImage image, float factorOne, float factorTwo,
float factorThree, float factorFour);
}
```
#### DefaultNoiseProducer
使用可配置的颜色添加噪点曲线。
```java
DefaultNoiseProducer noise = DefaultNoiseProducer.builder()
.noiseColour(Color.BLACK) // 默认: Color.BLACK
.build();
```
| 方法 | 默认值 | 描述 |
| ------------- | ------------- | -------- |
| `noiseColour` | `Color.BLACK` | 噪点颜色 |
#### NoNoiseProducer
不向图片添加任何噪点的空实现。
```java
NoiseProducer noise = NoNoiseProducer.builder()
.build();
```
---
## Background
### BackgroundProducer
向图片添加背景的接口。
```java
public interface BackgroundProducer {
BufferedImage addBackground(BufferedImage image);
}
```
#### DefaultBackgroundProducer
创建具有可配置起始和结束颜色的渐变背景。
```java
DefaultBackgroundProducer background = DefaultBackgroundProducer.builder()
.colourFrom(Color.WHITE) // 默认: Color.LIGHT_GRAY
.colourTo(Color.LIGHT_GRAY) // 默认: Color.WHITE
.build();
```
| 方法 | 默认值 | 描述 |
| ------------ | ------------------ | ------------ |
| `colourFrom` | `Color.LIGHT_GRAY` | 渐变起始颜色 |
| `colourTo` | `Color.WHITE` | 渐变结束颜色 |
@@ -1,86 +0,0 @@
---
title: Captcha
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
Captcha 是一个用于生成 CAPTCHA 验证码图片的 Java 库。本项目是 Google Kaptcha 的精神继承者,按照现代开发实践进行了现代化改造。
通过 Captcha,您可以轻松生成可定制的 CAPTCHA 图片,支持多种扭曲效果、噪点和背景选项,轻松集成到您的 Java 应用中。
## 特性
- **易于集成** — 基于 Builder 的简洁 API,快速完成配置和集成。
- **可定制的验证码生成** — 配置文本长度、字符集、字体样式和颜色。
- **多种扭曲效果** — 支持水波纹、鱼眼和阴影效果。
- **可配置噪点** — 使用自定义颜色添加噪点线条,或完全禁用噪点。
- **渐变背景** — 可自定义的渐变背景颜色。
- **简洁现代的代码** — 文档完善、类型安全且易于扩展。
## 安装
<Tabs>
<Tab label="Gradle with Kotlin DSL">
```kotlin title="build.gradle.kts" dependencies {implementation("com.onixbyte:captcha:1.0.0")}
```
</Tab>
<Tab label="Maven">
```xml title="pom.xml"
<dependencies>
<dependency>
<groupId>com.onixbyte</groupId>
<artifactId>captcha</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
```
</Tab>
</Tabs>
## 快速开始
```java
import com.onixbyte.captcha.Producer;
import com.onixbyte.captcha.background.BackgroundProducer;
import com.onixbyte.captcha.background.impl.DefaultBackgroundProducer;
import com.onixbyte.captcha.gimpy.GimpyEngine;
import com.onixbyte.captcha.gimpy.impl.WaterRipple;
import com.onixbyte.captcha.impl.DefaultCaptchaProducer;
import com.onixbyte.captcha.noise.NoiseProducer;
import com.onixbyte.captcha.noise.impl.DefaultNoiseProducer;
import com.onixbyte.captcha.text.TextProducer;
import com.onixbyte.captcha.text.WordRenderer;
import com.onixbyte.captcha.text.impl.DefaultTextProducer;
import com.onixbyte.captcha.text.impl.DefaultWordRenderer;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class CaptchaExample {
public static void main(String[] args) throws IOException {
// 使用默认配置创建 CAPTCHA 生产者
Producer captcha = DefaultCaptchaProducer.builder().build();
// 生成文本并创建图片
String captchaText = captcha.createText();
BufferedImage captchaImage = captcha.createImage(captchaText);
// 保存图片到文件
ImageIO.write(captchaImage, "png", new File("captcha.png"));
System.out.println("CAPTCHA 文本: " + captchaText);
}
}
```
以上代码生成一张包含 6 位随机字符的 CAPTCHA 图片,并将其保存为 PNG 文件。
## 许可证
Captcha 是采用 MIT 许可证发布的开源软件。
@@ -1,217 +0,0 @@
---
title: Claude Code Skills Manager
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
Claude Code Skills Manager(`ccsm`)是一个 CLI 工具,用于从基于 Git 的 Marketplace 仓库中发现、安装和管理 Claude Code Skills。你可以将任何公开的、包含 `skills.json` 清单的 Git 仓库注册为 Skill 来源,然后通过统一的命令行界面搜索、安装、更新和移除 Skills。
Skills 会被直接部署到 Claude Code 自身的 Skills 目录(`~/.claude/skills/`)中,Claude Code 启动时会自动加载。
## 安装
<Tabs>
<Tab label="预编译二进制">
从 [Releases 页面](https://onixbyte.dev/onixbyte/claude-code-skill-manager/releases) 下载对应平台的最新二进制文件。
| 平台 | 二进制文件 |
|------|-----------|
| Linux (amd64) | `ccsm-linux-amd64` |
| macOS (amd64) | `ccsm-darwin-amd64` |
| Windows (amd64) | `ccsm-windows-amd64.exe` |
将二进制文件放到 `PATH` 中的某个目录,并重命名为 `ccsm`(Windows 上为 `ccsm.exe`)。
</Tab>
<Tab label="Cargo 安装">
```bash
git clone https://onixbyte.dev/onixbyte/claude-code-skill-manager.git
cd claude-code-skill-manager
cargo install --path .
```
需要 Rust 工具链(通过 [rustup.rs](https://rustup.rs) 安装)。
</Tab>
<Tab label="从源码构建">
```bash
git clone https://onixbyte.dev/onixbyte/claude-code-skill-manager.git
cd claude-code-skill-manager
cargo build --release
```
二进制文件位于 `target/release/ccsm`(Windows 上为 `ccsm.exe`)。
</Tab>
</Tabs>
## 快速开始
### 注册 Marketplace
Marketplace 可以是任何在根目录包含 `skills.json` 清单的公开 Git 仓库。通过以下命令注册:
```bash
ccsm marketplace add https://github.com/example/skills
```
CCSM 会拉取清单并缓存到本地。你可以根据需要注册任意数量的 Marketplace。
### 浏览和安装 Skills
```bash
# 搜索所有已注册 Marketplace 中的 Skills
ccsm search
# 通过关键词过滤
ccsm search git
# 按名称安装 Skill
ccsm install my-awesome-skill
# 如果多个 Marketplace 中存在同名 Skill,可指定 Marketplace
ccsm install my-skill --marketplace my-marketplace
```
### 管理已安装的 Skills
```bash
# 列出已安装的 Skills
ccsm list
# 查看已安装 Skill 的详细信息
ccsm info my-awesome-skill
# 更新所有已安装的 Skills 到最新版本
ccsm update
# 更新指定 Skill
ccsm update my-awesome-skill
# 移除 Skill
ccsm remove my-awesome-skill
```
### Marketplace 管理
```bash
# 列出所有已注册的 Marketplace
ccsm marketplace list
# 刷新缓存的清单(所有 Marketplace)
ccsm marketplace update
# 刷新指定 Marketplace
ccsm marketplace update my-marketplace
# 移除 Marketplace
ccsm marketplace remove my-marketplace
```
### 安装模式
CCSM 支持两种部署模式:
| 模式 | 行为 | 适用场景 |
| -------------- | ------------------------------------------- | ------------------------------------- |
| `copy`(默认) | 将文件物理复制到 `~/.claude/skills/<name>/` | 日常使用 — 安全且自包含 |
| `link` | 创建指向本地存储的符号链接 | Skill 开发 — 对源文件的修改会立即生效 |
可在配置中设置默认模式,也可在安装时单独指定:
```bash
ccsm install my-skill --link
```
## 自建 Skill Marketplace
Marketplace 本质上就是一个包含 `skills.json` 清单的公开 Git 仓库。不需要服务器组件,不需要认证,也不需要 API 密钥 —— CCSM 会直接从 Git 托管平台的 raw 文件端点读取清单。
### Marketplace 仓库结构
```
your-marketplace-repo/
skills.json # 必需:清单文件
README.md # 可选
```
### `skills.json` 清单格式
```json
{
"name": "My Marketplace",
"version": "1.0.0",
"skills": [
{
"name": "my-skill",
"description": "在搜索结果中显示的简短描述",
"repository": "https://github.com/user/skill-repo",
"branch": "main"
},
{
"name": "another-skill",
"description": "位于 monorepo 子目录中的 Skill",
"repository": "https://github.com/user/monorepo",
"branch": "main",
"path": "skills/another-skill"
}
]
}
```
**顶层字段:**
| 字段 | 必需 | 说明 |
| --------- | ---- | ----------------------------- |
| `name` | 是 | Marketplace 的显示名称 |
| `version` | 否 | 用于自行追踪的任意版本号 |
| `skills` | 否 | Skill 条目数组(默认为 `[]`) |
**Skill 条目字段:**
| 字段 | 必需 | 说明 |
| ------------- | ---- | ----------------------------------------------------------- |
| `name` | 是 | Skill 的唯一标识符(用于 `ccsm install <name>`) |
| `description` | 是 | 在 `ccsm search` 输出中显示的一行摘要 |
| `repository` | 是 | Skill 的公开 Git 仓库 URL |
| `branch` | 否 | 安装时使用的 Git 分支(先尝试 `main`,再回退到 `master`) |
| `path` | 否 | 仓库中 Skill 文件所在的子目录 — 支持单个仓库托管多个 Skills |
### 工作原理
1. **清单拉取** — CCSM 根据 Marketplace 仓库 URL 构建 raw 文件 URL,从 `main` 或 `master` 分支获取 `skills.json`。兼容 GitHub、Gitea、GitLab 以及任何提供 raw 文件端点的 Git 托管平台。
2. **Skill 下载** — 当用户安装 Skill 时,CCSM 从 Git 托管平台下载 Skill 仓库的归档文件(ZIP 或 tar.gz),解压后将文件部署到 `~/.claude/skills/<name>/`。
3. **Monorepo 支持** — 如果 Skill 条目指定了 `path`,CCSM 会在解压后进入该子目录再进行部署,从而支持单个仓库托管多个 Skills。
### Skill 仓库的内容
每个 Skill 仓库都是一个标准的 Claude Code Skill 目录。至少需要包含一个 `SKILL.md` 清单文件:
```
my-skill-repo/
SKILL.md # Claude Code Skill 清单
script.py # 辅助文件
templates/
template.txt
```
### 发布步骤
1. 创建一个**公开**的 Git 仓库,在根目录放置符合上述格式的 `skills.json`。
2. 推送上去。
3. 任何人都可以通过 `ccsm marketplace add <url>` 注册使用。
## 数据存储位置
| 路径 | 用途 |
| ------------------------------ | ----------------------------------------------- |
| `~/.claude/skills/` | Claude Code 的 Skills 目录 —— Skills 的部署目标 |
| `~/.config/ccsm/config.json` | CCSM 配置文件(已注册的 Marketplace、默认模式) |
| `~/.config/ccsm/marketplaces/` | 缓存的 Marketplace 清单 |
| `~/.config/ccsm/skills/` | 下载的 Skill 文件(本地存储) |
在 Windows 上,`~/.config/` 对应 `C:\Users\<user>\AppData\Roaming\`。
## 许可证
Claude Code Skills Manager 是采用 MIT 许可证发布的开源软件。
@@ -1,91 +0,0 @@
---
title: Delta Force Guide API
---
## 后端 API 概览
后端是基于 Spring Boot 3 的应用程序,代码组织在基础包 `com.onixbyte.deltaforceguide` 下:
| 包 | 描述 |
| ------------- | --------------------------- |
| `controller` | REST API 端点控制器 |
| `service` | 业务逻辑层 |
| `repository` | MyBatis 数据访问层 |
| `domain` | JPA 实体与领域对象 |
| `config` | Spring 配置(安全、缓存等) |
| `properties` | 配置属性类 |
| `enumeration` | 枚举类型 |
| `shared` | 共享工具类 |
## API 端点
### 认证
| 方法 | 路径 | 描述 | 认证 |
| ------ | ------------------- | ----------------- | ---- |
| `POST` | `/api/auth/login` | 使用凭证登录 | 否 |
| `POST` | `/api/auth/refresh` | 刷新 access token | 否 |
| `GET` | `/api/auth/profile` | 获取当前用户信息 | 是 |
### 枪械
| 方法 | 路径 | 描述 |
| -------- | ------------------- | ------------------------------ |
| `GET` | `/api/firearms` | 枪械列表(分页,可按类型筛选) |
| `GET` | `/api/firearms/:id` | 按 ID 获取枪械详情 |
| `POST` | `/api/firearms` | 创建新枪械 |
| `PUT` | `/api/firearms/:id` | 更新枪械信息 |
| `DELETE` | `/api/firearms/:id` | 删除枪械 |
### 改装
| 方法 | 路径 | 描述 |
| -------- | --------------------------------- | ------------------------------------ |
| `GET` | `/api/modifications` | 改装列表(分页,可按枪械和标签筛选) |
| `GET` | `/api/modifications/:id` | 按 ID 获取改装详情 |
| `POST` | `/api/modifications` | 创建改装 |
| `POST` | `/api/modifications/batch` | 批量创建改装 |
| `PUT` | `/api/modifications/:id` | 更新改装 |
| `DELETE` | `/api/modifications/:id` | 删除改装 |
| `DELETE` | `/api/modifications/batch-delete` | 批量删除改装 |
### 标签
| 方法 | 路径 | 描述 |
| ----- | ----------- | ------------ |
| `GET` | `/api/tags` | 获取所有标签 |
## 前端路由
| 路径 | 布局 | 页面 | 描述 |
| ------------ | -------- | -------- | --------------- |
| `/` | 主布局 | 枪械浏览 | 首页 — 枪械列表 |
| `/firearms` | 主布局 | 枪械浏览 | 枪械详情与搜索 |
| `/mod-codes` | 主布局 | 改装代码 | 改装代码库 |
| `/legal` | 主布局 | 法律信息 | 法律与隐私信息 |
| `/login` | 空白布局 | 登录 | 用户认证页面 |
## 前端状态管理
Redux Toolkit 结合 Redux Persist 管理以下状态:
- **认证状态** — Access token、refresh token、用户信息
- **设置状态** — 用户偏好,持久化至本地存储
## API 客户端
前端使用基于 Axios 封装的共享 `WebClient`,提供以下能力:
- 基础 URL 配置(指向后端 API)
- 通过请求拦截器自动注入 JWT access token
- 在收到 401 响应时自动刷新 token
## 基础设施
| 组件 | 技术 |
| -------- | ---------------------------------------- |
| 数据库 | PostgreSQL(使用 Flyway 进行数据库迁移) |
| 缓存 | Redis(会话和 token 存储) |
| 文件存储 | AWS S3(枪械图片) |
| 容器化 | Docker |
| 部署 | Docker Compose + Nginx |
@@ -1,48 +0,0 @@
---
title: Delta Force Guide
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
Delta Force Guide 是一个全栈 Web 应用,为游戏 Delta Force(三角洲部队)提供可搜索的枪械改装代码库。项目包含两个组件:
- **Web 前端** — 基于 React 的单页应用,用于浏览、筛选和复制改装代码。
- **后端服务** — 基于 Spring Boot 的 REST API,处理数据持久化、身份认证和内容管理。
线上站点位于 **[dfguide.onixbyte.cn](https://dfguide.onixbyte.cn)**。
## 特性
- **枪械浏览** — 浏览精选的游戏内枪械列表及详细属性。
- **改装代码库** — 按武器、模式和标签搜索筛选改装代码。
- **一键复制** — 直接从界面复制改装代码。
- **高效渲染** — 通过窗口虚拟化实现大列表的流畅渲染。
- **身份认证** — 支持用户登录,采用 JWT 认证和验证码校验。
- **后台管理** — 枪械与改装数据完整的增删改查功能。
- **静态友好** — 前端以静态站点部署,通过 API 代理与后端通信。
## 架构
| 组件 | 技术栈 |
| -------- | --------------------------------------------------------------------------------------------------- |
| **前端** | React 19、TypeScript、Tailwind CSS 4、Ant Design 6、React Router 7、Redux Toolkit、TanStack Virtual |
| **后端** | Spring Boot 3、Java 21、PostgreSQL、MyBatis、Flyway、Redis |
| **认证** | JWT(access 和 refresh token)、验证码集成 |
| **存储** | AWS S3(图片上传) |
前端是由 Vite 构建的静态站点,通过 API 与 Spring Boot 后端通信。认证采用 JWT access 和 refresh token 机制,refresh token 通过 Redis 管理。
## 代码仓库
- [delta-force-guide-server](https://github.com/onixbyte/delta-force-guide-server) — Spring Boot 后端
- [delta-force-guide-web](https://github.com/onixbyte/delta-force-guide-web) — React 前端
## 快速链接
- **线上站点:** [dfguide.onixbyte.cn](https://dfguide.onixbyte.cn)
## 许可证
Delta Force Guide 是采用 MIT 许可证发布的开源软件。
@@ -1,73 +0,0 @@
---
title: Helix
---
## 介绍
Helix 是一个全栈企业级应用模板,旨在加速生产级业务应用的开发。它提供了预构建的基础,包含企业应用所需的全部基本功能,使团队能够专注于业务逻辑而非基础设施。
项目由两个组件构成:
- **Helix Server** — 基于 Spring Boot 的后端,提供 REST API、身份认证、权限管理和数据管理。
- **Helix Web** — 基于 React 的单页应用前端,具备现代化的 UI、基于角色的路由和企业级身份集成。
Helix 旨在作为项目的起点使用。Fork 或克隆它,然后在现成的基础上开始构建您自己的业务功能。
## 特性
### 后端(Helix Server)
- **身份认证与授权** — 基于 JWT 的 access token 和 refresh token 流程,配合 Spring Security。支持 Microsoft Entra ID(Azure AD)集成。
- **基于角色的访问控制** — 通过角色、权限和菜单级访问控制实现细粒度权限管理。
- **用户与组织管理** — 用户、部门、职位和角色的完整增删改查。
- **菜单管理** — 层级菜单树,支持按角色配置可见性。
- **资产管理** — 文件上传至 AWS S3,支持 CDN 前缀处理。
- **设置管理** — 类型化的应用配置项持久化至数据库。
- **验证码集成** — 登录和注册流程中的图形验证码校验。
- **Redis 缓存** — 高性能缓存,支持可配置 TTL 和自定义序列化器。
- **数据访问** — Spring Data JPA 与 MyBatis 混合持久化方案。
- **校验** — Bean Validation 分组校验与自定义校验约束。
### 前端(Helix Web)
- **企业级身份认证** — 通过 MSAL 实现 Microsoft Entra ID 登录。可扩展的社交登录提供商架构(Microsoft、Google、GitLab、Slack、Discord、钉钉、飞书、企业微信、邮箱/密码)。
- **仪表盘布局** — 响应式管理后台,可折叠侧边栏,基于角色的菜单渲染。
- **用户管理** — 数据表格支持搜索、分页,添加/编辑对话框,角色分配和手机号校验。
- **角色与菜单管理** — 角色增删改查与权限分配。基于树形的菜单配置。
- **部门与职位** — 组织层级管理界面。
- **路由保护** — 在页面加载前检查认证和授权状态。
- **状态管理** — Redux Toolkit 配合 Redux Persist 实现认证 token 的持久化。
- **HTTP 客户端** — 基于 Axios 封装的请求客户端,自动注入 JWT token 并在 401 时自动刷新。
## 架构
| 层 | 技术栈 |
| ---------- | ---------------------------------------------------------------------------------------------- |
| **前端** | React 19、TypeScript、Tailwind CSS 4、Ant Design 6、React Router 7、Redux Toolkit、Axios、MSAL |
| **后端** | Spring Boot 3.5、Java 21、Spring Security、MyBatis、Spring Data JPA |
| **数据库** | PostgreSQL(使用 Flyway 迁移) |
| **缓存** | Redis(会话和 token 存储) |
| **存储** | AWS S3(资源上传) |
前端通过 RESTful JSON API 与后端通信。认证流程使用短有效期的 JWT access token,refresh token 存储在 Redis 中。Microsoft Entra ID 集成在前端通过 MSAL 处理,并在后端进行校验。
## 代码仓库
Helix 采用单仓库(monorepo)结构,前后端都在同一个仓库中。
- [server/](https://github.com/onixbyte-opensource/helix/tree/main/server) — Spring Boot 后端
- [web/](https://github.com/onixbyte-opensource/helix/tree/main/web) — React 前端
## 快速开始
Helix 是一个模板项目。启动新项目的步骤:
1. Fork 或克隆 [Helix 仓库](https://github.com/onixbyte-opensource/helix)。
2. 在后端配置您的数据库、Redis 和 S3 设置。
3. 在前端配置您的认证提供商和 API 基础地址。
4. 启动后端:`./gradlew bootRun`
5. 启动前端:`pnpm install && pnpm dev`
## 许可证
Helix 是采用 MIT 许可证发布的开源软件。
-25
View File
@@ -1,25 +0,0 @@
---
title: 开源生态
---
由 OnixByte 开发的开源项目。
## 库和工具包
- **[OnixByte Toolbox](/opensource-projects/onixbyte-toolbox)** — 面向 Java 应用的开发工具包:AES、哈希、Base64、集合工具、密钥加载器、统计计算器、标识符生成器和泛型元组。
- **[regions4j](/opensource-projects/regions4j)** — 基于 ISO 3166-1 的类型安全 Java 枚举地区元数据,支持 O(1) 查找、E.164 拨号前缀和区域标签。
- **[Captcha](/opensource-projects/captcha)** — 现代化的 Java 验证码生成库,支持水波纹、鱼眼和阴影扭曲效果。
- **[Calendar Toolbox](/opensource-projects/calendar-toolbox)** — 遵循 iCalendar 规范(RFC 5545)创建 `.ics` 文件的 Java 库。
## 全栈应用
- **[Helix](/opensource-projects/helix)** — 企业级应用模板,后端使用 Spring Boot,前端使用 React,集成 JWT 认证、RBAC 权限管理、用户与组织管理以及 Microsoft Entra ID 集成。
- **[Delta Force Guide](/opensource-projects/delta-force-guide)** — 为游戏《三角洲行动》提供可搜索枪械改装代码库的 Web 应用,包含 React 单页应用和 Spring Boot REST API。
## 开发者工具
- **[Claude Code Skills Manager](/opensource-projects/claude-code-skill-manager)** — 用于从基于 Git 的 Marketplace 仓库中发现、安装和管理 Claude Code Skills 的 CLI 工具。
## Vite 插件
- **[Port Checker](/opensource-projects/vite-plugins/port-checker)** — 当 Vite 开发服务器监听浏览器限制的端口时发出警告,避免无响应的连接失败。
@@ -1,209 +0,0 @@
---
title: OnixByte Toolbox API
---
## 包结构概览
所有模块均在 `com.onixbyte` group ID 下发布。每个模块有自己独立的基础包:
| 模块 | Maven Artifact | 基础包 |
| ------------------ | -------------------- | -------------------------------- |
| Common Toolbox | `common-toolbox` | `com.onixbyte.common` |
| Crypto Toolbox | `crypto-toolbox` | `com.onixbyte.crypto` |
| Math Toolbox | `math-toolbox` | `com.onixbyte.math` |
| Tuple | `tuple` | `com.onixbyte.tuple` |
| Identity Generator | `identity-generator` | `com.onixbyte.identitygenerator` |
---
## Common Toolbox
### 适配器
`ObjectMapAdapter` — 对象与 Map 之间的转换器。适用于需要键值表示的场景,如序列化或数据转换。
```java
import com.onixbyte.common.adapter.ObjectMapAdapter;
```
### 工具类
| 类 | 描述 |
| ---------------- | ------------------------------------------- |
| `AesUtil` | AES 对称加密/解密,支持密钥生成 |
| `Base64Util` | Base64 编解码,支持多种字符集 |
| `BoolUtil` | 布尔值解析与转换工具 |
| `BranchUtil` | 条件分支辅助,支持函数式链式调用 |
| `CollectionUtil` | 集合操作 — 分组、分区、合并 |
| `HashUtil` | 哈希工具,支持 MD5、SHA-1、SHA-256、SHA-512 |
| `MapUtil` | Map 构建、合并和转换辅助 |
| `RangeUtil` | 数值范围操作,支持边界处理 |
**AesUtil 示例:**
```java
var key = AesUtil.generateKey();
var encrypted = AesUtil.encrypt("hello world", key);
var decrypted = AesUtil.decrypt(encrypted, key);
```
**HashUtil 示例:**
```java
var md5 = HashUtil.md5("hello world");
var sha256 = HashUtil.sha256("hello world");
var fileSha = HashUtil.sha256(new File("data.bin"));
```
**CollectionUtil 示例:**
```java
var partitioned = CollectionUtil.partition(list, 100);
var grouped = CollectionUtil.groupBy(users, User::getDepartment);
```
---
## Crypto Toolbox
提供了加载 PEM 格式密钥的简洁抽象。支持 RSA 和 ECDSA 算法。
### 密钥加载器
| 类 | 描述 |
| --------------------- | ---------------------------- |
| `RSAPrivateKeyLoader` | 从 PEM 字符串加载 RSA 私钥 |
| `RSAPublicKeyLoader` | 从 PEM 字符串加载 RSA 公钥 |
| `ECPrivateKeyLoader` | 从 PEM 字符串加载 ECDSA 私钥 |
| `ECPublicKeyLoader` | 从 PEM 字符串加载 ECDSA 公钥 |
所有密钥加载器实现 `PrivateKeyLoader` 或 `PublicKeyLoader` 接口。
**RSA 示例:**
```java
var privateKey = new RSAPrivateKeyLoader().load(pemString);
var publicKey = new RSAPublicKeyLoader().load(pemString);
```
**ECDSA 示例:**
```java
var ecPrivateKey = new ECPrivateKeyLoader().load(pemString);
var ecPublicKey = new ECPublicKeyLoader().load(pemString);
```
### 工具类
`CryptoUtil` — 提供便捷的数字签名生成和验证方法,以及通用密码学辅助功能。
### 异常
| 类 | 描述 |
| --------------------- | -------------------------- |
| `KeyLoadingException` | PEM 格式密钥解析失败时抛出 |
---
## Math Toolbox
用于处理数值数据集的统计计算工具。
### 核心类
| 类 | 描述 |
| ---------------------- | ------------------------------------------------------------- |
| `Calculator` | 统计计算器 — 均值、中位数、方差、标准差、总和、最小值、最大值 |
| `PercentileCalculator` | 百分位数计算器,支持可配置的插值策略 |
**Calculator 示例:**
```java
var stats = new Calculator(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0));
double mean = stats.getMean();
double median = stats.getMedian();
double stdDev = stats.getStandardDeviation();
```
**PercentileCalculator 示例:**
```java
var calculator = new PercentileCalculator(data);
double p95 = calculator.calculate(95);
double p99 = calculator.calculate(99);
```
### 数据模型
| 类 | 描述 |
| ---------------- | --------------------------------------------- |
| `QuartileBounds` | 包含 Q1、中位数(Q2)、Q3 值及 IQR 和须线边界 |
---
## Tuple
轻量级泛型元组类型,适用于需要返回多个值但无需定义专用类的场景。
### 类
| 类 | 类型参数 | 可变性 |
| -------------------------- | -------- | ------ |
| `Tuple<A, B>` | 两个元素 | 可变 |
| `ImmutableTuple<A, B>` | 两个元素 | 不可变 |
| `Triple<A, B, C>` | 三个元素 | 可变 |
| `ImmutableTriple<A, B, C>` | 三个元素 | 不可变 |
**示例:**
```java
var pair = Tuple.of("key", 42);
var triple = Triple.of("id", "name", 100);
var immutable = ImmutableTuple.of("constant", true);
// 访问器
String first = pair.getFirst();
Integer second = pair.getSecond();
// 三元组访问器
String a = triple.getFirst();
String b = triple.getSecond();
Integer c = triple.getThird();
```
---
## Identity Generator
生成适用于数据库主键或分布式 ID 的唯一标识符。
### 接口
`IdentityGenerator<T>` — 生成类型为 `T` 的标识符。
### 实现
| 类 | 描述 |
| ---------------------------- | --------------------------------------------- |
| `SequentialUuidGenerator` | 生成时间排序的顺序 UUID(优化数据库索引性能) |
| `SnowflakeIdentityGenerator` | 雪花算法分布式唯一 ID 生成 |
**顺序 UUID:**
```java
var generator = new SequentialUuidGenerator();
UUID id = generator.nextId(); // 时间排序 UUID,对 B-tree 友好
```
**雪花:**
```java
var generator = new SnowflakeIdentityGenerator(workerId, datacenterId);
long id = generator.nextId(); // 64 位雪花 ID
```
### 异常
| 类 | 描述 |
| ----------------- | ------------------------------------------ |
| `TimingException` | 系统时钟回拨时抛出,保证 ID 唯一性不受影响 |
@@ -1,54 +0,0 @@
---
title: OnixByte Toolbox
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
OnixByte Toolbox 是一个面向 Java 应用的开发工具包,提供了一系列便捷、可复用的工具,帮助开发者高效编写代码。它由 OnixByte 设计和维护,采用 monorepo 架构,包含多个专用库,每个库解决一个特定的开发需求 — 从通用工具类和密码学,到数学计算和标识符生成。
该工具包已发布到 Maven Central,使用 `com.onixbyte` 作为 group ID。每个模块独立版本管理,可在任何 Java 17+ 项目中使用。
## 模块
| 模块 | Artifact ID | 描述 |
| ------------------ | -------------------- | ----------------------------------------------------- |
| Common Toolbox | `common-toolbox` | 日常工具类 — AES 加密、哈希、Base64、集合、分支、范围 |
| Crypto Toolbox | `crypto-toolbox` | RSA 和 ECDSA 算法的公私钥加载 |
| Math Toolbox | `math-toolbox` | 统计计算、百分位数分析、四分位数计算 |
| Tuple | `tuple` | 泛型二元组和三元组,含不可变版本 |
| Identity Generator | `identity-generator` | 雪花算法和顺序 UUID 唯一标识符生成 |
| Version Catalogue | `version-catalogue` | OnixByte 项目的集中式 Gradle 版本目录 |
## 安装
在构建配置中添加所需模块的依赖:
<Tabs>
<Tab label="Gradle with Kotlin DSL">
```kotlin title="build.gradle.kts"
dependencies {
implementation("com.onixbyte:$artefactId:$version")
}
```
</Tab>
<Tab label="Maven">
```xml title="pom.xml"
<dependency>
<groupId>com.onixbyte</groupId>
<artifactId>${artefactId}</artifactId>
<version>${version}</version>
</dependency>
```
</Tab>
</Tabs>
## 要求
- Java 17 或更高版本
## 许可证
OnixByte Toolbox 是采用 MIT 许可证发布的开源软件。
@@ -1,104 +0,0 @@
---
title: Regions for Java
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
**regions4j** 是一个轻量级、类型安全的 Java 库,基于 ISO 3166-1 标准提供全面的世界地区元数据。它旨在简化 Java 企业应用中的国际化(i18n)、电信路由和地区数据管理工作。
该库将每个 ISO 3166-1 国家和地区建模为枚举常量,使您在引用地区时获得编译期安全性和 IDE 自动补全支持。凭借内置的 O(1) 查找、零外部依赖和 Java 8 兼容性,它适用于任何规模的项目 — 从微服务到整体式企业系统。
## 特性
- **ISO 3166-1 合规** — 完整支持所有已分配国家和地区的 Alpha-2(`abbreviation`)和 Alpha-3(`code`)格式。
- **电信元数据** — 包含每个地区的 E.164 国际拨号前缀。
- **区域支持** — 每个地区提供默认的区域标签(如 `zh_HK`、`en_US`、`en_GB` 等),辅助 i18n 工作流。
- **高性能** — 线程安全、不可变的内部哈希映射提供常量时间的缩写或代码查找。
- **零依赖** — 纯 Java 库,无任何外部依赖。
- **日历化版本管理** — 版本号采用 `YYYY.MM` 格式(如 `2025.12`),以透明地反映底层 ISO 数据的新鲜度,而非语义化版本号。
## 核心数据模型
每个地区暴露四个属性:
| 属性 | 类型 | 描述 | 示例 |
| -------------- | ------ | ----------------------- | ---------------------------------- |
| `abbreviation` | String | ISO 3166-1 alpha-2 缩写 | `HK`、`US`、`GB`、`RU` |
| `code` | String | ISO 3166-1 alpha-3 代码 | `HKG`、`USA`、`GBR`、`RUS` |
| `callingCode` | String | E.164 国际拨号前缀 | `852`、`1`、`44`、`7` |
| `locale` | String | 默认语言和地区标签 | `zh_HK`、`en_US`、`en_GB`、`ru_RU` |
## 安装
<Tabs>
<Tab label="Gradle with Kotlin DSL">
```kotlin title="build.gradle.kts"
dependencies {
implementation("com.onixbyte:regions4j:$version")
}
```
</Tab>
<Tab label="Maven">
```xml title="pom.xml"
<dependency>
<groupId>com.onixbyte</groupId>
<artifactId>regions4j</artifactId>
<version>${version}</version>
</dependency>
```
</Tab>
</Tabs>
> **版本说明:** 该库使用日历化版本管理(`YYYY.MM`)而非语义化版本号。版本字符串如 `2025.12` 可以直观地告诉您地区数据反映的是 2025 年 12 月的 ISO 3166-1 状态。
## 使用
### 直接访问
通过枚举常量直接引用任何地区:
```java
Region uk = Region.UNITED_KINGDOM;
uk.getAbbreviation(); // "GB"
uk.getCode(); // "GBR"
uk.getDialCode(); // "44"
uk.getLocale(); // "en_GB"
```
### 高效查找
从字符串输入中查找地区 — 非常适合处理 Web 请求、API 负载或数据库记录:
```java
// 按 Alpha-2 缩写查找(区分大小写)
Region region = Region.fromAbbreviation("HK");
// 按 Alpha-3 代码查找(不区分大小写)
Region region = Region.fromCode("HKG");
```
### Java Locale 集成
将地区的 locale 字符串转换为 `java.util.Locale` 以配合标准 Java i18n API 使用:
```java
Region region = Region.CHINA;
Locale javaLocale = Locale.forLanguageTag(region.getLocale().replace("_", "-"));
javaLocale.getDisplayCountry(Locale.UK); // "China"
```
## 性能
首次访问时,库会将所有地区初始化到不可变的 `HashMap` 结构中。后续通过 `fromAbbreviation()` 和 `fromCode()` 进行的查找均为线程安全且以常量时间运行 — 适用于高吞吐量的请求路径。
## 要求
- Java 8 或更高版本
## 许可证
regions4j 是采用 MIT 许可证发布的开源软件。
@@ -1,11 +0,0 @@
---
title: Vite 插件
---
## 概览
由 OnixByte 开发和维护的 Vite 插件合集。每个插件旨在解决 Vite 开发工作流中的特定问题 — 轻量级、零依赖、即插即用。
## 可用插件
- **[Port Checker](/opensource-projects/vite-plugins/port-checker)** — 当开发服务器监听浏览器限制的端口(依据 Fetch 规范)时发出警告,帮助您避免无响应的连接失败。
@@ -1,59 +0,0 @@
---
title: Port Checker
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
**vite-plugin-port-checker** 是一个轻量级的 Vite 插件,可在开发服务器监听被常见浏览器屏蔽的端口时发出警告。Chrome 和 Firefox 等浏览器维护了一份[受限制端口列表](https://fetch.spec.whatwg.org/#port-blocking) — 这些端口通常与 SMTP、SSH 和 DNS 等知名协议关联 — 并拒绝连接这些端口。如果不小心将开发服务器运行在这些端口上,会导致令人困惑的"连接被拒绝"或"已被屏蔽"错误,且没有任何明确的解释。
该插件会在启动时检测到这种情况,并向控制台打印一条清晰且带有颜色标记的警告,建议您修改 Vite 配置中的 `server.port` 设置。
## 特性
- **自动检测** — 对照完整的 Fetch 规范端口屏蔽列表(68 个受限端口)检查开发服务器端口。
- **清晰的警告** — 黄色高亮控制台输出,并提供可操作的指导。
- **零配置** — 开箱即用,无需设置任何选项。
- **轻量级** — 单文件插件,除 Vite 本身外无任何依赖。
## 安装
<Tabs>
<Tab label="npm">```bash npm install vite-plugin-port-checker -D ```</Tab>
<Tab label="pnpm">```bash pnpm add vite-plugin-port-checker -D ```</Tab>
</Tabs>
## 使用
在 `vite.config.ts` 中添加插件:
```ts title="vite.config.ts"
import { defineConfig } from "vite"
import checkRestrictedPort from "vite-plugin-port-checker"
export default defineConfig({
plugins: [checkRestrictedPort()],
})
```
当开发服务器启动在受限端口(如 25、22 或 53 端口)时,您将看到:
```
[Warning] The current listening port 25 is categorised as a restricted port
by most browsers. This may prevent you from accessing the application.
Please consider changing the port in your 'vite.config.ts' or 'vite.config.js'
via 'server.port'.
```
## 工作原理
该插件通过钩入 Vite 的 `configureServer` 生命周期工作。当 HTTP 服务器触发 `listening` 事件后,插件检查绑定的端口号,并与 [Fetch 规范](https://fetch.spec.whatwg.org/#port-blocking)中定义的 68 个受限端口进行比对。如果匹配,则通过黄色的 `console.warn` 消息提醒开发者。
## 要求
- Vite 3.0.0 或更高版本
## 许可证
vite-plugin-port-checker 是采用 MIT 许可证发布的开源软件。