程序开发之道

一个专注技术的程序员

本文是「AI Agent 开发系列」的第一篇,旨在帮有后端开发经验的读者快速建立 Agent 技术全景,理解核心概念和主流框架,为后续的实战开发打下基础。

什么是 AI Agent?

AI Agent(智能体)不是聊天机器人的升级版——它是一种范式转变。传统聊天机器人:你问,它答。AI Agent:你给一个目标,它自主规划、调用工具、执行任务、根据反馈调整,全程不需要你逐步引导。

举一个直观的例子:

场景 传统 ChatBot AI Agent
查天气 “今天北京天气怎么样?” → 回答 “帮我安排明天北京的户外活动” → 查天气 → 搜景点 → 规划路线 → 给出完整方案
写代码 “帮我写一个排序函数” → 返回代码 “帮我实现一个用户管理系统” → 分析需求 → 选技术栈 → 写代码 → 跑测试 → 修复 bug → 部署
数据分析 “这个表的数据汇总一下” → 返回统计 “分析这个季度的销售数据,找出增长点和问题” → 读数据 → 多维度分析 → 生成图表 → 写报告

核心差异在于:Agent 具备自主决策与执行能力,而不仅仅是文本生成。

Agent 的五大核心模块

一个成熟的 AI Agent 由以下五个模块组成:

1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────────┐
│ AI Agent 架构 │
├─────────┬─────────┬──────────┬──────────┤
│ 规划 │ 记忆 │ 工具 │ 行动 │
│ Planning │ Memory │ Tools │ Action │
├─────────┴─────────┴──────────┴──────────┤
│ 大语言模型 (LLM) │
│ 大脑 / 推理引擎 │
└─────────────────────────────────────────┘

1. 大语言模型 — 大脑

LLM 是 Agent 的推理引擎,负责理解任务、拆解步骤、决策下一步做什么。2026 年主流选择:

  • Qwen 系列(通义千问):Qwen3.7-Plus / Max,阿里云百炼首选
  • GPT-4o / GPT-5:OpenAI 生态
  • Claude Opus 4.x:Anthropic 推理能力突出
  • DeepSeek:开源高性能模型

2. 规划(Planning)

Agent 收到任务后,不是直接执行,而是先做规划:

  • 任务分解:将复杂目标拆解为可执行的子任务
  • 路径选择:判断用哪种方式完成(直接计算?调用工具?查知识库?)
  • 动态调整:执行过程中根据中间结果修正计划

经典的 ReAct(Reasoning + Acting)模式就是规划的核心实现:思考 → 行动 → 观察 → 再思考,循环直到任务完成。

3. 记忆(Memory)

Agent 需要”记住”东西,分为两类:

  • 短期记忆:当前对话上下文,这次的用户需求、中间结果
  • 长期记忆:跨会话的信息,用户偏好、历史决策、知识积累

例如百炼的 Memory 服务,支持将对话中的重要信息持久化存储,下次对话时自动检索关联记忆。

4. 工具(Tools)

让 Agent 能”动手”的关键。常见工具类型:

工具类型 说明 示例
API 调用 调用外部服务 天气接口、股票数据、数据库查询
代码执行 运行代码片段 Python 沙箱执行数据分析
知识库检索 搜索私有文档 RAG 检索企业知识库
网页搜索 获取实时信息 搜索引擎 API
MCP 服务 标准化工具协议 文件系统、数据库、第三方服务

MCP(Model Context Protocol) 是 Anthropic 提出的标准化工具协议,阿里云百炼等平台已支持,让 Agent 能像 USB 一样”即插即用”各种工具。

5. 行动(Action)

Agent 的最终产出:一段文字回复、一个 API 调用结果、一份生成的报告、一次数据库操作等。

主流开发框架(2026)

1
2
3
4
5
6
7
8
9
10
11
12
13
graph TD
A[AI Agent 开发框架] --> B[轻量级/入门]
A --> C[工作流编排]
A --> D[多 Agent 协作]
A --> E[企业级平台]
B --> B1[OpenAI Agents SDK]
B --> B2[ReAct 手写]
C --> C1[LangGraph]
C --> C2[Spring AI Alibaba]
D --> D1[AutoGen]
D --> D2[CrewAI]
E --> E1[阿里云百炼]
E --> E2[Coze / 扣子]

入门推荐:ReAct 手写 → LangGraph

