普通视图

发现新文章,点击刷新页面。
今天 — 2025年8月16日首页

npm发包自己的组件并安装更新版本应该如何做?

作者 林太白
2025年8月16日 10:42

npm发包组件

发布一个属于自己的npm包吧!接下来我们便使用Vue封装组件并发布到npm仓库

封装NPM组件-验证码

预览

npm-fabao1.png

1、创建账号注册登录

👉注册申请以及登录账号

官网

https://www.npmjs.com/

正常申请注册即可,选择 sign up 进入账户注册页面

npm-fabao2.png

2、创建vue3项目Tbcode

👉搭建项目

yarn create vite NexusCode --template vue

// 安装依赖
yarn 

👉创建组件

<template>
  <div class="necode">
    <div 
      class="codebox"
      :style="{
        'background': codeback,
        'width': width + 'px',
        'height': height + 'px'
      }"
      @click="getCode(length)"
    >
      {{ codevalue }}
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

// 接收父组件传递的 props
defineProps({
  value: {
    type: String,
    required: false,
  },
  length: {
    type: Number,
    default: 4,
    required: false,
  },
  back: {
    type: String,
    required: false,
  },
  width: {
    type: Number,
    default: 120,  // 默认宽度为120px
  },
  height: {
    type: Number,
    default: 40,   // 默认高度为40px
  }
});


const codelength = ref(4);

// 响应式变量
const codevalue = ref(''); // 验证码值
const codeback = ref('');  // 验证码背景色

// onMounted 是 Vue 3 的生命周期钩子,类似于 Vue 2 的 created
onMounted(() => {
  codelength.value=length?length:codelength.value;
  getCode(codelength.value); // 获取验证码
});

// 新增试用码-前端随机生成方法
const getCode = (row) => {
  // 随机背景颜色
  const r = Math.floor(Math.random() * 256);
  const g = Math.floor(Math.random() * 256);
  const b = Math.floor(Math.random() * 256);
  const rgb = `rgb(${r},${g},${b})`;
  codeback.value = rgb;

  const arrall = [
    'A', 'B', 'C', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
  ];
  let str = '';
  for (let i = 0; i < row; i++) {
    str += arrall[Math.floor(Math.random() * arrall.length)];
  }
  codevalue.value = str;
};
</script>

<style scoped>
.codebox {
  text-align: center;
  font-weight: 800;
  line-height: 40px;
  display: inline-block;
  float: left;
  cursor: pointer;
  font-size: 24px;
  color: #fff;
  border-radius: 4px;
}
</style>

👉配置package.json

私人化配置

"private": true,
这就代表私人的包

公共包配置(这里我们使用这个)

{
  "name": "tbcode",
  "version": "0.0.2",
  "files": [
    "dist"
  ],
  "module": "./dist/tbcode.es.js",
  "main": "./dist/tbcode.umd.js",
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/tbcode.es.ts",
      "require": "./dist/tbcode.umd.ts"
    },
    "./dist/style.css": {
      "import": "./dist/tbcode.css",
      "require": "./dist/tbcode.css"
    }
  },
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.5.18"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^6.0.1",
    "vite": "^7.1.2"
  }
}

3、发布组件

👉版本名字

🍎名称规则

包名使用了@开头,一般使用@符号开头的包都是私有包,npm需要收费

加上--access public可直接发布组件,当成公有包,不需要支付费用

name:发布的包名,默认是上级文件夹名。不得与现在npm中的包名重复。包名不能有大写字母/空格/下滑线!

👉版本登录发布

Major version:大版本,代表破坏性变更。
Minor version:小版本,代表向后兼容的新功能。
Patch version:修订版本,代表 bug 修复和小改进。
🍎 版本发布
//查看当前用户是否登录
npm whoami 

//登陆
npm login 

// 或  npm addUser  

//部署
npm publish

👉发布名称冲突报错

下面就是名字被占用了

npm notice
npm notice package: Necode@0.0.2
npm notice Tarball Contents
npm notice 385B README.md
npm notice 169B dist/Necode.css
npm notice 2.0kB dist/necode.es.js
npm notice 1.3kB dist/necode.umd.js
npm notice 613B package.json
npm notice Tarball Details
npm notice name: Necode
npm notice version: 0.0.2
npm notice filename: Necode-0.0.2.tgz
npm notice package size: 2.1 kB
npm notice unpacked size: 4.4 kB
npm notice shasum: 6534df3352b5d299457dbff0b6e363b1b6ab6d4f
npm notice integrity: sha512-UZ1QGtymSFl73[...]rc1luwb77w/4Q==
npm notice total files: 5
npm notice
npm notice Publishing to 
  https://registry.npmjs.org/ with tag latest and default access
npm error code E400
npm error 400 Bad Request - PUT https://registry.npmjs.org/Necode - 
"Necode" is invalid for new packages
npm error A complete log of this run can be found in: 
C:\Users\admin\AppData\Local\npm-cache\_logs\2025-08-15T08_24_42_644Z-debug-0.log

👉更新名字,再次发布

npm publish提示信息如下

npm notice
npm notice package: tbcode@0.0.2
npm notice Tarball Contents
npm notice 385B README.md
npm notice 169B dist/Necode.css
npm notice 2.0kB dist/necode.es.js
npm notice 1.3kB dist/necode.umd.js
npm notice 613B package.json
npm notice Tarball Details
npm notice name: tbcode
npm notice version: 0.0.2
npm notice filename: tbcode-0.0.2.tgz
npm notice package size: 2.1 kB
npm notice unpacked size: 4.4 kB
npm notice shasum: 97d3dc40035c0e3fcbcb590704e7c0d531d9d16e
npm notice integrity: sha512-M4EQ/J8XRHZNT[...]CC0OwXVluzH8Q==
npm notice total files: 5
npm notice
npm notice Publishing to https://registry.npmjs.org/ with tag latest and default access
+ tbcode@0.0.2

搜索已经可以发现我们的npm包了

tbcode

👉更新版本

接下来我们传第二个版本包

添加一些关键词

// 发布一个小的补丁号版本
npm version patch
npm publish

这个时候已经可以看到我们的关键词和版本信息了

Keywords
vuereacttbcodelintaibai林太白
npm version patch -m "xx"
【
    patch增加一位补丁号,
    minor增加一位小版本号,
    major增加一位大版本号
 】

👉取消版本

// 舍弃某个版本的模块  24小时使用这个即可(使用)
npm unpublish tbcode@1.0.0

// 舍弃某个版本的模块
npm deprecate my-npm@"< 1.0.8" "critical bug fixed in v1.0.2"

