refactor: re-organise doc structure
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: 炒菜调料指南
|
||||
tags:
|
||||
- cooking
|
||||
- cheatsheet
|
||||
author:
|
||||
name: Zihlu Wang
|
||||
email: real@zihluwang.me
|
||||
---
|
||||
|
||||
## 调料投放时机
|
||||
|
||||
| 调料类型 | 放调料时机 | 适用菜品 | 作用 |
|
||||
|--------------|----------|----------------|---------------------|
|
||||
| **盐** | 最后放 | 菜叶子 | 防止菜叶变软出水,保证口感干爽脆嫩 |
|
||||
| | 中间放 | 土豆丝、豆角、蒜苔等 | 更好入味 |
|
||||
| **味精、生抽、蚝油** | 偏后放 | 各类需要增鲜提香的菜品 | 增鲜提香,避免高温破坏鲜味和香味 |
|
||||
| **料酒** | 和食材一起放 | 需要去腥的食材 | 焯水时去腥效果更好 |
|
||||
| | 锅温最高的时候放 | 需要炒菜去腥的食材 | 炒菜时高温促进酒气挥发,炝锅去腥效果好 |
|
||||
| **醋** | 提前放 | 酸辣土豆丝、酸辣大白菜 | 使土豆丝变脆更入味 |
|
||||
| | 出锅前锅边醋 | 辣椒炒肉等需要猛火爆炒类菜品 | 大火炝锅增香 |
|
||||
| **老抽** | 炒半熟后放 | 需要上色的菜品 | 达到最佳上色效果 |
|
||||
|
||||
## 快速参考
|
||||
|
||||
| 调料 | 投放时机 | 作用 | 示例 |
|
||||
|-----------------|----------------|------------------------|------------------------|
|
||||
| **盐** | 出锅前 10 - 15 秒放 | 避免提前放盐导致菜叶出水、变软,保持脆嫩口感 | 炒生菜、菠菜时,关火前撒盐翻炒均匀 |
|
||||
| **蒜末 / 姜末** | 热油后先放 | 爆香提味,激发香味 | 油热后下蒜末炝锅,再放青菜翻炒 |
|
||||
| **生抽 / 蚝油**(可选) | 盐之后放 | 增鲜提味,少量即可,避免盖过蔬菜原味 | 炒上海青时,加盐后滴 1 - 2 滴生抽翻炒 |
|
||||
| **食用油** | 热锅热油 | 油热后快速炒,锁住水分 | 大火快炒时,油稍多一点,油温要高 |
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Docker 部署规范
|
||||
tags:
|
||||
- docker
|
||||
- deployment
|
||||
- standards
|
||||
- best-practice
|
||||
author:
|
||||
name: Zihlu Wang
|
||||
email: real@zihluwang.me
|
||||
---
|
||||
|
||||
- **Dockerfiles**: 为应用程序提供 `Dockerfile`,实现容器化部署。
|
||||
- **轻量级镜像**: 通过使用适当的基础镜像和多阶段构建,力求实现轻量级 Docker 镜像。
|
||||
- **配置**: 确保环境特定配置(例如,数据库连接字符串、外部服务 URL)通过注入到 Docker 容器中的环境变量进行管理。
|
||||
- **日志记录**: 配置容器化日志记录,将输出发送到 `stdout` 和 `stderr`,以便日志聚合系统轻松收集。
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
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`
|
||||
};
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
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 秒想一想:这句话能不能说得更像一个做决定的人?
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
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,它也连不上网的。
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
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 |
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
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) 的组件。
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
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>
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: 应用开发通用规范
|
||||
tags:
|
||||
- standards
|
||||
- best-practice
|
||||
- engineering
|
||||
author:
|
||||
name: Zihlu Wang
|
||||
email: real@zihluwang.me
|
||||
---
|
||||
|
||||
## 引言
|
||||
|
||||
本文件旨在概述应用程序开发的编码标准和最佳实践。遵守这些指导方针可确保所有项目的代码质量、可维护性和一致性。
|
||||
|
||||
## 通用原则
|
||||
|
||||
- **清晰性与可读性**: 代码必须易于阅读和理解。优先选择清晰、自文档化的代码,而非巧妙、简洁但晦涩的解决方案。
|
||||
- **一致性**: 在整个项目中保持一致的编码风格、命名约定和架构模式。
|
||||
- **模块化**: 设计组件时应实现松耦合和高内聚,以促进重用性和更简单的测试。
|
||||
- **可测试性**: 编写本质上可测试的代码。
|
||||
- **安全先行设计**: 在开发的每个阶段都将安全考虑融入其中。
|
||||
- **性能意识**: 对关键代码段和 API 端点考虑其性能影响。
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
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行代码,你应该考虑审查整个文件,并检查新添加的内容。它们是否会降低现有代码的质量?它们是否会导致现有功能成为重构的候选对象?
|
||||
|
||||
如果不在函数/方法或类的背景下审查添加的代码,则随着时间的流逝,你将会得到一个面临无法维护、纠缠不清、不易于测试等问题的类,而且这个类很难扩展或重构。
|
||||
|
||||
请记住,即便是微不足道的改进,随着时间的推移,也可能导致产品出现缺陷,同样,即便是轻微的代码降级或技术负债也可能在日积月累下导致产品难以维护和扩展。
|
||||
|
||||
## 在代码审查期间认同和鼓励出色的工作
|
||||
|
||||
如果看到出色的代码变更,请别忘了大力表扬和鼓励作者。代码审查的目的不仅仅是发现错误,而且还应该鼓励和指导开发人员出色的工作。
|
||||
|
||||
## 在代码审查期间应保持谨慎、尊重、友善和思路清晰
|
||||
|
||||
在代码审查期间,你应该保持友善、思路清晰、有礼貌和尊重别人,这一点至关重要,同时也要给予作者清晰的反馈和积极的帮助。在审查代码时,你需要做到对事不对人,即对代码做出评论,而非开发人员。
|
||||
|
||||
## 详细解释代码审查的意见,并注意尺度
|
||||
|
||||
每当代码审查意见提出替代方案或指出某些问题时,重要的是你需要解释其中的原因,并根据个人的知识和经验提供示例,以帮助开发人员理解为何你的建议能够提升代码质量。
|
||||
|
||||
在建议修改或变更代码时,你需要在如何指导作者修改代码方面找到适当的平衡。例如,我更喜欢指导、解释、提示或建议,而不是整个解决方案。
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: 博客
|
||||
---
|
||||
|
||||
# 博客
|
||||
|
||||
欢迎来到 OnixByte 博客,这里汇集了日常工程实践中沉淀的实用指南、速查手册和开发规范。
|
||||
|
||||
## 话题分类
|
||||
|
||||
- **前端** — React 模式、TailwindCSS 技巧、ECMAScript 语法指南、浏览器疑难杂症
|
||||
- **后端与数据库** — Java 开发、MyBatis、PostgreSQL、MySQL JSON
|
||||
- **DevOps 与基础设施** — Docker、GitLab CI/CD、LDAP、MinIO、macOS 故障排查
|
||||
- **工程规范** — 代码评审、版本控制、通用与语言专项编码标准
|
||||
- **工具与效率** — 邮件礼仪、字号换算、数据分析方法
|
||||
|
||||
侧边栏浏览或直接翻阅下方文章——每篇内容独立成文,即读即用。
|
||||
@@ -0,0 +1,203 @@
|
||||
---
|
||||
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);
|
||||
```
|
||||
@@ -0,0 +1,238 @@
|
||||
---
|
||||
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 攻击。
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
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 服务器是否持续下发不期望的主机名。
|
||||
@@ -0,0 +1,200 @@
|
||||
---
|
||||
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**
|
||||
|
||||
这种方式虽然需要命令行操作,但提供了更强大和灵活的管理能力,特别适合自动化部署和脚本化管理。
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
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" }
|
||||
```
|
||||
@@ -0,0 +1,212 @@
|
||||
---
|
||||
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` 分隔以规避解析异常。
|
||||
|
||||
### 词库编译与部署
|
||||
|
||||
:::tip
|
||||
对于使用 Windows 进行编译 `xdb` 二进制词典文件的用户,可以前往 OnixByte 的 [GitHub](https://github.com/onixbyte/scws/releases/tag/1.2.3) 或 [GitLab](https://git.onixbyte.cn/onixbyte/scws/-/releases/1.2.3) 页面下载使用 MingW 预编译好的,适用于 Windows 的原生 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` 激活。
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
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。
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
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 端口的通信。
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
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` 相似的查询性能和灵活性。
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
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` 是常用的选择,因为它们能为每个哈希值生成唯一且易于辨识的图片。
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
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`)。
|
||||
Reference in New Issue
Block a user