对于有后端的开发者,推荐的学习路径:

  1. 手写一个 ReAct Agent(50 行 Python)→ 理解 Agent 的本质
  2. 使用 LangGraph 做复杂工作流 → 掌握状态管理和条件路由
  3. 接入百炼平台 → 零代码构建 + 生产部署

框架对比

框架 定位 适合场景 学习曲线
OpenAI Agents SDK 轻量级 Agent 开发 快速原型、单 Agent
LangGraph 有状态工作流编排 复杂流程、多步骤推理 ⭐⭐⭐
AutoGen 多 Agent 对话协作 多角色协作、代码生成 ⭐⭐⭐
CrewAI 角色化多 Agent 模拟团队协作 ⭐⭐
阿里云百炼 企业级一站式平台 生产级应用、零代码构建

一个最简单的 Agent 示例

用 Python 手写一个 ReAct Agent,不到 50 行代码就能理解 Agent 的核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import re

# 模拟 LLM 调用(实际开发中替换为 API 调用)
def llm(prompt):
# 这里简化处理:用正则匹配工具调用意图
if "天气" in prompt:
return "Thought: 我需要查询天气\nAction: get_weather[北京]\n"
elif "get_weather" in prompt:
return "Thought: 已获取天气数据\nAction: FINISH[北京今天晴,22°C,适合户外活动]\n"
return "Action: FINISH[无法处理该请求]\n"

def get_weather(city):
# 模拟天气查询工具
return f"{city}今天晴,22°C,适合户外活动"

def agent_loop(query):
"""Agent 主循环:ReAct 模式"""
prompt = f"用户问题: {query}\n"
max_steps = 5

for _ in range(max_steps):
response = llm(prompt)
print(f"[Agent] {response.strip()}")

# 解析 Action
match = re.search(r"Action: (\w+)\[(.*?)\]", response)
if not match:
break

action, args = match.group(1), match.group(2)

if action == "FINISH":
return args

if action == "get_weather":
result = get_weather(args)
prompt += f"Observation: {result}\n"

return "任务未完成"

# 运行
result = agent_loop("帮我安排明天北京户外活动")
print(f"\n最终结果: {result}")

输出:

1
2
3
4
5
6
[Agent] Thought: 我需要查询天气
Action: get_weather[北京]
[Agent] Thought: 已获取天气数据
Action: FINISH[北京今天晴,22°C,适合户外活动]

最终结果: 北京今天晴,22°C,适合户外活动

这就是 ReAct 的精髓:Thought → Action → Observation → Thought → Action → FINISH,一个循环直到任务完成。

百炼 Agent 开发快速上手

如果你用阿里云百炼平台,构建 Agent 更加简单:

三种开发模式

模式 面向人群 说明
零代码 业务人员 拖拽配置,选择模型 + 知识库 + 工具即可
低代码/工作流 技术产品 可视化编排工作流,定义条件分支和循环
API/SDK 开发者 代码调用,完全控制 Agent 行为

核心能力

  • 知识库:上传 PDF/Word/网页,Agent 自动检索回答
  • MCP 服务:一键接入标准化工具,无需管理运维
  • Memory:跨会话记忆,Agent 记住用户偏好和历史
  • Skill:可复用的能力模块,类似”Agent 的技能包”
  • Agent 2.0:自主规划 + 工具调用,解决复杂多步骤任务

系列文章规划

标题 内容
1 从概念到实践 ← 本文 Agent 核心概念、主流框架、简单示例
2 ReAct 与规划机制详解 深入 ReAct 原理、Prompt 工程、规划策略
3 工具调用与 MCP 协议 工具注册、MCP 标准化、自定义工具开发
4 Agent 记忆系统设计 短期/长期记忆、Memory 服务、向量检索
5 LangGraph 实战 有状态工作流、条件路由、多步骤编排
6 百炼 Agent 生产实践 企业级部署、性能优化、线上踩坑

下一篇预告:深入 ReAct 模式的 Prompt 工程和规划策略,手写一个完整的任务规划 Agent。

如果你也在做 Agent 开发,欢迎在评论区交流 💬

作为 JavaScript/Node.js 开发者,发布自己的 NPM 包是技术成长的重要一步。本文将从零开始,详细介绍如何开发、打包、测试并发布一个 NPM 包。

为什么需要发布 NPM 包?

  • 代码复用:将常用工具封装成包,多项目共享
  • 开源贡献:分享你的解决方案,帮助其他开发者
  • 技术积累:规范化代码组织,提升工程质量
  • 简历加分:维护公开项目,展示技术能力

目录结构规范