// 如何撤销发布
npm --force unpublish my_npm

4、使用

发布到 npm 后,安装使用自己封装的组件

安装依赖

npm i tbcode

//或者
yarn add tbcode

项目引入使用

import tbCode from 'tbCode'
import 'tbcode/dist/style.css'

app.component('Tbcode', tbcode) 


<Tbcode/>

问题以及处理

👉组件样式不生效,

问题就出现在下面两点

(1)组件的导出方式配置不对

(2)使用时候引入没有

// 导出方式配置
"exports": {
    ".": {
      "import": "./dist/tbcode.es.js",
      "require": "./dist/tbcode.umd.js"
    },
    "./dist/style.css": {
      "import": "./dist/tbcode.css",
      "require": "./dist/tbcode.css"
    }
},


// 引入使用
import 'tbcode/dist/style.css'
昨天 — 2025年8月15日首页
昨天以前首页

市面上有多少智能体平台

一、大模型智能体平台:扣子(coze)

字节跳动的扣子平台提供了丰富的功能,包括插件、系统、记忆库、工作流等,支持知识库和自定义的插件构建的机器人可以轻松的部署到多个平台,几乎不需要具备编程的基础模型插件。还有知识库等核心技术都已经进行了非常好的封装,支持多agent模式,允许用户创建多个专注于特定任务的单agent,并且可以统一管理。

二、大模型智能体平台:腾讯元器

腾讯元器的开发者可以通过插件、知识库、工作流等方式,快速的、低门槛的来打造高质量的智能体,支持发布到QQ、微信等平台的同时,也支持API的调用。

三、大模型智能体平台:智谱清言

中国版的对话语言模型与GLM大模型进行对话,基于ChatGLM中英双语大模型进行开发,通过万亿字符的文本与代码的预训练,结合有监督的微调技术,具备了通用问答、多轮对话、创意写作、代码生成、虚拟对话、AI画图、文档和图片解读等等的能力。

四、大模型智能体平台:百度灵境

百度灵境智能体支持低代码的开发模式,灵活度相对更高,可以一键分发到微信客服、微信公众号、Web端、H5以及百度灵境矩阵等主流渠道之上。基于这些渠道应用,还能够在百度搜索、百度信息流等主流场景下进行分发与挂载。

AI质量专项报告自动分析生成|得物技术

作者 得物技术
2025年8月14日 15:14

一、背景

在日常工作中,常需要通过各项数据指标,确保驱动版本项目进展正常推进,并通过各种形式报表数据,日常总结日报、周会进展、季度进行总结输出归因,分析数据变化原因,做出对应决策变化,优化运营方式,目前在梳理整理校准分析数据需要大量的时间投入、结合整体目标及当前进展,分析问题优化的后续规划。

常见形式

人工收集

数据来源依赖于各系统平台页面,通过人工收集校准后填写再通过表格公式计算,或者可以通过多维表格工作流触发通知等功能。

图片

quickbi报表

通过ODPS搭建自定义报表,实现快速收集数据,复制报表到飞书文档内进行异动分析。

图片

平台能力开发

通过代码开发文档导出能力,根据固定模板生成数据分析,该能力开发人力成本较高,需要针对不同平台数据源定制化开发。

图片图片

AI Studio智能体平台

研发效能团队基于开源Dify项目社区部署,可以根据需求自定义sop,多模型的可选项,选择最适合业务的模型。每个工作流节点可自定义流程的判断分析,轻松上线可投产的AI Agents。

Dify是一个支持工作流编排的AI智能体平台,轻松构建并部署生产级 AI 应用。其核心功能包含:

  1. 以工作流的方式编排AI应用,在工作流中可以添加LLM、知识库、Agent工具、MCP服务等节点,工作流支持分支流转、节点循环、自定义节点等高级能力项。

  2. 支持在工作流中调用公司内部的Dubbo/gRPC服务。(插件实现)

  3. 知识库管理,通过构建私有知识库以增强 LLM 的上下文。

  4. 与内部平台集成,支持H5页面嵌入、API的方式与内部平台集成。

  5. 主流模型集成,支持使用多种主流模型如DeepSeek、OpenAI等,支持多模态模型。

对标的业界产品有:

✅ 多模型选择(适配不同业务场景)

✅ 可视化工作流搭建(支持自定义SOP)

✅ 全链路可观测性(实时调试优化)

综上本期实践利用AI工作流平台针对报告进行生成分析输出,让使用方回归到聚焦数据归因分析上,减少数据收集分析、文档编写成本。

图片

二、应用实践

实践效果

整体分析数据从哪来->需要输出什么样的格式->优化模型输出结果,三步骤针对输出结果进行调优。

图片

自动化成熟度分析工作流搭建案例

图片

运用效果

图片图片

报告效果

图片

飞书机器人通知归因分析

图片图片

数据处理

图片

LLM:通过用户输入分析获取数据源请求格式,配置好对应数据的映射关系模型自行获取对应数据。

提示词输入

图片

格式化输出配置

图片

http请求:通过用户输入分析后的参数构造请求参数,通过固定接口拉取数据,支持curl导入功能。

图片图片

代码执行:支持python、js代码对结果数据进行处理过滤,提升分析结果准确性。

图片

模型提示词

如文档整体分为不同模块可设定不同模型节点处理,每个模块增加特定提示词处理节点内容,模型并行分析处理,提升输出稳定性和输出效率,再通过LLM输出整合进行整体输出。

图片

在模型输入上下文及用户输入,通过获取的数据指定输出格式,设定提示词,提供AI结合模板输出对应形式。

图片

通过衔接上下节点返回内容最终整合报表输出结果,统一输出样式格式。

图片

优化输出

切换可用模型

遇到模型输出不稳定或者未达到预期效果,可切换可用模型,寻找适配模型。

图片

设定模型预载参数

设定模型预载参数,提升模型输出准确度。

图片

优化增加提示词

优化增加提示词提升输出形式稳定性:角色定义 ->  字段映射 -> 模板说明 -> 实际数据填充 -> 输出格式定义。

`## 角色定义 你是一位接口自动化测试专家以及报告生成专家,负责将接口返回的数据映射字段结合模板输出一份有效的自动化成熟度报告-稳定性部分。

接口返回数据字段映射关系:

基础字段: bu_name:业务域名称。 parent_bu_id:业务域。

稳定性指标字段: total_auto_stability_score:稳定性评分 iter_case_success_rate: 迭代自动化成功率 iter_case_success_rate_cpp: 迭代自动化成功率环比 auto_case_failed_rate: 自动化失败率 auto_case_failed_rate_cpp: 自动化失败率环比 case_aigc_avg_score: 用例健壮有效性评分 case_aigc_avg_score_cpp: 有效性评分环比

模板:

2.2 自动化稳定性 用表格展示自动化稳定性,表格内容包含所有一级业务域、二级业务域。 表头按照顺序输出: 1、业务域 2、自动化稳定性评分 3、迭代自动化成功率 4、迭代自动化成功率环比 5、自动化失败率 6、失败率环比 7、用例健壮有效性评分 8、有效性评分环比

重点关注项:xxx --仅分析二级业务域的稳定性性指标字段,列出需重点关注指标。

模板说明:

1、以html格式输出,增加内容丰富度,不输出任何多余内容。 2、表格说明:表格需要包含所有业务域数据。不要省略或者缺少任何业务域数据,将所有业务域展示在同一个表格内。 3、表格行排序:根据评分从高到低排序。 4、环比字段说明:指标环比下降环比字段标记红色,环比提升字段标记绿色,不标记背景色。

任务说明

1、用户将提供接口返回的JSON数据。 2、根据接口数据和匹配字段映射关系。 3、结合模板以及模板说明html形式输出,不输出任何多余内容。 请你根据以上内容,回复用户,不需要输出示例。`

模板转换

输出的表格形式通过模板转化固定输出html表格形式,提升模型输出稳定性。

图片

输出形式

以markdown形式或以html形式输出,复制到飞书文档上进行输出。

html最终效果

图片

markdown最终效果

图片

飞书机器人通知归因分析

图片

生成飞书文档

支持飞书应用直接新建飞书文档,markdown形式输出。

图片

对话返回生成后的飞书文档地址及分析:

图片

三、总结

在日常工作中如何有效利用数据指标驱动项目进展,现有数据收集和分析流程中面临的挑战。通过手动收集数据、生成报表、平台开发等传统方式,需要投入大量时间和人力资源,导致工作效率低下。

为此,引入了研发效能AI 智能体平台,AI工作流平台不仅改进了数据处理方式,还提升了报告生成的效率和准确性,从而增强了业务洞察力。进一步丰富工作流和知识库,提高对核心数据指标的分析能力,并针对异常数据指标进行细致剖析,为团队提供更深入的指导和支持。

此外,相似场景的处理也可以借助AI工作流进行优化,有望在多个业务领域推广应用。

四、后续规划

  • 丰富工作流:丰富结合知识库,针对每项核心数据指标提升建议以及业务域现状给予业务域具体指导建议。

  • 明细下钻分析:获取对应数据指标异常后,结合明细数据进行分析,具体到用例、人员级别。

  • 类似场景可通过AI工作流处理:固定模板数据源报告类、周会均可使用该方法减少人工投入成本。

往期回顾

1.Rust 性能提升“最后一公里”:详解 Profiling 瓶颈定位与优化|得物技术

2.Valkey 单点性能比肩 Redis 集群了?Valkey8.0 新特性分析|得物技术

3.社区搜索离线回溯系统设计:架构、挑战与性能优化|得物技术

4.正品库拍照PWA应用的实现与性能优化|得物技术

5.得物社区活动:组件化的演进与实践

文 / 笠

关注得物技术,每周更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

利用 Python 爬虫获取淘宝商品评论实战指南

作者 onejason
2025年8月13日 14:10

在电商领域,淘宝的商品评论数据是商家优化产品、提升用户体验以及进行市场分析的重要资源。以下是一个详细的实战指南,帮助你利用 Python 爬虫技术获取淘宝商品评论。

一、准备工作

(一)开发环境

确保你的开发环境中已经安装了 Python,并且启用了 requestsBeautifulSoup 库。

(二)安装必要的库

安装以下库,用于发送 HTTP 请求和解析 HTML 数据:

pip install requests beautifulsoup4 pandas

二、编写爬虫代码

(一)发送 HTTP 请求

使用 requests 库发送 GET 请求,获取商品评论页面的 HTML 内容。

import requests