一个标准的 NPM 包项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
my-package/
├── src/ # 源代码目录
│ ├── index.js # 主入口文件
│ └── utils.js # 辅助模块
├── dist/ # 构建产物(可选)
├── test/ # 测试文件
├── package.json # 包配置文件
├── README.md # 说明文档
├── LICENSE # 许可证文件
├── .gitignore # Git忽略配置
├── .npmignore # NPM忽略配置(可选)
└── CHANGELOG.md # 变更日志(推荐)

package.json 配置详解

这是包的核心配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"name": "my-awesome-package",
"version": "1.0.0",
"description": "一个实用的工具库",
"main": "src/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": [
"src",
"dist"
],
"scripts": {
"test": "jest",
"build": "rollup -c",
"prepublishOnly": "npm run build && npm run test"
},
"keywords": [
"utility",
"tools",
"helper"
],
"author": "FoleyDang",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/foleydang/my-package.git"
},
"bugs": {
"url": "https://github.com/foleydang/my-package/issues"
},
"homepage": "https://github.com/foleydang/my-package#readme",
"dependencies": {},
"devDependencies": {
"jest": "^29.0.0",
"rollup": "^3.0.0"
},
"engines": {
"node": ">=14.0.0"
}
}

关键字段说明

字段 说明
name 包名,必须唯一,建议使用小写+连字符
version 版本号,遵循 SemVer 规范
main CommonJS 入口
module ES Module 入口
types TypeScript 类型声明文件
files 发布时包含的文件/目录
exports 现代包导出配置(推荐)

exports 配置(推荐)

现代 NPM 包推荐使用 exports 字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./src/index.js",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.esm.js",
"require": "./src/utils.js"
}
}
}

版本号规范(SemVer)

版本号格式:主版本.次版本.补丁版本(如 1.2.3

  • 主版本(Major):不兼容的 API 变化
  • 次版本(Minor):向后兼容的功能新增
  • 补丁版本(Patch):向后兼容的问题修复

常用命令:

1
2
3
4
5
6
7
8
9
10
11
# 补丁版本(1.0.0 -> 1.0.1)
npm version patch

# 次版本(1.0.0 -> 1.1.0)
npm version minor

# 主版本(1.0.0 -> 2.0.0)
npm version major

# 预发布版本
npm version prerelease --preid=beta # 1.0.0 -> 1.0.1-beta.0

包代码开发

入口文件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// src/index.js

/**
* 格式化日期
* @param {Date|string|number} date 日期对象/字符串/时间戳
* @param {string} format 格式模板
* @returns {string} 格式化后的日期字符串
*/
function formatDate(date, format = 'YYYY-MM-DD') {
const d = new Date(date);
const map = {
YYYY: d.getFullYear(),
MM: String(d.getMonth() + 1).padStart(2, '0'),
DD: String(d.getDate()).padStart(2, '0'),
HH: String(d.getHours()).padStart(2, '0'),
mm: String(d.getMinutes()).padStart(2, '0'),
ss: String(d.getSeconds()).padStart(2, '0')
};
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => map[match]);
}

/**
* 深度克隆对象
* @param {*} obj 要克隆的对象
* @returns {*} 克隆后的对象
*/
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const cloned = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}

module.exports = {
formatDate,
deepClone
};

ES Module 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// dist/index.esm.js

export function formatDate(date, format = 'YYYY-MM-DD') {
const d = new Date(date);
const map = {
YYYY: d.getFullYear(),
MM: String(d.getMonth() + 1).padStart(2, '0'),
DD: String(d.getDate()).padStart(2, '0'),
HH: String(d.getHours()).padStart(2, '0'),
mm: String(d.getMinutes()).padStart(2, '0'),
ss: String(d.getSeconds()).padStart(2, '0')
};
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => map[match]);
}

export function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const cloned = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}

使用 Rollup 构建

Rollup 是打包库的最佳选择,输出干净、高效。

安装依赖

1
npm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-terser

rollup.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';

export default [
// ES Module 输出
{
input: 'src/index.js',
output: {
file: 'dist/index.esm.js',
format: 'esm',
sourcemap: true
},
plugins: [resolve(), commonjs()]
},
// CommonJS 输出
{
input: 'src/index.js',
output: {
file: 'dist/index.cjs.js',
format: 'cjs',
sourcemap: true
},
plugins: [resolve(), commonjs()]
},
// UMD 输出(浏览器兼容)
{
input: 'src/index.js',
output: {
file: 'dist/index.umd.js',
format: 'umd',
name: 'MyPackage',
sourcemap: true,
plugins: [terser()]
},
plugins: [resolve(), commonjs()]
}
];