def get_html(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.text
    else:
        print("Failed to retrieve the page")
        return None

(二)解析 HTML 内容

使用 BeautifulSoup 解析 HTML 内容,提取评论数据。

from bs4 import BeautifulSoup

def parse_html(html):
    soup = BeautifulSoup(html, 'lxml')
    comments = []
    comment_items = soup.find_all('div', class_='comment-item')
    for item in comment_items:
        content = item.find('p', class_='comment-content').text.strip()
        comments.append(content)
    return comments

(三)按关键字搜索商品评论

根据商品 ID 构建评论请求 URL,并获取评论数据。

def fetch_comments(item_id, page=1):
    url = f"https://rate.taobao.com/feedRateList.htm?auctionNumId={item_id}&currentPageNum={page}"
    html = get_html(url)
    if html:
        return parse_html(html)
    return []

(四)整合代码

将上述功能整合到主程序中,实现完整的爬虫程序。

def main():
    item_id = "12345678"  # 替换为实际的商品 ID
    max_pages = 3
    all_comments = []

    for page in range(1, max_pages + 1):
        comments = fetch_comments(item_id, page)
        all_comments.extend(comments)
        print(f"Page {page} comments fetched.")

    # 打印所有评论
    for comment in all_comments:
        print(comment)

if __name__ == "__main__":
    main()

三、注意事项与优化建议

(一)遵守法律法规

在进行爬虫操作时,必须严格遵守相关法律法规,尊重网站的 robots.txt 文件规定。

(二)合理设置请求频率

避免过高的请求频率导致对方服务器压力过大,甚至被封禁 IP。

(三)应对反爬机制

淘宝平台可能会采取一些反爬措施,如限制 IP 访问频率、识别爬虫特征等。可以通过使用动态代理、模拟正常用户行为等方式应对。

(四)数据存储与分析

将抓取到的评论数据存储到数据库或文件中,以便后续分析和使用。

四、总结

通过上述步骤和代码示例,你可以高效地利用爬虫技术获取淘宝商品评论数据。无论是用于市场调研、竞品分析还是用户体验优化,这些数据都将为你提供强大的支持。希望本文的示例和策略能帮助你在爬虫开发中更好地应对各种挑战,确保爬虫程序的高效、稳定运行。

开发一个支持支付功能的微信小程序的注意事项,含泪送上

2025年8月12日 17:49

介绍

从来没有做过该功能,只是最近想做一个股票的小工具。顺便想收点费用支撑下自己的荷包。所以就做了一个支付功能。然后就开始了自己的踩坑之旅....

我的痛,你不知道...

服务器

阿里云、腾讯云、华为云,其实都可以。因为开发微信小程序,所以优先选了腾讯云。

开通云服务器的同时,买了一个SSL。知道小程序需要https。

开发代码

前端使用了Taro、后端使用的nodejs。完成了基础功能的开发。微信开发者工具中基本能够使用,除了支付功能。

踩坑之旅

域名的痛

本来以为买了一个域名后,这个域名跟我服务器的IP做绑定后,就能直接使用了。本地使用ping都能通的。结果下面:

image.png

访问XXXX.asia

image.png

坑爹的是,访问XXXX.asia,结果能通! 问了下deepseek,说是加密了,所以是否备案无法拦截。

但是,如果发布小程序后,所有的访问,都是不通的,接口返回的信息是 request:fail。

没经验的我,伤的不要不要的。

然后去,备案。。

这个还好是跟腾讯云一起的,因为备案过程中,没经验的,一定选择【强烈推荐】的选项,去做,不然检验死活不过。我也是醉了。

然后等10+天.....

小程序 与 商户号

如果小程序需要开通支付功能,是需要跟商户号绑定的。然后商户平台开通相关的支付模式,小程序中调用相关的支付。

问题是,小程序的主体,必须是这个商户,其次,小程序的类目,必须跟商户一致,不然,小程序连微信支付的菜单都看不到。

第一次做支付的我,伤的不要不要的....

因为 商户的科目与我小程序的不一致,小程绑定的主体,不是商户号的主体,我咋办哦....

总结

如果开发一个小程序支持,支付功能,注意事项如下:

  1. 服务器、域名、SSL
  2. 开发前端、后端
  3. 有一个商户号,他是你小程序的主体,开通相关支付功能。绑定小程序。
  4. 注册一个小程序,与商户号绑定。配置相关的支付能力。
  5. 如果你是个人,无法实现支付功能,请知道.....

最后的最后

后来,找了一个服务商平台,注册了一个小微商户,然后用这个小微商户,登录微信的商户平台,做了相关的配置,与小程序做了绑定,做了相关实现。(被收费了,还很贵啊)(灬ꈍ ꈍ灬)。

腾讯元器的优点是什么

元器本身的优点有三个:

1、团队功能,一个团队可以有50人,支持创建5个。方便小企业和团队,在元器上以团队方式创建智能体、工作流、插件、知识库,并共享这些资产。也方便大家共同协助,相互学习,我创立了一个”浪洋洋和朋友们”的团队,欢迎大家加入。

2、有简单的官方说明文档,能在元器平台内直接加入官方群,有负责人解答。

3、免费TOKEN额度高,估计是用的人少,现在单个账号免费额度有1个亿,轻松实现人生小目标,用不完真的用不完。扣子的免费TOKEN额度很少了,初学者练手可以试试元器。

PaddleMIX推出扩散模型推理加速Fast-Diffusers:自研蒸馏加速方法FLUX-Lightning实现4步图像生成

作者 百度Geek说
2025年8月12日 16:00
扩散模型推理成本亟待优化 扩散模型(Diffusion Models)近年来在高保真图像和视频生成上取得了令人瞩目的成果。然而,这类模型在推理阶段需要经过数十步乃至上百步的迭代去噪,每一步都

n8n 创建多维表格犯的错误

作者 jiguanghover
2025年8月12日 14:56

n8n创建的多维表格在应用的身份下,而我在同步数据的时候用的是用户身份下创建的多维表格,所以使用用户身份下多维表格的表格token 跟表格id是一直报错的。

1.在用户身份下创建应用并发布后,打开n8n创建多维表格并添加AI News 信息;

image.png 2. n8n中的流程图内容都是一样的,重点说一下飞书节点的创建;

  1. 首先用n8n创建一个多维表格,然后用url地址在浏览器中打开;

image.png 4. 然后列出字段,并记录下字段的id值;

image.png 5. 更新字段,因为AI news 需要的字段为author,title,description,url,publishedAt, 所以更新文本字段为title,更新单选字段为description,更新日期字段为url;

image.png

  1. 以上内容完成后 “新增记录”,添加news 到多维表格中

image.png "新增记录"中的请求体json: { "fields": {"title": "{{ $("Code").item.json.title }}", "description": "{{ $("Code").item.json.description }}", "url": "{{ $("Code").item.json.url }}" } }

  1. Code中的JavaScript内容
const items = [];
const inputData = $input.first().json;
const articles = inputData.articles || []; 

// 数据清理函数
function cleanString(str) {
    if (!str) return '';
    return str
        .replace(/[\r\n\t]/g' ')  // 替换换行符、回车符、制表符
        .replace(/[\u0000-\u001F\u007F]/g''// 去除控制字符
        .replace(/"/g'\"')  // 转义双引号
        .trim();
}


for (const article of articles) {
    items.push({
        authorcleanString(article.author),
        titlecleanString(article.title),
        descriptioncleanString(article.description),
        urlcleanString(article.url)
       
    });
}

return items;

  1. 结果内容(问题是前10条未添加内容):

image.png

Rust 性能提升“最后一公里”:详解 Profiling 瓶颈定位与优化|得物技术

作者 得物技术
2025年8月12日 13:49

一、Profiling:揭示性能瓶颈的“照妖镜”

在过去的一年里,我们团队完成了一项壮举:将近万核的 Java 服务成功迁移到 Rust,并收获了令人瞩目的性能提升。我们的实践经验已在《RUST练习生如何在生产环境构建万亿流量》一文中与大家分享。然而,在这次大规模迁移中,我们观察到一个有趣的现象:大多数服务在迁移后性能都得到了显著提升,但有那么一小部分服务,性能提升却不尽如人意,仅仅在 10% 左右徘徊。

这让我们感到疑惑。明明已经用上了性能“王者”Rust,为什么还会遇到瓶颈?为了解开这个谜团,我们决定深入剖析这些“低提升”服务。今天,我就来和大家分享,我们是如何利用 Profiling 工具,找到并解决写入过程中的性能瓶颈,最终实现更高性能飞跃的!

在性能优化领域,盲目猜测是最大的禁忌。你需要一把锋利的“手术刀”,精准地找到问题的根源。在 Rust 生态中,虽然不像 Java 社区那样拥有 VisualVM 或 JProfiler 这类功能强大的成熟工具,但我们依然可以搭建一套高效的性能分析体系。

为了在生产环境中实现高效的性能监控,我们引入了 Jemalloc 内存分配器和 pprof CPU 分析器。这套方案不仅支持定时自动生成 Profile 文件,还可以在运行时动态触发,极大地提升了我们定位问题的能力。

二、配置项目:让Profiling“武装到牙齿”

首先,我们需要在 Cargo.toml 文件中添加必要的依赖,让我们的 Rust 服务具备 Profiling 的能力。以下是我们的配置,Rust 版本为 1.87.0。

[target.'cfg(all(not(target_env = "msvc"), not(target_os = "windows")))'.dependencies]
# 使用 tikv-jemallocator 作为内存分配器,并启用性能分析功能
tikv-jemallocator = { version = "0.6", features = ["profiling""unprefixed_malloc_on_supported_platforms"] }
# 用于在运行时控制和获取 jemalloc 的统计信息
tikv-jemalloc-ctl = { version = "0.6", features = ["use_std""stats"] }
# tikv-jemallocator 的底层绑定,同样启用性能分析
tikv-jemalloc-sys = { version = "0.6", features = ["profiling"] }
# 用于生成与 pprof 兼容的内存剖析数据,并支持符号化和火焰图
jemalloc_pprof = { version = "0.7", features = ["symbolize","flamegraph"] }
# 用于生成 CPU 性能剖析数据和火焰图
pprof = { version = "0.14", features = ["flamegraph""protobuf-codec"] }

简单来说,这几个依赖各司其职:

※ tikv-jemallocator

基于 jemalloc 的 Rust 实现,以其高效的内存管理闻名。

※ jemalloc_pprof

负责将 jemalloc 的内存剖析数据转换成标准的 pprof 格式。

※ pprof

用于 CPU 性能分析,可以生成 pprof 格式的 Profile 文件。

三、  全局配置:启动Profiling开关

接下来,在 main.rs 中进行全局配置,指定 Jemalloc 的 Profiling 参数,并将其设置为默认的全局内存分配器。

// 配置 Jemalloc 内存分析参数
#[export_name = "malloc_conf"]
pub static malloc_conf: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:16\0";


#[cfg(not(target_env = "msvc"))]
use tikv_jemallocator::Jemalloc;


// 将 Jemalloc 设置为全局内存分配器
#[cfg(not(target_env = "msvc"))]
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;

这段配置中的 lg_prof_sample:16 是一个关键参数。

它表示 jemalloc 会对大约每 2^16 字节(即 64KB)的内存分配进行一次采样。这个值越大,采样频率越低,内存开销越小,但精度也越低;反之则精度越高,开销越大。在生产环境中,我们需要根据实际情况进行权衡。

四、实现Profile生成函数:打造你的“数据采集器”

我们将 Profile 文件的生成逻辑封装成异步函数,这样就可以在服务的任意时刻按需调用,非常灵活。

内存Profile生成函数

#[cfg(not(target_env = "msvc"))]
async fn dump_memory_profile() -> Result<StringString> {
    // 获取 jemalloc 的 profiling 控制器
    let prof_ctl = jemalloc_pprof::PROF_CTL.as_ref()
        .ok_or_else(|| "Profiling controller not available".to_string())?;


    let mut prof_ctl = prof_ctl.lock().await;
    
    // 检查 profiling 是否已激活
    if !prof_ctl.activated() {
        return Err("Jemalloc profiling is not activated".to_string());
    }
   
    // 调用 dump_pprof() 方法生成 pprof 数据
    let pprof_data = prof_ctl.dump_pprof()
        .map_err(|e| format!("Failed to dump pprof: {}", e))?;


    // 使用时间戳生成唯一文件名
    let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
    let filename = format!("memory_profile_{}.pb", timestamp);


    // 将 pprof 数据写入本地文件
    std::fs::write(&filename, pprof_data)
        .map_err(|e| format!("Failed to write profile file: {}", e))?;


    info!("Memory profile dumped to: {}", filename);
    Ok(filename)
}

CPU Profile生成函数

类似地,我们使用 pprof 库来实现 CPU Profile 的生成。

#[cfg(not(target_env = "msvc"))]
async fn dump_cpu_profile() -> Result<String, String> {
    use pprof::ProfilerGuard;
    use pprof::protos::Message;


    info!("Starting CPU profiling for 60 seconds...");


    // 创建 CPU profiler,设置采样频率为 100 Hz
    let guard = ProfilerGuard::new(100).map_err(|e| format!("Failed to create profiler: {}", e))?;


    // 持续采样 60 秒
    tokio::time::sleep(std::time::Duration::from_secs(60)).await;


    // 生成报告
    let report = guard.report().build().map_err(|e| format!("Failed to build report: {}", e))?;


    // 使用时间戳生成文件名
    let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
    let filenameformat!("cpu_profile_{}.pb", timestamp);


    // 创建文件并写入 pprof 数据
    let mut file = std::fs::File::create(&filename)
        .map_err(|e| format!("Failed to create file: {}", e))?;


    report.pprof()
        .map_err(|e| format!("Failed to convert to pprof: {}", e))?
        .write_to_writer(&mut file)
        .map_err(|e| format!("Failed to write profile: {}", e))?;


    info!("CPU profile dumped to: {}", filename);
    Ok(filename)
}
  •  ProfilerGuard::new()   100  Hz 意味着每秒钟会随机中断程序 100 次,以记录当前正在执行的函数调用栈
  • tokio::time::sleep(std::time::Duration::from_secs(60)).await 表示 pprof 将会持续采样 60 秒钟
  •  guard.report().build() 这个方法用于将收集到的所有采样数据进行处理和聚合,最终生成一个 Report 对象。这个 Report 对象包含了所有调用栈的统计信息,但还没有转换成特定的文件格式
  •  report.pprof() 这是 Report 对象的一个方法,用于将报告数据转换成 pprof 格式

五、 触发和使用 Profiling:随时随地捕捉性能数据

有了上述函数,我们实现了两种灵活的触发方式。

※ 定时自动生成

通过异步定时任务,每隔一段时间自动调用 dump_memory_profile() 和  dump_cpu_profile() 。

fn start_profilers() {
    // Memory profiler
    tokio::spawn(async {
        let mut interval = tokio::time::interval(std::time::Duration::from_secs(300));
        loop {
            interval.tick().await;
            #[cfg(not(target_env = "msvc"))]
            {
                info!("Starting memory profiler...");
                match dump_memory_profile().await {
                    Ok(profile_path) => info!("Memory profile dumped successfully: {}", profile_path),
                    Err(e) => info!("Failed to dump memory profile: {}", e),
                }
            }
        }
    });
    // 同理可以实现CPU profiler
}

※ 手动 HTTP 触发

通过提供 /profile/memory 和 /profile/cpu 两个 HTTP 接口,可以随时按需触发 Profile 文件的生成。

async fn trigger_memory_profile() -> Result<impl warp::Reply, std::convert::Infallible> {
    #[cfg(not(target_env = "msvc"))]
    {
        info!("HTTP triggered memory profile dump...");
        match dump_memory_profile().await {
            Ok(profile_path) => Ok(warp::reply::with_status(
                format!("Memory profile dumped successfully: {}", profile_path),
                warp::http::StatusCode::OK,
            )),
            Err(e) => Ok(warp::reply::with_status(
                format!("Failed to dump memory profile: {}", e),
                warp::http::StatusCode::INTERNAL_SERVER_ERROR,
            )),
        }
    }
}
//同理也可实现trigger_cpu_profile()函数
fn profile_routes() -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
    let memory_profile = warp::post()
        .and(warp::path("profile"))
        .and(warp::path("memory"))
        .and(warp::path::end())
        .and_then(trigger_memory_profile);
    
    
    let cpu_profile = warp::post()
        .and(warp::path("profile"))
        .and(warp::path("cpu"))
        .and(warp::path::end())
        .and_then(trigger_cpu_profile);
    memory_profile.or(cpu_profile)
}

现在,我们就可以通过 curl 命令,随时在生产环境中采集性能数据了:

curl -X POST http://localhost:8080/profile/memory
curl -X POST http://localhost:8080/profile/cpu

生成的 .pb 文件,我们就可以通过 go tool pprof 工具,启动一个交互式 Web UI,在浏览器中直观查看调用图、火焰图等。

go tool pprof -http=localhost:8080 ./target/debug/otel-storage ./otel_storage_cpu_profile_20250806_032509.pb

六、性能剖析:火焰图下的“真相”

通过 go tool pprof 启动的 Web UI,我们可以看到程序的火焰图

如何阅读火焰图

※ 顶部: 代表程序的根函数。

※ 向下延伸; 子函数调用关系。

※ 火焰条的宽度: 代表该函数在 CPU 上消耗的时间。宽度越宽,消耗的时间越多,越可能存在性能瓶颈

CPU Profile

Memory Profile

在我们的 CPU 火焰图中,一个令人意外的瓶颈浮出水面:OSS::new 占用了约 19.1% 的 CPU 时间。深入分析后发现, OSS::new 内部的 TlsConnector 在每次新建连接时都会进行 TLS 握手,这是导致 CPU 占用过高的根本原因。

原来,我们的代码在每次写入 OSS 时,都会新建一个 OSS 实例,随之而来的是一个全新的 HTTP 客户端和一次耗时的 TLS 握手。尽管 oss-rust-sdk 内部有连接池机制,但由于我们每次都创建了新实例,这个连接池根本无法发挥作用!

七、优化方案:从“每次新建”到“共享复用”

问题的核心在于重复创建 OSS 实例。我们的优化思路非常清晰:复用 OSS 客户端实例,避免不必要的 TLS 握手开销

优化前

每次写入都新建 OSS 客户端。

fn write_oss() {
    // 每次写入都新建一个OSS实例
    let oss_instancecreate_oss_client(oss_config.clone());
    tokio::spawn(async move {
        // 获取写入偏移量、文件名
        // 构造OSS写入所需资源和头信息
        // 写入OSS
        let result = oss_instance
            .append_object(data, file_name, headers, resources)
            .await;
}
fn create_oss_client(config: OssWriteConfig) -> OSS {
    OSS::new(
    ……
    )
}

这种方案在流量较小时可能问题不大,但在万亿流量的生产环境中,频繁的实例创建会造成巨大的性能浪费。

优化前

※ 共享实例

让每个处理任务( DecodeTask )持有 Arc 共享智能指针,确保所有写入操作都使用同一个 OSS 实例。

let oss_client = Arc::new(create_oss_client(oss_config.clone()));
let oss_instance = self.oss_client.clone()
// ...
let result = oss_instance
    .append_object(data, file_name, headers, resources)
    .await;

※ 自动重建机制

为了应对连接失效或网络问题,我们引入了自动重建机制。当写入次数达到阈值或发生写入失败时,我们会自动创建一个新的 OSS 实例来替换旧实例,从而保证服务的健壮性。

// 使用原子操作确保多线程环境下的计数安全
let write_countself.oss_write_count.load(std::sync::atomic::Ordering::SeqCst);
let failure_countself.oss_failure_count.load(std::sync::atomic::Ordering::SeqCst);


// 检查是否需要重建实例...
fn recreate_oss_client(&mut self) {
 
    let new_oss_client = Arc::new(create_oss_client(self.oss_config.clone()));
    self.oss_client = new_oss_client;
    self.oss_write_count.store(0, std::sync::atomic::Ordering::SeqCst);
    self.oss_failure_count.store(0, std::sync::atomic::Ordering::SeqCst);
    // 记录OSS客户端重建次数指标
    OSS_CLIENT_RECREATE_COUNT
        .with_label_values(&[])
        .inc();
    info!("OSS client recreated");
}

八、优化效果:性能数据“一飞冲天”

优化后的服务上线后,我们观察到了显著的性能提升。

CPU 资源使用率

同比下降约 20%

OSS 写入耗时

同比下降约 17.2% ,成为集群中最短的写入耗时。

※ OSS写入耗时

※ OSS相关资源只占千分之一

内存使用率

平均下降 8.77% ,这部分下降可能也得益于我们将内存分配器从 mimalloc 替换为 jemalloc 的综合效果。

这次优化不仅解决了特定服务的性能问题,更重要的是,它验证了在 Rust 中通过 Profiling 工具进行深度性能分析的可行性。即使在已经实现了初步性能提升的 Rust 服务中,仍然存在巨大的优化空间。

未来,我们将继续探索更高效的 Profiling 方案,并深入挖掘其他潜在的性能瓶颈,以在万亿流量的生产环境中实现极致的性能和资源利用率。

引用

往期回顾

1.Valkey 单点性能比肩 Redis 集群了?Valkey8.0 新特性分析|得物技术

2.Java volatile 关键字到底是什么|得物技术

3.社区搜索离线回溯系统设计:架构、挑战与性能优化|得物技术

4.正品库拍照PWA应用的实现与性能优化|得物技术

5.得物社区活动:组件化的演进与实践

文 / 炯帆 南风

关注得物技术,每周更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

面试官:为什么文件上传时要用 MD5 重命名,而不是时间戳❓❓❓

作者 Moment
2025年8月12日 09:00

最近在出一个前端的体系课程,里面的内容非常详细,如果你感兴趣,可以加我 v 进行联系 yunmz777:

image.png

浪费你几秒钟时间,内容正式开始

在文件上传场景中,文件标识的生成方式直接影响到唯一性、完整性验证以及安全性等多个方面。相比直接使用时间戳,基于文件内容生成的 MD5 哈希值在技术和安全性上都有明显优势。下面将从几个维度对比分析 MD5 与 时间戳 的优缺点,并解释为何 MD5 更适合用于文件上传。

1. 文件唯一性与内容验证

MD5 的哈希值基于文件内容生成,不同内容必然生成不同值,相同内容生成相同值,因此能确保文件标识的唯一性,并可直接用于文件去重和完整性验证。即使文件名相同,只要内容不同,MD5 值也会不同;反之,内容相同则 MD5 值一致。

而时间戳仅记录上传时间(通常精确到毫秒),与文件内容无关,无法判断内容是否一致。高并发场景下,两个用户几乎同时上传相同文件时,即使时间戳不同,文件依然可能重复;文件内容变更较大但时间戳变化很小的情况,也无法通过时间戳准确检测。

如下代码所示:

import fs from "fs";
import crypto from "crypto";

const FILE_PATH = "./moment.json";

// 计算文件 MD5
function calcFileMD5(filePath) {
  const buffer = fs.readFileSync(filePath);
  return crypto.createHash("md5").update(buffer).digest("hex");
}

const md5 = calcFileMD5(FILE_PATH);
console.log(`${FILE_PATH} => ${md5}`);

可以看到,相同的内容,它的 hash 值是相同的:

20250812084809

借助我们可以修改一下内容,加一个 ! 的符号,你会发现它的内容是变了的:

20250812084901

可以看到,哪怕只对文件内容做了极小的改动(如增加一个字符),计算得到的 MD5 哈希值也会完全不同。这种对输入微小变化的高度敏感性,称为哈希算法的“雪崩效应”,也是其在内容验证中有效性的关键原因。

2. 防止文件名冲突

MD5 基于文件内容生成哈希值,即使文件名相同,只要内容不同,MD5 值也会不同,从而确保每个文件都有唯一标识,避免覆盖或丢失。

而时间戳虽能降低冲突概率,但在高并发场景下(尤其是几乎同时上传相同文件时)仍可能重复。更重要的是,时间戳与文件内容无关,无法区分内容相同且上传时间一致的文件,因此仍有冲突或覆盖风险。

3. 文件验证与完整性检查

MD5 可用于文件上传后的完整性验证:服务器接收文件后重新计算其 MD5,并与客户端提供的值比对,一致则说明文件在传输过程中未被篡改或损坏;不一致则可拒绝文件或提示错误,从而保证数据的正确性。

而时间戳仅记录上传时间,与文件内容无关,即使文件在传输中被修改,时间戳也不会变化,因此无法提供有效的完整性校验,更无法确保内容一致性。

4. 跨系统兼容性

MD5 生成固定长度的 32 位十六进制字符串,跨平台计算结果一致,不受操作系统、编程语言或框架影响。在分布式系统、缓存管理、文件去重等场景中,可稳定确保文件唯一性与一致性。

而时间戳可能受时区、系统时间精度及服务器同步状态影响,在不同地区或系统中结果可能不一致;且其本身无法反映文件内容的唯一性,因此在跨系统或跨时区应用中更易出现冲突与不一致问题。

5. 跨域访问与缓存控制

MD5 基于文件内容生成,内容变化即 MD5 变化,能精准反映更新情况。在 CDN 加速 和 浏览器缓存 中,这种特性可确保仅在内容更新时才重新加载资源,避免不必要的带宽消耗和缓存失效,从而提升加载速度和资源利用率。

而时间戳仅与上传时间相关,无法准确反映文件内容。当时间戳变化但内容未变时,缓存系统会误判为新文件,导致重复请求和带宽浪费;反之,内容变了但时间戳没变,则可能造成缓存未及时更新。

6. 避免文件内容泄露

MD5 属于单向哈希算法,即使获取到哈希值,也难以直接反推出原始内容,因此在一定程度上可防止文件内容泄露。

而时间戳虽然本身无直接隐私风险,但若与用户身份、文件名等信息结合,可能间接暴露上传时间和用户行为;同时它不具备 MD5 这种基于内容的加密与隐藏能力。

总结

MD5 基于文件内容生成哈希值,能精准反映文件的唯一性,即使文件名相同,只要内容不同,MD5 值也会不同,从而有效避免重名、重复内容和冲突。同时,MD5 可在文件传输后进行完整性验证,确保文件在传输过程中未被损坏或篡改。此外,MD5 具有跨平台一致性和良好的缓存控制特性,在分布式系统、缓存管理等场景中能保持高效与稳定。

相比之下,时间戳只是记录文件的上传时间,既无法保证文件内容的唯一性,也不能验证文件完整性;在高并发下还可能出现冲突或误判,因此更适合用作上传记录,而非文件内容的唯一标识。

结论:在需要唯一标识和完整性验证的文件上传场景中,应优先使用 MD5;时间戳可作为辅助信息存储上传时间,但不能替代内容标识。值得一提的是,基于 MD5 的文件指纹机制还可以实现 “秒传” —— 上传前客户端先计算文件 MD5 并发送给服务器,若服务器已存在相同 MD5 的文件,则直接返回上传成功,省去重复传输的过程,从而显著节省带宽并提升上传速度。

源码安装 Nginx 并加载第三方模块指南

作者 子洋
2025年8月12日 08:23

Nginx 安装

一、安装所需依赖

sudo apt-get install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev perl libperl-dev libxslt-dev

这些依赖是编译 nginx 以及可选模块(如 gzip、ssl、perl、xslt 等)所需要的。

  • build-essential:提供 gccmake 等基本编译工具
  • libpcre3libpcre3-dev:支持正则表达式处理
  • zlib1gzlib1g-dev:提供对 gzip 压缩的支持
  • libssl-dev:启用 https 所需
  • perllibperl-dev:为 http_perl_module 模块准备
  • libxslt-dev:支持 http_xslt_module

二、下载 Nginx 源码包

你可以访问官网 nginx.org/en/download… 查找最新版本号。以下以 1.28.0 为例:

wget http://nginx.org/download/nginx-1.28.0.tar.gz 
tar -zxvf nginx-1.28.0.tar.gz 
cd nginx-1.28.0

注意:

优先使用稳定版(Stable version),主线版本(Mainline version)虽然有新特性但风险更高,你可以替换 <version> 来下载其他版本。

三、执行 ./configure 配置构建参数

在源码编译中,./configure 是最关键的一步,负责:

  • 定义 nginx 的路径结构(配置路径、pid 路径、日志路径等)
  • 决定是否开启模块(比如:gzip、ssl、http_v2 等)
  • 设置编译优化参数(cc-opt, ld-opt

以下为参考 Ubuntu 官方包构建方式的参数(移除了部分不需要的模块):

./configure \
  # 编译器优化参数(调试符号、路径映射、防御编译策略等)
  --with-cc-opt='-g -O2 -Werror=implicit-function-declaration -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=/build/nginx-lUDsEK/nginx-1.26.3=. -flto=auto -ffat-lto-objects -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -mbranch-protection=standard -fdebug-prefix-map=/build/nginx-lUDsEK/nginx-1.26.3=/usr/src/nginx-1.26.3-2ubuntu1.1 -fPIC -Wdate-time -D_FORTIFY_SOURCE=3' \
  --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -Wl,-z,relro -Wl,-z,now -fPIC' \

  # 基础路径配置
  --prefix=/usr/share/nginx \
  --sbin-path=/usr/sbin \
  --conf-path=/etc/nginx/nginx.conf \
  --http-log-path=/var/log/nginx/access.log \
  --error-log-path=stderr \
  --lock-path=/var/lock/nginx.lock \
  --pid-path=/run/nginx.pid \
  --modules-path=/usr/lib/nginx/modules \

  # 临时文件目录配置
  --http-client-body-temp-path=/var/lib/nginx/body \
  --http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
  --http-proxy-temp-path=/var/lib/nginx/proxy \
  --http-scgi-temp-path=/var/lib/nginx/scgi \
  --http-uwsgi-temp-path=/var/lib/nginx/uwsgi \

  # 核心特性
  --with-compat \        # 支持动态模块加载
  --with-debug \         # 启用调试模式
  --with-pcre-jit \      # 提升正则匹配性能
  --with-threads \       # 启用多线程支持

  # HTTP 模块
  --with-http_ssl_module \
  --with-http_stub_status_module \
  --with-http_realip_module \
  --with-http_auth_request_module \
  --with-http_v2_module \
  --with-http_v3_module \
  --with-http_dav_module \
  --with-http_slice_module \
  --with-http_addition_module \
  --with-http_flv_module \
  --with-http_gunzip_module \
  --with-http_gzip_static_module \
  --with-http_mp4_module \
  --with-http_random_index_module \
  --with-http_secure_link_module \
  --with-http_sub_module \

  # Mail & Stream 模块
  --with-mail=dynamic \
  --with-mail_ssl_module \
  --with-stream=dynamic \
  --with-stream_ssl_module \
  --with-stream_ssl_preread_module \
  --with-stream_realip_module \

  # 可选动态模块
  --with-http_perl_module=dynamic \
  --with-http_xslt_module=dynamic

注意:

如果想使用上面的构建参数命令,需要删除所有的注释换行,否则运行会报错。

在官网构建参数的基础上移除了以下模块(因为还需要额外添加依赖,有需要自己安装即可):

--with-http_image_filter_module=dynamic
--with-http_geoip_module=dynamic
--with-stream_geoip_module=dynamic

运行成功如下图所示:

四、构建与安装

# 创建 nginx 运行需要的目录`
mkdir -p /var/lib/nginx/

# 编译
make -j$(nproc)

# 安装到指定路径
sudo make install

运行 Nginx 服务

在执行 ./configure 时,可以通过指定 --sbin-path=/usr/sbin 参数来设置 nginx 可执行文件的安装路径,从而使系统能够全局调用 nginx 命令(无需额外配置 PATH)。

如果在构建时未指定该参数,默认情况下 nginx 可执行文件会被安装到 ./objs/nginx 中。此时你可以手动将其复制到系统可执行目录:

sudo cp objs/nginx /usr/sbin/

检测配置文件是否正确:

nginx -t

启动 nginx:

nginx

测试是否启动成功:

curl 127.0.0.1

查看完整的构建参数:

nginx -V 2>&1 | awk -F: '/configure arguments/ {print $2}' | xargs -n1

加载第三方模块

Nginx 支持两种模块集成方式:

1. 编译时静态集成

./configure --add-module=模块路径

2. 动态模块

./configure --add-dynamic-module=模块路径

动态模块 .so 需要通过 load_module 在配置文件中显式加载。

集成 echo-nginx-module

echo-nginx-module 是由 OpenResty 团队开发的调试模块,可在配置文件中直接返回文本、变量等内容,适合用于测试、演示、调试。

方法一:静态编译进 nginx

# 下载源码
git clone https://github.com/openresty/echo-nginx-module.git
# 执行编译配置
./configure --add-module=./echo-nginx-module
# 先关掉 nginx 服务
nginx -s quit
# 编译并安装
make && sudo make install

编译并安装后,修改 nginx.conf 配置文件:

worker_processes  1;
events {
    worker_connections  1024;
}

http {
  include       mime.types;
  default_type  application/octet-stream;
  sendfile        on;
  keepalive_timeout  65;

  server {
    listen 8080;

    location / {
      add_header Content-Type text/html;
      echo "Hello, this is the echo module! Cureent Time: $time_local";
    }
  }
}

重启 Nginx 后访问 http://127.0.0.1:8080,将看到如下输出:

方法二:构建为动态模块(.so)

Nginx 1.9.11+ 开始支持 --add-dynamic-module,生成 .so 文件,在运行时通过 load_module 加载。

./configure --add-dynamic-module=./echo-nginx-module
make && sudo make install

找到 objs/ngx_http_echo_module.so,并移动到 nginx 模块目录:

sudo cp objs/ngx_http_echo_module.so /usr/lib/nginx/modules/

修改配置文件,在文件首行添加模块加载语句:

load_module /usr/lib/nginx/modules/ngx_http_echo_module.so;

worker_processes  1;
events {
    worker_connections  1024;
}

http {
  include       mime.types;
  default_type  application/octet-stream;
  sendfile        on;
  keepalive_timeout  65;

  server {
    listen 8080;

    location / {
      add_header Content-Type text/html;
      echo "Hello, this is the echo module! Cureent Time: $time_local";
    }
  }
}

重新加载配置后即可访问:

nginx -s reload

相关链接

❌
❌