编写测试

使用 Jest 编写单元测试:

1
npm install --save-dev jest

test/index.test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const { formatDate, deepClone } = require('../src/index');

describe('formatDate', () => {
test('格式化默认格式', () => {
const date = new Date('2026-05-09');
expect(formatDate(date)).toBe('2026-05-09');
});

test('自定义格式', () => {
const date = new Date('2026-05-09 11:30:00');
expect(formatDate(date, 'YYYY/MM/DD HH:mm:ss')).toBe('2026/05/09 11:30:00');
});

test('时间戳输入', () => {
const timestamp = 1746662400000; // 2026-05-09 00:00:00 UTC
expect(formatDate(timestamp, 'YYYY-MM-DD')).toBe('2026-05-09');
});
});

describe('deepClone', () => {
test('克隆简单对象', () => {
const obj = { a: 1, b: 'test' };
const cloned = deepClone(obj);
expect(cloned).toEqual(obj);
expect(cloned).not.toBe(obj);
});

test('克隆嵌套对象', () => {
const obj = { a: { b: { c: 1 } } };
const cloned = deepClone(obj);
cloned.a.b.c = 2;
expect(obj.a.b.c).toBe(1);
});

test('克隆数组', () => {
const arr = [1, { a: 2 }, [3, 4]];
const cloned = deepClone(arr);
expect(cloned).toEqual(arr);
cloned[1].a = 3;
expect(arr[1].a).toBe(2);
});
});

README.md 编写规范

README 是包的门面,必须清晰完整:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# my-awesome-package

> 一个实用的 JavaScript 工具库

## 安装

\`\`\`bash
npm install my-awesome-package
# 或
yarn add my-awesome-package
# 或
pnpm add my-awesome-package
\`\`\`

## 使用

\`\`\`javascript
// CommonJS
const { formatDate, deepClone } = require('my-awesome-package');

// ES Module
import { formatDate, deepClone } from 'my-awesome-package';

// 格式化日期
formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss');
// 输出: '2026-05-09 11:30:00'

// 深度克隆
const cloned = deepClone({ a: { b: 1 } });
\`\`\`

## API 文档

### formatDate(date, format)

| 参数 | 类型 | 说明 |
|------|------|------|
| date | Date/string/number | 日期对象、字符串或时间戳 |
| format | string | 格式模板,默认 'YYYY-MM-DD' |

### deepClone(obj)

| 参数 | 类型 | 说明 |
|------|------|------|
| obj | any | 要克隆的对象 |

## 许可证

MIT

NPM 注册与登录

1. 注册账号

1
2
3
# 在官网注册:https://www.npmjs.com/signup
# 或使用命令行
npm adduser

按提示输入用户名、密码、邮箱,完成邮箱验证。

2. 登录验证

1
2
3
4
npm login

# 验证登录状态
npm whoami

3. 配置 2FA(推荐)

在 npmjs.com 站点设置页面启用双因素认证,保护账号安全。

本地测试发布

发布前先本地测试:

1
2
3
4
5
6
7
8
# 在包目录
npm link

# 在测试项目目录
npm link my-awesome-package

# 现在可以在测试项目中引用
const pkg = require('my-awesome-package');

使用 npm pack

1
2
3
4
5
# 打包成 .tgz 文件
npm pack

# 在测试项目中安装
npm install ./my-awesome-package-1.0.0.tgz

正式发布

发布流程

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 确保代码已提交
git status

# 2. 运行测试
npm test

# 3. 构建(如有)
npm run build

# 4. 发布
npm publish

# 发布成功后会收到邮件通知

发布特定标签

1
2
3
4
5
6
7
8
# 发布 beta 版本
npm publish --tag beta

# 发布 next 版本
npm publish --tag next

# 用户安装指定标签
npm install my-awesome-package@beta

发布 scoped 包

scoped 包(如 @username/package)默认为私有包,需要付费。

发布公开 scoped 包:

1
npm publish --access public

常见问题处理

包名已存在

  • 换一个包名
  • 使用 scoped 包:@yourname/package-name
  • 检查是否拼写错误

发布失败

1
2
3
4
5
6
7
8
# 检查登录状态
npm whoami

# 检查包配置
npm publish --dry-run

# 查看将要发布的文件
npm pack --dry-run

更新已发布的包

1
2
3
4
5
6
# 1. 修改代码
# 2. 更新版本号
npm version patch/minor/major

# 3. 发布
npm publish

删除已发布的包

1
2
3
4
5
6
7
8
# 撤销 24 小时内发布的版本
npm unpublish my-awesome-package@1.0.0

# 撤销整个包(谨慎操作)
npm unpublish my-awesome-package --force

# 弃用包(推荐,不删除)
npm deprecate my-awesome-package "此包已弃用,请使用 xxx 替代"

最佳实践

  1. 语义化版本:严格遵循 SemVer 规范
  2. 完善的测试:发布前确保测试通过
  3. 清晰的文档:README 必须包含安装、使用、API 说明
  4. 变更日志:维护 CHANGELOG.md 记录版本变化
  5. 类型声明:提供 TypeScript 类型文件(.d.ts)
  6. 持续集成:使用 GitHub Actions 自动测试发布

GitHub Actions 自动发布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# .github/workflows/publish.yml
name: Publish to NPM

on:
push:
tags:
- 'v*'

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm test
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

在 GitHub 项目设置 → Secrets 中添加 NPM_TOKEN(从 npmjs.com 获取)。

总结

发布 NPM 包的完整流程:

  1. 规划包的功能和结构
  2. 编写代码和测试
  3. 配置 package.json
  4. 编写 README 和 LICENSE
  5. 本地测试(npm link / npm pack)
  6. 注册登录 NPM
  7. 发布(npm publish)
  8. 维护更新(版本管理)

掌握了这些,你就可以将自己的代码分享给全世界了!

参考资料

WireGuard 是一个现代化、高性能的 VPN 协议,相比 OpenVPN 更轻量、更快速。本文记录了在阿里云 ECS 上部署 WireGuard VPN 服务器的完整过程,包括服务器端配置、客户端配置、防火墙设置以及常见问题排查。

为什么选择 WireGuard?

WireGuard 相比传统 VPN 方案的优势:

  • 性能优异:内核级实现,延迟更低
  • 配置简洁:无需复杂的证书管理,只需公钥/私钥配对
  • 安全性高:使用现代加密协议(ChaCha20、Poly1305 等)
  • 跨平台:支持 Linux、Windows、macOS、iOS、Android

服务器端部署

1. 安装 WireGuard

1
2
3
4
5
# CentOS/RHEL
yum install wireguard-tools

# Ubuntu/Debian
apt install wireguard

2. 生成密钥对

WireGuard 使用公钥加密来验证身份,每个参与者都需要一对密钥(私钥 + 公钥)。

为什么需要两对密钥?

打个比方:

  • 服务器就像一个「门」,它有自己的钥匙(私钥)和门牌号(公钥)
  • 客户端就像一个「人」,有自己的身份证(私钥)和证件号(公钥)

要进入这扇门:

  1. 人需要知道门的公钥(门牌号),才能找到正确的门
  2. 门需要知道人的公钥(证件号),才能验证是不是允许这个人进来

这就是为什么双方都需要互相交换公钥

密钥 保存在哪里 作用
服务器私钥 服务器配置 [Interface] 服务器身份验证,绝不外泄
服务器公钥 客户端配置 [Peer] 客户端用它找到服务器
客户端私钥 客户端配置 [Interface] 客户端身份验证,绝不外泄
客户端公钥 服务器配置 [Peer] 服务器用它验证客户端身份

密钥流向图

1
2
3
4
5
6
7
8
9
服务器配置文件                     客户端配置文件
┌─────────────────────┐ ┌─────────────────────┐
│ [Interface] │ │ [Interface] │
│ PrivateKey = 服务器私钥 │ │ PrivateKey = 客户端私钥 │
│ │ │ │
│ [Peer] │ │ [Peer] │
│ PublicKey = 客户端公钥 │◄──交换──►│ PublicKey = 服务器公钥 │
│ AllowedIPs = ... │ │ Endpoint = ... │
└─────────────────────┘ └─────────────────────┘

记住这个原则

  • 自己的私钥 → 写在自己的 [Interface]
  • 对方的公钥 → 写在自己的 [Peer]

生成密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建配置目录
mkdir -p /etc/wireguard
cd /etc/wireguard

# 生成服务器密钥对
wg genkey | tee server_private.key | wg pubkey > server_public.key

# 生成客户端密钥对
wg genkey | tee client_private.key | wg pubkey > client_public.key

# 查看生成的密钥
cat server_private.key # 服务器私钥 → 写入服务器 [Interface]
cat server_public.key # 服务器公钥 → 写入客户端 [Peer]
cat client_private.key # 客户端私钥 → 写入客户端 [Interface]
cat client_public.key # 客户端公钥 → 写入服务器 [Peer]

重要:私钥绝对不能泄露!公钥可以公开分享给对方。

3. 创建服务器配置文件

1
2
3
4
5
6
7
8
9
10
cat > /etc/wireguard/wg0.conf << EOF
[Interface]
PrivateKey = <服务器私钥>
Address = 10.100.0.1/24
ListenPort = 443

[Peer]
PublicKey = <客户端公钥>
AllowedIPs = 10.100.0.2/32
EOF

配置说明:

  • ListenPort = 443:使用 HTTPS 端口,避免运营商封锁非标准端口(如 51820)
  • Address = 10.100.0.1/24:VPN 内网地址段,服务器使用 .1
  • AllowedIPs = 10.100.0.2/32:只允许该客户端使用 .2 地址

4. 启动 WireGuard 服务

1
2
3
4
5
6
# 启动服务
wg-quick up wg0

# 或使用 systemd 管理
systemctl start wg-quick@wg0
systemctl enable wg-quick@wg0 # 开机自启

验证服务状态:

1
wg show

输出示例:

1
2
3
4
5
6
7
interface: wg0
public key: <服务器公钥>
private key: (hidden)
listening port: 443

peer: <客户端公钥>
allowed ips: 10.100.0.2/32

防火墙与 NAT 配置

1. 开放防火墙端口

1
2
3
4
5
6
# firewalld
firewall-cmd --permanent --add-port=443/udp
firewall-cmd --reload

# iptables
iptables -A INPUT -p udp --dport 443 -j ACCEPT

2. 配置 NAT 规则(让客户端能上网)

WireGuard 客户端通过 VPN 连接后,需要 NAT 规则才能访问外网:

1
2
3
4
5
6
# 使用 firewalld 持久化 NAT 规则
firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.100.0.0/24 -o eth0 -j MASQUERADE
firewall-cmd --reload

# 或使用 iptables(重启后会丢失)
iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -o eth0 -j MASQUERADE

3. 开启 IP 转发

1
2
3
4
5
6
7
8
9
# 检查是否已开启
cat /proc/sys/net/ipv4/ip_forward

# 如果为 0,需要开启
sysctl -w net.ipv4.ip_forward=1

# 持久化设置
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p

4. FORWARD 规则持久化

1
2
3
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i wg0 -o eth0 -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i eth0 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
firewall-cmd --reload

5. 阿里云安全组配置

在阿里云控制台添加安全组规则:

方向 协议 端口 来源
入站 UDP 443 0.0.0.0/0
出站 UDP 全部 0.0.0.0/0

客户端配置

配置文件内容

1
2
3
4
5
6
7
8
9
10
[Interface]
PrivateKey = <客户端私钥>
Address = 10.100.0.2/24
DNS = 8.8.8.8

[Peer]
PublicKey = <服务器公钥>
Endpoint = <服务器公网IP>:443
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

配置说明:

  • AllowedIPs = 0.0.0.0/0:所有流量都走 VPN
  • PersistentKeepalive = 25:保持连接活跃,避免断开

生成二维码(方便手机导入)

1
2
3
4
5
6
7
8
# 安装 qrencode
yum install qrencode

# 生成二维码
qrencode -t PNG -o wireguard-qr.png -r client.conf

# 或直接显示在终端
qrencode -t ANSIUTF8 -r client.conf

各平台客户端下载

平台 下载方式
iOS App Store 搜索 “WireGuard”
Android Google Play 或 F-Droid
Windows https://www.wireguard.com/install/
macOS App Store 或 brew install wireguard-tools

常见问题排查

1. 能连接但无法上网

检查 NAT 规则:

1
iptables -t nat -L POSTROUTING -n -v

如果没有 VPN 网段的 MASQUERADE 规则:

1
iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -o eth0 -j MASQUERADE

2. 握手失败(无 latest handshake)

检查密钥是否匹配:

1
2
3
4
5
# 服务器端:客户端公钥应该和配置文件一致
wg show | grep peer

# 客户端配置里的服务器公钥应该和服务器实际公钥一致
cat /etc/wireguard/wg0.conf | grep PrivateKey | awk '{print $3}' | wg pubkey

3. 数据包发送但无响应

可能是端口被封锁。尝试:

  • 改用 UDP 443(HTTPS 端口)
  • 检查阿里云安全组是否开放 UDP 出站

4. 连接后流量不走 VPN

检查客户端 AllowedIPs:

1
2
AllowedIPs = 0.0.0.0/0  # 全局代理
AllowedIPs = 10.100.0.0/24 # 只代理 VPN 内网

5. 规则重启后丢失

确保使用 firewalld 持久化:

1
2
# 检查持久化规则
firewall-cmd --permanent --direct --get-all-rules

服务管理命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 启动
wg-quick up wg0
systemctl start wg-quick@wg0

# 停止
wg-quick down wg0
systemctl stop wg-quick@wg0

# 状态
wg show
systemctl status wg-quick@wg0

# 重启
systemctl restart wg-quick@wg0

添加更多客户端

每个客户端需要独立的密钥对和 IP 地址:

1
2
3
4
5
6
7
8
# 生成新客户端密钥
wg genkey | tee client2_private.key | wg pubkey > client2_public.key

# 在服务器端添加 peer
wg set wg0 peer <客户端2公钥> allowed-ips 10.100.0.3/32

# 更新服务器配置文件(持久化)
# 在 /etc/wireguard/wg0.conf 添加新的 [Peer] 块

小结

WireGuard 的部署比 OpenVPN 简单得多,核心要点:

  1. 密钥匹配:服务器和客户端的公钥/私钥必须正确配对
  2. 端口选择:用 UDP 443 避免运营商封锁
  3. NAT 规则:确保 VPN 网段有 MASQUERADE
  4. 规则持久化:用 firewalld direct 规则或 iptables-save

配置完成后,重启服务会自动恢复,无需手动干预。

作为一名程序员,终端是日常工作中最频繁使用的工具之一。一个美观、高效的终端环境不仅能提升工作效率,还能让编程体验更加愉悦。本文将介绍如何使用 Starship 打造一个现代化、跨平台的终端提示符。

什么是 Starship?

Starship 是一个轻量、快速、跨 shell 的现代化提示符工具:

  • 跨平台:支持 macOS、Linux、Windows
  • 跨 Shell:支持 Bash、Zsh、Fish、PowerShell 等
  • 高度可定制:丰富的配置选项
  • 性能优异:使用 Rust 编写,启动速度极快

安装前置条件

安装一个 Nerd Font 字体,否则图标可能显示为乱码:

1
2
3
# 推荐 MesloLGS NF 字体
brew tap homebrew/cask-fonts
brew install --cask font-meslo-lg-nerd-font

安装后在终端设置中将字体改为 MesloLGS NF

安装 Starship

1
2
3
4
5
# macOS
brew install starship

# Linux
curl -sS https://starship.rs/install.sh | sh

配置 Shell

根据你使用的 Shell 添加初始化命令:

1
2
3
4
5
6
7
# Zsh
echo 'eval "$(starship init zsh)"' >> ~/.zshrc
source ~/.zshrc

# Bash
echo 'eval "$(starship init bash)"' >> ~/.bashrc
source ~/.bashrc

创建配置文件

1
2
mkdir -p ~/.config
touch ~/.config/starship.toml

推荐配置(青色背景风格)

一个简洁美观的配置效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ~/.config/starship.toml

format = """[$username@$hostname$directory$git_branch$git_status](bg:cyan fg:black) $character"""
add_newline = false

[username]
show_always = true
format = "$user"

[hostname]
ssh_only = false
format = "$hostname"

[directory]
format = ">$path"
truncation_length = 3

[git_branch]
format = ">$branch"

[git_status]
format = "$all_status"

[character]
success_symbol = "[❯](bold cyan)"
error_symbol = "[❯](bold red)"

效果展示:

1
foleydang@hostname>workspace>master ❯

Powerline 风格配置

如果你喜欢更丰富的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
format = """
[](#9A348E)$directory\
[](fg:#9A348E bg:#DA627D)$git_branch$git_status\
[](fg:#DA627D bg:#FCA17D)$nodejs$python$rust\
[](fg:#FCA17D bg:#86BBD8)$time\
[ ](fg:#86BBD8)\
$line_break$character"""

[directory]
style = "bg:#9A348E"
truncation_length = 3
truncation_symbol = "…/"

[git_branch]
symbol = " "
style = "bg:#DA627D"
format = '[$symbol$branch ]($style)'

[git_status]
style = "bg:#DA627D"
format = '[$all_status$ahead_behind ]($style)'

[nodejs]
symbol = " "
style = "bg:#FCA17D"
format = '[$symbol($version )]($style)'

[python]
symbol = " "
style = "bg:#FCA17D"
format = '[$symbol($version )]($style)'

[time]
disabled = false
time_format = "%R"
style = "bg:#86BBD8"
format = '[$time ]($style)'

[character]
success_symbol = "[➜](bold green)"
error_symbol = "[➜](bold red)"

一键安装脚本

保存为 install-starship.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/bash
# Starship 一键安装脚本

# 安装 Starship
if command -v brew &> /dev/null; then
brew install starship
elif command -v curl &> /dev/null; then
curl -sS https://starship.rs/install.sh | sh
else
echo "需要 Homebrew 或 curl 来安装 Starship"
exit 1
fi

# 创建配置文件
mkdir -p ~/.config
cat > ~/.config/starship.toml << 'STARSHIP_EOF'
format = """[$username@$hostname$directory$git_branch$git_status](bg:cyan fg:black) $character"""
add_newline = false

[username]
show_always = true
format = "$user"

[hostname]
ssh_only = false
format = "$hostname"

[directory]
format = ">$path"
truncation_length = 3

[git_branch]
format = ">$branch"

[git_status]
format = "$all_status"

[character]
success_symbol = "[❯](bold cyan)"
error_symbol = "[❯](bold red)"
STARSHIP_EOF

# 添加到 shell 配置
if [ -f ~/.zshrc ]; then
grep -q "starship init zsh" ~/.zshrc || echo 'eval "$(starship init zsh)"' >> ~/.zshrc
echo "✅ 已添加到 ~/.zshrc"
elif [ -f ~/.bashrc ]; then
grep -q "starship init bash" ~/.bashrc || echo 'eval "$(starship init bash)"' >> ~/.bashrc
echo "✅ 已添加到 ~/.bashrc"
fi

echo "✅ Starship 配置完成"
echo "运行 source ~/.zshrc 或 source ~/.bashrc 使配置生效"

运行:

1
2
chmod +x install-starship.sh
./install-starship.sh

远程主机迁移

复制配置到远程主机

1
2
3
4
5
6
# 复制配置文件
scp ~/.config/starship.toml user@remote:~/.config/

# 在远程主机安装
ssh user@remote "curl -sS https://starship.rs/install.sh | sh"
ssh user@remote "echo 'eval \"\$(starship init zsh)\"' >> ~/.zshrc"

使用一键脚本

1
scp install-starship.sh user@remote:~/ssh user@remote "chmod +x ~/install-starship.sh && ~/install-starship.sh"

自定义颜色

修改 bg:cyan fg:black 可以更换配色:

可用颜色:

  • black, white, red, green, yellow, blue, magenta, cyan
  • 或使用 hex:bg:#1e1e2e fg:#cdd6f4

示例(紫色背景):

1
format = """[$username@$hostname$directory](bg:magenta fg:white) $character"""

配合 iTerm2 使用

进一步优化终端体验:

  1. 设置字体:Preferences → Profiles → Text → Font → MesloLGS NF
  2. 启用真彩色:Preferences → Profiles → Terminal → Enable True Color
  3. 推荐配色:Dracula 或 Snazzy 主题

常见问题

图标显示为乱码

确保终端字体设置为 Nerd Font。

启动速度慢

减少不必要的模块可以提升启动速度。

颜色不显示

  • 不要手动设置 TERM 环境变量
  • 某些终端(如 basic TUI)不支持颜色

配置文件位置

文件 位置
Starship 配置 ~/.config/starship.toml
Zsh 初始化 ~/.zshrc
Bash 初始化 ~/.bashrc

总结

Starship 让终端焕然一新,推荐配置组合:

  • 终端:iTerm2
  • Shell:Zsh
  • 提示符:Starship
  • 字体:MesloLGS NF
  • 配色:青色背景 + 黑色字体

Happy Coding!


参考:Starship 官方文档

使用 Hexo 搭建博客,且采用 NexT 主题,详细介绍写一篇新博客的步骤。

创建新博客文章

1
2
cd blog-source
hexo new "你的文章标题"

编辑博客文章

用编辑器打开 source/_posts/ 下的 Markdown 文件,补充 Front-Matter:

1
2
3
4
5
6
7
8
---
title: 你的文章标题
date: 2026-08-12 19:00:00
categories: 技术
tags:
- 标签1
- 标签2
---

本地预览

1
2
hexo generate && hexo server
# 浏览器访问 http://localhost:4000

部署

1
2
hexo deploy
# 访问 https://foleydang.github.io

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

0%