<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>柴少的官方网站</title><link>https://blog.51niux.com/</link><description>技术在学习中进步，水平在分享中升华</description><item><title>Jenkins基础补充(五)</title><link>https://blog.51niux.com/?id=331</link><description>&lt;p&gt;#距离jenkins系统文章已经过去了N多年,jenkins版本及使用方法已经发生了很大改变,这里借这篇文章回顾一下基础知识,起到承上启下的作用,基础牢固者可略过&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;一、Scaling Jenkins(扩展Jenkins)&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;官网文档：&lt;a href=&quot;https://www.jenkins.io/doc/book/scaling/&quot; _src=&quot;https://www.jenkins.io/doc/book/scaling/&quot;&gt;https://www.jenkins.io/doc/book/scaling/&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.1 jenkins目录结构&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#之前没有讲jenkins的目录结构,这里就以当前最新版2.541.1举例&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2026/02/202602051770285837986254.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;#先介绍文件&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;config.xml&amp;nbsp;#Jenkins全局核心配置文件,包含Jenkins运行的基础配置:端口、工作目录、安全策略、全局工具配置、代理设置等,修改后重启Jenkins生效
hudson.model.UpdateCenter.xml&amp;nbsp;#插件更新中心配置,定义Jenkins插件下载的源地址默认官方源
hudson.plugins.emailext.ExtendedEmailPublisher.xml&amp;nbsp;#扩展邮件插件(Email&amp;nbsp;Ext)全局配置,配置邮件发送的全局参数
hudson.plugins.git.GitTool.xml&amp;nbsp;#Git工具的全局配置
identity.key.enc&amp;nbsp;#身份加密密钥,Jenkins用于加密身份信息(如账号密码、远程服务器密钥)的核心文件
jenkins.install.InstallUtil.lastExecVersion&amp;nbsp;#Jenkins最后执行的版本标识
jenkins.install.UpgradeWizard.state&amp;nbsp;#升级向导状态文件,避免重复触发升级流程
jenkins.model.JenkinsLocationConfiguration.xml&amp;nbsp;#Jenkins位置配置,定义Jenkins的访问地址、节点通信等的基础地址配置
jenkins.telemetry.Correlator.xml&amp;nbsp;#Jenkins遥测配置,记录Jenkins的遥测标识，用于向官方上报基础运行数据
nodeMonitors.xml&amp;nbsp;#节点监控配置,用于节点健康检查
org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml&amp;nbsp;#流水线执行列表配置,记录Pipeline的运行实例状态、执行记录关联信息，用于流水线历史查询、断点续跑
queue.xml&amp;nbsp;#任务队列配置,记录Jenkins等待执行的任务队列(如触发了多个Job但执行器不足时的排队信息)，重启Jenkins后队列不会丢失
secret.key&amp;nbsp;#Jenkins核心加密密钥,用于加密所有敏感数据(如密码、令牌、私钥),是Jenkins安全的核心文件,丢失后所有加密数据将无法解密
secret.key.not-so-secret&amp;nbsp;#辅助加密密钥(非敏感),配合secret.key使用的轻量密钥，无敏感信息，仅做加密补充&lt;/pre&gt;&lt;p&gt;#然后再介绍下目录&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;caches&amp;nbsp;#Jenkins缓存目录,存储插件、更新中心、依赖包的下载缓存，减少重复下载；可手动删除，重启Jenkins后会重新生成，不影响核心功能
jobs&amp;nbsp;#所有Job(任务)的核心存储目录,每个Job对应一个子目录,子目录内包含：
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#1.config.xml:该Job的专属配置(构建步骤、触发器、源码地址等)；
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#2.builds:该Job的所有构建历史(按构建号命名，包含日志、产物、报告）；
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#3.nextBuildNumber:&amp;nbsp;当前构建到的BuildId
logs&amp;nbsp;#Jenkins系统日志目录,存储Jenkins的运行日志、节点通信日志、插件日志&amp;nbsp;
plugins&amp;nbsp;#插件安装目录,&amp;nbsp;核心目录,每个已安装的插件对应一个子目录(插件名+版本)，子目录内包含插件的jar包、配置文件、依赖；
secrets&amp;nbsp;#敏感信息存储目录,存储Jenkins的所有敏感数据
updates&amp;nbsp;#插件更新缓存目录,存储插件更新中心的索引、插件版本列表、更新包缓存，配合UpdateCenter.xml使用，用于插件的检查更新、一键安装&amp;nbsp;&amp;nbsp;&amp;nbsp;
userContent&amp;nbsp;#Jenkins自定义静态资源目录、可存放自定义的静态文件(如图片、HTML、报告模板)，通过JenkinsURL/userContent/[文件名]可直接访问
users&amp;nbsp;#Jenkins用户信息目录,每个用户对应一个子目录（以用户ID&amp;nbsp;命名），子目录内包含config.xml（用户信息：昵称、邮箱、权限、加密后的密码）
war&amp;nbsp;#Jenkins&amp;nbsp;WAR包解压目录,包含核心前端页面、后端类文件、默认配置；不可随意修改，修改后可能导致&amp;nbsp;Jenkins&amp;nbsp;启动失败
workspace&amp;nbsp;#Job默认工作空间目录,每个Job的构建会在workspace/[Job名]下拉取源码、执行构建命令、生成中间产物&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.2&amp;nbsp;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJQbFdCdzVWOU9LJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJHbG9zc2FyeSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiU4RCVFNiVCMSU4NyVFOCVBMSVBOCUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTVCJTVEJTIyJTJDJTIybWF0Y2hlcyUyMiUzQSU1QiU1RCUyQyUyMnRyYW5zbGF0ZWRCeSUyMiUzQSU3QiUyMnR5cGUlMjIlM0ElMjJ1c2VyX2FpJTIyJTJDJTIyaWQlMjIlM0ElMjJkZWVwc2Vlay12MyUyMiUyQyUyMnJlYXNvbiUyMiUzQSUyMnNob3J0cXVlcnkyNTAxJTJGY2FjaGVkJTIyJTdEJTJDJTIyYWRhcHRlZE1hdGNoZXMlMjIlM0ElN0IlMjJ0ZXJtcyUyMiUzQSU1QiU1RCUyQyUyMm1lbXMlMjIlM0ElNUIlNUQlN0QlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMmJLVGVybURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyR2xvc3NhcnklMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve; background-color: #CCC1D9;&quot;&gt;Glossary(词汇表)&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJQbFdCdzVWOU9LJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJHbG9zc2FyeSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiU4RCVFNiVCMSU4NyVFOCVBMSVBOCUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTVCJTVEJTIyJTJDJTIybWF0Y2hlcyUyMiUzQSU1QiU1RCUyQyUyMnRyYW5zbGF0ZWRCeSUyMiUzQSU3QiUyMnR5cGUlMjIlM0ElMjJ1c2VyX2FpJTIyJTJDJTIyaWQlMjIlM0ElMjJkZWVwc2Vlay12MyUyMiUyQyUyMnJlYXNvbiUyMiUzQSUyMnNob3J0cXVlcnkyNTAxJTJGY2FjaGVkJTIyJTdEJTJDJTIyYWRhcHRlZE1hdGNoZXMlMjIlM0ElN0IlMjJ0ZXJtcyUyMiUzQSU1QiU1RCUyQyUyMm1lbXMlMjIlM0ElNUIlNUQlN0QlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMmJLVGVybURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyR2xvc3NhcnklMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;基本词组：&lt;/span&gt;&lt;/strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJQbFdCdzVWOU9LJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJHbG9zc2FyeSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiU4RCVFNiVCMSU4NyVFOCVBMSVBOCUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTVCJTVEJTIyJTJDJTIybWF0Y2hlcyUyMiUzQSU1QiU1RCUyQyUyMnRyYW5zbGF0ZWRCeSUyMiUzQSU3QiUyMnR5cGUlMjIlM0ElMjJ1c2VyX2FpJTIyJTJDJTIyaWQlMjIlM0ElMjJkZWVwc2Vlay12MyUyMiUyQyUyMnJlYXNvbiUyMiUzQSUyMnNob3J0cXVlcnkyNTAxJTJGY2FjaGVkJTIyJTdEJTJDJTIyYWRhcHRlZE1hdGNoZXMlMjIlM0ElN0IlMjJ0ZXJtcyUyMiUzQSU1QiU1RCUyQyUyMm1lbXMlMjIlM0ElNUIlNUQlN0QlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMmJLVGVybURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyR2xvc3NhcnklMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Agent&amp;nbsp;#代理,执行任务的节点
Artifact&amp;nbsp;#这次构建任务，最后产出的&amp;quot;成品文件&amp;quot;
Build&amp;nbsp;#构建,作业单次执行的结果
Cloud&amp;nbsp;#Cloud(动态代理节点)就是让Jenkins能&amp;quot;自动创建、用完就删&amp;quot;的代理节点
Controller&amp;nbsp;#jenkins主节点也就是服务主控节点本身
Core&amp;nbsp;#jenkins的应用程序
Downstream&amp;nbsp;#下游,被当前任务自动触发的任务
Executor&amp;nbsp;#执行数,一个节点能同时执行任务的数量
Fingerprint&amp;nbsp;#指纹,给Jar/文件生成唯一指纹(唯一哈希值)，用来追踪：这个文件是哪次构建生成的、被哪些任务用过。
Folder&amp;nbsp;#用来给Jenkins任务分类、放一起的文件夹
Item&amp;nbsp;#&amp;nbsp;Jenkins里点[新建任务]出来的所有东西，全都叫Item
Jenkins&amp;nbsp;URL&amp;nbsp;#用户访问的jenkins应用程序的主url
Job&amp;nbsp;#Jenkins里真正用来跑构建、打包、部署的任务
Kubernetes&amp;nbsp;#就是你理解的K8S
Label&amp;nbsp;#给节点打&amp;quot;标签/分组标记&amp;quot;，用来分配任务跑在哪个节点上面
LTS&amp;nbsp;#长期稳定版
Node&amp;nbsp;#能被Jenkins用来跑任务的机器，都叫Node。
Project&amp;nbsp;#一个被废弃的术语,与job同义
Pipeline&amp;nbsp;#用代码写的自动化部署流程
Plugin&amp;nbsp;#插件,功能扩展包
Publisher&amp;nbsp;#构建跑完之后，才执行的操作
Resource&amp;nbsp;Root&amp;nbsp;URL&amp;nbsp;#专门放静态文件的&amp;quot;副域名&amp;quot;
Release&amp;nbsp;#版本上线
Stage&amp;nbsp;#阶段,pipeline流程里面的每个阶段
Step&amp;nbsp;#步骤,上面阶段里面的每一小步操作
Trigger&amp;nbsp;#自动启动任务的开关,满足某个条件，Jenkins自己跑Job
Update&amp;nbsp;Center&amp;nbsp;#更新中心
Upstream&amp;nbsp;#上游任务,触发当前任务的前一个任务
View&amp;nbsp;#视图,自定义的任务列表
Workspace&amp;nbsp;#工作区,Job运行时,代码拉下来、打包、生成jar所在的那个文件夹&lt;/pre&gt;&lt;h2&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJQbFdCdzVWOU9LJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJHbG9zc2FyeSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiU4RCVFNiVCMSU4NyVFOCVBMSVBOCUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTVCJTVEJTIyJTJDJTIybWF0Y2hlcyUyMiUzQSU1QiU1RCUyQyUyMnRyYW5zbGF0ZWRCeSUyMiUzQSU3QiUyMnR5cGUlMjIlM0ElMjJ1c2VyX2FpJTIyJTJDJTIyaWQlMjIlM0ElMjJkZWVwc2Vlay12MyUyMiUyQyUyMnJlYXNvbiUyMiUzQSUyMnNob3J0cXVlcnkyNTAxJTJGY2FjaGVkJTIyJTdEJTJDJTIyYWRhcHRlZE1hdGNoZXMlMjIlM0ElN0IlMjJ0ZXJtcyUyMiUzQSU1QiU1RCUyQyUyMm1lbXMlMjIlM0ElNUIlNUQlN0QlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMmJLVGVybURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyR2xvc3NhcnklMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;二、Pipeline学习&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;官网文档：&lt;a href=&quot;https://www.jenkins.io/doc/book/pipeline/&quot; _src=&quot;https://www.jenkins.io/doc/book/pipeline/&quot;&gt;https://www.jenkins.io/doc/book/pipeline/&lt;/a&gt;&lt;br/&gt;#这里主要是跟着官方文档的结构学习一下Pipeline&lt;br/&gt; &lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.1 Pipeline介绍&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;什么是Pipeline？&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Jenkins本质上是一个支持多种自动化模式的自动化引擎。Pipeline为Jenkins添加了一组强大的自动化工具，支持从简单的持续集成到全面的CD管道的用例。通过对一系列相关任务进行建模，用户可以利用Pipeline的许多功能：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;code(代码)：管道在代码中实现，通常签入源代码管理，使团队能够编辑、审查和迭代他们的交付管道。
Durable(持久)：管道可以在Jenkins控制器的计划内和计划外重启中生存。
Pausable(可暂停)：管道可以选择停止并等待人工输入或批准，然后再继续运行管道。
Versatile(多功能)：管道支持复杂的现实CD需求，包括分叉/连接、循环和并行执行工作的能力。
Extensible(可扩展)：Pipeline插件支持对其DSL(写代码定义任务)的自定义扩展，以及与其他插件集成的多种选项。&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;声明式管道定义&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;在声明性管道语法中，管道块定义了整个管道中完成的所有工作。先来一个最简单的例子：&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;pipeline&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;agent&amp;nbsp;any&amp;nbsp;//必须：指定执行任务的节点（any&amp;nbsp;表示任意可用节点）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stages&amp;nbsp;{&amp;nbsp;//&amp;nbsp;必须：定义流水线的所有阶段
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stage(&amp;#39;Build&amp;#39;){&amp;nbsp;//&amp;nbsp;至少一个stage
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps{&amp;nbsp;//&amp;nbsp;必须：该阶段要执行的具体操作
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;This&amp;nbsp;is&amp;nbsp;Build&amp;quot;&amp;nbsp;//&amp;nbsp;执行命令（如shell、git等）,这里不能为空不然任务会报错
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stage(&amp;#39;Test&amp;#39;){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;This&amp;nbsp;is&amp;nbsp;Test&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stage(&amp;#39;Deploy&amp;#39;){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;This&amp;nbsp;is&amp;nbsp;Deploy&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;#上面就是一个简单的声明式流水线的例子,是一种结构化、易读、面向声明式编程的DSL语法,核心是&amp;quot;声明要做什么&amp;quot;。只需要按照固定的语法结构定义流水线的目标（比如 “拉代码→编译→测试→部署”），Jenkins 会自动处理底层执行逻辑。&lt;/p&gt;&lt;p&gt;声明式管道内置的结构化关键字：&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;pipeline：顶层容器，包裹所有声明式逻辑（必须）
agent：指定流水线执行的节点
stages：所有阶段的容器，至少包含一个&amp;nbsp;stage（必须）
stage：单个阶段（如&amp;nbsp;“拉代码”“测试”），每个&amp;nbsp;stage&amp;nbsp;对应一个业务步骤
steps：单个阶段的具体操作（如执行&amp;nbsp;shell&amp;nbsp;命令、调用&amp;nbsp;maven）
when：条件判断：仅当满足条件时执行该&amp;nbsp;stage（如仅&amp;nbsp;main&amp;nbsp;分支部署）
parameters：定义流水线运行时的参数（如分支名、版本号）
post：后置操作：流水线&amp;nbsp;/&amp;nbsp;阶段结束后执行（如成功通知、失败发邮件）
environment：定义环境变量（如&amp;nbsp;ENV_NAME&amp;nbsp;=&amp;nbsp;&amp;#39;prod&amp;#39;）&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong style=&quot;text-wrap-mode: wrap;&quot;&gt;脚本式管道定义&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;脚本式管道是基于 Groovy 语言 的全功能编程脚本，语法更灵活，更像传统的编程方式，适合需要复杂逻辑控,关注&amp;quot;如何做&amp;quot;。先来一个简单的例子:&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;node(&amp;#39;linux&amp;#39;){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stage(&amp;#39;拉取代码&amp;#39;){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;#39;开始从Git仓库拉取代码...&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;checkout([
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$class:&amp;nbsp;&amp;#39;GitSCM&amp;#39;,&amp;nbsp;//指定Pipeline使用Jenkins&amp;nbsp;Git插件(GitSCM类)来处理Git仓库的代码拉取；
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;branches:&amp;nbsp;[[name:&amp;nbsp;&amp;#39;*/main&amp;#39;]],&amp;nbsp;//&amp;nbsp;你的分支
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userRemoteConfigs:&amp;nbsp;[[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url:&amp;nbsp;&amp;#39;git@git.test.com:apache/seatunnel-web.git&amp;#39;,&amp;nbsp;//&amp;nbsp;SSH格式URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;credentialsId:&amp;nbsp;&amp;#39;5b6bbc50-3c8d-4912-8f92-9eee90d0fa0c&amp;#39;&amp;nbsp;//对应SSH私钥的凭证ID,注意如果指定的node节点有git权限这里可以不加
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;gitTool:&amp;nbsp;&amp;#39;Default&amp;#39;&amp;nbsp;//GitSCM的参数：指定Jenkins配置的Git工具
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;#39;代码拉取完成！&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stage(&amp;#39;编译构建&amp;#39;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;#39;开始执行Maven编译...&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;加载环境变量后执行mvn
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sh&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;source&amp;nbsp;/etc/profile
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;export&amp;nbsp;PATH=$PATH
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mvn&amp;nbsp;-v&amp;nbsp;&amp;nbsp;#&amp;nbsp;先验证Maven版本
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mvn&amp;nbsp;clean&amp;nbsp;package&amp;nbsp;-DskipTests
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;#39;编译构建成功！&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(Exception&amp;nbsp;e)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;编译失败:&amp;nbsp;${e.getMessage()}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error(&amp;#39;构建终止，编译失败！&amp;#39;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;&lt;span data-type=&quot;text&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.2 Blue Ocean介绍&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#官方文档：&lt;a href=&quot;https://www.jenkins.io/doc/book/blueocean/&quot; _src=&quot;https://www.jenkins.io/doc/book/blueocean/&quot;&gt;https://www.jenkins.io/doc/book/blueocean/&lt;/a&gt;&amp;nbsp; &amp;nbsp;#不过看官方文档这个插件不再功能更新了&lt;br/&gt; &lt;/p&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;Blue Ocean是Jenkins 的现代化可视化界面插件，专为Pipeline流水线设计，核心是让CI/CD流程更直观、易用、协作友好。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;#插件安装就不介绍了,就是插件搜索Blue Ocean然后安装下就行,下面是安装后,去job任务点击Blue Ocean之后跳转界面效果：&lt;br/&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2026/03/202603271774605026648512.png&quot; alt=&quot;image.png&quot; width=&quot;1029&quot; height=&quot;502&quot; style=&quot;width: 1029px; height: 502px;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;#左上角的天气是健康图标：&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;太阳:&amp;nbsp;超过80%的运行成功
太阳有乌云：&amp;nbsp;61%&amp;nbsp;to&amp;nbsp;80%的任务运行成功
多云：41%&amp;nbsp;to&amp;nbsp;60%的任务运行成功
下雨：21%&amp;nbsp;to&amp;nbsp;40%的任务运行成功
风暴：少于21%的任务运行成功&lt;/pre&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;&lt;/span&gt;#中间位置点击运行就是开始执行此任务&lt;br/&gt;#下面的状态是执行任务的状态列表信息,其中左边的状态图标分别对应：&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;转圈：进行中
绿色：通过
黄色：不稳定
红色：失败
灰色：已中止&lt;/pre&gt;&lt;p&gt;#然后我们点击一个最近执行的任务就会跳入到详情页：&lt;br/&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2026/03/202603271774605636529231.png&quot; alt=&quot;image.png&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;width: 1px; height: 1px;&quot;/&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2026/03/202603271774605688205034.png&quot; alt=&quot;image.png&quot; width=&quot;1021&quot; height=&quot;515&quot; style=&quot;width: 1021px; height: 515px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#点击那个动作那个动作的圆圈变大,当前界面就显示的哪个动作的日志哦,现在是编译构建圆圈大所以显示的这个动作的日志&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.3 Stage View(完整阶段视图)介绍&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#还是插件选择：Stage View&lt;br/&gt;#这个插件的作用是：直观展示流水线执行流程、快速定位构建失败位置、查看阶段耗时、多分支/多次构建对比、直接进入阶段日志&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2026/03/202603271774608115703428.png&quot; alt=&quot;image.png&quot; width=&quot;1019&quot; height=&quot;574&quot; style=&quot;width: 1019px; height: 574px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Thu, 05 Feb 2026 17:56:56 +0800</pubDate></item><item><title>测试环境按需动态加载(三)</title><link>https://blog.51niux.com/?id=330</link><description>&lt;p&gt;#紧接上文:https://blog.51niux.com/?id=329&amp;nbsp; 我们已经手工创建网关文件并将入口流量进行按需路由&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FAC08F;&quot;&gt;一、自动创建网关配置文件&lt;/span&gt;&lt;br/&gt;&lt;/h2&gt;&lt;p&gt;#比如现在我们在web管理平台页面新增一个网关域名,或者是又新增了一个location,又或者是location改变了后端集群(比如集群合并了),那么都需要触发一个web接口去新增或者修改线下和沙箱网关的域名,这时候就需要提供一个web接口进行传参调用了。&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.1 创建模版文件&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# cat /opt/web/pod_consul/template/gateway_service.conf&amp;nbsp; #可见模版文件中只需要替换一个域名就可以了&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;443&amp;nbsp;ssl;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name&amp;nbsp;Domain_Name;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;强制HTTPS时开启
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($scheme&amp;nbsp;!=&amp;nbsp;&amp;quot;https&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;301&amp;nbsp;https://$host$request_uri;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#关于SSL部分的内容请参照前面的配置

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;指定&amp;nbsp;dnsmasq&amp;nbsp;的地址&amp;nbsp;127.0.0.1，优先解析&amp;nbsp;hosts&amp;nbsp;里的域名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resolver&amp;nbsp;127.0.0.1&amp;nbsp;valid=10s&amp;nbsp;ipv6=off;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resolver_timeout&amp;nbsp;5s;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/opt/soft/nginx/conf.d/location/Domain_Name/*.location.conf;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_log&amp;nbsp;/opt/log/nginx/Domain_Name/Domain_Name_access.log&amp;nbsp;main;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_log&amp;nbsp;/opt/log/nginx/Domain_Name/Domain_Name_error.log&amp;nbsp;error;
}&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;&lt;/span&gt;# cat /opt/web/pod_consul/template/gateway_location.conf&amp;nbsp; #驼峰式的变量就是需要替换的,值就来自于咱们前面插入的那些数据&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;=====&amp;nbsp;固定配置（直接写死）=====
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$demand_number&amp;nbsp;&amp;quot;ReqId&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;初始化默认值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$trace_tag&amp;nbsp;&amp;quot;ReqId&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$client_ip&amp;nbsp;&amp;quot;&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;核心：初始化自定义变量client_ip
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$proxy_env&amp;nbsp;&amp;quot;Env_Name&amp;quot;;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;环境（固定：offline/mirror）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$stable_domain&amp;nbsp;&amp;quot;ModuleName-Env_Name-ReqId.test.com&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;stable后端域名（固定）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$module_name&amp;nbsp;&amp;quot;ModuleName&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;模块名（固定）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cache_expire&amp;nbsp;60;&amp;nbsp;&amp;nbsp;&amp;nbsp;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;执行核心业务逻辑（Lua会写入专属日志）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_by_lua_file&amp;nbsp;/opt/soft/nginx/conf.d/lua/ip_demand_query.lua;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;域名拼接（直接使用Nginx固定变量）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($demand_number&amp;nbsp;=&amp;nbsp;&amp;quot;stable&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$proxy_domain&amp;nbsp;$stable_domain;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($demand_number&amp;nbsp;!=&amp;nbsp;&amp;quot;stable&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$proxy_domain&amp;nbsp;&amp;quot;$module_name-$proxy_env-$trace_tag.test.com&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;代理转发
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;https://$proxy_domain$request_uri;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;Host&amp;nbsp;$proxy_domain;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Real-IP&amp;nbsp;$remote_addr;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-For&amp;nbsp;$proxy_add_x_forwarded_for;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_next_upstream&amp;nbsp;http_502&amp;nbsp;&amp;nbsp;error&amp;nbsp;timeout&amp;nbsp;invalid_header;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-Proto&amp;nbsp;&amp;nbsp;$scheme;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;基础代理配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_connect_timeout&amp;nbsp;5s;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_send_timeout&amp;nbsp;5s;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_read_timeout&amp;nbsp;5s;
}&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.2 基于域名的网关域名修改&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space-collapse: preserve; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;#pip install django djangorestframework pymysql paramiko&lt;/p&gt;&lt;p&gt;#cd&amp;nbsp;/opt/web/pod_consul/&amp;nbsp; &amp;amp;&amp;amp;&amp;nbsp;django-admin startproject&amp;nbsp; gateway_api&lt;/p&gt;&lt;p&gt;# vim pod_consul/settings.py&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;INSTALLED_APPS&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;django.contrib.admin&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;django.contrib.auth&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;django.contrib.contenttypes&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;django.contrib.sessions&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;django.contrib.messages&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;django.contrib.staticfiles&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;新增以下内容
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;rest_framework&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;nginx_conf_app&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;gateway_api&amp;#39;,
]&lt;/pre&gt;&lt;p&gt;# vi pod_consul/urls.py&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;urlpatterns&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;......
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;挂载应用路由，前缀为&amp;nbsp;/gateway/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path(&amp;#39;gateway/&amp;#39;,include(&amp;#39;gateway_api.urls&amp;#39;)),
]&lt;/pre&gt;&lt;p&gt;# vi gateway_api/urls.py&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;from&amp;nbsp;django.urls&amp;nbsp;import&amp;nbsp;path
from&amp;nbsp;.views&amp;nbsp;import&amp;nbsp;GatewayConfigGenerateView

urlpatterns&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;接口访问路径：http://域名/gateway/generate-config/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path(&amp;#39;generate-config/&amp;#39;,&amp;nbsp;GatewayConfigGenerateView.as_view(),&amp;nbsp;name=&amp;#39;generate_gateway_config&amp;#39;),
]&lt;/pre&gt;&lt;p&gt;#vi&amp;nbsp;gateway_api/views.py&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#&amp;nbsp;-*-&amp;nbsp;coding:&amp;nbsp;utf-8&amp;nbsp;-*-
import&amp;nbsp;os
import&amp;nbsp;re
import&amp;nbsp;paramiko
import&amp;nbsp;pymysql
import&amp;nbsp;logging
import&amp;nbsp;logging.handlers
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime
from&amp;nbsp;rest_framework.views&amp;nbsp;import&amp;nbsp;APIView
from&amp;nbsp;rest_framework.response&amp;nbsp;import&amp;nbsp;Response
from&amp;nbsp;rest_framework&amp;nbsp;import&amp;nbsp;status

#&amp;nbsp;====================&amp;nbsp;本地配置（根据实际环境修改）&amp;nbsp;====================
TEMPLATE_DIR&amp;nbsp;=&amp;nbsp;&amp;quot;/opt/web/pod_consul/template&amp;quot;&amp;nbsp;&amp;nbsp;#&amp;nbsp;模板文件目录（干净的模板，无rewrite）
LOCAL_TEMP_DIR&amp;nbsp;=&amp;nbsp;&amp;quot;/opt/web/temp&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;本地临时目录
LOG_DIR&amp;nbsp;=&amp;nbsp;&amp;quot;/opt/web/pod_consul/logs&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;日志目录
LOG_FILE&amp;nbsp;=&amp;nbsp;os.path.join(LOG_DIR,&amp;nbsp;&amp;quot;gateway_api.log&amp;quot;)
#&amp;nbsp;自动创建目录
for&amp;nbsp;dir_path&amp;nbsp;in&amp;nbsp;[LOCAL_TEMP_DIR,&amp;nbsp;LOG_DIR]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;os.path.exists(dir_path):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;os.makedirs(dir_path)

#&amp;nbsp;====================&amp;nbsp;日志配置&amp;nbsp;====================
logger&amp;nbsp;=&amp;nbsp;logging.getLogger(&amp;quot;gateway_api&amp;quot;)
logger.setLevel(logging.DEBUG)
if&amp;nbsp;not&amp;nbsp;logger.handlers:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;日志格式：时间&amp;nbsp;+&amp;nbsp;级别&amp;nbsp;+&amp;nbsp;内容
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handler&amp;nbsp;=&amp;nbsp;logging.handlers.RotatingFileHandler(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LOG_FILE,&amp;nbsp;maxBytes=50*1024*1024,&amp;nbsp;backupCount=5,&amp;nbsp;encoding=&amp;quot;utf-8&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;formatter&amp;nbsp;=&amp;nbsp;logging.Formatter(&amp;quot;%(asctime)s&amp;nbsp;%(levelname)s&amp;nbsp;%(message)s&amp;quot;,&amp;nbsp;datefmt=&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handler.setFormatter(formatter)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.addHandler(handler)

#&amp;nbsp;====================&amp;nbsp;数据库配置（根据实际环境修改）&amp;nbsp;====================
DB_CONFIG&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;host&amp;quot;:&amp;nbsp;&amp;quot;192.168.1.101&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;port&amp;quot;:&amp;nbsp;3306,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;user&amp;quot;:&amp;nbsp;&amp;quot;route&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;password&amp;quot;:&amp;nbsp;&amp;quot;route123456&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;database&amp;quot;:&amp;nbsp;&amp;quot;route&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;charset&amp;quot;:&amp;nbsp;&amp;quot;utf8mb4&amp;quot;
}

#&amp;nbsp;====================&amp;nbsp;远端Nginx服务器配置（根据实际环境修改）&amp;nbsp;====================
REMOTE_NGINX_SERVERS&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;name&amp;quot;:&amp;nbsp;&amp;quot;nginx-server-1&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;host&amp;quot;:&amp;nbsp;&amp;quot;192.168.1.102&amp;quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;远端Nginx服务器IP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;port&amp;quot;:&amp;nbsp;22,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;SSH端口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username&amp;quot;:&amp;nbsp;&amp;quot;root&amp;quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;SSH登录用户名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key_filename&amp;quot;:&amp;nbsp;&amp;quot;~/.ssh/id_rsa&amp;quot;,#&amp;nbsp;本地SSH私钥路径（优先用密钥，也可替换为password）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;&amp;quot;password&amp;quot;:&amp;nbsp;&amp;quot;your_ssh_password&amp;quot;,&amp;nbsp;#&amp;nbsp;若不用密钥，取消注释并填写密码
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;remote_service_dir&amp;quot;:&amp;nbsp;&amp;quot;/opt/soft/nginx/conf.d&amp;quot;,&amp;nbsp;&amp;nbsp;#&amp;nbsp;远端service.conf存放目录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;remote_location_root&amp;quot;:&amp;nbsp;&amp;quot;/opt/soft/nginx/conf.d/location&amp;quot;,&amp;nbsp;&amp;nbsp;#&amp;nbsp;远端location目录根路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;nginx_reload_cmd&amp;quot;:&amp;nbsp;&amp;quot;/opt/soft/nginx/sbin/nginx&amp;nbsp;-s&amp;nbsp;reload&amp;quot;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Nginx重载命令
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
]

#&amp;nbsp;====================&amp;nbsp;核心工具函数&amp;nbsp;====================
def&amp;nbsp;is_valid_rewrite_rule(rule):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;校验rewrite规则是否有效：
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;非None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;非字符串&amp;quot;NULL&amp;quot;（大小写不敏感）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3.&amp;nbsp;去除空格后非空
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;rule&amp;nbsp;is&amp;nbsp;None&amp;nbsp;or&amp;nbsp;(isinstance(rule,&amp;nbsp;str)&amp;nbsp;and&amp;nbsp;rule.strip().upper()&amp;nbsp;==&amp;nbsp;&amp;quot;NULL&amp;quot;):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;False
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;isinstance(rule,&amp;nbsp;str)&amp;nbsp;or&amp;nbsp;not&amp;nbsp;rule.strip():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;False
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;True

def&amp;nbsp;ensure_remote_dir_exists(sftp,&amp;nbsp;remote_dir):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;确保远端目录存在（SFTP方式创建，比SSH命令更可靠，且能立即生效）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:param&amp;nbsp;sftp:&amp;nbsp;paramiko的SFTP客户端对象
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:param&amp;nbsp;remote_dir:&amp;nbsp;远端目录路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;尝试列出目录，判断是否存在
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.listdir(remote_dir)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;远端目录【{remote_dir}】已存在&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;IOError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;目录不存在，递归创建（mkdir&amp;nbsp;-p&amp;nbsp;效果）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;拆分目录层级
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dir_parts&amp;nbsp;=&amp;nbsp;remote_dir.split(&amp;#39;/&amp;#39;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;current_path&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;part&amp;nbsp;in&amp;nbsp;dir_parts:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;part:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;current_path&amp;nbsp;+=&amp;nbsp;&amp;quot;/&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;current_path&amp;nbsp;+=&amp;nbsp;part&amp;nbsp;+&amp;nbsp;&amp;quot;/&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.stat(current_path)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;IOError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.mkdir(current_path)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;创建远端目录：{current_path}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;远端目录【{remote_dir}】创建成功&amp;quot;)

class&amp;nbsp;GatewayConfigGenerateView(APIView):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;核心功能：生成线下/沙箱网关的Nginx配置，仅当rewrite_rule有效时才添加rewrite行
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;传参：domain_name（线下网关域名）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;post(self,&amp;nbsp;request):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;记录请求日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;接收到配置生成请求，参数：{request.data}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;1.&amp;nbsp;基础参数验证
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;domain_name&amp;nbsp;=&amp;nbsp;request.data.get(&amp;quot;domain_name&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;domain_name&amp;nbsp;or&amp;nbsp;not&amp;nbsp;isinstance(domain_name,&amp;nbsp;str)&amp;nbsp;or&amp;nbsp;len(domain_name)&amp;nbsp;&amp;gt;&amp;nbsp;255:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.error(f&amp;quot;参数错误：线下网关域名无效，传入值：{domain_name}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code&amp;quot;:&amp;nbsp;400,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;&amp;quot;线下网关域名不能为空，且长度不能超过255个字符&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;data&amp;quot;:&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&amp;nbsp;status=status.HTTP_400_BAD_REQUEST)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;2.&amp;nbsp;数据库查询（获取线下网关+location+沙箱网关信息）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db_conn&amp;nbsp;=&amp;nbsp;pymysql.connect(**DB_CONFIG)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor&amp;nbsp;=&amp;nbsp;db_conn.cursor(pymysql.cursors.DictCursor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;查询线下网关关联的location信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;offline_sql&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;o.id&amp;nbsp;AS&amp;nbsp;offline_gateway_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;o.domain_name&amp;nbsp;AS&amp;nbsp;offline_domain,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;l.location_path,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;l.module_name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;l.cluster_name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;l.rewrite_rule
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM&amp;nbsp;offline_gateway&amp;nbsp;o
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JOIN&amp;nbsp;offline_location&amp;nbsp;l&amp;nbsp;ON&amp;nbsp;o.id&amp;nbsp;=&amp;nbsp;l.offline_gateway_id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE&amp;nbsp;o.domain_name&amp;nbsp;=&amp;nbsp;%s
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ORDER&amp;nbsp;BY&amp;nbsp;l.location_path;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.execute(offline_sql,&amp;nbsp;(domain_name,))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;offline_location_list&amp;nbsp;=&amp;nbsp;cursor.fetchall()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;无location配置直接返回
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;offline_location_list:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.warning(f&amp;quot;未查询到线下网关【{domain_name}】关联的location配置&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db_conn.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code&amp;quot;:&amp;nbsp;404,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;f&amp;quot;未查询到线下网关【{domain_name}】关联的location配置&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;data&amp;quot;:&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&amp;nbsp;status=status.HTTP_404_NOT_FOUND)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;查询关联的沙箱网关
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mirror_sql&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT&amp;nbsp;domain_name&amp;nbsp;AS&amp;nbsp;mirror_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM&amp;nbsp;mirror_gateway
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE&amp;nbsp;offline_gateway_id&amp;nbsp;=&amp;nbsp;%s;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.execute(mirror_sql,&amp;nbsp;(offline_location_list[0][&amp;quot;offline_gateway_id&amp;quot;],))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mirror_gateway_list&amp;nbsp;=&amp;nbsp;cursor.fetchall()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;关闭数据库连接
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db_conn.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;数据库查询完成：线下location数量={len(offline_location_list)}，沙箱网关数量={len(mirror_gateway_list)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;3.&amp;nbsp;整理需要生成配置的域名列表（线下+沙箱）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;domain_list&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;添加线下网关
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;domain_list.append({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;domain&amp;quot;:&amp;nbsp;offline_location_list[0][&amp;quot;offline_domain&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;domain_type&amp;quot;:&amp;nbsp;&amp;quot;offline&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;location_list&amp;quot;:&amp;nbsp;offline_location_list
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;添加沙箱网关
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;mirror&amp;nbsp;in&amp;nbsp;mirror_gateway_list:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;domain_list.append({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;domain&amp;quot;:&amp;nbsp;mirror[&amp;quot;mirror_domain&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;domain_type&amp;quot;:&amp;nbsp;&amp;quot;mirror&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;location_list&amp;quot;:&amp;nbsp;offline_location_list
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;4.&amp;nbsp;生成配置文件并上传到远端Nginx服务器
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;upload_results&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;server&amp;nbsp;in&amp;nbsp;REMOTE_NGINX_SERVERS:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_result&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;server_name&amp;quot;:&amp;nbsp;server[&amp;quot;name&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;server_host&amp;quot;:&amp;nbsp;server[&amp;quot;host&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;status&amp;quot;:&amp;nbsp;&amp;quot;success&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;&amp;quot;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;data&amp;quot;:&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;开始处理远端服务器：{server[&amp;#39;name&amp;#39;]}（{server[&amp;#39;host&amp;#39;]}）&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;建立SSH连接
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh&amp;nbsp;=&amp;nbsp;paramiko.SSHClient()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh_connect_kwargs&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;hostname&amp;quot;:&amp;nbsp;server[&amp;quot;host&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;port&amp;quot;:&amp;nbsp;server[&amp;quot;port&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username&amp;quot;:&amp;nbsp;server[&amp;quot;username&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;&amp;quot;key_filename&amp;quot;&amp;nbsp;in&amp;nbsp;server&amp;nbsp;and&amp;nbsp;server[&amp;quot;key_filename&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh_connect_kwargs[&amp;quot;key_filename&amp;quot;]&amp;nbsp;=&amp;nbsp;os.path.expanduser(server[&amp;quot;key_filename&amp;quot;])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elif&amp;nbsp;&amp;quot;password&amp;quot;&amp;nbsp;in&amp;nbsp;server&amp;nbsp;and&amp;nbsp;server[&amp;quot;password&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh_connect_kwargs[&amp;quot;password&amp;quot;]&amp;nbsp;=&amp;nbsp;server[&amp;quot;password&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh.connect(**ssh_connect_kwargs)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp&amp;nbsp;=&amp;nbsp;ssh.open_sftp()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;成功连接远端服务器：{server[&amp;#39;host&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;遍历每个域名生成配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;domain_item&amp;nbsp;in&amp;nbsp;domain_list:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;current_domain&amp;nbsp;=&amp;nbsp;domain_item[&amp;quot;domain&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;domain_type&amp;nbsp;=&amp;nbsp;domain_item[&amp;quot;domain_type&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_list&amp;nbsp;=&amp;nbsp;domain_item[&amp;quot;location_list&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_result[&amp;quot;data&amp;quot;][current_domain]&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;service_conf&amp;quot;:&amp;nbsp;&amp;quot;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;location_dir&amp;quot;:&amp;nbsp;&amp;quot;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;location_files&amp;quot;:&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;开始生成域名【{current_domain}】的配置文件&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;==========&amp;nbsp;生成service.conf文件&amp;nbsp;==========
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;service_template_path&amp;nbsp;=&amp;nbsp;os.path.join(TEMPLATE_DIR,&amp;nbsp;&amp;quot;gateway_service.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;open(service_template_path,&amp;nbsp;&amp;quot;r&amp;quot;,&amp;nbsp;encoding=&amp;quot;utf-8&amp;quot;)&amp;nbsp;as&amp;nbsp;f:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;service_content&amp;nbsp;=&amp;nbsp;f.read()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;service_content&amp;nbsp;=&amp;nbsp;service_content.replace(&amp;quot;Domain_Name&amp;quot;,&amp;nbsp;current_domain)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local_service_file&amp;nbsp;=&amp;nbsp;os.path.join(LOCAL_TEMP_DIR,&amp;nbsp;f&amp;quot;{current_domain}.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;open(local_service_file,&amp;nbsp;&amp;quot;w&amp;quot;,&amp;nbsp;encoding=&amp;quot;utf-8&amp;quot;)&amp;nbsp;as&amp;nbsp;f:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f.write(service_content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;remote_service_file&amp;nbsp;=&amp;nbsp;os.path.join(server[&amp;quot;remote_service_dir&amp;quot;],&amp;nbsp;f&amp;quot;{current_domain}.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.put(local_service_file,&amp;nbsp;remote_service_file)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;os.remove(local_service_file)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_result[&amp;quot;data&amp;quot;][current_domain][&amp;quot;service_conf&amp;quot;]&amp;nbsp;=&amp;nbsp;remote_service_file
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;成功上传service文件：{remote_service_file}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;==========&amp;nbsp;生成location.conf文件&amp;nbsp;==========
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;远端location目录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;remote_location_dir&amp;nbsp;=&amp;nbsp;os.path.join(server[&amp;quot;remote_location_root&amp;quot;],&amp;nbsp;current_domain)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;用SFTP方式确保目录存在（立即生效，无时序问题）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ensure_remote_dir_exists(sftp,&amp;nbsp;remote_location_dir)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_result[&amp;quot;data&amp;quot;][current_domain][&amp;quot;location_dir&amp;quot;]&amp;nbsp;=&amp;nbsp;remote_location_dir
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;按集群名分组location
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cluster_loc_map&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;loc&amp;nbsp;in&amp;nbsp;location_list:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cluster_name&amp;nbsp;=&amp;nbsp;loc[&amp;quot;cluster_name&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;cluster_name&amp;nbsp;not&amp;nbsp;in&amp;nbsp;cluster_loc_map:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cluster_loc_map[cluster_name]&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cluster_loc_map[cluster_name].append(loc)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;遍历每个集群生成带序号的location文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;cluster_name,&amp;nbsp;locs&amp;nbsp;in&amp;nbsp;cluster_loc_map.items():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;idx,&amp;nbsp;loc&amp;nbsp;in&amp;nbsp;enumerate(locs,&amp;nbsp;1):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;读取模板并替换变量
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_template_path&amp;nbsp;=&amp;nbsp;os.path.join(TEMPLATE_DIR,&amp;nbsp;&amp;quot;gateway_location.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;open(location_template_path,&amp;nbsp;&amp;quot;r&amp;quot;,&amp;nbsp;encoding=&amp;quot;utf-8&amp;quot;)&amp;nbsp;as&amp;nbsp;f:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content&amp;nbsp;=&amp;nbsp;f.read()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content&amp;nbsp;=&amp;nbsp;location_content.replace(&amp;quot;ReqId&amp;quot;,&amp;nbsp;&amp;quot;stable&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content&amp;nbsp;=&amp;nbsp;location_content.replace(&amp;quot;Env_Name&amp;quot;,&amp;nbsp;domain_type)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content&amp;nbsp;=&amp;nbsp;location_content.replace(&amp;quot;ModuleName&amp;quot;,&amp;nbsp;loc[&amp;quot;module_name&amp;quot;])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;==========&amp;nbsp;非/路径改为正则匹配（location&amp;nbsp;~）&amp;nbsp;==========
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;loc_path&amp;nbsp;=&amp;nbsp;loc[&amp;quot;location_path&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;loc_path&amp;nbsp;!=&amp;nbsp;&amp;quot;/&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;替换为正则匹配形式：location&amp;nbsp;~&amp;nbsp;路径&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;注意：正则中的/无需转义，常规路径直接拼接即可
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content&amp;nbsp;=&amp;nbsp;re.sub(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;r&amp;quot;location&amp;nbsp;/&amp;nbsp;{&amp;quot;,&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;location&amp;nbsp;~&amp;nbsp;{loc_path}&amp;nbsp;{{&amp;quot;,&amp;nbsp;&amp;nbsp;#&amp;nbsp;关键：添加~符号，改为正则匹配
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;【{current_domain}】的location【{loc_path}】设置正则匹配形式：location&amp;nbsp;~&amp;nbsp;{loc_path}&amp;nbsp;{{&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;路径为/时，保留原普通匹配形式
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;【{current_domain}】的location【{loc_path}】保留普通匹配形式：location&amp;nbsp;/&amp;nbsp;{{&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;==========&amp;nbsp;rewrite规则插入到access_by_lua_file上方&amp;nbsp;==========
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rewrite_rule&amp;nbsp;=&amp;nbsp;loc[&amp;quot;rewrite_rule&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is_rewrite_valid&amp;nbsp;=&amp;nbsp;is_valid_rewrite_rule(rewrite_rule)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;is_rewrite_valid:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clean_rule&amp;nbsp;=&amp;nbsp;rewrite_rule.strip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rewrite_line&amp;nbsp;=&amp;nbsp;f&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rewrite&amp;nbsp;{clean_rule}&amp;nbsp;break;\n&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;找到access_by_lua_file的位置，在其上方插入rewrite
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_file_pos&amp;nbsp;=&amp;nbsp;location_content.find(&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_by_lua_file&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;lua_file_pos&amp;nbsp;!=&amp;nbsp;-1:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content&amp;nbsp;=&amp;nbsp;location_content[:lua_file_pos]&amp;nbsp;+&amp;nbsp;rewrite_line&amp;nbsp;+&amp;nbsp;location_content[lua_file_pos:]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;【{current_domain}】的location【{loc[&amp;#39;location_path&amp;#39;]}】添加rewrite规则到access_by_lua_file上方：{clean_rule}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;==========&amp;nbsp;仅当有有效rewrite时，替换proxy_pass&amp;nbsp;==========
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content&amp;nbsp;=&amp;nbsp;re.sub(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;r&amp;quot;proxy_pass&amp;nbsp;https://\$proxy_domain\$request_uri;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;proxy_pass&amp;nbsp;https://$proxy_domain$uri?$args;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;【{current_domain}】的location【{loc[&amp;#39;location_path&amp;#39;]}】更新proxy_pass为：proxy_pass&amp;nbsp;https://$proxy_domain$uri?$args;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;若模板中无access_by_lua_file，仍插入到proxy_pass上方
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass_pos&amp;nbsp;=&amp;nbsp;location_content.find(&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;proxy_pass_pos&amp;nbsp;!=&amp;nbsp;-1:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content&amp;nbsp;=&amp;nbsp;location_content[:proxy_pass_pos]&amp;nbsp;+&amp;nbsp;rewrite_line&amp;nbsp;+&amp;nbsp;location_content[proxy_pass_pos:]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.warning(f&amp;quot;模板中未找到access_by_lua_file，为【{current_domain}】的location【{loc[&amp;#39;location_path&amp;#39;]}】添加rewrite规则到proxy_pass上方：{clean_rule}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;==========仅当有有效rewrite时，替换proxy_pass&amp;nbsp;==========
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content&amp;nbsp;=&amp;nbsp;re.sub(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;r&amp;quot;proxy_pass&amp;nbsp;https://\$proxy_domain\$request_uri;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;proxy_pass&amp;nbsp;https://$proxy_domain$uri?$args;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_content
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;【{current_domain}】的location【{loc[&amp;#39;location_path&amp;#39;]}】更新proxy_pass为：proxy_pass&amp;nbsp;https://$proxy_domain$uri?$args;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;【{current_domain}】的location【{loc[&amp;#39;location_path&amp;#39;]}】rewrite规则无效（值：{rewrite_rule}），保留原始proxy_pass格式&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;生成文件并上传
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;loc_filename&amp;nbsp;=&amp;nbsp;f&amp;quot;{cluster_name}-{idx:02d}.location.conf&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local_loc_file&amp;nbsp;=&amp;nbsp;os.path.join(LOCAL_TEMP_DIR,&amp;nbsp;loc_filename)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;open(local_loc_file,&amp;nbsp;&amp;quot;w&amp;quot;,&amp;nbsp;encoding=&amp;quot;utf-8&amp;quot;)&amp;nbsp;as&amp;nbsp;f:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f.write(location_content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;远端文件路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;remote_loc_file&amp;nbsp;=&amp;nbsp;os.path.join(remote_location_dir,&amp;nbsp;loc_filename)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;上传文件（添加异常捕获，确保上传失败能记录日志）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.put(local_loc_file,&amp;nbsp;remote_loc_file)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;成功上传location文件：{remote_loc_file}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;upload_err:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.error(f&amp;quot;上传location文件【{remote_loc_file}】失败：{str(upload_err)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raise&amp;nbsp;upload_err&amp;nbsp;&amp;nbsp;#&amp;nbsp;抛出异常，让外层捕获
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;删除本地临时文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;os.remove(local_loc_file)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;记录文件信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_result[&amp;quot;data&amp;quot;][current_domain][&amp;quot;location_files&amp;quot;].append({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;filename&amp;quot;:&amp;nbsp;loc_filename,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;remote_path&amp;quot;:&amp;nbsp;remote_loc_file,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;location_path&amp;quot;:&amp;nbsp;loc[&amp;quot;location_path&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;rewrite_rule&amp;quot;:&amp;nbsp;rewrite_rule,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;is_rewrite_valid&amp;quot;:&amp;nbsp;is_rewrite_valid,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;location_match_type&amp;quot;:&amp;nbsp;&amp;quot;regex&amp;quot;&amp;nbsp;if&amp;nbsp;loc_path&amp;nbsp;!=&amp;nbsp;&amp;quot;/&amp;quot;&amp;nbsp;else&amp;nbsp;&amp;quot;normal&amp;quot;,&amp;nbsp;&amp;nbsp;#&amp;nbsp;标记匹配类型
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;proxy_pass_format&amp;quot;:&amp;nbsp;&amp;quot;https://$proxy_domain$uri?$args;&amp;quot;&amp;nbsp;if&amp;nbsp;is_rewrite_valid&amp;nbsp;else&amp;nbsp;&amp;quot;https://$proxy_domain$request_uri;&amp;quot;&amp;nbsp;&amp;nbsp;#&amp;nbsp;新增：标记proxy_pass格式
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;==========&amp;nbsp;重载Nginx配置&amp;nbsp;==========
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;执行Nginx重载命令：{server[&amp;#39;nginx_reload_cmd&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stdin,&amp;nbsp;stdout,&amp;nbsp;stderr&amp;nbsp;=&amp;nbsp;ssh.exec_command(server[&amp;quot;nginx_reload_cmd&amp;quot;])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reload_stderr&amp;nbsp;=&amp;nbsp;stderr.read().decode().strip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;reload_stderr:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_result[&amp;quot;msg&amp;quot;]&amp;nbsp;=&amp;nbsp;f&amp;quot;Nginx重载警告：{reload_stderr}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.warning(f&amp;quot;服务器【{server[&amp;#39;host&amp;#39;]}】Nginx重载警告：{reload_stderr}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;关闭连接
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;完成远端服务器【{server[&amp;#39;host&amp;#39;]}】的配置生成和上传&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_result[&amp;quot;status&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;failed&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_result[&amp;quot;msg&amp;quot;]&amp;nbsp;=&amp;nbsp;str(e)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.error(f&amp;quot;处理远端服务器【{server[&amp;#39;host&amp;#39;]}】失败：{str(e)}&amp;quot;,&amp;nbsp;exc_info=True)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;upload_results.append(server_result)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;返回结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.info(f&amp;quot;配置生成请求处理完成，线下网关域名：{domain_name}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code&amp;quot;:&amp;nbsp;200,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;&amp;quot;配置生成并上传完成&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;data&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;offline_domain&amp;quot;:&amp;nbsp;domain_name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;mirror_domains&amp;quot;:&amp;nbsp;[m[&amp;quot;mirror_domain&amp;quot;]&amp;nbsp;for&amp;nbsp;m&amp;nbsp;in&amp;nbsp;mirror_gateway_list],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upload_results&amp;quot;:&amp;nbsp;upload_results
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&amp;nbsp;status=status.HTTP_200_OK)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;pymysql.MySQLError&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.error(f&amp;quot;数据库操作失败：{str(e)}&amp;quot;,&amp;nbsp;exc_info=True)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code&amp;quot;:&amp;nbsp;500,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;f&amp;quot;数据库操作失败：{str(e)}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;data&amp;quot;:&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&amp;nbsp;status=status.HTTP_500_INTERNAL_SERVER_ERROR)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logger.error(f&amp;quot;配置生成失败：{str(e)}&amp;quot;,&amp;nbsp;exc_info=True)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code&amp;quot;:&amp;nbsp;500,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;f&amp;quot;配置生成失败：{str(e)}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;data&amp;quot;:&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&amp;nbsp;status=status.HTTP_500_INTERNAL_SERVER_ERROR)&lt;/pre&gt;&lt;p&gt;#nohup&amp;nbsp;python3 manage.py runserver 0.0.0.0:8000 &amp;amp;&lt;/p&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space-collapse: preserve; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.3 调用下接口检查创建的文件&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# curl -X POST http://127.0.0.1:8000/gateway/generate-config/ -H &amp;quot;Content-Type: application/json&amp;quot; -d &amp;#39;{&amp;quot;domain_name&amp;quot;: &amp;quot;zhongtai.test.com&amp;quot;}&amp;#39;&lt;/p&gt;&lt;p&gt;# curl -X POST http://127.0.0.1:8000/gateway/generate-config/ -H &amp;quot;Content-Type: application/json&amp;quot; -d &amp;#39;{&amp;quot;domain_name&amp;quot;: &amp;quot;houtai.test.com&amp;quot;}&amp;#39;&lt;/p&gt;&lt;p&gt;#可以去网关域名服务器查看一下conf.d下面是不是已经创建了线下和沙箱域名,然后location下面的域名下面是不是location文件也有了,然后要注意的是我的location文件都是集群名-01.location.conf,集群名-02.location.conf依次类推的形式,并且location /对应的集群必须是集群名-01.location.conf的命名形式,为啥要多加一个01/02依次累加呢,因为了兼顾一个集群可能会存在于多个location下面。&lt;/p&gt;&lt;p&gt;#然后你可以访问网关域名查看一下不同的location路径是不是到了指定集群下面,包括rewrite配置,可以看一个产生的location文件&lt;/p&gt;&lt;p&gt;# cat /opt/soft/nginx/conf.d/location/houtai.test.com/集群名-02.location.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;~&amp;nbsp;/api/token/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;=====&amp;nbsp;固定配置（直接写死）=====
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$demand_number&amp;nbsp;&amp;quot;stable&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;初始化默认值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$trace_tag&amp;nbsp;&amp;quot;stable&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$client_ip&amp;nbsp;&amp;quot;&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;核心：初始化自定义变量client_ip
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$proxy_env&amp;nbsp;&amp;quot;offline&amp;quot;;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;环境（固定：offline/mirror）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$stable_domain&amp;nbsp;&amp;quot;模块名-offline-stable.test.com&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;stable后端域名（固定）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$module_name&amp;nbsp;&amp;quot;模块名&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;模块名（固定）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cache_expire&amp;nbsp;60;&amp;nbsp;&amp;nbsp;&amp;nbsp;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;执行核心业务逻辑（Lua会写入专属日志）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rewrite&amp;nbsp;/api/(.*)&amp;nbsp;/api/auth/$1&amp;nbsp;break;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_by_lua_file&amp;nbsp;/opt/soft/nginx/conf.d/lua/ip_demand_query.lua;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;域名拼接（直接使用Nginx固定变量）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($demand_number&amp;nbsp;=&amp;nbsp;&amp;quot;stable&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$proxy_domain&amp;nbsp;$stable_domain;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($demand_number&amp;nbsp;!=&amp;nbsp;&amp;quot;stable&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$proxy_domain&amp;nbsp;&amp;quot;$module_name-$proxy_env-$trace_tag.test.com&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;代理转发
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;https://$proxy_domain$uri?$args;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;Host&amp;nbsp;$proxy_domain;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Real-IP&amp;nbsp;$remote_addr;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-For&amp;nbsp;$proxy_add_x_forwarded_for;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_next_upstream&amp;nbsp;http_502&amp;nbsp;&amp;nbsp;error&amp;nbsp;timeout&amp;nbsp;invalid_header;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-Proto&amp;nbsp;&amp;nbsp;$scheme;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;基础代理配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_connect_timeout&amp;nbsp;5s;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_send_timeout&amp;nbsp;5s;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_read_timeout&amp;nbsp;5s;
}&lt;/pre&gt;&lt;p&gt;#这里注意一下$request_uri和$uri的区别：&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;$request_uri:原始请求的完整路径&amp;nbsp;+&amp;nbsp;参数（不含锚点&amp;nbsp;#），只读、永不改变
$uri:Nginx&amp;nbsp;内部处理后的路径部分（无参数）&lt;/pre&gt;&lt;p&gt;我们以https://houtai.test.com/api/token/dasdsada?a=123&amp;amp;b=456&amp;nbsp; 请求举例:&lt;/p&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;$request_uri: 从网关nginx到后端nginx接收到的请求都是GET /api/token/dasdsada?a=123&amp;amp;b=456&amp;nbsp; &amp;nbsp;#rewrite并没有生效参数也传递了&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;$uri: 网关是/api/token/dasdsada?a=123&amp;amp;b=456 后端Nginx是/api/auth/token/dasdsada&amp;nbsp; #可以看到rewrite生效但是参数无法传递&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;$uri?$args：网关是/api/token/dasdsada?a=123&amp;amp;b=456&amp;nbsp; 后端nginx是/api/auth/token/dasdsada?a=123&amp;amp;b=456&amp;nbsp; #可以看到rewrite生效参数也传递了&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.4 测试一下trace_tag是否实现了透传&lt;/span&gt;&lt;span data-type=&quot;text&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;#我们采用一个简单的方式,直接修改nginx的日志格式：&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#&amp;nbsp;=====&amp;nbsp;自定义日志格式（包含常用Header&amp;nbsp;+&amp;nbsp;核心变量）=====
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_format&amp;nbsp;custom_format&amp;nbsp;&amp;#39;$remote_addr&amp;nbsp;[$time_local]&amp;nbsp;&amp;quot;$request&amp;quot;&amp;nbsp;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;$status&amp;nbsp;$body_bytes_sent&amp;nbsp;&amp;quot;$http_referer&amp;quot;&amp;nbsp;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;quot;$http_user_agent&amp;quot;&amp;nbsp;&amp;quot;$http_x_real_ip&amp;quot;&amp;nbsp;&amp;quot;$http_x_forwarded_for&amp;quot;&amp;nbsp;&amp;#39;&amp;nbsp;&amp;nbsp;#&amp;nbsp;客户端IP相关Header
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;quot;$http_host&amp;quot;&amp;nbsp;&amp;nbsp;&amp;quot;$http_trace_tag&amp;quot;&amp;nbsp;&amp;#39;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;自定义业务Header（按需改）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;$request_uri&amp;nbsp;$uri&amp;nbsp;$args&amp;#39;;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;路径/参数变量（排查rewrite用）&lt;/pre&gt;&lt;p&gt;#然后让我们测试的网关域名和后端nginx域名都引用这个日志格式,然后开始浏览器访问请求看日志输出：&lt;br/&gt;#https://houtai.test.com/api/auth/token/dasdsada?a=123&amp;amp;b=456&amp;nbsp; &amp;nbsp;#有rewrite的域名&lt;/p&gt;&lt;p&gt;# tail -f /opt/log/nginx/houtai.test.com/houtai.test.com_access.log&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;quot;houtai.test.com&amp;quot;&amp;nbsp;&amp;nbsp;&amp;quot;35184&amp;quot;&amp;nbsp;/api/token/dasdsada?a=123&amp;amp;b=456&amp;nbsp;/api/auth/token/dasdsada&amp;nbsp;a=123&amp;amp;b=456&lt;/pre&gt;&lt;p&gt;# tail -f /opt/log/nginx/http-develop-offline-stable.test.com/&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;http-develop-offline-stable.test.com&lt;/span&gt;_access.log&amp;nbsp; #估计没创建35184的需求后端让其走稳定&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;quot;http-develop-offline-stable.test.com&amp;quot;&amp;nbsp;&amp;nbsp;&amp;quot;35184&amp;quot;&amp;nbsp;/api/auth/token/dasdsada?a=123&amp;amp;b=456&amp;nbsp;/api/auth/token/dasdsada&amp;nbsp;a=123&amp;amp;b=456&lt;/pre&gt;&lt;p&gt;#从上面可以看到trace_tag这个自定义变量是往后透传的,虽然请求的是stable域名,但是trace_tag还是需求号,这样交给后端集群后还是tag染色的&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;#https://houtai.test.com/api/service/dadsada&amp;nbsp; #再来一个没有rewrite的域名&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# tail -f /opt/log/nginx/houtai.test.com/houtai.test.com_access.log&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;quot;houtai.test.com&amp;quot;&amp;nbsp;&amp;nbsp;&amp;quot;35184&amp;quot;&amp;nbsp;/api/service/dadsada&amp;nbsp;/api/service/dadsada&amp;nbsp;-&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# tail -f /opt/log/nginx/http-develop-offline-stable.test.com/&lt;/span&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;http-develop-offline-stable.test.com&lt;/span&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;_access.log&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;quot;http-develop-offline-stable.test.com&amp;quot;&amp;nbsp;&amp;nbsp;&amp;quot;35184&amp;quot;&amp;nbsp;/api/service/dadsada&amp;nbsp;/api/service/dadsada&amp;nbsp;-&lt;/pre&gt;&lt;p&gt;#重点说一下,可以看到trace_tag已经可以传递下去了,其实也可以再优化一下,比如你的需求环境和稳定环境有所区分,需求环境的location会赋值一个trace_tag的默认值,lua脚本做个判断如果是稳定环境再往header里面写trace_tag,如果是需求环境就直接用自己配置文件中固定好的trace_tag,这样能保证不会因为程序错误导致trace_tag赋值错了,因为既然都到需求环境的域名了,这trace_tag肯定就是需求号了。&lt;/p&gt;&lt;p&gt;#好了到此,我们按需环境从网关流量入口到后端域名创建以及与容器相关联就介绍完毕了,当然这只是一个大体的流程,细节点还需要自己完善,怎么跟平台结合使用还需要结合实际场景来,比如需求环境销毁后如何及时的清理consul和nginx中的无效配置文件。&lt;/p&gt;</description><pubDate>Thu, 15 Jan 2026 14:38:32 +0800</pubDate></item><item><title> 测试环境按需动态加载(二)	</title><link>https://blog.51niux.com/?id=329</link><description>&lt;p&gt;#紧接上文:&lt;a href=&quot;https://blog.51niux.com/?id=326&quot; _src=&quot;https://blog.51niux.com/?id=326&quot;&gt;www.51niux.com/?id=326&lt;/a&gt;&amp;nbsp; 前面以及介绍了pod生命周期在consul中的操作,nginx后端跟consul的关联以及相关的表创建,下面进入到第二部分流量侧操作&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;一、初始化集群域名的创建&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;#前面我们已经首先介绍了集群pod在nginx的配置文件中如何存在可以让域名通过consul获取到pod的信息进而调用到pod集群的http接口。&lt;br/&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.1 创建模版文件&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# ls&amp;nbsp; /opt/web/pod_consul/template&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location.conf&amp;nbsp;&amp;nbsp;location_upstream_demand.conf&amp;nbsp;&amp;nbsp;location_upstream_stable.conf&amp;nbsp;&amp;nbsp;service.conf&lt;/pre&gt;&lt;p&gt;# cat /opt/web/pod_consul/template/service.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;include&amp;nbsp;/opt/soft/nginx/conf.d/location/Domain_Name/*.location_upstream.conf;

server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;443&amp;nbsp;ssl;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name&amp;nbsp;Domain_Name;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;强制HTTPS时开启
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($scheme&amp;nbsp;!=&amp;nbsp;&amp;quot;https&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;301&amp;nbsp;https://$host$request_uri;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_certificate&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;certification/test.com.pem;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_certificate_key&amp;nbsp;&amp;nbsp;certification/test.com.key;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_session_cache&amp;nbsp;shared:SSL:10m;&amp;nbsp;##&amp;nbsp;size&amp;nbsp;suggest:&amp;nbsp;10m,50m
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_session_timeout&amp;nbsp;&amp;nbsp;1d;&amp;nbsp;#&amp;nbsp;time&amp;nbsp;suggest:&amp;nbsp;10m,1d
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_prefer_server_ciphers&amp;nbsp;on;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_protocols&amp;nbsp;TLSv1&amp;nbsp;TLSv1.1&amp;nbsp;TLSv1.2;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_ciphers&amp;nbsp;&amp;#39;ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/opt/soft/nginx/conf.d/cors;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_tokens&amp;nbsp;off;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;charset&amp;nbsp;utf-8;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/opt/soft/nginx/conf.d/location/Domain_Name/*.location.conf;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_log&amp;nbsp;/opt/log/nginx/Domain_Name/Domain_Name_access.log&amp;nbsp;main;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_log&amp;nbsp;/opt/log/nginx/Domain_Name/Domain_Name_error.log&amp;nbsp;error;

}&lt;/pre&gt;&lt;p&gt;# cat /opt/web/pod_consul/template/location.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_next_upstream&amp;nbsp;http_502&amp;nbsp;&amp;nbsp;error&amp;nbsp;timeout&amp;nbsp;invalid_header;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;http://Domain_Name_pool;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;Host&amp;nbsp;$host;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-For&amp;nbsp;$remote_addr;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Real-IP&amp;nbsp;$remote_addr;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-Proto&amp;nbsp;&amp;nbsp;$scheme;
}&lt;/pre&gt;&lt;p&gt;# cat /opt/web/pod_consul/template/location_upstream_demand.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;Domain_Name_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;server&amp;nbsp;127.0.0.1:11111&amp;nbsp;down;
&amp;nbsp;&amp;nbsp;upsync&amp;nbsp;192.168.1.101:8500/v1/kv/upstreams/Env_Name/demand/Req_Id/Cluster_Name&amp;nbsp;upsync_timeout=5s&amp;nbsp;upsync_interval=500ms&amp;nbsp;upsync_type=consul&amp;nbsp;strong_dependency=off;
&amp;nbsp;&amp;nbsp;upsync_dump_path&amp;nbsp;/data/nginxconf/upstream/Cluster_Name-Env_Name-Req_Id.conf;
&amp;nbsp;&amp;nbsp;include&amp;nbsp;/data/nginxconf/upstream/Cluster_Name-Env_Name-Req_Id.conf;
}&lt;/pre&gt;&lt;p&gt;# cat /opt/web/pod_consul/template/location_upstream_stable.conf&amp;nbsp; #稳定环境和需求环境kv路径不一样所以upstream模版是两个不同文件&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;Domain_Name_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;server&amp;nbsp;127.0.0.1:11111&amp;nbsp;down;
&amp;nbsp;&amp;nbsp;upsync&amp;nbsp;192.168.1.101:8500/v1/kv/upstreams/Env_Name/stable/Cluster_Name&amp;nbsp;upsync_timeout=5s&amp;nbsp;upsync_interval=500ms&amp;nbsp;upsync_type=consul&amp;nbsp;strong_dependency=off;
&amp;nbsp;&amp;nbsp;upsync_dump_path&amp;nbsp;/data/nginxconf/upstream/Cluster_Name-Env_Name-Req_Id.conf;
&amp;nbsp;&amp;nbsp;include&amp;nbsp;/data/nginxconf/upstream/Cluster_Name-Env_Name-Req_Id.conf;
}&lt;/pre&gt;&lt;p&gt;#好了模版文件创建完了,下一步就可以写程序,根据接口传参然后把修改好后的文件发送到对端nginx机器了&lt;/p&gt;&lt;p&gt;#cat&amp;nbsp;/opt/soft/nginx/conf.d/cors #在目标nginx机器上面创建的允许跨域访问的文件&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;##跨域配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cors_origin&amp;nbsp;&amp;quot;&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cors_cred&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cors_header&amp;nbsp;&amp;quot;&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cors_method&amp;nbsp;&amp;quot;&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($http_origin&amp;nbsp;~*&amp;nbsp;&amp;#39;https?://([^/]*\.test\.com|[^/]*\.test\.cn)$&amp;#39;){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cors_origin&amp;nbsp;$http_origin;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cors_cred&amp;nbsp;&amp;nbsp;&amp;nbsp;true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cors_header&amp;nbsp;$http_access_control_request_headers;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cors_method&amp;nbsp;$http_access_control_request_method;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;add_header&amp;nbsp;Access-Control-Allow-Origin&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$cors_origin;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;add_header&amp;nbsp;Access-Control-Allow-Credentials&amp;nbsp;$cors_cred;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;add_header&amp;nbsp;Access-Control-Allow-Headers&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$cors_header;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;add_header&amp;nbsp;Access-Control-Allow-Methods&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$cors_method;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;add_header&amp;nbsp;Access-Control-Max-Age&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;86400;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($request_method&amp;nbsp;=&amp;nbsp;&amp;#39;OPTIONS&amp;#39;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;204;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.2 编写接口程序&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#调用接口创建域名那步操作就不介绍了,你可以自建dns服务也可以调用公共dns服务接口进行域名维护,或者除网关外的内网域名全部更新网关机器的hosts文件,只有公网域名才会去公网dns服务创建,这都看各自的需求了&lt;/p&gt;&lt;p&gt;#下面程序的大概意思就是,当你发布页面进行集群初始化的时候,会触发nginx模版文件的创建&lt;/p&gt;&lt;p&gt;# cat /opt/web/pod_consul/nginx_conf_app/views.py&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#&amp;nbsp;-*-&amp;nbsp;coding:&amp;nbsp;utf-8&amp;nbsp;-*-
import&amp;nbsp;os
import&amp;nbsp;shutil
import&amp;nbsp;paramiko
from&amp;nbsp;rest_framework.views&amp;nbsp;import&amp;nbsp;APIView
from&amp;nbsp;rest_framework.response&amp;nbsp;import&amp;nbsp;Response
from&amp;nbsp;rest_framework&amp;nbsp;import&amp;nbsp;status
from&amp;nbsp;rest_framework.exceptions&amp;nbsp;import&amp;nbsp;ValidationError

#&amp;nbsp;====================&amp;nbsp;本地配置（务必修改）&amp;nbsp;====================
TEMPLATE_DIR&amp;nbsp;=&amp;nbsp;&amp;quot;/opt/web/pod_consul/template&amp;quot;&amp;nbsp;&amp;nbsp;#&amp;nbsp;模板文件目录（service/location等）
LOCAL_TEMP_DIR&amp;nbsp;=&amp;nbsp;&amp;quot;/opt/web/temp&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;本地临时生成目录

#&amp;nbsp;====================&amp;nbsp;远端服务器配置（务必修改）&amp;nbsp;====================
REMOTE_NGINX_SERVERS&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;name&amp;quot;:&amp;nbsp;&amp;quot;nginx-server-1&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;host&amp;quot;:&amp;nbsp;&amp;quot;192.168.1.102&amp;quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;远端IP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;port&amp;quot;:&amp;nbsp;22,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username&amp;quot;:&amp;nbsp;&amp;quot;root&amp;quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;SSH用户名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key_filename&amp;quot;:&amp;nbsp;&amp;quot;~/.ssh/id_rsa&amp;quot;,#&amp;nbsp;本地私钥路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;远端Nginx目录（绝对路径）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;remote_service_dir&amp;quot;:&amp;nbsp;&amp;quot;/opt/soft/nginx/conf.d&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;remote_location_root&amp;quot;:&amp;nbsp;&amp;quot;/opt/soft/nginx/conf.d/location&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;remote_upstream_dir&amp;quot;:&amp;nbsp;&amp;quot;/data/nginxconf/upstream&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;nginx_reload_cmd&amp;quot;:&amp;nbsp;&amp;quot;/opt/soft/nginx/sbin/nginx&amp;nbsp;-s&amp;nbsp;reload&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
]

#&amp;nbsp;允许的环境值
ALLOWED_CLUSTER_ENV&amp;nbsp;=&amp;nbsp;{&amp;quot;offline&amp;quot;,&amp;nbsp;&amp;quot;mirror&amp;quot;}
ALLOWED_DEMAND_ENV&amp;nbsp;=&amp;nbsp;{&amp;quot;demand&amp;quot;,&amp;nbsp;&amp;quot;stable&amp;quot;}

def&amp;nbsp;replace_file_content(file_path,&amp;nbsp;replace_map):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;替换文件内容&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;open(file_path,&amp;nbsp;&amp;#39;r&amp;#39;,&amp;nbsp;encoding=&amp;#39;utf-8&amp;#39;)&amp;nbsp;as&amp;nbsp;f:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content&amp;nbsp;=&amp;nbsp;f.read()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;old,&amp;nbsp;new&amp;nbsp;in&amp;nbsp;replace_map.items():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content&amp;nbsp;=&amp;nbsp;content.replace(old,&amp;nbsp;new)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;open(file_path,&amp;nbsp;&amp;#39;w&amp;#39;,&amp;nbsp;encoding=&amp;#39;utf-8&amp;#39;)&amp;nbsp;as&amp;nbsp;f:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f.write(content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;True,&amp;nbsp;&amp;quot;替换成功&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;False,&amp;nbsp;f&amp;quot;替换失败:&amp;nbsp;{str(e)}&amp;quot;

def&amp;nbsp;exec_ssh_command(ssh_client,&amp;nbsp;command):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;执行SSH命令并返回结果&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stdin,&amp;nbsp;stdout,&amp;nbsp;stderr&amp;nbsp;=&amp;nbsp;ssh_client.exec_command(command,&amp;nbsp;timeout=10)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exit_code&amp;nbsp;=&amp;nbsp;stdout.channel.recv_exit_status()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;success&amp;quot;:&amp;nbsp;exit_code&amp;nbsp;==&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stdout&amp;quot;:&amp;nbsp;stdout.read().decode(&amp;#39;utf-8&amp;#39;).strip(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stderr&amp;quot;:&amp;nbsp;stderr.read().decode(&amp;#39;utf-8&amp;#39;).strip(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;exit_code&amp;quot;:&amp;nbsp;exit_code
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;success&amp;quot;:&amp;nbsp;False,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stdout&amp;quot;:&amp;nbsp;&amp;quot;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stderr&amp;quot;:&amp;nbsp;f&amp;quot;命令执行异常:&amp;nbsp;{str(e)}&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;exit_code&amp;quot;:&amp;nbsp;-1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

class&amp;nbsp;GenerateNginxConfView(APIView):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;post(self,&amp;nbsp;request):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;1.&amp;nbsp;参数校验
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;[&amp;quot;module_name&amp;quot;,&amp;nbsp;&amp;quot;cluster_name&amp;quot;,&amp;nbsp;&amp;quot;cluster_env&amp;quot;,&amp;nbsp;&amp;quot;demand_env&amp;quot;,&amp;nbsp;&amp;quot;req_id&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;{k:&amp;nbsp;request.data.get(k)&amp;nbsp;for&amp;nbsp;k&amp;nbsp;in&amp;nbsp;params}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;all(data.values()):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code&amp;quot;:&amp;nbsp;400,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;f&amp;quot;缺少参数:&amp;nbsp;{[k&amp;nbsp;for&amp;nbsp;k,&amp;nbsp;v&amp;nbsp;in&amp;nbsp;data.items()&amp;nbsp;if&amp;nbsp;not&amp;nbsp;v]}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&amp;nbsp;status=400)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;data[&amp;quot;cluster_env&amp;quot;]&amp;nbsp;not&amp;nbsp;in&amp;nbsp;ALLOWED_CLUSTER_ENV&amp;nbsp;or&amp;nbsp;data[&amp;quot;demand_env&amp;quot;]&amp;nbsp;not&amp;nbsp;in&amp;nbsp;ALLOWED_DEMAND_ENV:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code&amp;quot;:&amp;nbsp;400,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;f&amp;quot;cluster_env只能是{ALLOWED_CLUSTER_ENV}，demand_env只能是{ALLOWED_DEMAND_ENV}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&amp;nbsp;status=400)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;2.&amp;nbsp;基础变量
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;domain&amp;nbsp;=&amp;nbsp;f&amp;quot;{data[&amp;#39;module_name&amp;#39;]}-{data[&amp;#39;cluster_env&amp;#39;]}-{data[&amp;#39;req_id&amp;#39;]}.test.com&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;upstream_file&amp;nbsp;=&amp;nbsp;f&amp;quot;{data[&amp;#39;cluster_name&amp;#39;]}-{data[&amp;#39;cluster_env&amp;#39;]}-{data[&amp;#39;req_id&amp;#39;]}.conf&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code&amp;quot;:&amp;nbsp;200,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;&amp;quot;操作成功&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;data&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;domain&amp;quot;:&amp;nbsp;domain,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;servers&amp;quot;:&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;3.&amp;nbsp;本地生成配置文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;创建本地临时目录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;os.makedirs(LOCAL_TEMP_DIR,&amp;nbsp;exist_ok=True)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local_files&amp;nbsp;=&amp;nbsp;{}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;3.1&amp;nbsp;生成service.conf
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;service_local&amp;nbsp;=&amp;nbsp;os.path.join(LOCAL_TEMP_DIR,&amp;nbsp;f&amp;quot;{domain}.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shutil.copy(os.path.join(TEMPLATE_DIR,&amp;nbsp;&amp;quot;service.conf&amp;quot;),&amp;nbsp;service_local)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;replace_file_content(service_local,&amp;nbsp;{&amp;quot;Domain_Name&amp;quot;:&amp;nbsp;domain})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local_files[&amp;quot;service&amp;quot;]&amp;nbsp;=&amp;nbsp;service_local

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;3.2&amp;nbsp;生成location.conf
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_dir&amp;nbsp;=&amp;nbsp;os.path.join(LOCAL_TEMP_DIR,&amp;nbsp;domain)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;os.makedirs(location_dir,&amp;nbsp;exist_ok=True)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location_local&amp;nbsp;=&amp;nbsp;os.path.join(location_dir,&amp;nbsp;f&amp;quot;{domain}.location.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shutil.copy(os.path.join(TEMPLATE_DIR,&amp;nbsp;&amp;quot;location.conf&amp;quot;),&amp;nbsp;location_local)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;replace_file_content(location_local,&amp;nbsp;{&amp;quot;Domain_Name&amp;quot;:&amp;nbsp;domain})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local_files[&amp;quot;location&amp;quot;]&amp;nbsp;=&amp;nbsp;location_local

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;3.3&amp;nbsp;生成location_upstream.conf
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;upstream_local&amp;nbsp;=&amp;nbsp;os.path.join(location_dir,&amp;nbsp;f&amp;quot;{domain}.location_upstream.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shutil.copy(os.path.join(TEMPLATE_DIR,&amp;nbsp;f&amp;quot;location_upstream_{data[&amp;#39;demand_env&amp;#39;]}.conf&amp;quot;),&amp;nbsp;upstream_local)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;replace_file_content(upstream_local,&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Domain_Name&amp;quot;:&amp;nbsp;domain,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Env_Name&amp;quot;:&amp;nbsp;data[&amp;quot;cluster_env&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Req_Id&amp;quot;:&amp;nbsp;data[&amp;quot;req_id&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Cluster_Name&amp;quot;:&amp;nbsp;data[&amp;quot;cluster_name&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local_files[&amp;quot;upstream_conf&amp;quot;]&amp;nbsp;=&amp;nbsp;upstream_local

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code&amp;quot;:&amp;nbsp;500,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;msg&amp;quot;:&amp;nbsp;f&amp;quot;本地生成文件失败:&amp;nbsp;{str(e)}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&amp;nbsp;status=500)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;4.&amp;nbsp;处理远端服务器
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;server&amp;nbsp;in&amp;nbsp;REMOTE_NGINX_SERVERS:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_success&amp;nbsp;=&amp;nbsp;True
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh_client&amp;nbsp;=&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;4.1&amp;nbsp;建立SSH连接
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh_client&amp;nbsp;=&amp;nbsp;paramiko.SSHClient()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh_client.connect(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hostname=server[&amp;quot;host&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port=server[&amp;quot;port&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username=server[&amp;quot;username&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key_filename=os.path.expanduser(server[&amp;quot;key_filename&amp;quot;]),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout=15
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(f&amp;quot;SSH连接成功:&amp;nbsp;{server[&amp;#39;host&amp;#39;]}&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;4.2&amp;nbsp;创建远端目录（核心修复：用SSH命令创建，确保目录存在）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;创建service目录（一般已存在，兜底）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cmd&amp;nbsp;=&amp;nbsp;f&amp;quot;mkdir&amp;nbsp;-p&amp;nbsp;{server[&amp;#39;remote_service_dir&amp;#39;]}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res&amp;nbsp;=&amp;nbsp;exec_ssh_command(ssh_client,&amp;nbsp;cmd)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res[&amp;quot;success&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raise&amp;nbsp;Exception(f&amp;quot;创建service目录失败:&amp;nbsp;{res[&amp;#39;stderr&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;创建location域名目录（关键！之前失败的核心）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;remote_location_dir&amp;nbsp;=&amp;nbsp;os.path.join(server[&amp;quot;remote_location_root&amp;quot;],&amp;nbsp;domain)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cmd&amp;nbsp;=&amp;nbsp;f&amp;quot;mkdir&amp;nbsp;-p&amp;nbsp;{remote_location_dir}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res&amp;nbsp;=&amp;nbsp;exec_ssh_command(ssh_client,&amp;nbsp;cmd)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res[&amp;quot;success&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raise&amp;nbsp;Exception(f&amp;quot;创建location目录失败:&amp;nbsp;{res[&amp;#39;stderr&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(f&amp;quot;创建远端目录:&amp;nbsp;{remote_location_dir}&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;创建upstream目录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cmd&amp;nbsp;=&amp;nbsp;f&amp;quot;mkdir&amp;nbsp;-p&amp;nbsp;{server[&amp;#39;remote_upstream_dir&amp;#39;]}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res&amp;nbsp;=&amp;nbsp;exec_ssh_command(ssh_client,&amp;nbsp;cmd)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res[&amp;quot;success&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raise&amp;nbsp;Exception(f&amp;quot;创建upstream目录失败:&amp;nbsp;{res[&amp;#39;stderr&amp;#39;]}&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;4.3&amp;nbsp;上传文件（SFTP，目录已确保存在）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp&amp;nbsp;=&amp;nbsp;ssh_client.open_sftp()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;上传service.conf
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;remote_service&amp;nbsp;=&amp;nbsp;os.path.join(server[&amp;quot;remote_service_dir&amp;quot;],&amp;nbsp;f&amp;quot;{domain}.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.put(local_files[&amp;quot;service&amp;quot;],&amp;nbsp;remote_service)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(f&amp;quot;上传service.conf:&amp;nbsp;{remote_service}&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;上传location.conf（核心修复：指定正确的远端路径）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;remote_location&amp;nbsp;=&amp;nbsp;os.path.join(remote_location_dir,&amp;nbsp;f&amp;quot;{domain}.location.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.put(local_files[&amp;quot;location&amp;quot;],&amp;nbsp;remote_location)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(f&amp;quot;上传location.conf:&amp;nbsp;{remote_location}&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;上传location_upstream.conf
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;remote_upstream_conf&amp;nbsp;=&amp;nbsp;os.path.join(remote_location_dir,&amp;nbsp;f&amp;quot;{domain}.location_upstream.conf&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.put(local_files[&amp;quot;upstream_conf&amp;quot;],&amp;nbsp;remote_upstream_conf)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(f&amp;quot;上传location_upstream.conf:&amp;nbsp;{remote_upstream_conf}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sftp.close()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;4.4&amp;nbsp;创建upstream空文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cmd&amp;nbsp;=&amp;nbsp;f&amp;quot;touch&amp;nbsp;{os.path.join(server[&amp;#39;remote_upstream_dir&amp;#39;],&amp;nbsp;upstream_file)}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res&amp;nbsp;=&amp;nbsp;exec_ssh_command(ssh_client,&amp;nbsp;cmd)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res[&amp;quot;success&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raise&amp;nbsp;Exception(f&amp;quot;创建upstream空文件失败:&amp;nbsp;{res[&amp;#39;stderr&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(f&amp;quot;创建upstream空文件:&amp;nbsp;{os.path.join(server[&amp;#39;remote_upstream_dir&amp;#39;],&amp;nbsp;upstream_file)}&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;4.5&amp;nbsp;重启Nginx
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cmd&amp;nbsp;=&amp;nbsp;server[&amp;quot;nginx_reload_cmd&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res&amp;nbsp;=&amp;nbsp;exec_ssh_command(ssh_client,&amp;nbsp;cmd)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res[&amp;quot;success&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(f&amp;quot;Nginx重启警告:&amp;nbsp;{res[&amp;#39;stderr&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(&amp;quot;Nginx重启成功&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_success&amp;nbsp;=&amp;nbsp;False
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(f&amp;quot;执行失败:&amp;nbsp;{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result[&amp;quot;code&amp;quot;]&amp;nbsp;=&amp;nbsp;500
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result[&amp;quot;msg&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;部分服务器执行失败&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;finally:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ssh_client:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh_client.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_log.append(&amp;quot;SSH连接关闭&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;记录单服务器结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result[&amp;quot;data&amp;quot;][&amp;quot;servers&amp;quot;].append({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;name&amp;quot;:&amp;nbsp;server[&amp;quot;name&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;host&amp;quot;:&amp;nbsp;server[&amp;quot;host&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;success&amp;quot;:&amp;nbsp;server_success,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;log&amp;quot;:&amp;nbsp;server_log
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Response(result,&amp;nbsp;status=result[&amp;quot;code&amp;quot;])&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.3 调用接口创建配置文件&lt;/span&gt;&lt;br/&gt;&lt;/h3&gt;&lt;p&gt;# curl -X POST&amp;nbsp; &amp;nbsp;http://localhost:8000/nginx_conf_app/api/generate_nginx_conf/&amp;nbsp; &amp;nbsp;-H &amp;quot;Content-Type: application/json&amp;quot;&amp;nbsp; &amp;nbsp;-d &amp;#39;{&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;module_name&amp;quot;: &amp;quot;模块名&amp;quot;,&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;cluster_name&amp;quot;: &amp;quot;集群名&amp;quot;,&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;cluster_env&amp;quot;: &amp;quot;offline&amp;quot;,&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;demand_env&amp;quot;: &amp;quot;stable&amp;quot;,&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;req_id&amp;quot;: &amp;quot;stable&amp;quot;&lt;/p&gt;&lt;p&gt;&amp;nbsp; }&amp;#39;&lt;/p&gt;&lt;p&gt;# curl -X POST&amp;nbsp; &amp;nbsp;http://localhost:8000/nginx_conf_app/api/generate_nginx_conf/&amp;nbsp; &amp;nbsp;-H &amp;quot;Content-Type: application/json&amp;quot;&amp;nbsp; &amp;nbsp;-d &amp;#39;{&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;module_name&amp;quot;: &amp;quot;模块名&amp;quot;,&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;cluster_name&amp;quot;: &amp;quot;集群名&amp;quot;,&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;cluster_env&amp;quot;: &amp;quot;offline&amp;quot;,&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;demand_env&amp;quot;: &amp;quot;demand&amp;quot;,&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;quot;req_id&amp;quot;: &amp;quot;35184&amp;quot;&lt;/p&gt;&lt;p&gt;&amp;nbsp; }&amp;#39;&lt;/p&gt;&lt;p&gt;#这样就会创建server.conf域名文件,location文件以及upstream文件,并且在nginx -s reload的时候还会创建本地缓存文件,可以自行配置域名浏览器访问看是否到了后端对应的pod服务,这里不就截图测试结果了。&lt;/p&gt;&lt;p&gt;#通过上面的程序我们在发布页面测试环境初始化阶段就将集群以及对应的域名和nginx配置好了,这些域名呢都是默认location / 逻辑很简单就是将集群通过域名映射出去了而已,如果是多location的域名怎么办呢？-那就靠网关域名来进行转发了(现在一般网站都在搞前后端分离,大部分后端域名都是location /对应的一个集群,然后具体的入口控制在了前端域名那里),当然肯定还有一些后端域名存在多location的情况,这就通过网关进行路由了,毕竟搞了按需这种方式就是重度依赖网关域名的转发。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space-collapse: preserve; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;二、网关域名的操作&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.1.网关域名数据插入&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#接着上章节的表数据,我们先插入一些新的数据用于测试&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;MariaDB&amp;nbsp;[route]&amp;gt;&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;offline_gateway&amp;nbsp;(domain_name,network_type,applicant)&amp;nbsp;values&amp;nbsp;(&amp;#39;houtai.test.com&amp;#39;,&amp;#39;intranet&amp;#39;,&amp;#39;guliu&amp;#39;);
MariaDB&amp;nbsp;[route]&amp;gt;&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;offline_location&amp;nbsp;(offline_gateway_id,module_name,cluster_name,location_path,rewrite_rule,applicant)&amp;nbsp;values&amp;nbsp;(4,&amp;#39;模块名&amp;#39;,&amp;#39;集群名&amp;#39;,&amp;#39;/&amp;#39;,&amp;#39;NULL&amp;#39;,&amp;#39;guliu&amp;#39;);
MariaDB&amp;nbsp;[route]&amp;gt;&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;offline_location&amp;nbsp;(offline_gateway_id,module_name,cluster_name,location_path,rewrite_rule,applicant)&amp;nbsp;values&amp;nbsp;(4,&amp;#39;模块名&amp;#39;,&amp;#39;集群名&amp;#39;,&amp;#39;/api/(path|service)/&amp;#39;,&amp;#39;NULL&amp;#39;,&amp;#39;guliu&amp;#39;);
MariaDB&amp;nbsp;[route]&amp;gt;&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;offline_location&amp;nbsp;(offline_gateway_id,module_name,cluster_name,location_path,rewrite_rule,applicant)&amp;nbsp;values&amp;nbsp;(4,&amp;#39;模块名&amp;#39;,&amp;#39;集群名&amp;#39;,&amp;#39;/api/token/&amp;#39;,&amp;#39;/api/(.*)&amp;nbsp;/api/auth/$1&amp;#39;,&amp;#39;guliu&amp;#39;);
MariaDB&amp;nbsp;[route]&amp;gt;&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;mirror_gateway&amp;nbsp;(domain_name,offline_gateway_id,network_type,applicant)&amp;nbsp;values&amp;nbsp;(&amp;#39;houtai-mirror.test.com&amp;#39;,4,&amp;#39;intranet&amp;#39;,&amp;#39;guliu&amp;#39;);
MariaDB&amp;nbsp;[route]&amp;gt;&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;on_demand&amp;nbsp;(source_ip,demand_number,user)&amp;nbsp;values&amp;nbsp;(&amp;#39;192.168.1.135&amp;#39;,35184,&amp;#39;test01&amp;#39;);&lt;/pre&gt;&lt;p&gt;#这样我们创建了一个新的测试网关,也让集群跟网关域名的location关联上了,也让沙箱网关域名跟测试域名网关关联上了,关联的意义就是获取location与集群的对应信息&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.2 编写网关调用的lua脚本&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# vi /opt/soft/nginx/main-conf/nginx.conf&amp;nbsp; #这里创建一个需求号缓存就是避免频繁的访问mysql(访问mysql获取的结果缓存1分钟)&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;lua_package_path&amp;nbsp;&amp;quot;/opt/soft/package/lua-resty-redis-0.29/lib/?.lua;/usr/local/lua_core/lib/lua/?.lua;;&amp;quot;;
#&amp;nbsp;仅保留IP-reqid缓存字典（1分钟过期）
lua_shared_dict&amp;nbsp;ip_reqid_cache&amp;nbsp;100m;&lt;/pre&gt;&lt;p&gt;#mkdir /opt/soft/nginx/conf.d/lua/logs/&lt;/p&gt;&lt;p&gt;# vi /opt/soft/nginx/conf.d/lua/ip_demand_query.lua&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;1.&amp;nbsp;初始化IP-reqid缓存
local&amp;nbsp;ip_reqid_cache&amp;nbsp;=&amp;nbsp;ngx.shared.ip_reqid_cache

--&amp;nbsp;2.&amp;nbsp;Lua专属日志文件配置（单独写入，不混Nginx日志）
local&amp;nbsp;lua_log_path&amp;nbsp;=&amp;nbsp;&amp;quot;/opt/soft/nginx/conf.d/lua/logs/gateway_proxy.log&amp;quot;
--&amp;nbsp;日志格式化函数（带时间、级别、内容）
local&amp;nbsp;function&amp;nbsp;lua_log(level,&amp;nbsp;content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;time_str&amp;nbsp;=&amp;nbsp;os.date(&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_str&amp;nbsp;=&amp;nbsp;string.format(&amp;quot;[%s]&amp;nbsp;[%s]&amp;nbsp;%s\n&amp;quot;,&amp;nbsp;time_str,&amp;nbsp;level,&amp;nbsp;content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;追加写入日志文件（权限需确保nginx用户可写）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;f,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;io.open(lua_log_path,&amp;nbsp;&amp;quot;a&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;f&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f:write(log_str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;兜底：写入Nginx错误日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;Lua日志写入失败：&amp;quot;,&amp;nbsp;err,&amp;nbsp;&amp;quot;&amp;nbsp;内容：&amp;quot;,&amp;nbsp;log_str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
end

--&amp;nbsp;补全string.trim函数（Lua原生无trim，解决reqid带空格问题）
if&amp;nbsp;not&amp;nbsp;string.trim&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;function&amp;nbsp;string.trim(s)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(s:gsub(&amp;quot;^%s+&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;):gsub(&amp;quot;%s+$&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
end

--&amp;nbsp;3.&amp;nbsp;从Nginx配置中获取固定变量（用于拼接转发域名和日志）
local&amp;nbsp;gateway_domain&amp;nbsp;=&amp;nbsp;ngx.var.server_name&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;网关域名（Nginx的server_name）
local&amp;nbsp;proxy_env&amp;nbsp;=&amp;nbsp;ngx.var.proxy_env&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;环境变量（offline/mirror）
local&amp;nbsp;stable_domain&amp;nbsp;=&amp;nbsp;ngx.var.stable_domain&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;stable兜底域名
local&amp;nbsp;module_name&amp;nbsp;=&amp;nbsp;ngx.var.module_name&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;模块名
local&amp;nbsp;request_uri&amp;nbsp;=&amp;nbsp;ngx.var.request_uri&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;转发的location（请求路径）

--&amp;nbsp;4.&amp;nbsp;初始化Nginx变量（供转发/头信息使用）
ngx.var.client_ip&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
ngx.var.trace_tag&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
ngx.var.demand_number&amp;nbsp;=&amp;nbsp;&amp;quot;stable&amp;quot;

--&amp;nbsp;5.&amp;nbsp;MySQL配置（保持原有配置）
local&amp;nbsp;mysql_config&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host&amp;nbsp;=&amp;nbsp;&amp;quot;192.168.1.101&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port&amp;nbsp;=&amp;nbsp;3306,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;&amp;quot;route&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;&amp;quot;route&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;route123456&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout&amp;nbsp;=&amp;nbsp;1000,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pool_size&amp;nbsp;=&amp;nbsp;100,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pool_idle_time&amp;nbsp;=&amp;nbsp;10000
}

--&amp;nbsp;6.&amp;nbsp;防SQL注入函数（转义IP中的特殊字符）
local&amp;nbsp;function&amp;nbsp;escape_sql_str(str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;str&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;&amp;#39;&amp;#39;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;转义单引号（SQL注入核心风险点）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str&amp;nbsp;=&amp;nbsp;string.gsub(str,&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;,&amp;nbsp;&amp;quot;&amp;#39;&amp;#39;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;仅保留IP合法字符（数字+点），进一步防护
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str&amp;nbsp;=&amp;nbsp;string.gsub(str,&amp;nbsp;&amp;quot;[^0-9%.]&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;&amp;nbsp;..&amp;nbsp;str&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;
end

--&amp;nbsp;7.&amp;nbsp;工具函数：获取客户端真实IP
local&amp;nbsp;function&amp;nbsp;get_client_ip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;x_real_ip&amp;nbsp;=&amp;nbsp;ngx.req.get_headers()[&amp;quot;X-Real-IP&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;x_forwarded_for&amp;nbsp;=&amp;nbsp;ngx.req.get_headers()[&amp;quot;X-Forwarded-For&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;x_forwarded_for&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip_list&amp;nbsp;=&amp;nbsp;string.gmatch(x_forwarded_for,&amp;nbsp;&amp;quot;([^,]+)&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;x_forwarded_for&amp;nbsp;=&amp;nbsp;ip_list()&amp;nbsp;and&amp;nbsp;string.trim(ip_list())&amp;nbsp;or&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;client_ip&amp;nbsp;=&amp;nbsp;x_real_ip&amp;nbsp;or&amp;nbsp;x_forwarded_for&amp;nbsp;or&amp;nbsp;ngx.var.remote_addr
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.client_ip&amp;nbsp;=&amp;nbsp;client_ip
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;记录网关域名、客户端IP、请求路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;基础信息&amp;nbsp;|&amp;nbsp;网关域名：&amp;quot;&amp;nbsp;..&amp;nbsp;gateway_domain&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;|&amp;nbsp;客户端IP：&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;|&amp;nbsp;转发Location：&amp;quot;&amp;nbsp;..&amp;nbsp;request_uri)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;client_ip
end

--&amp;nbsp;8.&amp;nbsp;工具函数：reqid合法性校验（长度+逐字符判断，避开正则兼容问题）
local&amp;nbsp;function&amp;nbsp;validate_reqid(reqid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第一步：先trim去空格
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;trimmed_reqid&amp;nbsp;=&amp;nbsp;string.trim(reqid&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;reqid校验&amp;nbsp;|&amp;nbsp;原始reqid：[&amp;quot;&amp;nbsp;..&amp;nbsp;tostring(reqid)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;nbsp;|&amp;nbsp;去空格后：[&amp;quot;&amp;nbsp;..&amp;nbsp;trimmed_reqid&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第二步：空值判断
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;trimmed_reqid&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;WARN&amp;quot;,&amp;nbsp;&amp;quot;reqid校验&amp;nbsp;|&amp;nbsp;reqid为空，强制转为stable&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;stable&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第三步：校验规则（长度=5&amp;nbsp;+&amp;nbsp;逐字符判断是否为数字，替代正则）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;is_stable&amp;nbsp;=&amp;nbsp;(trimmed_reqid&amp;nbsp;==&amp;nbsp;&amp;quot;stable&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;is_5digit&amp;nbsp;=&amp;nbsp;false

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;判断是否是5位长度&amp;nbsp;+&amp;nbsp;每个字符都是数字（0-9）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;string.len(trimmed_reqid)&amp;nbsp;==&amp;nbsp;5&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;all_digit&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;5&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;c&amp;nbsp;=&amp;nbsp;string.sub(trimmed_reqid,&amp;nbsp;i,&amp;nbsp;i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;(c&amp;nbsp;&amp;gt;=&amp;nbsp;&amp;quot;0&amp;quot;&amp;nbsp;and&amp;nbsp;c&amp;nbsp;&amp;lt;=&amp;nbsp;&amp;quot;9&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;all_digit&amp;nbsp;=&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is_5digit&amp;nbsp;=&amp;nbsp;all_digit
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;调试日志：打印逐字符校验结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;DEBUG&amp;quot;,&amp;nbsp;&amp;quot;reqid校验&amp;nbsp;|&amp;nbsp;5位长度：&amp;quot;&amp;nbsp;..&amp;nbsp;tostring(string.len(trimmed_reqid)==5)&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;|&amp;nbsp;全数字：&amp;quot;&amp;nbsp;..&amp;nbsp;tostring(all_digit))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;is_stable&amp;nbsp;or&amp;nbsp;is_5digit&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;reqid校验&amp;nbsp;|&amp;nbsp;reqid合法：&amp;quot;&amp;nbsp;..&amp;nbsp;trimmed_reqid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;trimmed_reqid
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;WARN&amp;quot;,&amp;nbsp;&amp;quot;reqid校验&amp;nbsp;|&amp;nbsp;reqid非法(&amp;quot;&amp;nbsp;..&amp;nbsp;trimmed_reqid&amp;nbsp;..&amp;nbsp;&amp;quot;)，强制转为stable（规则：仅支持5位纯数字或stable）&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;stable&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
end

--&amp;nbsp;9.&amp;nbsp;工具函数：查询MySQL获取原始reqid
local&amp;nbsp;function&amp;nbsp;query_db_reqid(client_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;DB查询&amp;nbsp;|&amp;nbsp;缓存未命中，查询MySQL：IP=&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;db,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;require(&amp;quot;resty.mysql&amp;quot;):new()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;db&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;&amp;quot;DB错误&amp;nbsp;|&amp;nbsp;创建MySQL连接失败：&amp;quot;&amp;nbsp;..&amp;nbsp;(err&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;ERROR&amp;quot;,&amp;nbsp;err_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db:set_timeout(mysql_config.timeout)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接数据库
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err,&amp;nbsp;errno&amp;nbsp;=&amp;nbsp;db:connect({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host&amp;nbsp;=&amp;nbsp;mysql_config.host,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port&amp;nbsp;=&amp;nbsp;mysql_config.port,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;mysql_config.database,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;mysql_config.user,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;mysql_config.password
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;&amp;quot;DB错误&amp;nbsp;|&amp;nbsp;连接MySQL失败：&amp;quot;&amp;nbsp;..&amp;nbsp;(err&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;)&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;errno=&amp;quot;&amp;nbsp;..&amp;nbsp;(errno&amp;nbsp;or&amp;nbsp;&amp;quot;nil&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;ERROR&amp;quot;,&amp;nbsp;err_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;替换?占位符，手动拼接SQL并做防注入处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;escaped_ip&amp;nbsp;=&amp;nbsp;escape_sql_str(client_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;sql&amp;nbsp;=&amp;nbsp;string.format(&amp;quot;SELECT&amp;nbsp;demand_number&amp;nbsp;FROM&amp;nbsp;on_demand&amp;nbsp;WHERE&amp;nbsp;source_ip&amp;nbsp;=&amp;nbsp;%s&amp;nbsp;LIMIT&amp;nbsp;1&amp;quot;,&amp;nbsp;escaped_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;DB查询&amp;nbsp;|&amp;nbsp;执行SQL：&amp;quot;&amp;nbsp;..&amp;nbsp;sql)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;db:query(sql)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;放回连接池
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err_pool&amp;nbsp;=&amp;nbsp;db:set_keepalive(mysql_config.pool_idle_time,&amp;nbsp;mysql_config.pool_size)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;WARN&amp;quot;,&amp;nbsp;&amp;quot;DB警告&amp;nbsp;|&amp;nbsp;MySQL连接放回池失败：&amp;quot;&amp;nbsp;..&amp;nbsp;(err_pool&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;err&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;&amp;quot;DB错误&amp;nbsp;|&amp;nbsp;查询MySQL失败：&amp;quot;&amp;nbsp;..&amp;nbsp;(err&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;ERROR&amp;quot;,&amp;nbsp;err_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;db_reqid&amp;nbsp;=&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;res&amp;nbsp;and&amp;nbsp;#res&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db_reqid&amp;nbsp;=&amp;nbsp;res[1].demand_number
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;DB结果&amp;nbsp;|&amp;nbsp;IP=&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;reqid=&amp;quot;&amp;nbsp;..&amp;nbsp;tostring(db_reqid))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;DB结果&amp;nbsp;|&amp;nbsp;MySQL未查到IP(&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip&amp;nbsp;..&amp;nbsp;&amp;quot;)的reqid&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;db_reqid
end

--&amp;nbsp;10.&amp;nbsp;DNS解析校验函数（判断域名是否可解析）
local&amp;nbsp;function&amp;nbsp;is_domain_resolvable(domain,&amp;nbsp;timeout)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;默认超时时间100ms，避免阻塞请求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;dns_timeout&amp;nbsp;=&amp;nbsp;timeout&amp;nbsp;or&amp;nbsp;100
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用ngx.socket.tcp进行DNS解析（纯Nginx+Lua环境兼容）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;sock&amp;nbsp;=&amp;nbsp;ngx.socket.tcp()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sock:settimeout(dns_timeout)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;尝试连接域名的80端口（仅检测解析，不实际建立连接）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;sock:connect(domain,&amp;nbsp;80)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sock:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;常见解析失败错误：host&amp;nbsp;not&amp;nbsp;found、timeout
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;WARN&amp;quot;,&amp;nbsp;&amp;quot;DNS解析&amp;nbsp;|&amp;nbsp;域名[&amp;quot;&amp;nbsp;..&amp;nbsp;domain&amp;nbsp;..&amp;nbsp;&amp;quot;]不可访问：&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sock:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
end

--&amp;nbsp;11.&amp;nbsp;工具函数：计算最终转发域名（DNS校验+兜底逻辑）
local&amp;nbsp;function&amp;nbsp;get_final_proxy_domain(final_reqid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;target_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;拼接目标域名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;final_reqid&amp;nbsp;==&amp;nbsp;&amp;quot;stable&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;target_domain&amp;nbsp;=&amp;nbsp;stable_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;转发域名&amp;nbsp;|&amp;nbsp;直接使用stable兜底域名：&amp;quot;&amp;nbsp;..&amp;nbsp;target_domain)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.proxy_domain&amp;nbsp;=&amp;nbsp;target_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;target_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;target_domain&amp;nbsp;=&amp;nbsp;module_name&amp;nbsp;..&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;..&amp;nbsp;proxy_env&amp;nbsp;..&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;..&amp;nbsp;final_reqid&amp;nbsp;..&amp;nbsp;&amp;quot;.test.com&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;转发域名&amp;nbsp;|&amp;nbsp;拼接目标域名：&amp;quot;&amp;nbsp;..&amp;nbsp;target_domain)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;校验目标域名是否可解析，不可解析则切换到stable
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;is_domain_resolvable(target_domain)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;转发域名&amp;nbsp;|&amp;nbsp;域名[&amp;quot;&amp;nbsp;..&amp;nbsp;target_domain&amp;nbsp;..&amp;nbsp;&amp;quot;]可解析，正常转发&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.proxy_domain&amp;nbsp;=&amp;nbsp;target_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;target_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;WARN&amp;quot;,&amp;nbsp;&amp;quot;转发域名&amp;nbsp;|&amp;nbsp;域名[&amp;quot;&amp;nbsp;..&amp;nbsp;target_domain&amp;nbsp;..&amp;nbsp;&amp;quot;]不可解析，切换到stable兜底域名&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.proxy_domain&amp;nbsp;=&amp;nbsp;stable_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;stable_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
end

--&amp;nbsp;12.&amp;nbsp;主逻辑：缓存查询&amp;nbsp;+&amp;nbsp;DB兜底（仅60秒缓存）
local&amp;nbsp;function&amp;nbsp;get_reqid()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;client_ip&amp;nbsp;=&amp;nbsp;get_client_ip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cache_expire&amp;nbsp;=&amp;nbsp;tonumber(ngx.var.cache_expire)&amp;nbsp;or&amp;nbsp;60

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;先查缓存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;reqid&amp;nbsp;=&amp;nbsp;ip_reqid_cache:get(client_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;reqid&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;缓存命中&amp;nbsp;|&amp;nbsp;IP=&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;reqid=&amp;quot;&amp;nbsp;..&amp;nbsp;reqid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;validate_reqid(reqid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;缓存未命中：查DB
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;db_reqid&amp;nbsp;=&amp;nbsp;query_db_reqid(client_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;无论是否查到，都缓存60秒（避免频繁查库）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cache_val&amp;nbsp;=&amp;nbsp;db_reqid&amp;nbsp;or&amp;nbsp;&amp;quot;NULL&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;ip_reqid_cache:set(client_ip,&amp;nbsp;cache_val,&amp;nbsp;cache_expire)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;ERROR&amp;quot;,&amp;nbsp;&amp;quot;缓存错误&amp;nbsp;|&amp;nbsp;缓存设置失败：IP=&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;错误=&amp;quot;&amp;nbsp;..&amp;nbsp;(err&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;缓存设置&amp;nbsp;|&amp;nbsp;IP=&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;值=&amp;quot;&amp;nbsp;..&amp;nbsp;cache_val&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;过期时间=&amp;quot;&amp;nbsp;..&amp;nbsp;cache_expire&amp;nbsp;..&amp;nbsp;&amp;quot;秒&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;校验并返回
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;validate_reqid(db_reqid&amp;nbsp;or&amp;nbsp;&amp;quot;stable&amp;quot;)
end

--&amp;nbsp;13.&amp;nbsp;主执行逻辑
local&amp;nbsp;function&amp;nbsp;main()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;=====&amp;nbsp;新请求开始&amp;nbsp;=====&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;final_reqid&amp;nbsp;=&amp;nbsp;get_reqid()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.trace_tag&amp;nbsp;=&amp;nbsp;final_reqid
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.demand_number&amp;nbsp;=&amp;nbsp;final_reqid

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算并记录最终转发域名（含DNS校验+兜底）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;final_proxy_domain&amp;nbsp;=&amp;nbsp;get_final_proxy_domain(final_reqid)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置Trace-Tag请求头（非空才设）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;final_reqid&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.req.set_header(&amp;quot;Trace-Tag&amp;quot;,&amp;nbsp;final_reqid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;请求头&amp;nbsp;|&amp;nbsp;设置Trace-Tag：&amp;quot;&amp;nbsp;..&amp;nbsp;final_reqid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;全链路信息汇总日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;请求完成&amp;nbsp;|&amp;nbsp;网关域名：&amp;quot;&amp;nbsp;..&amp;nbsp;gateway_domain&amp;nbsp;..&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;nbsp;|&amp;nbsp;客户端IP：&amp;quot;&amp;nbsp;..&amp;nbsp;ngx.var.client_ip&amp;nbsp;..&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;nbsp;|&amp;nbsp;转发Location：&amp;quot;&amp;nbsp;..&amp;nbsp;request_uri&amp;nbsp;..&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;nbsp;|&amp;nbsp;最终trace_tag：&amp;quot;&amp;nbsp;..&amp;nbsp;final_reqid&amp;nbsp;..&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;nbsp;|&amp;nbsp;最终转发域名：&amp;quot;&amp;nbsp;..&amp;nbsp;final_proxy_domain)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;=====&amp;nbsp;请求结束&amp;nbsp;=====\n&amp;quot;)
end

--&amp;nbsp;执行主逻辑（捕获异常，写入Lua日志）
local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;pcall(main)
if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;FATAL&amp;quot;,&amp;nbsp;&amp;quot;Lua执行异常：&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;异常时强制使用stable
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.trace_tag&amp;nbsp;=&amp;nbsp;&amp;quot;stable&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.demand_number&amp;nbsp;=&amp;nbsp;&amp;quot;stable&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.proxy_domain&amp;nbsp;=&amp;nbsp;stable_domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;异常时也记录基础信息，方便排查
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;ERROR&amp;quot;,&amp;nbsp;&amp;quot;异常兜底&amp;nbsp;|&amp;nbsp;网关域名：&amp;quot;&amp;nbsp;..&amp;nbsp;gateway_domain&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;|&amp;nbsp;客户端IP：&amp;quot;&amp;nbsp;..&amp;nbsp;ngx.var.client_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;|&amp;nbsp;强制使用stable转发&amp;quot;)
end&lt;/pre&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space-collapse: preserve; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;#vi&amp;nbsp;/opt/soft/nginx/conf.d/lua/clear_ip_cache.lua&amp;nbsp; #这个脚本的作用就是把ip对应的需求号从缓存中清理掉方便及时重新获取&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;1.&amp;nbsp;初始化缓存和响应参数
local&amp;nbsp;ip_reqid_cache&amp;nbsp;=&amp;nbsp;ngx.shared.ip_reqid_cache
local&amp;nbsp;resp_code&amp;nbsp;=&amp;nbsp;200
local&amp;nbsp;resp_msg&amp;nbsp;=&amp;nbsp;&amp;quot;操作成功&amp;quot;
local&amp;nbsp;resp_ip&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
local&amp;nbsp;resp_old_val&amp;nbsp;=&amp;nbsp;&amp;quot;NULL&amp;quot;

--&amp;nbsp;2.&amp;nbsp;Lua专属日志文件配置
local&amp;nbsp;lua_log_path&amp;nbsp;=&amp;nbsp;&amp;quot;/opt/soft/nginx/conf.d/lua/logs/lua_cache_api.log&amp;quot;
local&amp;nbsp;function&amp;nbsp;lua_log(level,&amp;nbsp;content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;time_str&amp;nbsp;=&amp;nbsp;os.date(&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_str&amp;nbsp;=&amp;nbsp;string.format(&amp;quot;[%s]&amp;nbsp;[%s]&amp;nbsp;%s\n&amp;quot;,&amp;nbsp;time_str,&amp;nbsp;level,&amp;nbsp;content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;f,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;io.open(lua_log_path,&amp;nbsp;&amp;quot;a&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;f&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f:write(log_str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;Lua接口日志写入失败：&amp;quot;,&amp;nbsp;err,&amp;nbsp;&amp;quot;&amp;nbsp;内容：&amp;quot;,&amp;nbsp;log_str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
end

--&amp;nbsp;3.&amp;nbsp;解析POST参数（获取要清理的IP）
local&amp;nbsp;function&amp;nbsp;get_post_ip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.req.read_body()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;post_data&amp;nbsp;=&amp;nbsp;ngx.req.get_post_args()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;target_ip&amp;nbsp;=&amp;nbsp;post_data.ip&amp;nbsp;or&amp;nbsp;post_data.target_ip&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;接收清理请求：参数IP=&amp;quot;&amp;nbsp;..&amp;nbsp;target_ip)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;简单IP格式校验
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;is_valid_ip&amp;nbsp;=&amp;nbsp;string.match(target_ip,&amp;nbsp;&amp;quot;^%d+%.%d+%.%d+%.%d+$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;is_valid_ip&amp;nbsp;or&amp;nbsp;target_ip&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;&amp;quot;参数错误：IP格式非法或为空（接收值：&amp;quot;&amp;nbsp;..&amp;nbsp;target_ip&amp;nbsp;..&amp;nbsp;&amp;quot;）&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;WARN&amp;quot;,&amp;nbsp;err_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_code&amp;nbsp;=&amp;nbsp;400
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_msg&amp;nbsp;=&amp;nbsp;err_msg
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_ip&amp;nbsp;=&amp;nbsp;target_ip
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;IP格式校验通过：&amp;quot;&amp;nbsp;..&amp;nbsp;target_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;target_ip
end

--&amp;nbsp;4.&amp;nbsp;清理缓存核心逻辑
local&amp;nbsp;function&amp;nbsp;clear_cache(target_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;target_ip&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;查询原缓存值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;old_val&amp;nbsp;=&amp;nbsp;ip_reqid_cache:get(target_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_old_val&amp;nbsp;=&amp;nbsp;old_val&amp;nbsp;or&amp;nbsp;&amp;quot;NULL&amp;quot;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;清理前缓存值：IP=&amp;quot;&amp;nbsp;..&amp;nbsp;target_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;值=&amp;quot;&amp;nbsp;..&amp;nbsp;resp_old_val)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;删除缓存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;ip_reqid_cache:delete(target_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;&amp;quot;缓存清理失败：IP=&amp;quot;&amp;nbsp;..&amp;nbsp;target_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;错误=&amp;quot;&amp;nbsp;..&amp;nbsp;(err&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;ERROR&amp;quot;,&amp;nbsp;err_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_code&amp;nbsp;=&amp;nbsp;500
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_msg&amp;nbsp;=&amp;nbsp;err_msg
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;记录操作日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;success_msg&amp;nbsp;=&amp;nbsp;&amp;quot;缓存清理成功：IP=&amp;quot;&amp;nbsp;..&amp;nbsp;target_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;原缓存值=&amp;quot;&amp;nbsp;..&amp;nbsp;resp_old_val
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;success_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end

--&amp;nbsp;5.&amp;nbsp;纯Lua拼接JSON字符串（核心：无cjson依赖）
local&amp;nbsp;function&amp;nbsp;build_json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;json_str&amp;nbsp;=&amp;nbsp;string.format(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;{&amp;quot;code&amp;quot;:%d,&amp;quot;msg&amp;quot;:&amp;quot;%s&amp;quot;,&amp;quot;data&amp;quot;:{&amp;quot;ip&amp;quot;:&amp;quot;%s&amp;quot;,&amp;quot;old_cache_value&amp;quot;:&amp;quot;%s&amp;quot;}}&amp;#39;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_code,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.escape_uri(resp_msg):gsub(&amp;quot;%%22&amp;quot;,&amp;nbsp;&amp;quot;\\\&amp;quot;&amp;quot;),&amp;nbsp;&amp;nbsp;--&amp;nbsp;转义双引号
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_ip,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_old_val
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;还原URI编码，仅保留JSON需要的转义
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;json_str&amp;nbsp;=&amp;nbsp;ngx.unescape_uri(json_str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;json_str
end

--&amp;nbsp;6.&amp;nbsp;主执行逻辑（捕获异常）
local&amp;nbsp;function&amp;nbsp;main()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;=====&amp;nbsp;缓存清理接口请求开始&amp;nbsp;=====&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;target_ip&amp;nbsp;=&amp;nbsp;get_post_ip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clear_cache(target_ip)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;构建并返回JSON响应（纯Lua拼接）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res_json&amp;nbsp;=&amp;nbsp;build_json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Content-Type&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;application/json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(res_json)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;接口响应：&amp;quot;&amp;nbsp;..&amp;nbsp;res_json)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;&amp;quot;=====&amp;nbsp;缓存清理接口请求结束&amp;nbsp;=====\n&amp;quot;)
end

--&amp;nbsp;执行并捕获异常
local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;pcall(main)
if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;&amp;quot;接口执行异常：&amp;quot;&amp;nbsp;..&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_log(&amp;quot;FATAL&amp;quot;,&amp;nbsp;err_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;异常时返回错误JSON
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;err_json&amp;nbsp;=&amp;nbsp;string.format(&amp;#39;{&amp;quot;code&amp;quot;:500,&amp;quot;msg&amp;quot;:&amp;quot;%s&amp;quot;,&amp;quot;data&amp;quot;:{}}&amp;#39;,&amp;nbsp;err_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Content-Type&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;application/json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(err_json)
end&lt;/pre&gt;&lt;p&gt;# vim /opt/soft/nginx/conf.d/cache-api.test.com.conf&amp;nbsp; #配置上后nginx reload一下&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name&amp;nbsp;cache-api.test.com;&amp;nbsp;&amp;nbsp;#&amp;nbsp;接口专属域名

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/clear_ip_cache&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;IP白名单（仅内网可访问）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;allow&amp;nbsp;127.0.0.1;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;allow&amp;nbsp;192.168.1.0/24;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;deny&amp;nbsp;all;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;仅允许POST请求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($request_method&amp;nbsp;!=&amp;nbsp;POST)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;405&amp;nbsp;&amp;quot;Method&amp;nbsp;Not&amp;nbsp;Allowed&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;执行缓存清理逻辑（Lua写入专属日志）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_file&amp;nbsp;/opt/soft/nginx/conf.d/lua/clear_ip_cache.lua;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;add_header&amp;nbsp;Content-Type&amp;nbsp;application/json;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_log&amp;nbsp;/opt/log/nginx/cache-api.test.com/cache-api.test.com_access.log&amp;nbsp;main;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_log&amp;nbsp;/opt/log/nginx/cache-api.test.com/cache-api.test.com_error.log&amp;nbsp;error;
}&lt;/pre&gt;&lt;p&gt;# curl -X POST &amp;quot;http://cache-api.test.com/clear_ip_cache&amp;quot; -d &amp;quot;ip=192.168.1.101&amp;quot;&amp;nbsp; #可以清理测试一下,看下接口是否正常&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{&amp;quot;code&amp;quot;:200,&amp;quot;msg&amp;quot;:&amp;quot;操作成功&amp;quot;,&amp;quot;data&amp;quot;:{&amp;quot;ip&amp;quot;:&amp;quot;192.168.1.101&amp;quot;,&amp;quot;old_cache_value&amp;quot;:&amp;quot;NULL&amp;quot;}}&lt;/pre&gt;&lt;p&gt;#&amp;nbsp;yum install dnsmasq -y&lt;/p&gt;&lt;p&gt;# vim /etc/dnsmasq.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;listen-address=127.0.0.1&lt;/pre&gt;&lt;p&gt;#&amp;nbsp;systemctl start dnsmasq #Nginx的resolver不支持直接读取/etc/hosts，必须通过DNS服务中转&lt;/p&gt;&lt;p&gt;# vi /opt/soft/nginx/conf.d/zhongtai.test.com.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;443&amp;nbsp;ssl;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name&amp;nbsp;zhongtai.test.com;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#ssl配置引用上面的nginx的配置这里就不粘贴占用篇幅了

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_tokens&amp;nbsp;off;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;charset&amp;nbsp;utf-8;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#指定dnsmasq的地址127.0.0.1，优先解析hosts里的域名,valid=10s是域名解析缓存时间，避免频繁解析，可根据需要调整（如60或者300s）；
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这个valid有一点是注意的,这是nginx缓存域名的时间,不管是正确还是错误的缓存,都要等缓存过期后重新解析,如果你域名变更不频繁可以调大缓存时间
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resolver&amp;nbsp;127.0.0.1:53&amp;nbsp;valid=10s&amp;nbsp;ipv6=off;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#默认等待时间,如果只依赖于本地hosts解析可以设置成如500ms,如果是本地hosts+dns服务这里可以设置的大一点,nginx有缓存也不会频繁的域名解析
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resolver_timeout&amp;nbsp;5s;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/opt/soft/nginx/conf.d/location/zhongtai.test.com/*.location.conf;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_log&amp;nbsp;/opt/log/nginx/zhongtai.test.com/zhongtai.test.com_access.log&amp;nbsp;main;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_log&amp;nbsp;/opt/log/nginx/zhongtai.test.com/zhongtai.test.com_error.log&amp;nbsp;error;
}&lt;/pre&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space-collapse: preserve; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;# vi /opt/soft/nginx/conf.d/location/zhongtai.test.com/zhongtai.test.com.location.conf&amp;nbsp; &amp;nbsp;#配置好后nginx reload一下&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;=====&amp;nbsp;固定配置（直接写死）=====
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$demand_number&amp;nbsp;&amp;quot;stable&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;初始化默认值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$trace_tag&amp;nbsp;&amp;quot;stable&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$client_ip&amp;nbsp;&amp;quot;&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;核心：初始化自定义变量client_ip
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$proxy_env&amp;nbsp;&amp;quot;offline&amp;quot;;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;环境（固定：offline/mirror）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$stable_domain&amp;nbsp;&amp;quot;模块名-offline-stable.test.com&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;stable后端域名（固定）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$module_name&amp;nbsp;&amp;quot;模块名&amp;quot;;&amp;nbsp;&amp;nbsp;#&amp;nbsp;模块名（固定）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$cache_expire&amp;nbsp;60;&amp;nbsp;&amp;nbsp;&amp;nbsp;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;执行核心业务逻辑（Lua会写入专属日志）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_by_lua_file&amp;nbsp;/opt/soft/nginx/conf.d/lua/ip_demand_query.lua;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;域名拼接（直接使用Nginx固定变量）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($demand_number&amp;nbsp;=&amp;nbsp;&amp;quot;stable&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$proxy_domain&amp;nbsp;$stable_domain;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;($demand_number&amp;nbsp;!=&amp;nbsp;&amp;quot;stable&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set&amp;nbsp;$proxy_domain&amp;nbsp;&amp;quot;$module_name-$proxy_env-$trace_tag&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;代理转发
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;https://$proxy_domain$request_uri;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;=====&amp;nbsp;Trace-Tag传递：仅非空时设置&amp;nbsp;=====
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;Host&amp;nbsp;$proxy_domain;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Real-IP&amp;nbsp;$remote_addr;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-For&amp;nbsp;$proxy_add_x_forwarded_for;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_next_upstream&amp;nbsp;http_502&amp;nbsp;&amp;nbsp;error&amp;nbsp;timeout&amp;nbsp;invalid_header;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-Proto&amp;nbsp;&amp;nbsp;$scheme;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;基础代理配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_connect_timeout&amp;nbsp;5s;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_send_timeout&amp;nbsp;5s;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_read_timeout&amp;nbsp;5s;
}&lt;/pre&gt;&lt;p&gt;#然后就可以浏览器访问zhongtai.test.com做测试了,主要测试三种场景:&lt;br/&gt;1. ip指向的需求有对应的模块域名(比如正好是我们要测试的变更模块)&lt;br/&gt;2. ip指向的需求没有对应的模块域名(比如请求域名不是我们变更的模块自然就不会再需求里面了,会不会走到stable环境)&lt;/p&gt;&lt;p&gt;3. 来源IP压根就不存在于任何需求中或者绑定了stable环境,请求是否走到stable的域名&lt;/p&gt;&lt;p&gt;可以观察对应域名的访问日志来观测路由请求是否符合需求。&lt;/p&gt;&lt;p&gt;# tail -f /opt/soft/nginx/conf.d/lua/logs/gateway_proxy.log&amp;nbsp; #比如网关完整的路由记录便于排查问题&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[INFO]&amp;nbsp;=====&amp;nbsp;新请求开始&amp;nbsp;=====
[INFO]&amp;nbsp;基础信息&amp;nbsp;|&amp;nbsp;网关域名：zhongtai.test.com&amp;nbsp;|&amp;nbsp;客户端IP：192.168.1.135&amp;nbsp;|&amp;nbsp;转发Location：/dasdsadsa/dadasdas/
[INFO]&amp;nbsp;DB查询&amp;nbsp;|&amp;nbsp;缓存未命中，查询MySQL：IP=192.168.1.135
[INFO]&amp;nbsp;DB查询&amp;nbsp;|&amp;nbsp;执行SQL：SELECT&amp;nbsp;demand_number&amp;nbsp;FROM&amp;nbsp;on_demand&amp;nbsp;WHERE&amp;nbsp;source_ip&amp;nbsp;=&amp;nbsp;192.168.1.135&amp;#39;&amp;nbsp;LIMIT&amp;nbsp;1
[INFO]&amp;nbsp;DB结果&amp;nbsp;|&amp;nbsp;IP=192.168.1.135&amp;nbsp;reqid=35184
[INFO]&amp;nbsp;缓存设置&amp;nbsp;|&amp;nbsp;IP=192.168.1.135&amp;nbsp;值=35184&amp;nbsp;过期时间=60秒
[INFO]&amp;nbsp;reqid校验&amp;nbsp;|&amp;nbsp;原始reqid：[35184]&amp;nbsp;|&amp;nbsp;去空格后：[35184]
[DEBUG]&amp;nbsp;reqid校验&amp;nbsp;|&amp;nbsp;5位长度：true&amp;nbsp;|&amp;nbsp;全数字：true
[INFO]&amp;nbsp;reqid校验&amp;nbsp;|&amp;nbsp;reqid合法：35184
[INFO]&amp;nbsp;转发域名&amp;nbsp;|&amp;nbsp;最终转发到：http_develop-offline-35184.test.com&amp;nbsp;
[INFO]&amp;nbsp;请求头&amp;nbsp;|&amp;nbsp;设置Trace-Tag：35184
[INFO]&amp;nbsp;请求完成&amp;nbsp;|&amp;nbsp;网关域名：zhongtai.test.com&amp;nbsp;&amp;nbsp;|&amp;nbsp;客户端IP：192.168.1.135&amp;nbsp;|&amp;nbsp;转发Location：/dasdsadsa/dadasdas/&amp;nbsp;|&amp;nbsp;最终trace_tag：35184&amp;nbsp;|&amp;nbsp;转发域名：http_develop-offline-35184.test.com&amp;nbsp;
[INFO]&amp;nbsp;=====&amp;nbsp;请求结束&amp;nbsp;=====&lt;/pre&gt;</description><pubDate>Wed, 31 Dec 2025 14:39:54 +0800</pubDate></item><item><title>SeaTunnel搭建部署</title><link>https://blog.51niux.com/?id=327</link><description>&lt;article class=&quot;4ever-article&quot;&gt;&lt;span data-type=&quot;text&quot;&gt;SeaTunnel原名Waterdrop(水滴),2021年10月更名为&lt;/span&gt;SeaTunnel,2021年12月9日加入Apache孵化,2023年6月1日毕业成为Apache顶级项目。&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;SeaTunnel是一个非常易用、超高性能的分布式数据集成平台,支持实时海量数据同步。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;具体介绍请看官网文档：https://seatunnel.apache.org/zh-CN/docs/2.3.12/about&lt;/span&gt;&lt;/p&gt;&lt;/article&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;一、&lt;span style=&quot;text-wrap-mode: wrap; background-color: #FBD5B5;&quot;&gt;SeaTunnel-2.3.12部署&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;text-wrap-mode: wrap; background-color: #B2A2C7;&quot;&gt;1.1 二进制包环境部署&lt;/span&gt;&lt;span style=&quot;text-wrap-mode: wrap; background-color: #FAC08F;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;安装Java (Java 8 或 11， 其他高于Java 8的版本理论上也可以工作) 以及设置 JAVA_HOME。&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap; background-color: #FAC08F;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;&lt;strong&gt;安装二进制包&lt;/strong&gt;&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;官网下载地址：https://seatunnel.apache.org/download/&lt;/p&gt;&lt;p&gt;#export version=&amp;quot;2.3.12&amp;quot;&amp;nbsp; &amp;nbsp;#通过终端下载一下&lt;/p&gt;&lt;p&gt;#wget &amp;quot;https://archive.apache.org/dist/seatunnel/${version}/apache-seatunnel-${version}-bin.tar.gz&amp;quot;&lt;/p&gt;&lt;p&gt;#tar -xzvf &amp;quot;apache-seatunnel-${version}-bin.tar.gz&amp;quot;&lt;/p&gt;&lt;article class=&quot;4ever-article&quot;&gt;&lt;strong&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;下载连接器插件&lt;/span&gt;&lt;/strong&gt;&lt;/article&gt;&lt;p&gt;#从2.2.0-beta版本开始，二进制包不再默认提供连接器依赖，因此在第一次使用时，需要执行以下命令来安装连接器：(当然，也可以从Apache Maven Repository 手动下载连接器:https://repo.maven.apache.org/maven2/org/apache/seatunnel/，然后将其移动至connectors/目录下，如果是2.3.5之前则需要放入connectors/seatunnel目录下)。&lt;/p&gt;&lt;p&gt;# sh bin/install-plugin.sh 2.3.12&amp;nbsp; #下载指定版本的连接器,然后就开始疯狂报错了&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512011764585153458452.png&quot; alt=&quot;image.png&quot; width=&quot;1016&quot; height=&quot;199&quot; style=&quot;width: 1016px; height: 199px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#通常情况下，你不需要所有的连接器插件。你可以通过配置config/plugin_config来指定所需的插件,&lt;/p&gt;&lt;p&gt;&lt;span data-type=&quot;text&quot;&gt;可以在${SEATUNNEL_HOME}/connectors/plugins-mapping.properties下找到所有支持的连接器和相应的plugin_config配置名称。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#下面我通过maven的方式解决上面的下载连接器报错,当然可能稍微麻烦一点,安装maven这步不再介绍&lt;/p&gt;&lt;p&gt;# vim ~/.m2/settings.xml&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;lt;?xml&amp;nbsp;version=&amp;quot;1.0&amp;quot;&amp;nbsp;encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;settings&amp;nbsp;xmlns=&amp;quot;http://maven.apache.org/SETTINGS/1.0.0&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;xsi:schemaLocation=&amp;quot;http://maven.apache.org/SETTINGS/1.0.0&amp;nbsp;http://maven.apache.org/xsd/settings-1.0.0.xsd&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;lt;mirrors&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;阿里云公共仓库&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;mirror&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;id&amp;gt;aliyunmaven&amp;lt;/id&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;name&amp;gt;阿里云公共仓库&amp;lt;/name&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;url&amp;gt;https://maven.aliyun.com/repository/public&amp;lt;/url&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;mirrorOf&amp;gt;*&amp;lt;/mirrorOf&amp;gt;&amp;nbsp;&amp;lt;!--&amp;nbsp;所有请求都走阿里云仓库&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/mirror&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;lt;/mirrors&amp;gt;
&amp;lt;/settings&amp;gt;&lt;/pre&gt;&lt;p&gt;# vim /opt/soft/apache-seatunnel-2.3.12/bin/install-plugin.sh&amp;nbsp; #改成使用mvn&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#${SEATUNNEL_HOME}/mvnw&amp;nbsp;dependency:get&amp;nbsp;-Dtransitive=false&amp;nbsp;-DgroupId=org.apache.seatunnel&amp;nbsp;-DartifactId=${line}&amp;nbsp;-Dversion=${version}&amp;nbsp;-Ddest=${SEATUNNEL_HOME}/connectors
mvn&amp;nbsp;dependency:get&amp;nbsp;-Dtransitive=false&amp;nbsp;-DgroupId=org.apache.seatunnel&amp;nbsp;-DartifactId=${line}&amp;nbsp;-Dversion=${version}&amp;nbsp;-Ddest=${SEATUNNEL_HOME}/connectors&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# sh bin/install-plugin.sh 2.3.12&amp;nbsp; #然后再次下载连接器&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512011764586314283673.png&quot; alt=&quot;image.png&quot; width=&quot;994&quot; height=&quot;207&quot; style=&quot;width: 994px; height: 207px;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#上图表示下载连接器成功&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# rsync -avz ~/.m2/repository/org/apache/seatunnel/ /opt/soft/apache-seatunnel-2.3.12/connectors/ &amp;gt;/dev/null&amp;nbsp; #手工将这些连接器移到指定目录&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;&lt;strong&gt;&lt;span style=&quot;text-wrap-mode: wrap; background-color: #B7DDE8;&quot;&gt;验证一下从mysql查询数据&lt;/span&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#&lt;/span&gt;确认连接器connector-jdbc、connector-doris在${SEATUNNEL_HOME}/connectors/目录下即可&lt;/p&gt;&lt;p&gt;#需要去https://repo1.maven.org/maven2/mysql/mysql-connector-java/下载jdbc driver jar package 驱动，并放置在 ${SEATUNNEL_HOME}/lib/目录下&lt;/p&gt;&lt;p&gt;# cd /opt/soft/apache-seatunnel-2.3.12/lib/&lt;/p&gt;&lt;p&gt;# wget https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.30/mysql-connector-java-8.0.30.jar&lt;/p&gt;&lt;p&gt;# cd /opt/soft/apache-seatunnel-2.3.12&lt;/p&gt;&lt;p&gt;# mkdir job&lt;/p&gt;&lt;p&gt;#基本配置结构：https://seatunnel.apache.org/zh-CN/docs/2.3.12/concept/config/&lt;/p&gt;&lt;p&gt;# vim /opt/soft/apache-seatunnel-2.3.12/job/st.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;env&amp;nbsp;{
&amp;nbsp;&amp;nbsp;parallelism&amp;nbsp;=&amp;nbsp;2
&amp;nbsp;&amp;nbsp;job.mode&amp;nbsp;=&amp;nbsp;&amp;quot;BATCH&amp;quot;
}

source&amp;nbsp;{
&amp;nbsp;&amp;nbsp;#SeaTunnel插件名称区分大小写，必须严格写为Jdbc(首字母大写)，如果写成小写jdbc或全大写JDBC，也会导致插件找不到
&amp;nbsp;&amp;nbsp;Jdbc&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.110:3306/seatunnel?useSSL=false&amp;amp;serverTimezone=UTC&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;driver&amp;nbsp;=&amp;nbsp;&amp;quot;com.mysql.cj.jdbc.Driver&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connection_check_timeout_sec&amp;nbsp;=&amp;nbsp;100
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;保持在&amp;nbsp;MySQL&amp;nbsp;中拼接的逻辑
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;query&amp;nbsp;=&amp;nbsp;&amp;quot;SELECT&amp;nbsp;CONCAT(&amp;#39;id=&amp;#39;,id,&amp;#39;,&amp;nbsp;type=&amp;#39;,type,&amp;#39;,&amp;nbsp;role_name=&amp;#39;,role_name,&amp;#39;,&amp;nbsp;description=&amp;#39;,description,&amp;#39;,&amp;nbsp;create_time=&amp;#39;,create_time,&amp;#39;,&amp;nbsp;update_time=&amp;#39;,update_time)&amp;nbsp;AS&amp;nbsp;readable_output&amp;nbsp;FROM&amp;nbsp;seatunnel.role&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fetch_size&amp;nbsp;=&amp;nbsp;1000
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;schema&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fields&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;readable_output&amp;nbsp;=&amp;nbsp;&amp;quot;VARCHAR&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;添加&amp;nbsp;table&amp;nbsp;配置，统一&amp;nbsp;tableId&amp;nbsp;为简短名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table&amp;nbsp;=&amp;nbsp;&amp;quot;role&amp;quot;
&amp;nbsp;&amp;nbsp;}
}

sink&amp;nbsp;{
&amp;nbsp;&amp;nbsp;Console&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print-header&amp;nbsp;=&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;encoding&amp;nbsp;=&amp;nbsp;&amp;quot;UTF-8&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fields&amp;nbsp;=&amp;nbsp;[&amp;quot;readable_output&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;2.3.12&amp;nbsp;版本特有的简化输出配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;format&amp;nbsp;=&amp;nbsp;&amp;quot;simple&amp;quot;
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# cd /opt/soft/apache-seatunnel-2.3.12&lt;/p&gt;&lt;p&gt;# ./bin/seatunnel.sh --config ./job/st.conf -m local&amp;nbsp; #跑一下我们的读取mysql的任务,输出下面的报错信息&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;ERROR&amp;nbsp;[o.a.s.c.s.SeaTunnel&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]&amp;nbsp;[main]&amp;nbsp;-&amp;nbsp;Exception&amp;nbsp;StackTrace:org.apache.seatunnel.core.starter.exception.CommandExecuteException:&amp;nbsp;SeaTunnel&amp;nbsp;job&amp;nbsp;executed&amp;nbsp;failed
	at&amp;nbsp;org.apache.seatunnel.core.starter.seatunnel.command.ClientExecuteCommand.execute(ClientExecuteCommand.java:228)
	at&amp;nbsp;org.apache.seatunnel.core.starter.SeaTunnel.run(SeaTunnel.java:40)
	at&amp;nbsp;org.apache.seatunnel.core.starter.seatunnel.SeaTunnelClient.main(SeaTunnelClient.java:40)
Caused&amp;nbsp;by:&amp;nbsp;org.apache.seatunnel.api.table.factory.FactoryException:&amp;nbsp;ErrorCode:[API-06],&amp;nbsp;ErrorDescription:[Factory&amp;nbsp;initialize&amp;nbsp;failed]&amp;nbsp;-&amp;nbsp;Unable&amp;nbsp;to&amp;nbsp;create&amp;nbsp;a&amp;nbsp;source&amp;nbsp;for&amp;nbsp;identifier&amp;nbsp;&amp;#39;Jdbc&amp;#39;.&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512041764841977728737.png&quot; alt=&quot;image.png&quot; width=&quot;1013&quot; height=&quot;219&quot; style=&quot;width: 1013px; height: 219px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#哎呀竟然报错了,啥原因我们解决一下&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#SeaTunnel&amp;nbsp;作业配置中使用了&amp;nbsp;Jdbc&amp;nbsp;作为数据源（source），但当前&amp;nbsp;SeaTunnel&amp;nbsp;环境中没有安装或配置&amp;nbsp;Jdbc&amp;nbsp;源插件
#SeaTunnel&amp;nbsp;2.x+版本的插件默认存放路径：$SEATUNNEL_HOME/plugins，检查是否存在&amp;nbsp;source&amp;nbsp;目录下的seatunnel-source-jdbc文件夹&lt;/pre&gt;&lt;p&gt;#mkdir /opt/soft/apache-seatunnel-2.3.12/plugins/source&amp;nbsp; &amp;nbsp;&amp;amp;&amp;amp; cd&amp;nbsp;&amp;nbsp;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;/opt/soft/apache-seatunnel-2.3.12/plugins/source&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#wget https://repo1.maven.org/maven2/org/apache/seatunnel/connector-jdbc/2.3.12/connector-jdbc-2.3.12.jar&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;# cd /opt/soft/apache-seatunnel-2.3.12&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;# ./bin/seatunnel.sh --config ./job/st.conf -m local&amp;nbsp; #再次执行读取mysql打印到屏幕的任务,从输出结果可以看到读取了2行输出了2行&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;......
INFO&amp;nbsp;&amp;nbsp;[.a.s.c.s.c.s.ConsoleSinkWriter]&amp;nbsp;[st-multi-table-sink-writer-1]&amp;nbsp;-&amp;nbsp;subtaskIndex=0&amp;nbsp;&amp;nbsp;rowIndex=1:&amp;nbsp;&amp;nbsp;SeaTunnelRow#tableId=default.default.default&amp;nbsp;SeaTunnelRow#kind=INSERT&amp;nbsp;:&amp;nbsp;id=1,&amp;nbsp;type=0,&amp;nbsp;role_name=ADMIN_ROLE,&amp;nbsp;description=Admin&amp;nbsp;User
INFO&amp;nbsp;&amp;nbsp;[.a.s.c.s.c.s.ConsoleSinkWriter]&amp;nbsp;[st-multi-table-sink-writer-1]&amp;nbsp;-&amp;nbsp;subtaskIndex=0&amp;nbsp;&amp;nbsp;rowIndex=2:&amp;nbsp;&amp;nbsp;SeaTunnelRow#tableId=default.default.default&amp;nbsp;SeaTunnelRow#kind=INSERT&amp;nbsp;:&amp;nbsp;id=2,&amp;nbsp;type=1,&amp;nbsp;role_name=NORMAL_ROLE,&amp;nbsp;description=Normal&amp;nbsp;User
......
Job&amp;nbsp;Statistic&amp;nbsp;Information
***********************************************
......
Total&amp;nbsp;Time(s)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3
Total&amp;nbsp;Read&amp;nbsp;Count&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2
Total&amp;nbsp;Write&amp;nbsp;Count&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2
Total&amp;nbsp;Failed&amp;nbsp;Count&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;0
***********************************************&lt;/pre&gt;&lt;p&gt;#可以看到输出了两行内容,但是还有元数据信息并非标准输出,这是因为在2.3.12版本中，Console Sink无法完全去除元数据前缀，但通过配置优化可以简化它；核心的 “字段名 = 值&amp;quot; 数据已经正确输出，这部分是有效的；若需要完全干净的输出，可以将结果输出到文件再查看。&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.2 源码包安装&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# mvn -version&amp;nbsp; &amp;nbsp;#首先mvn先安装一下&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Apache&amp;nbsp;Maven&amp;nbsp;3.9.11&lt;/pre&gt;&lt;p&gt;# vim ~/.m2/settings.xml&amp;nbsp; #增加华为镜像源,不然会有编译报错,因为有两个包是华为云maven仓库专属&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;lt;settings&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;mirrors&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;保留阿里云镜像&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;mirror&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;id&amp;gt;aliyunmaven&amp;lt;/id&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;mirrorOf&amp;gt;central&amp;lt;/mirrorOf&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;url&amp;gt;https://maven.aliyun.com/repository/public&amp;lt;/url&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/mirror&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;添加华为云镜像&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;mirror&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;id&amp;gt;huaweicloud-maven&amp;lt;/id&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;mirrorOf&amp;gt;huaweicloud&amp;lt;/mirrorOf&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;url&amp;gt;https://repo.huaweicloud.com/repository/maven/&amp;lt;/url&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/mirror&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/mirrors&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;profiles&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;profile&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;id&amp;gt;huaweicloud&amp;lt;/id&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;repositories&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;repository&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;id&amp;gt;huaweicloud-maven&amp;lt;/id&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;url&amp;gt;https://repo.huaweicloud.com/repository/maven/&amp;lt;/url&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;releases&amp;gt;&amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;&amp;lt;/releases&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;snapshots&amp;gt;&amp;lt;enabled&amp;gt;false&amp;lt;/enabled&amp;gt;&amp;lt;/snapshots&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/repository&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/repositories&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/profile&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/profiles&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;activeProfiles&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;activeProfile&amp;gt;huaweicloud&amp;lt;/activeProfile&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/activeProfiles&amp;gt;
&amp;lt;/settings&amp;gt;&lt;/pre&gt;&lt;p&gt;# wget&amp;nbsp;https://dlcdn.apache.org/seatunnel/2.3.12/&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;apache-seatunnel-2.3.12-src.tar.gz&lt;/span&gt; &lt;/p&gt;&lt;p&gt;# tar xf&amp;nbsp;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;apache-seatunnel-2.3.12-src.tar.gz&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# cd&amp;nbsp;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;apache-seatunnel-2.3.12-src&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#&amp;nbsp;mvn&amp;nbsp;&lt;/span&gt;clean install -DskipTests -Dskip.spotless=true&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&amp;nbsp;#上面那个阿里云源的配置记得配置上啊，如果一开始执行就报错的话可以执行mvn spotless:apply修复一下&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512151765796488288040.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#到上面这个界面就表示编译成功了&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# cp seatunnel-dist/target/apache-seatunnel-2.3.12-bin.tar.gz /opt/soft/&lt;/p&gt;&lt;p&gt;# cd /opt/soft&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# tar xf apache-seatunnel-2.3.12-bin.tar.gz&lt;/p&gt;&lt;p&gt;#&amp;nbsp;cd apache-seatunnel-2.3.12&amp;nbsp; #源码构建所有的连接器插件和一些必要的依赖都包含在二进制包中。可以直接使用连接器插件，而无需单独安装它们。&lt;/p&gt;&lt;p&gt;# 可以用我们上面的mysql驱动器的测试命令测试一下,没有任何报错&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# sh bin/seatunnel-cluster.sh start --daemon&amp;nbsp; #后台启动下服务&lt;/p&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space-collapse: preserve; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;二、apache-seatunnel-web部署&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.1 mysql5.7安装&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;apache-seatunnel-web需要mysql5.7+,这里也记录一下编译过程吧&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#cd /opt/soft&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# wget https://github.com/Kitware/CMake/releases/download/v3.31.10/cmake-3.31.10.tar.gz &lt;/span&gt;&lt;/p&gt;&lt;p&gt;# tar xf cmake-3.31.10.tar.gz&amp;nbsp;&lt;/p&gt;&lt;p&gt;# cd cmake-3.31.10&lt;/p&gt;&lt;p&gt;# ./bootstrap&lt;/p&gt;&lt;p&gt;# gmake &amp;amp;&amp;amp; gmake install&lt;/p&gt;&lt;p&gt;# /usr/local/bin/cmake -version&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;cmake&amp;nbsp;version&amp;nbsp;3.31.10&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# cd /opt/soft&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# wget https://archives.boost.io/release/1.59.0/source/boost_1_59_0.tar.gz&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# tar xf boost_1_59_0.tar.gz&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.44.tar.gz&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# tar xf mysql-5.7.44.tar.gz&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#&amp;nbsp;cd mysql-5.7.44&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# /usr/local/bin/cmake -DCMAKE_INSTALL_PREFIX=/opt/soft/mysql -DMYSQL_DATADIR=/opt/soft/mysql/data -DSYSCONFDIR=/etc -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DWITH_FEDERATED_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DWITH_MYISAM_STORAGE_ENGINE=1 -DENABLED_LOCAL_INFILE=1 -DENABLE_DTRACE=0 -DDEFAULT_CHARSET=utf8mb4 -DDEFAULT_COLLATION=utf8mb4_general_ci -DWITH_EMBEDDED_SERVER=1 -DDOWNLOAD_BOOST=1 -DFORCE_INSOURCE_BUILD=1 -DWITHOUT_PARTITION_STORAGE_ENGINE=0 -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DWITH_BOOST=/opt/soft/boost_1_59_0 -DDOWNLOAD_BOOST=0&lt;/p&gt;&lt;p&gt;# make -j 4&lt;/p&gt;&lt;p&gt;# make install&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# useradd mysql&amp;nbsp; -s /sbin/nologin -M&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# cp support-files/mysql.server /etc/init.d/mysqld&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# chmod +x /etc/init.d/mysqld&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# mkdir /opt/soft/mysql/data&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#&amp;nbsp;chown mysql:mysql /opt/soft/mysql/data&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# mkdir /opt/soft/mysql/log&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# chown mysql:mysql /opt/soft/mysql/log&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# /opt/soft/mysql/bin/mysqld --initialize --user=mysql --basedir=/opt/soft/mysql --datadir=/opt/soft/mysql/data&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# vim&amp;nbsp;/etc/my.cnf&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[client]
port=3306
socket=/tmp/mysql.sock
default-character-set=utf8mb4
[mysqld]
server-id=1
log-bin=mysql-bin
binlog-format=ROW
#skip-grant-tables
port=3306
user=mysql
max_connections=200
socket=/tmp/mysql.sock
basedir=/opt/soft/mysql
datadir=/opt/soft/mysql/data
pid-file=/opt/soft/mysql/log/mysql.pid
character-set-client-handshake=FALSE
character-set-server=utf8mb4
collation-server=utf8mb4_bin
init_connect=&amp;#39;SET&amp;nbsp;NAMES&amp;nbsp;utf8mb4&amp;#39;
default-storage-engine=INNODB
log_error=/opt/soft/mysql/log/mysql-error.log
slow_query_log_file=/opt/soft/mysql/log/mysql-slow.log
expire_logs_days=3
[mysqldump]
quick
max_allowed_packet=16M&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;#touch&amp;nbsp; /opt/soft/mysql/log/mysql-error.log&lt;/p&gt;&lt;p&gt;#chown mysql:mysql /opt/soft/mysql/log/mysql-error.log&lt;/p&gt;&lt;p&gt;#/etc/init.d/mysqld start&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.2 二进制包安装&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# wget https://dlcdn.apache.org/seatunnel/seatunnel-web/1.0.2/apache-seatunnel-web-1.0.2-bin.tar.gz&lt;/p&gt;&lt;p&gt;# tar xf apache-seatunnel-web-1.0.2-bin.tar.gz&lt;/p&gt;&lt;p&gt;#&amp;nbsp;vim conf/application.yml&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;datasource下面的数据库信息修改成实际的.jwt下面的secretKey要填一个比如mysecretkeyforseatunnelweb2025111&lt;/pre&gt;&lt;p&gt;# cp /opt/soft/apache-seatunnel-2.3.8/config/hazelcast-client.yaml&amp;nbsp; /opt/soft/&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;apache-seatunnel-web-1.0.2/&lt;/span&gt;conf/&lt;/p&gt;&lt;p&gt;# cp /opt/soft/apache-seatunnel-2.3.8/connectors/plugin-mapping.properties&amp;nbsp; /opt/soft/&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;apache-seatunnel-web-1.0.2/&lt;/span&gt;conf/&lt;/p&gt;&lt;p&gt;# vim script/seatunnel_server_env.sh&amp;nbsp; #修改成实际的数据库信息&lt;/p&gt;&lt;p&gt;# sh &lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;/opt/soft/apache-seatunnel-web-1.0.2&lt;/span&gt;/script/init_sql.sh&amp;nbsp; #进行数据库表的初始化,需要的mysql版本是5.7+&lt;/p&gt;&lt;p&gt;# cd&amp;nbsp;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;/opt/soft/apache-seatunnel-web-1.0.2/libs/ &amp;amp;&amp;amp;&amp;nbsp;&amp;nbsp;wget https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.30/mysql-connector-java-8.0.30.jar&amp;nbsp; #不做这步操作的话启动日志会报数据库连接器报错:Caused by: java.lang.IllegalStateException: Cannot load driver class: com.mysql.cj.jdbc.Driver&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512101765364342736780.png&quot; alt=&quot;image.png&quot; width=&quot;1011&quot; height=&quot;236&quot; style=&quot;width: 1011px; height: 236px;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&amp;nbsp;cd&amp;nbsp;/opt/soft/apache-seatunnel-web-1.0.2&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# vim bin/seatunnel-backend-daemon.sh&amp;nbsp; &amp;nbsp;#加一条环境变量,不然启动报错找不到seatunnel&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;set&amp;nbsp;-
SEATUNNEL_HOME=/opt/soft/apache-seatunnel-2.3.8&lt;/pre&gt;&lt;p&gt;# sh bin/seatunnel-backend-daemon.sh start&lt;/p&gt;&lt;p&gt;#we端口为8801,访问下web页面发现有一个报错&amp;nbsp;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512101765364545551773.png&quot; alt=&quot;image.png&quot; width=&quot;974&quot; height=&quot;229&quot; style=&quot;width: 974px; height: 229px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;查看下报错日志,大概意思是少了datasource：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;ERROR&amp;nbsp;[tr:,sp:]&amp;nbsp;[qtp546936087-21]&amp;nbsp;[GlobalExceptionHandler.logError():78]&amp;nbsp;-&amp;nbsp;No&amp;nbsp;supported&amp;nbsp;data&amp;nbsp;source&amp;nbsp;found
org.apache.seatunnel.datasource.exception.DataSourceSDKException:&amp;nbsp;No&amp;nbsp;supported&amp;nbsp;data&amp;nbsp;source&amp;nbsp;found
	at&amp;nbsp;org.apache.seatunnel.datasource.AbstractDataSourceClient.&amp;lt;init&amp;gt;(AbstractDataSourceClient.java:107)
	at&amp;nbsp;org.apache.seatunnel.datasource.DataSourceClient.&amp;lt;init&amp;gt;(DataSourceClient.java:26)
	at&amp;nbsp;org.apache.seatunnel.app.thirdparty.datasource.DataSourceClientFactory.getDataSourceClient(DataSourceClientFactory.java:32)
	at&amp;nbsp;org.apache.seatunnel.app.service.impl.DatasourceServiceImpl.queryAllDatasourcesGroupByType(DatasourceServiceImpl.java:478)
	at&amp;nbsp;org.apache.seatunnel.app.controller.SeatunnelDatasourceController.getSupportDatasources(SeatunnelDatasourceController.java:302)
	at&amp;nbsp;sun.reflect.NativeMethodAccessorImpl.invoke0(Native&amp;nbsp;Method)
	at&amp;nbsp;sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at&amp;nbsp;sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at&amp;nbsp;java.lang.reflect.Method.invoke(Method.java:498)
......	&lt;/pre&gt;&lt;p&gt;又两种方法解决上面的报错：&lt;br/&gt;第一种手工方式:&lt;br/&gt;# mkdir &lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;datasource&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# cd&amp;nbsp;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;datasource&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#&amp;nbsp;https://repo.maven.apache.org/maven2/org/apache/seatunnel/&amp;nbsp; &amp;nbsp;#去这里把对应版本的jar包手工下载下来&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512111765422260894415.png&quot; alt=&quot;image.png&quot; width=&quot;614&quot; height=&quot;231&quot; style=&quot;width: 614px; height: 231px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;第二种方式就是用脚本：&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# vim bin/download_datasource.sh&amp;nbsp; #还是那个&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;mvnw命令那个ssl证书报错我没有解决所以换成mvn的方式了&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;quot;$SEATUNNEL_WEB_HOME&amp;quot;/mvnw&amp;nbsp;dependency:get&amp;nbsp;-DgroupId=org.apache.seatunnel&amp;nbsp;-DartifactId=&amp;quot;$i&amp;quot;&amp;nbsp;-Dversion=&amp;quot;$version&amp;quot;&amp;nbsp;-Ddest=&amp;quot;$DATASOURCE_DIR&amp;quot;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mvn&amp;nbsp;dependency:get&amp;nbsp;-DgroupId=org.apache.seatunnel&amp;nbsp;-DartifactId=&amp;quot;$i&amp;quot;&amp;nbsp;-Dversion=&amp;quot;$version&amp;quot;&amp;nbsp;-Ddest=&amp;quot;$DATASOURCE_DIR&amp;quot;&lt;/pre&gt;&lt;p&gt;# sh bin/download_datasource.sh 1.0.2&amp;nbsp; #搞不懂web-1.0.2脚本里面的version=1.0.0,要么修改脚本变量要么传参&lt;/p&gt;&lt;p&gt;# for num in `find ~/.m2/repository/org/apache/seatunnel/|grep datasource|grep &amp;#39;.jar$&amp;#39;|grep &amp;quot;1.0.2&amp;quot;`;do rsync -avz $num datasource/;done&lt;/p&gt;&lt;p&gt;做了上述操作后,我们重启下apache-seatunnel-web-1.0.2服务再来查看下页面：&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512111765424412138693.png&quot; alt=&quot;image.png&quot; width=&quot;785&quot; height=&quot;373&quot; style=&quot;width: 785px; height: 373px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#从上面的截图可以看到数据源页面不再报错了,当然也可以再查下日志没有异常报错了。&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.3 源码包安装&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#如果有条件的话还是建议进行源码包安装,安装出来的插件都比较全,比二进制包各种报错然后花精力解决要好&lt;/p&gt;&lt;p&gt;#https://github.com/apache/seatunnel-web&amp;nbsp; &amp;nbsp;#git地址&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512111765435418206998.png&quot; alt=&quot;image.png&quot; width=&quot;512&quot; height=&quot;221&quot; style=&quot;width: 512px; height: 221px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#官网给出的版本对应表,保险期间还是用2.3.8吧,我看web的libs目录下面关于&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;seatunnel的都是2.3.8的&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#wget&amp;nbsp;https://downloads.apache.org/seatunnel/seatunnel-web/1.0.2/apache-seatunnel-web-1.0.2-src.tar.gz&lt;/p&gt;&lt;p&gt;#tar xf apache-seatunnel-web-1.0.2-src.tar.gz&lt;/p&gt;&lt;p&gt;#cd apache-seatunnel-web-1.0.2-src&lt;/p&gt;&lt;p&gt;# vim build.sh&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;nbsp;&amp;nbsp;#/bin/sh&amp;nbsp;$WORKDIR/mvnw&amp;nbsp;clean&amp;nbsp;package&amp;nbsp;-DskipTests&amp;nbsp;-Pci
&amp;nbsp;&amp;nbsp;mvn&amp;nbsp;clean&amp;nbsp;package&amp;nbsp;-DskipTests&amp;nbsp;-Pci&lt;/pre&gt;&lt;p&gt;#mvn spotless:apply&lt;/p&gt;&lt;p&gt;# sh build.sh code&amp;nbsp; #正常没有报错的话二进制包就打包了&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512111765436154660267.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;# cp seatunnel-web-dist/target/apache-seatunnel-web-1.0.2.tar.gz&amp;nbsp; /opt/soft/&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;#&amp;nbsp;cd /opt/soft/&lt;/p&gt;&lt;p&gt;# tar xf apache-seatunnel-web-1.0.2.tar.gz&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;#&amp;nbsp;vim conf/application.yml&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;datasource下面的数据库信息修改成实际的.jwt下面的secretKey要填一个比如mysecretkeyforseatunnelweb2025111&lt;/pre&gt;&lt;p&gt;# cp /opt/soft/apache-seatunnel-2.3.8/config/hazelcast-client.yaml /opt/soft/apache-seatunnel-web-1.0.2/conf/&lt;/p&gt;&lt;p&gt;# cp /opt/soft/apache-seatunnel-2.3.8/connectors/plugin-mapping.properties /opt/soft/apache-seatunnel-web-1.0.2/conf/&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;# vim script/seatunnel_server_env.sh&amp;nbsp; #修改成实际的数据库信息&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;# sh&amp;nbsp;/opt/soft/apache-seatunnel-web-1.0.2/script/init_sql.sh&amp;nbsp; #进行数据库表的初始化&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# vim bin/seatunnel-backend-daemon.sh&amp;nbsp; &amp;nbsp;#加一条环境变量,不然启动报错找不到seatunnel&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;set&amp;nbsp;-
SEATUNNEL_HOME=/opt/soft/apache-seatunnel-2.3.8&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;# sh bin/seatunnel-backend-daemon.sh start&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space-collapse: preserve; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h2 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #FAC08F;&quot;&gt;三、数据库表同步的一些简单示例&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;#现在基础环境搭建完毕了,现在让我们来一些简单的数据同步&lt;/p&gt;&lt;h3 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;3.1 命令行单表同步&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#先来一个最简单的示例，从A库同步完整表到B库&lt;/p&gt;&lt;p&gt;# vim job/test.conf&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#&amp;nbsp;定义运行时环境
env&amp;nbsp;{
&amp;nbsp;&amp;nbsp;parallelism&amp;nbsp;=&amp;nbsp;4
&amp;nbsp;&amp;nbsp;job.mode&amp;nbsp;=&amp;nbsp;&amp;quot;BATCH&amp;quot;
}
source{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Jdbc&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.110:3306/seatunnel?serverTimezone=GMT%2b8&amp;amp;useUnicode=true&amp;amp;characterEncoding=UTF-8&amp;amp;rewriteBatchedStatements=true&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;driver&amp;nbsp;=&amp;nbsp;&amp;quot;com.mysql.cj.jdbc.Driver&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connection_check_timeout_sec&amp;nbsp;=&amp;nbsp;100
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;query&amp;nbsp;=&amp;nbsp;&amp;quot;select&amp;nbsp;*&amp;nbsp;from&amp;nbsp;role&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

transform&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;如果您想了解更多关于如何配置&amp;nbsp;SeaTunnel&amp;nbsp;的信息，并查看完整的转换插件列表，
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;请访问&amp;nbsp;https://seatunnel.apache.org/docs/transform-v2/sql
}

sink&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jdbc&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.111:3306/seatunnel_bak?useUnicode=true&amp;amp;characterEncoding=UTF-8&amp;amp;rewriteBatchedStatements=true&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;driver&amp;nbsp;=&amp;nbsp;&amp;quot;com.mysql.cj.jdbc.Driver&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel_bak&amp;quot;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table&amp;nbsp;=&amp;nbsp;&amp;quot;role&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;generate_sink_sql&amp;nbsp;=&amp;nbsp;true&amp;nbsp;&amp;nbsp;#自动生成插入语句
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_insert&amp;nbsp;=&amp;nbsp;true&amp;nbsp;&amp;nbsp;#&amp;nbsp;开启批量插入（提升性能）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_size&amp;nbsp;=&amp;nbsp;1000&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;批量大小（可选，默认500）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#如果你想了解更多关于如何配置seatunnel的信息，并查看完整的sink插件列表，
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#请前往https://seatunnel.apache.org/docs/connector-v2/sink
}&lt;/pre&gt;&lt;p&gt;#特别注意目标数据库是要存在的,不然数据同步不过去会有报错提示：Caused by: java.sql.SQLSyntaxErrorException: Unknown database &amp;#39;seatunnel_bak&amp;#39;&lt;/p&gt;&lt;p&gt;# ./bin/seatunnel.sh --config ./job/test.conf -m local&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512111765440895427956.png&quot; alt=&quot;image.png&quot; width=&quot;373&quot; height=&quot;328&quot; style=&quot;width: 373px; height: 328px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#从上图的数据库插入情况来看,也可以看出来我们只要库存在,表就能自动创建并自动插入数据&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;第一点:仅同步数据不同步表结构&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;好了,那么有一个问题,如果再执行一遍这个脚本会怎么样呢？&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512111765441436426364.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;#从上图可以看出同样的数据重复insert插入了,明明源表id是主键啊,但是sink表没有这个主键的设置才会这样。Seatunnel JDBC Sink 的核心特性 ——Seatunnel 仅负责 “数据同步”，不负责 “表结构同步 / 创建”，包括主键、索引、字段类型、约束等表结构信息，都不会自动同步 / 创建，这是导致主键未同步的根本原因。就用推荐方法提前手工创建目标表。&lt;/p&gt;&lt;p&gt;#这时候你执行脚本第一遍可以,第二遍就会报错:&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Caused&amp;nbsp;by:&amp;nbsp;java.sql.SQLIntegrityConstraintViolationException:&amp;nbsp;Duplicate&amp;nbsp;entry&amp;nbsp;&amp;#39;1&amp;#39;&amp;nbsp;for&amp;nbsp;key&amp;nbsp;&amp;#39;PRIMARY&amp;#39;&lt;/pre&gt;&lt;p&gt;#直接说结论不贴图片了,一旦设置了主键,只要主键冲突,后面的insert插入也就不会再执行,也就是说这种同步操作适用于新表的全量同步&lt;br/&gt;&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;第二点:绕过主键冲突报错,实现增删改效果&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;#vim&amp;nbsp;job/test.conf&amp;nbsp; #注意不仅是追加插入数据哦可自行测试效果&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#&amp;nbsp;定义运行时环境
env&amp;nbsp;{
&amp;nbsp;&amp;nbsp;parallelism&amp;nbsp;=&amp;nbsp;4
&amp;nbsp;&amp;nbsp;job.mode&amp;nbsp;=&amp;nbsp;&amp;quot;BATCH&amp;quot;
}

source&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Jdbc&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.110:3306/seatunnel?serverTimezone=GMT%2b8&amp;amp;useUnicode=true&amp;amp;characterEncoding=UTF-8&amp;amp;rewriteBatchedStatements=true&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;driver&amp;nbsp;=&amp;nbsp;&amp;quot;com.mysql.cj.jdbc.Driver&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connection_check_timeout_sec&amp;nbsp;=&amp;nbsp;100
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;query&amp;nbsp;=&amp;nbsp;&amp;quot;select&amp;nbsp;*&amp;nbsp;from&amp;nbsp;role&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

transform&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;无需转换
}

sink&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jdbc&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.111:3306/seatunnel?useUnicode=true&amp;amp;characterEncoding=UTF-8&amp;amp;rewriteBatchedStatements=true&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;driver&amp;nbsp;=&amp;nbsp;&amp;quot;com.mysql.cj.jdbc.Driver&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table&amp;nbsp;=&amp;nbsp;&amp;quot;role&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;核心：主键冲突时跳过，不报错
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;skip_on_conflict&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;指定主键字段（需与目标表主键一致）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;primary_keys&amp;nbsp;=&amp;nbsp;[&amp;quot;id&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;保留批量插入提升性能
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;generate_sink_sql&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_insert&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_size&amp;nbsp;=&amp;nbsp;1000
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;可选：开启错误重试（进一步避免偶发报错）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_retries&amp;nbsp;=&amp;nbsp;3
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;&lt;/span&gt;#剩下的多表,指定字段同步就不写示例了,网上一查就查到了&lt;/p&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space-collapse: preserve; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;四、Mysql CDC模式&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;#前面介绍的都是命令行模式,如果你不需要实时数据同步可以采用上面那种定时任务的方式,但是如果想要类似于主从数据同步那种方式就要用到CDC模式了&lt;br/&gt;&lt;/p&gt;&lt;p&gt;#MySQL CDC连接器允许从MySQL数据库读取快照数据和增量数据。&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;4.1 创建mysql用户&lt;/span&gt;&lt;br/&gt;&lt;/h3&gt;&lt;p&gt;#在源库创建有指定权限的用户&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;mysql&amp;gt;&amp;nbsp;CREATE&amp;nbsp;USER&amp;nbsp;&amp;#39;source-cdc&amp;#39;@&amp;#39;%&amp;#39;&amp;nbsp;IDENTIFIED&amp;nbsp;BY&amp;nbsp;&amp;#39;source-cdc&amp;#39;;
mysql&amp;gt;&amp;nbsp;GRANT&amp;nbsp;SELECT,&amp;nbsp;RELOAD,&amp;nbsp;SHOW&amp;nbsp;DATABASES,&amp;nbsp;REPLICATION&amp;nbsp;SLAVE,&amp;nbsp;REPLICATION&amp;nbsp;CLIENT&amp;nbsp;ON&amp;nbsp;*.*&amp;nbsp;TO&amp;nbsp;&amp;#39;source-cdc&amp;#39;@&amp;#39;%&amp;#39;&amp;nbsp;IDENTIFIED&amp;nbsp;BY&amp;nbsp;&amp;#39;source-cdc&amp;#39;;
#SELECT是查询权限、RELOAD重载/刷新权限(允许执行FLUSH类命令如FLUSH&amp;nbsp;PRIVILEGES、FLUSH&amp;nbsp;BINARY&amp;nbsp;LOGS，CDC&amp;nbsp;需刷新binlog元信息)
#SHOW&amp;nbsp;DATABASES是查询数据库列表权限,REPLICATION&amp;nbsp;SLAVE是复制从库权限(允许用户模拟MySQL从库，读取主库的binlog,CDC核心获取数据变更日志)
#REPLICATION&amp;nbsp;CLIENT是复制客户端权限,允许执行SHOW&amp;nbsp;MASTER&amp;nbsp;STATUS/SHOW&amp;nbsp;SLAVE&amp;nbsp;STATUS，获取binlog位点(文件名+偏移量)，CDC&amp;nbsp;需定位同步起点。&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
mysql&amp;gt;&amp;nbsp;FLUSH&amp;nbsp;PRIVILEGES;&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;4.2 启动mysql的binlog&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;mysql&amp;gt; show variables where variable_name in (&amp;#39;log_bin&amp;#39;, &amp;#39;binlog_format&amp;#39;, &amp;#39;binlog_row_image&amp;#39;, &amp;#39;gtid_mode&amp;#39;, &amp;#39;enforce_gtid_consistency&amp;#39;);&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;+--------------------------+----------------+
|&amp;nbsp;Variable_name&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&amp;nbsp;Value&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|
+--------------------------+----------------+
|&amp;nbsp;binlog_format&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&amp;nbsp;ROW&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|
|&amp;nbsp;binlog_row_image&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&amp;nbsp;FULL&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|
|&amp;nbsp;enforce_gtid_consistency&amp;nbsp;|&amp;nbsp;ON&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|
|&amp;nbsp;gtid_mode&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&amp;nbsp;ON&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|
|&amp;nbsp;log_bin&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&amp;nbsp;ON&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|
+--------------------------+----------------+
5&amp;nbsp;rows&amp;nbsp;in&amp;nbsp;set&amp;nbsp;(0.00&amp;nbsp;sec)&lt;/pre&gt;&lt;p&gt;#如果与上述结果不一致，请使用以下属性配置MySQL服务器配置文件（$MYDateTimeHOME/MySQL.cnf），如下表所示：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;server-id&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;223344
log_bin&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;mysql-bin
expire_logs_days&amp;nbsp;&amp;nbsp;=&amp;nbsp;10
binlog_format&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;row
#&amp;nbsp;mysql&amp;nbsp;5.6+&amp;nbsp;requires&amp;nbsp;binlog_row_image&amp;nbsp;to&amp;nbsp;be&amp;nbsp;set&amp;nbsp;to&amp;nbsp;FULL
binlog_row_image&amp;nbsp;&amp;nbsp;=&amp;nbsp;FULL

#&amp;nbsp;enable&amp;nbsp;gtid&amp;nbsp;mode
#&amp;nbsp;mysql&amp;nbsp;5.6+&amp;nbsp;requires&amp;nbsp;gtid_mode&amp;nbsp;to&amp;nbsp;be&amp;nbsp;set&amp;nbsp;to&amp;nbsp;ON
gtid_mode&amp;nbsp;=&amp;nbsp;on
enforce_gtid_consistency&amp;nbsp;=&amp;nbsp;on&lt;/pre&gt;&lt;p&gt;#上面只是官方的实例,server-id没必要一致啊&lt;/p&gt;&lt;p&gt;# /etc/init.d/mysqld restart&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;4.3 记录下相关参数&lt;/span&gt;&lt;br/&gt;&lt;/h3&gt;&lt;p&gt;#官方文档：https://seatunnel.apache.org/zh-CN/docs/2.3.12/connector-v2/source/MySQL-CDC&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;下载相关依赖包,不然会启动报错：&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;# ls&amp;nbsp; plugins/*.jar&amp;nbsp; #有下面三个jar包就行&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;plugins/connector-cdc-base-2.3.12.jar&amp;nbsp;&amp;nbsp;plugins/connector-cdc-mysql-2.3.12.jar&amp;nbsp;&amp;nbsp;plugins/connector-jdbc-2.3.12.jar&lt;/pre&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;先来一个-m local方式的数据同步：&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;#vim&amp;nbsp;job/cdc_test.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;env&amp;nbsp;{
&amp;nbsp;&amp;nbsp;#本地模式仅支持单并行度
&amp;nbsp;&amp;nbsp;parallelism&amp;nbsp;=&amp;nbsp;1
&amp;nbsp;&amp;nbsp;#流式同步模式（CDC必需）
&amp;nbsp;&amp;nbsp;job.mode&amp;nbsp;=&amp;nbsp;&amp;quot;STREAMING&amp;quot;
&amp;nbsp;&amp;nbsp;#检查点间隔
&amp;nbsp;&amp;nbsp;checkpoint.interval&amp;nbsp;=&amp;nbsp;10000
}

source&amp;nbsp;{
&amp;nbsp;&amp;nbsp;MySQL-CDC&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.110:3306/seatunnel?useSSL=false&amp;amp;allowPublicKeyRetrieval=true&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#CDC同步账号（需有binlog读取权限）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;source-cdc&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;source-cdc&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table-names&amp;nbsp;=&amp;nbsp;[&amp;quot;seatunnel.role&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#唯一server-id（避免与MySQL自身server-id冲突）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server-id&amp;nbsp;=&amp;nbsp;5401
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#启动模式：先全量同步，再增量同步
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;startup.mode&amp;nbsp;=&amp;nbsp;&amp;quot;initial&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#可选：指定binlog起始位置（避免找不到binlog，需替换为你的实际值）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#先执行&amp;nbsp;show&amp;nbsp;master&amp;nbsp;status;&amp;nbsp;获取File和Position后填写
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;startup.specific.offset.file&amp;nbsp;=&amp;nbsp;&amp;quot;mysql-bin.000001&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;startup.specific.offset.pos&amp;nbsp;=&amp;nbsp;4
&amp;nbsp;&amp;nbsp;}
}

sink&amp;nbsp;{
&amp;nbsp;&amp;nbsp;jdbc&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.111:3306/seatunnel?useSSL=false&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;driver&amp;nbsp;=&amp;nbsp;&amp;quot;com.mysql.cj.jdbc.Driver&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#自动生成写入SQL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;generate_sink_sql&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#目标表名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table&amp;nbsp;=&amp;nbsp;&amp;quot;role&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;键字段（保障更新/删除同步）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;primary_keys&amp;nbsp;=&amp;nbsp;[&amp;quot;id&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#关闭XA事务（2.3.12本地模式XA有Bug）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is_exactly_once&amp;nbsp;=&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#关闭批量写入（避免静默失败）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_size&amp;nbsp;=&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#开启调试日志，排查错误的时候使用
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#log.enabled&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#log.level&amp;nbsp;=&amp;nbsp;&amp;quot;DEBUG&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#retry_times&amp;nbsp;=&amp;nbsp;3
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#retry_interval&amp;nbsp;=&amp;nbsp;1000
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&amp;nbsp;# ./bin/seatunnel.sh --config job/cdc_test.conf -m local&amp;nbsp; &amp;nbsp; #这是一个常驻进程了就不是一次性的了,但是是前台启动,可以放到后台并将输入写入到一个日志文件&lt;/p&gt;&lt;p&gt;#如果目的库跟源库数据不一致,只在第一次启动的时候会把目标库的数据更新过来,这个进程常态的可以实现源库数据往目标库的插入和删除和update动作触发同步。&lt;/p&gt;&lt;p&gt;#注意这种方式用# bin/seatunnel.sh --list是查不到在运行任务的,只有使用集群模式才可以&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;再来2.3.12版本的cdc调用集群方式表同步：&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;#这就需要#bin/seatunnel-cluster.sh start --daemon 启动服务并确保5801端口是启动的&lt;/p&gt;&lt;p&gt;#vim job/cluster_cdc_job.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;env&amp;nbsp;{
&amp;nbsp;&amp;nbsp;#集群并行度
&amp;nbsp;&amp;nbsp;execution.parallelism&amp;nbsp;=&amp;nbsp;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;job.mode&amp;nbsp;=&amp;nbsp;&amp;quot;STREAMING&amp;quot;
&amp;nbsp;&amp;nbsp;checkpoint.interval&amp;nbsp;=&amp;nbsp;10000
&amp;nbsp;&amp;nbsp;#Engine集群配置
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.mode&amp;nbsp;=&amp;nbsp;&amp;quot;cluster&amp;quot;
&amp;nbsp;&amp;nbsp;seatunnel.engine.server.address&amp;nbsp;=&amp;nbsp;&amp;quot;127.0.0.1:5801&amp;quot;
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.heartbeat.interval&amp;nbsp;=&amp;nbsp;30000
&amp;nbsp;&amp;nbsp;#自定义Job&amp;nbsp;Name（支持中文/英文，建议包含业务+表名）
&amp;nbsp;&amp;nbsp;job.name&amp;nbsp;=&amp;nbsp;&amp;quot;MySQL_CDC_同步_seatunnel_role表&amp;quot;
&amp;nbsp;&amp;nbsp;#任务失败自动重启（避免变成CANCELED）
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.restart.count&amp;nbsp;=&amp;nbsp;3
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.restart.interval&amp;nbsp;=&amp;nbsp;5000
&amp;nbsp;&amp;nbsp;#任务超时时间（避免长时间卡壳）
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.timeout&amp;nbsp;=&amp;nbsp;3600000
}

source&amp;nbsp;{
&amp;nbsp;&amp;nbsp;MySQL-CDC&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.110:3306/seatunnel?useSSL=false&amp;amp;allowPublicKeyRetrieval=true&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;source-cdc&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;source-cdc&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#数组格式表名（集群模式不支持单字符串）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table-names&amp;nbsp;=&amp;nbsp;[&amp;quot;seatunnel.role&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#集群内唯一（避免与其他节点冲突）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server-id&amp;nbsp;=&amp;nbsp;5401
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#首次启动全量+后续增量同步&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;startup.mode&amp;nbsp;=&amp;nbsp;&amp;quot;initial&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#集群模式优化：增大binlog读取缓存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debezium.connector.properties.max.queue.size&amp;nbsp;=&amp;nbsp;8192
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debezium.connector.properties.max.batch.size&amp;nbsp;=&amp;nbsp;2048
&amp;nbsp;&amp;nbsp;}
}

sink&amp;nbsp;{
&amp;nbsp;&amp;nbsp;jdbc&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.111:3306/seatunnel?useSSL=false&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;driver&amp;nbsp;=&amp;nbsp;&amp;quot;com.mysql.cj.jdbc.Driver&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;generate_sink_sql&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table&amp;nbsp;=&amp;nbsp;&amp;quot;role&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;primary_keys&amp;nbsp;=&amp;nbsp;[&amp;quot;id&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#集群模式关闭XA（避免分布式事务问题）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is_exactly_once&amp;nbsp;=&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#集群模式恢复批量（提升性能）&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_size&amp;nbsp;=&amp;nbsp;50
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#批量刷新间隔&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_flush_interval&amp;nbsp;=&amp;nbsp;1000&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#集群模式重试
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;retry_times&amp;nbsp;=&amp;nbsp;5
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;retry_interval&amp;nbsp;=&amp;nbsp;2000
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#全字段同步
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;upsert_all_fields&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#连接池（集群模式避免连接耗尽）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connection_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type&amp;nbsp;=&amp;nbsp;&amp;quot;hikari&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hikaricp&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;maximum-pool-size&amp;nbsp;=&amp;nbsp;10
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;minimum-idle&amp;nbsp;=&amp;nbsp;2
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connection-timeout&amp;nbsp;=&amp;nbsp;30000
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# ./bin/seatunnel.sh --config job/cluster_cdc_job.conf -m cluster&amp;nbsp; &amp;nbsp;#使用集群模式启动服务,一样首次启动的时候会把目标库的数据更新到跟源库表一致&lt;/p&gt;&lt;p&gt;# bin/seatunnel.sh --list&amp;nbsp; #查看再运行的&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/12/202512181766051100191657.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;h4 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;&lt;/span&gt;&lt;/h4&gt;&lt;h4 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;再来2.3.8版本的cdc调用集群方式表同步：&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;# vim job/cluster_cdc_job.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;env&amp;nbsp;{
&amp;nbsp;&amp;nbsp;#并行度
&amp;nbsp;&amp;nbsp;execution.parallelism&amp;nbsp;=&amp;nbsp;2
&amp;nbsp;&amp;nbsp;#流模式（CDC必须用STREAMING）
&amp;nbsp;&amp;nbsp;job.mode&amp;nbsp;=&amp;nbsp;&amp;quot;STREAMING&amp;quot;
&amp;nbsp;&amp;nbsp;#检查点间隔
&amp;nbsp;&amp;nbsp;checkpoint.interval&amp;nbsp;=&amp;nbsp;10000
&amp;nbsp;&amp;nbsp;#集群模式相关
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.mode&amp;nbsp;=&amp;nbsp;&amp;quot;cluster&amp;quot;
&amp;nbsp;&amp;nbsp;seatunnel.engine.server.address&amp;nbsp;=&amp;nbsp;&amp;quot;127.0.0.1:5801&amp;quot;
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.heartbeat.interval&amp;nbsp;=&amp;nbsp;30000
&amp;nbsp;&amp;nbsp;#任务名称
&amp;nbsp;&amp;nbsp;job.name&amp;nbsp;=&amp;nbsp;&amp;quot;MySQL_CDC_同步_seatunnel_role表&amp;quot;
&amp;nbsp;&amp;nbsp;#失败重启配置
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.restart.count&amp;nbsp;=&amp;nbsp;3
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.restart.interval&amp;nbsp;=&amp;nbsp;5000
&amp;nbsp;&amp;nbsp;seatunnel.engine.job.timeout&amp;nbsp;=&amp;nbsp;3600000
&amp;nbsp;&amp;nbsp;#基础配置（避免类加载冲突）
&amp;nbsp;&amp;nbsp;job.type&amp;nbsp;=&amp;nbsp;&amp;quot;STREAM&amp;quot;
&amp;nbsp;&amp;nbsp;checkpoint.storage&amp;nbsp;=&amp;nbsp;&amp;quot;MEMORY&amp;quot;
}

source&amp;nbsp;{
&amp;nbsp;&amp;nbsp;MySQL-CDC&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base-url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.110:3306/seatunnel?serverTimezone=Asia/Shanghai&amp;amp;useSSL=false&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;source-cdc&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;source-cdc&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database-name&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table-names&amp;nbsp;=&amp;nbsp;[&amp;quot;seatunnel.role&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#server-id必须唯一（避免和其他CDC任务冲突，建议范围5000-65535）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server-id&amp;nbsp;=&amp;nbsp;5401
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#启动模式：initial（全量+增量）、latest-offset（仅增量）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;startup.mode&amp;nbsp;=&amp;nbsp;&amp;quot;initial&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#时区
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server-time-zone&amp;nbsp;=&amp;nbsp;&amp;quot;Asia/Shanghai&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#binlog消费性能配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max.queue.size&amp;nbsp;=&amp;nbsp;8192
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max.batch.size&amp;nbsp;=&amp;nbsp;2048
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#批量读取超时
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;poll.interval.ms&amp;nbsp;=&amp;nbsp;1000
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;}
}

sink&amp;nbsp;{
&amp;nbsp;&amp;nbsp;jdbc&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;=&amp;nbsp;&amp;quot;jdbc:mysql://192.168.1.111:3306/seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;driver&amp;nbsp;=&amp;nbsp;&amp;quot;com.mysql.cj.jdbc.Driver&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;seatunnel&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;generate_sink_sql&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;seatunnel
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table&amp;nbsp;=&amp;nbsp;role
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;primary_keys&amp;nbsp;=&amp;nbsp;[&amp;quot;id&amp;quot;]
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# ./bin/seatunnel.sh --cluster seatunnel --config job/cluster_cdc_job.conf&amp;nbsp; #注意&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;--cluster seatunnel 就是对应&lt;/span&gt;hazelcast.yaml中的cluster-name&lt;/p&gt;&lt;h3 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;4.4 零星记录&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;color: #000000; background-color: #B7DDE8;&quot;&gt;&lt;strong&gt;取消任务：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;&lt;/span&gt;#2.3.8的版本不行应该是有BUG不能取消掉停止的任务需要重启seatunnel集群清空内存中的任务才行&lt;/p&gt;&lt;p&gt;正常的操作来说(命令和api中没有直接delete的操作命令)：&lt;/p&gt;&lt;p&gt;history-job-expire-minutes: 1440&amp;nbsp; &amp;nbsp;#配置文件里面有多久清理掉停止任务的参数,单位是分钟,想调小这个回收时间就把时间调小点&lt;br/&gt;#然后你把任务手工停止或者,# bin/seatunnel.sh -can jobid&amp;nbsp; 把任务变成CANCELED状态,然后到了时间seatunnel就会把任务回收掉了&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;重新恢复任务：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;#2.3.8和2.3.12关于checkpoint的位置不一样,以2.3.12为例:/tmp/seatunnel/checkpoint_snapshot&amp;nbsp; #如果任务正常启动的话,这里会有id号的目录&lt;br/&gt;&lt;/p&gt;&lt;p&gt;#如果你的任务非正常停掉了,你正常启动任务的话就是一个全新jobid就跟之前记录的内容关联不上了,你如果想还启原来的jobid跟之前的断点续传文件关联式：&lt;br/&gt;#sh bin/seatunnel.sh --config job/cdc_test.conf -r 之前的jobid&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;&lt;strong&gt;启动任务：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#前面介绍启动作业的方式都是前台启动,这样你ps进程的话会看到除了sea以外还有其他进程,如果启动的作业太多就会有好多进程,这样显然不太好&lt;/p&gt;&lt;p&gt;# sh bin/seatunnel.sh --config job/cdc_test.conf&amp;nbsp; --async&amp;nbsp; &amp;nbsp;#这个 --async 参数可以让作业在后台运行，当作业提交后，客户端会退出。这样ps进程的时候就只有&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;seatunnel本身的集群服务进程了,就不会再有额外的作业进程了&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;指定作业名称：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;# sh bin/seatunnel.sh --config job/cdc_test.conf&amp;nbsp; --async&amp;nbsp; -n myjob&amp;nbsp; #-n 或 --name 参数可以指定作业的名称,配置文件里面定义了名称也会被这个名称覆盖&lt;/p&gt;&lt;p&gt;#官网中文文档讲的很清楚,详细的可参照官网文档,集群的模式啊监控啊之类的可以看官方文档:&amp;nbsp;https://seatunnel.apache.org/zh-CN/docs/2.3.12/seatunnel-engine/about&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Mon, 01 Dec 2025 17:36:01 +0800</pubDate></item><item><title>测试环境按需动态加载(一)</title><link>https://blog.51niux.com/?id=326</link><description>&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;总算到了最后一步了哈,前面又是consul又是lua的,就是为了实现本文要讲的内容&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#紧接上文,我们consul的使用已经总结的差不多了,但是是不是感觉没有展现一个实际的场景来把consul结合起来使其作用更大化呢?&lt;/span&gt;&lt;br style=&quot;text-wrap-mode: wrap;&quot;/&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;我下面描述一个具体的场景：一般我们会有一套稳定环境,但是会有N多套测试需求环境,而且这是一个动态变化的过程,这里我们需求用需求号代替,比如今天测试一个集群的功能创建了一个2001的需求,后天又创建了一个2002的需求,那么正常来说你访问网站是不是就变成了aaa-2001.xxx.com/aaa-2002.xxx.com,这就带来一个问题测试起来太繁琐了(每次测试你都要更换URL),现在很多都是app测试了,app更换域名还是挺麻烦的。还有一个问题现在很多都是微服务架构,就是你是不是要1比1的部署测试环境,不然你的测试流程很可能走不下去。&lt;/span&gt;&lt;br style=&quot;text-wrap-mode: wrap;&quot;/&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;那么我们怎么解决上述问题呢,一方面让测试更便捷另一方面让成本更低,我们可以试想一下:先提供一个web平台用户可以选择自己要访问哪个环境,然后nginx+lua成为网关,一个固定域名基于来源来判断要访问哪个环境并路由,然后后端是nginx+consul将请求转发到执行的需求环境容器,然后需求号再作为tag一直透传下去,有对应的需求就将请求交给对应的需求容器,没有对应的需求容器就将请求交给稳定环境是不是就解决了我们上面提到的问题,好了大体思路有了我们去实现它。&lt;/span&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;text-wrap-mode: wrap; background-color: #FBD5B5;&quot;&gt;一、容器在consul中的管理&lt;/span&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;text-wrap-mode: wrap; background-color: #CCC1D9;&quot;&gt;1.1 容器在consul中存在结构？&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#容器一般怎么命名呢,肯定是有一个固有的结构对吧,而且我们会把容器的一些信息写入到k8s的yaml文件中的label,比如下面：&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;spec:
&amp;nbsp;&amp;nbsp;template:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metadata:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;labels:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app:&amp;nbsp;模块名-2001
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env_type:&amp;nbsp;offline
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reqid:&amp;nbsp;&amp;quot;2001&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k8scluster:&amp;nbsp;offline_k8s01
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clustername:&amp;nbsp;集群名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modulename:&amp;nbsp;模块名&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;#这样我们在consul中去创建kv的原始信息就有了,那么我们是扁平的创建还是目录层级的创建呢？&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/08/202508181755510875209940.png&quot; alt=&quot;image.png&quot; width=&quot;642&quot; height=&quot;250&quot; style=&quot;width: 642px; height: 250px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;下面看一下完整的示例：&lt;/p&gt;&lt;p&gt;# curl http://127.0.0.1:8500/v1/kv/upstreams/?keys&amp;nbsp; &amp;nbsp;#可以看到如果采用目录层级的形式的话会比较直观,层层目录递进最后直到最后ip:端口形成的key&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/dianshang_http_develop-mirror-stable/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/dianshang_http_develop-mirror-stable/192.168.1.187:30660&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/dianshang_tcp_develop-mirror-stable/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/dianshang_tcp_develop-mirror-stable/192.168.1.252:30660&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/mirror/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/mirror/stable/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/22384/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/22384/dianshang_http_develop/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/22384/dianshang_http_develop/192.168.1.187:30660&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/22384/dianshang_tcp_develop/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/22384/dianshang_tcp_develop/192.168.1.252:30660&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/25568/dianshang_http_develop/192.168.1.187:3066&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/stable/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/stable/dianshang_http_develop/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/stable/dianshang_http_develop/192.168.1.187:30660&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/stable/dianshang_tcp_develop/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/stable/dianshang_tcp_develop/192.168.1.252:30660&amp;quot;
]&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#选择哪种影响都有限就在程序上面做判断就可以了,但是我倾向于第二种一方面比较直观另一方面删除需求的时候也比较方便,比如我现在要删除25568需求(需求已释放)&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#&amp;nbsp; curl --request DELETE http://127.0.0.1:8500/v1/kv/upstreams/offline/demand/25568&amp;nbsp; &amp;nbsp;#执行此命令就行&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# curl -X PUT -d &amp;#39;{&amp;quot;weight&amp;quot;:1, &amp;quot;max_fails&amp;quot;:2, &amp;quot;fail_timeout&amp;quot;:10}&amp;#39;&amp;nbsp; http://127.0.0.1:8500/v1/kv/upstreams/offline/demand/25568/dianshang_http_develop/192.168.1.187:3066&amp;nbsp; &amp;nbsp; #这是一个创建需求kv的操作&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#一般测试环境容器就起一个pod,这既节省成本管理起来也比较简单,理论上每个需求集群下面只应该有一个pod，pod发生启动关闭无非两个事件,一个就是重启,一个就是副本更新启动新的关闭旧的,所以一旦接收到这个需求集群的启动上报就直接PUT更新就可以了,更新的肯定是最新的podip。&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;text-wrap-mode: wrap; background-color: #CCC1D9;&quot;&gt;1.2 容器重启/关闭发送信息&lt;/span&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#仔细思考一下,如果只启动一个pod节点的话,我们还需要关闭事件吗,我们只需要delete/put这2个动作就行了,因为每个需求集群下面只有一个podip:端口存在(因为目录下面是追加而不是覆盖,所以添加新IP:端口的key前需要将目录下面清空)。&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;好了那么我们假设我们有一个web接口负责接收pod启动和关闭时候的上报,那么我们如何触发这个动作呢&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;第一种方法钩子触发：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;在yaml文件中的spec:的containers:下面定义(我pod_name和pod_ip分别用了两种取值的方式哦)&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;env:
&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;POD_NAME
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;valueFrom:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fieldRef:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;apiVersion:&amp;nbsp;v1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fieldPath:&amp;nbsp;metadata.name
&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;POD_IP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;valueFrom:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fieldRef:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fieldPath:&amp;nbsp;status.podIP
&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;PATH
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value:&amp;nbsp;&amp;quot;/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/soft/java/bin&amp;quot;
lifecycle:
&amp;nbsp;&amp;nbsp;postStart:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exec:&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;command:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;quot;/bin/sh&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;quot;-c&amp;quot;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;#39;curl&amp;nbsp;-i&amp;nbsp;-X&amp;nbsp;POST&amp;nbsp;-H&amp;nbsp;&amp;quot;Content-type:application/json&amp;quot;&amp;nbsp;-d&amp;nbsp;&amp;quot;{\&amp;quot;clustername\&amp;quot;:\&amp;quot;dianshang_http_develop\&amp;quot;,\&amp;quot;modulename\&amp;quot;:\&amp;quot;http-develop\&amp;quot;,\&amp;quot;env_type\&amp;quot;:\&amp;quot;offline\&amp;quot;,\&amp;quot;reqid\&amp;quot;:\&amp;quot;25568\&amp;quot;,\
&amp;quot;pod_name\&amp;quot;:\&amp;quot;`hostname`\&amp;quot;,\&amp;quot;pod_ip\&amp;quot;:\&amp;quot;$POD_IP\&amp;quot;,\&amp;quot;port\&amp;quot;:\&amp;quot;30553\&amp;quot;}&amp;quot;&amp;nbsp;http://lua.test.com/docker/report/start/&amp;#39;
&amp;nbsp;&amp;nbsp;preStop:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exec:&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;command:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;quot;/bin/sh&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;quot;-c&amp;quot;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;#39;curl&amp;nbsp;-i&amp;nbsp;-X&amp;nbsp;POST&amp;nbsp;-H&amp;nbsp;&amp;quot;Content-type:application/json&amp;quot;&amp;nbsp;-d&amp;nbsp;&amp;quot;{\&amp;quot;clustername\&amp;quot;:\&amp;quot;dianshang_http_develop\&amp;quot;,\&amp;quot;modulename\&amp;quot;:\&amp;quot;http-develop\&amp;quot;,\&amp;quot;env_type\&amp;quot;:\&amp;quot;offline\&amp;quot;,\&amp;quot;reqid\&amp;quot;:\&amp;quot;25568\&amp;quot;,\
&amp;quot;pod_name\&amp;quot;:\&amp;quot;`hostname`\&amp;quot;,\&amp;quot;pod_ip\&amp;quot;:\&amp;quot;$POD_IP\&amp;quot;,\&amp;quot;port\&amp;quot;:\&amp;quot;30553\&amp;quot;}&amp;quot;&amp;nbsp;http://lua.test.com/docker/report/stop/&amp;#39;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;题外话：你说我想一个钩子发给多个接口怎么办(确实有这个需求啊,监控平台可能也想要这个事件呢),以关闭钩子举例：&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;preStop:
&amp;nbsp;&amp;nbsp;exec:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;command:&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;quot;/bin/sh&amp;quot;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;quot;-c&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&amp;#39;curl&amp;nbsp;-i&amp;nbsp;-X&amp;nbsp;POST&amp;nbsp;-H&amp;nbsp;&amp;quot;Content-type:application/json&amp;quot;&amp;nbsp;-d&amp;nbsp;&amp;quot;{\&amp;quot;clustername\&amp;quot;:\&amp;quot;dianshang_http_develop\&amp;quot;,\&amp;quot;pod_ip\&amp;quot;:\&amp;quot;`ifconfig&amp;nbsp;|grep&amp;nbsp;-v&amp;nbsp;127.0.0.1|grep&amp;nbsp;-o&amp;nbsp;inet.*[nB]|grep&amp;nbsp;-o&amp;nbsp;[0-9].*[0-9
]`\&amp;quot;}&amp;quot;&amp;nbsp;http://watcher.test.com/pod/stop&amp;nbsp;;&amp;nbsp;curl&amp;nbsp;-d&amp;nbsp;&amp;quot;docker_name=`hostname`&amp;amp;action_state=Stop&amp;quot;&amp;nbsp;http://report.test.com/docker/hook_report/&amp;#39;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;#通过;就可以实现各取所需,各接口要自己的信息了,这个;的作用是执行多个命令而且;后面的命令是不受;前面命令成功与否的状态影响的。&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong style=&quot;text-wrap-mode: wrap;&quot;&gt;第二种方法启动触发：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;在yaml文件命令哪里是启动脚本并传参,让脚本去做一些必要的操作并拉起服务,当然这种方式就是只能启动上报了&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;command:&amp;nbsp;[&amp;quot;/bin/sh&amp;quot;]
args:&amp;nbsp;[&amp;quot;/root/init.sh&amp;quot;,&amp;nbsp;&amp;quot;集群名&amp;quot;,&amp;nbsp;&amp;quot;offline&amp;quot;,&amp;nbsp;&amp;quot;服务类型&amp;quot;,&amp;quot;镜像版本&amp;quot;,&amp;quot;监听端口&amp;quot;,&amp;quot;模块名&amp;quot;,&amp;quot;stable/也可以是需求号&amp;quot;]&lt;/pre&gt;&lt;p&gt;#然后把上面的启动的curl命令就可以放到这个/root/init.sh脚本中了,脚本都不陌生了啊。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;#题外话,这种脚本的方式在启动容器的时候有很大的作用,比如我们如何实现一个镜像多环境使用呢,就是通过在镜像中埋一个脚本的形式,默认打的是线上的配置,这样什么都不用动服务直接启免去了改动风险,因为线上和测试环境是资源隔离的也不用担心测试环境调用到了线上因为服务压根就启动不起来。然后脚本判断如果是测试环境,就再拉一个脚本所有的操作都在这个脚本中操作(有好处的,比如你要修改点东西正常流程的话是需要重新打一个镜像,但是这样只需要重启一下容器就可以重新拉一下脚本就可以做最新的处理了),然后检测到是测试或者沙箱环境,就把对应的配置文件包拉下来替换配置文件目录然后启动服务就行了。&lt;/p&gt;&lt;p&gt;#但是我们线上环境要的是稳定,不能每次都拉一个新脚本吧如果拉取失败我容器岂不是启动不起来了,所以在管理平台做一个开关,启动的时候做个判断,如果发现接口返回的状态码是要进行更新,就去拉取脚本并执行这个新脚本直至再次发版这个开关就会自动关闭了。(这个功能很适合大批量的操作,比如在我们批量更新log4j之类的漏洞的时候很好用,只需要批量重启容器就可以完成漏洞修复,一些大批量的修复只需要运维重启就可以了,不需要重新构建镜像大批量的发版了)。&lt;/p&gt;&lt;p&gt;&lt;strong style=&quot;color: rgb(68, 85, 102); font-family: 微软雅黑, Arial; text-wrap: wrap; background-color: rgb(254, 254, 254);&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;text-decoration-line: none; color: rgb(58, 110, 165); white-space-collapse: preserve; background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h3 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.3 web接口接上报并处理&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# vim /opt/web/pod_consul/docker/urls.py&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;from&amp;nbsp;django.urls&amp;nbsp;import&amp;nbsp;path,include
from&amp;nbsp;.&amp;nbsp;import&amp;nbsp;views
urlpatterns&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path(&amp;#39;index/&amp;#39;,views.index),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path(&amp;#39;report/start/&amp;#39;,views.StartView.as_view(),name=&amp;#39;start_report&amp;#39;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path(&amp;#39;report/stop/&amp;#39;,views.StopView.as_view(),name=&amp;#39;stop_report&amp;#39;),
]&lt;/pre&gt;&lt;p&gt;# cat /opt/web/pod_consul/docker/views.py&amp;nbsp; &amp;nbsp;#下面是一个简单的接收上报维护consul的接口demo&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#&amp;nbsp;~*~&amp;nbsp;coding:&amp;nbsp;utf-8&amp;nbsp;~*~
&amp;quot;&amp;quot;&amp;quot;
容器上报管理系统的视图层代码
负责处理容器启动/停止的上报请求，并与Consul服务进行交互
&amp;quot;&amp;quot;&amp;quot;
import&amp;nbsp;sys
import&amp;nbsp;json
from&amp;nbsp;typing&amp;nbsp;import&amp;nbsp;Dict,&amp;nbsp;Any,&amp;nbsp;Optional&amp;nbsp;&amp;nbsp;#&amp;nbsp;导入类型提示工具，增强代码可读性和IDE支持
import&amp;nbsp;requests&amp;nbsp;&amp;nbsp;#&amp;nbsp;用于发送HTTP请求到Consul服务
from&amp;nbsp;django.http&amp;nbsp;import&amp;nbsp;JsonResponse,&amp;nbsp;HttpResponse&amp;nbsp;&amp;nbsp;#&amp;nbsp;Django的HTTP响应处理类
from&amp;nbsp;django.views&amp;nbsp;import&amp;nbsp;View&amp;nbsp;&amp;nbsp;#&amp;nbsp;Django基础视图类
from&amp;nbsp;django.conf&amp;nbsp;import&amp;nbsp;settings&amp;nbsp;&amp;nbsp;#&amp;nbsp;用于获取Django项目配置

#&amp;nbsp;从Django配置中获取Consul服务地址，若未配置则使用默认值
#&amp;nbsp;这样的设计使得Consul地址可通过配置文件修改，无需改动代码
CONSUL_URL&amp;nbsp;=&amp;nbsp;getattr(settings,&amp;nbsp;&amp;#39;CONSUL_URL&amp;#39;,&amp;nbsp;&amp;#39;http://127.0.0.1:8500/v1/kv&amp;#39;)

#&amp;nbsp;定义对外暴露的视图类，控制导入时的可见性
__all__&amp;nbsp;=&amp;nbsp;[&amp;#39;StartView&amp;#39;,&amp;nbsp;&amp;#39;StopView&amp;#39;,&amp;nbsp;&amp;#39;index&amp;#39;]

def&amp;nbsp;index(request)&amp;nbsp;-&amp;gt;&amp;nbsp;HttpResponse:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;系统根路径视图函数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;request:&amp;nbsp;Django请求对象
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;HttpResponse:&amp;nbsp;返回简单的HTML响应
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;HttpResponse(&amp;#39;&amp;lt;h2&amp;gt;docker&amp;lt;/h2&amp;gt;&amp;#39;)

class&amp;nbsp;ConsulClient:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Consul服务交互客户端类
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;封装了与Consul&amp;nbsp;API的所有交互操作，职责单一，便于维护和测试
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@staticmethod
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;_construct_base_url(env_type:&amp;nbsp;str,&amp;nbsp;reqid:&amp;nbsp;str,&amp;nbsp;clustername:&amp;nbsp;str)&amp;nbsp;-&amp;gt;&amp;nbsp;str:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;构建Consul操作的基础URL路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;根据环境类型(env_type)和请求ID(reqid)决定不同的存储路径，
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;稳定版本(stable)和需求版本(demand)使用不同的目录结构
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env_type:&amp;nbsp;环境类型(如prod/test等)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reqid:&amp;nbsp;请求ID，用于区分不同的部署需求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clustername:&amp;nbsp;集群名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str:&amp;nbsp;构建好的基础URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;reqid&amp;nbsp;==&amp;nbsp;&amp;quot;stable&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;f&amp;quot;{CONSUL_URL}/upstreams/{env_type}/stable/{clustername}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;f&amp;quot;{CONSUL_URL}/upstreams/{env_type}/demand/{reqid}/{clustername}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@staticmethod
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;_construct_full_url(env_type:&amp;nbsp;str,&amp;nbsp;reqid:&amp;nbsp;str,&amp;nbsp;clustername:&amp;nbsp;str,&amp;nbsp;pod_ip:&amp;nbsp;str,&amp;nbsp;port:&amp;nbsp;str)&amp;nbsp;-&amp;gt;&amp;nbsp;str:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;构建包含完整服务信息的Consul&amp;nbsp;URL路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在基础URL的基础上添加Pod的IP和端口，形成唯一标识服务实例的路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env_type:&amp;nbsp;环境类型
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reqid:&amp;nbsp;请求ID
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clustername:&amp;nbsp;集群名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pod_ip:&amp;nbsp;容器/Pod的IP地址
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port:&amp;nbsp;服务端口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str:&amp;nbsp;完整的Consul操作URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base_url&amp;nbsp;=&amp;nbsp;ConsulClient._construct_base_url(env_type,&amp;nbsp;reqid,&amp;nbsp;clustername)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;f&amp;quot;{base_url}/{pod_ip}:{port}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@staticmethod
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;clear_existing_entries(env_type:&amp;nbsp;str,&amp;nbsp;reqid:&amp;nbsp;str,&amp;nbsp;clustername:&amp;nbsp;str)&amp;nbsp;-&amp;gt;&amp;nbsp;Optional[requests.Response]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;清除Consul中指定集群的现有条目
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在新服务上线时，先清除该集群的旧有记录，避免无效服务信息残留
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env_type:&amp;nbsp;环境类型
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reqid:&amp;nbsp;请求ID
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clustername:&amp;nbsp;集群名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Optional[requests.Response]:&amp;nbsp;Consul&amp;nbsp;API的响应对象，失败时返回None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;构建清除操作的URL，recurse参数表示递归删除该路径下的所有条目
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clear_url&amp;nbsp;=&amp;nbsp;f&amp;quot;{ConsulClient._construct_base_url(env_type,&amp;nbsp;reqid,&amp;nbsp;clustername)}?recurse&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;发送DELETE请求清除现有记录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response&amp;nbsp;=&amp;nbsp;requests.delete(clear_url)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;检查HTTP响应状态码，非2xx状态会抛出异常
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response.raise_for_status()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;response
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;requests.exceptions.RequestException&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;捕获所有HTTP请求相关异常并记录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;清除Consul条目时出错:&amp;nbsp;{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@staticmethod
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;update_service_entry(env_type:&amp;nbsp;str,&amp;nbsp;reqid:&amp;nbsp;str,&amp;nbsp;clustername:&amp;nbsp;str,&amp;nbsp;pod_ip:&amp;nbsp;str,&amp;nbsp;port:&amp;nbsp;str)&amp;nbsp;-&amp;gt;&amp;nbsp;Optional[requests.Response]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在Consul中更新服务实例信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;当容器启动时，将其服务信息(IP、端口、权重等)注册到Consul
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env_type:&amp;nbsp;环境类型
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reqid:&amp;nbsp;请求ID
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clustername:&amp;nbsp;集群名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pod_ip:&amp;nbsp;容器/Pod的IP地址
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port:&amp;nbsp;服务端口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Optional[requests.Response]:&amp;nbsp;Consul&amp;nbsp;API的响应对象，失败时返回None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;构建更新操作的URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;update_url&amp;nbsp;=&amp;nbsp;ConsulClient._construct_full_url(env_type,&amp;nbsp;reqid,&amp;nbsp;clustername,&amp;nbsp;pod_ip,&amp;nbsp;port)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;服务配置数据，包含负载均衡相关参数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;service_data&amp;nbsp;=&amp;nbsp;json.dumps({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;weight&amp;quot;:&amp;nbsp;1,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;服务权重，用于负载均衡
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;max_fails&amp;quot;:&amp;nbsp;3,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;最大失败次数，超过后标记服务不可用
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;fail_timeout&amp;quot;:&amp;nbsp;1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;失败超时时间(秒)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;发送PUT请求更新服务信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response&amp;nbsp;=&amp;nbsp;requests.put(update_url,&amp;nbsp;data=service_data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response.raise_for_status()&amp;nbsp;&amp;nbsp;#&amp;nbsp;检查HTTP响应状态
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;response
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;requests.exceptions.RequestException&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;更新Consul条目时出错:&amp;nbsp;{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@staticmethod
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;remove_service_entry(env_type:&amp;nbsp;str,&amp;nbsp;reqid:&amp;nbsp;str,&amp;nbsp;clustername:&amp;nbsp;str,&amp;nbsp;pod_ip:&amp;nbsp;str,&amp;nbsp;port:&amp;nbsp;str)&amp;nbsp;-&amp;gt;&amp;nbsp;Optional[requests.Response]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;从Consul中移除服务实例信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;当容器停止时，从Consul中删除其服务信息，避免流量被路由到已停止的服务
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;env_type:&amp;nbsp;环境类型
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reqid:&amp;nbsp;请求ID
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clustername:&amp;nbsp;集群名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pod_ip:&amp;nbsp;容器/Pod的IP地址
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port:&amp;nbsp;服务端口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Optional[requests.Response]:&amp;nbsp;Consul&amp;nbsp;API的响应对象，失败时返回None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;构建删除操作的URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;delete_url&amp;nbsp;=&amp;nbsp;ConsulClient._construct_full_url(env_type,&amp;nbsp;reqid,&amp;nbsp;clustername,&amp;nbsp;pod_ip,&amp;nbsp;port)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;发送DELETE请求移除服务信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response&amp;nbsp;=&amp;nbsp;requests.delete(delete_url)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response.raise_for_status()&amp;nbsp;&amp;nbsp;#&amp;nbsp;检查HTTP响应状态
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;response
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;requests.exceptions.RequestException&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;移除Consul条目时出错:&amp;nbsp;{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

class&amp;nbsp;BaseContainerView(View):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;容器操作的基础视图类
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;封装了StartView和StopView的共同功能，减少代码重复，
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;遵循DRY(Don&amp;#39;t&amp;nbsp;Repeat&amp;nbsp;Yourself)原则
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;_parse_request_data(self,&amp;nbsp;request)&amp;nbsp;-&amp;gt;&amp;nbsp;Dict[str,&amp;nbsp;str]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;解析并验证请求中的JSON数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;提取请求中的容器信息字段，并为缺失的字段提供默认值(空字符串)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;request:&amp;nbsp;Django请求对象
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Dict[str,&amp;nbsp;str]:&amp;nbsp;解析后的容器信息字典
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Raises:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;json.JSONDecodeError:&amp;nbsp;当请求数据不是有效的JSON格式时抛出
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;解析请求体中的JSON数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;json.loads(request.body)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;提取所需字段，使用get方法确保即使字段缺失也不会报错
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;clustername&amp;#39;:&amp;nbsp;data.get(&amp;#39;clustername&amp;#39;,&amp;nbsp;&amp;#39;&amp;#39;),&amp;nbsp;&amp;nbsp;#&amp;nbsp;集群名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;modulename&amp;#39;:&amp;nbsp;data.get(&amp;#39;modulename&amp;#39;,&amp;nbsp;&amp;#39;&amp;#39;),&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;模块名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;port&amp;#39;:&amp;nbsp;data.get(&amp;#39;port&amp;#39;,&amp;nbsp;&amp;#39;&amp;#39;),&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;服务端口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;pod_ip&amp;#39;:&amp;nbsp;data.get(&amp;#39;pod_ip&amp;#39;,&amp;nbsp;&amp;#39;&amp;#39;),&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Pod/容器IP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;reqid&amp;#39;:&amp;nbsp;data.get(&amp;#39;reqid&amp;#39;,&amp;nbsp;&amp;#39;&amp;#39;),&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;请求ID
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;env_type&amp;#39;:&amp;nbsp;data.get(&amp;#39;env_type&amp;#39;,&amp;nbsp;&amp;#39;&amp;#39;),&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;环境类型
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;pod_name&amp;#39;:&amp;nbsp;data.get(&amp;#39;pod_name&amp;#39;,&amp;nbsp;&amp;#39;&amp;#39;)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Pod/容器名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;json.JSONDecodeError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;记录解析错误信息便于调试
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;错误：POST数据不是有效的JSON格式&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;原始数据：&amp;quot;,&amp;nbsp;request.body)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raise&amp;nbsp;&amp;nbsp;#&amp;nbsp;重新抛出异常，由调用方处理响应
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;get(self,&amp;nbsp;request)&amp;nbsp;-&amp;gt;&amp;nbsp;HttpResponse:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;处理GET请求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;提供简单的欢迎信息，告知客户端应使用POST方法上报容器信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;request:&amp;nbsp;Django请求对象
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;HttpResponse:&amp;nbsp;包含欢迎信息的响应
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;HttpResponse(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;欢迎使用容器上报管理系统，请进行容器信息的上报&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_type=&amp;quot;application/json;charset=utf-8&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

class&amp;nbsp;StartView(BaseContainerView):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;处理容器启动上报的视图类
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;当容器启动时，接收其信息并更新到Consul服务发现系统
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;post(self,&amp;nbsp;request)&amp;nbsp;-&amp;gt;&amp;nbsp;JsonResponse:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;处理容器启动的POST请求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;解析请求数据，记录容器启动信息，并更新Consul中的服务注册信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;request:&amp;nbsp;Django请求对象，包含容器启动信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JsonResponse:&amp;nbsp;包含处理结果的JSON响应
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;解析请求数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;self._parse_request_data(request)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;打印容器启动信息，用于日志记录和调试
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;容器启动上报:&amp;nbsp;{data[&amp;#39;clustername&amp;#39;]},&amp;nbsp;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;{data[&amp;#39;modulename&amp;#39;]},&amp;nbsp;{data[&amp;#39;port&amp;#39;]},&amp;nbsp;{data[&amp;#39;pod_ip&amp;#39;]},&amp;nbsp;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;{data[&amp;#39;reqid&amp;#39;]},&amp;nbsp;{data[&amp;#39;env_type&amp;#39;]},&amp;nbsp;{data[&amp;#39;pod_name&amp;#39;]}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;清除该集群的现有条目
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clear_response&amp;nbsp;=&amp;nbsp;ConsulClient.clear_existing_entries(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;#39;env_type&amp;#39;],&amp;nbsp;data[&amp;#39;reqid&amp;#39;],&amp;nbsp;data[&amp;#39;clustername&amp;#39;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;检查清除操作是否成功
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;clear_response&amp;nbsp;is&amp;nbsp;None:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;JsonResponse(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;#39;status&amp;#39;:&amp;nbsp;&amp;#39;error&amp;#39;,&amp;nbsp;&amp;#39;message&amp;#39;:&amp;nbsp;&amp;#39;清除现有条目失败&amp;#39;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status=500&amp;nbsp;&amp;nbsp;#&amp;nbsp;500表示服务器内部错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;更新服务条目，注册新的容器信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;update_response&amp;nbsp;=&amp;nbsp;ConsulClient.update_service_entry(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;#39;env_type&amp;#39;],&amp;nbsp;data[&amp;#39;reqid&amp;#39;],&amp;nbsp;data[&amp;#39;clustername&amp;#39;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;#39;pod_ip&amp;#39;],&amp;nbsp;data[&amp;#39;port&amp;#39;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;检查更新操作是否成功
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;update_response&amp;nbsp;is&amp;nbsp;None:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;JsonResponse(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;#39;status&amp;#39;:&amp;nbsp;&amp;#39;error&amp;#39;,&amp;nbsp;&amp;#39;message&amp;#39;:&amp;nbsp;&amp;#39;更新服务条目失败&amp;#39;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status=500
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;所有操作成功完成
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;JsonResponse({&amp;#39;status&amp;#39;:&amp;nbsp;&amp;#39;success&amp;#39;,&amp;nbsp;&amp;#39;message&amp;#39;:&amp;nbsp;&amp;#39;数据已接收并处理&amp;#39;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;json.JSONDecodeError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;处理JSON解析错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;JsonResponse(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;#39;status&amp;#39;:&amp;nbsp;&amp;#39;error&amp;#39;,&amp;nbsp;&amp;#39;message&amp;#39;:&amp;nbsp;&amp;#39;无效的JSON数据&amp;#39;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status=400&amp;nbsp;&amp;nbsp;#&amp;nbsp;400表示请求参数错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;捕获所有未预料到的异常，避免服务崩溃
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;StartView中发生未预期错误:&amp;nbsp;{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;JsonResponse(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;#39;status&amp;#39;:&amp;nbsp;&amp;#39;error&amp;#39;,&amp;nbsp;&amp;#39;message&amp;#39;:&amp;nbsp;&amp;#39;发生未预期的错误&amp;#39;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status=500
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

class&amp;nbsp;StopView(BaseContainerView):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;处理容器停止上报的视图类
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;当容器停止时，接收其信息并从Consul服务发现系统中移除该服务
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;post(self,&amp;nbsp;request)&amp;nbsp;-&amp;gt;&amp;nbsp;JsonResponse:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;处理容器停止的POST请求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;解析请求数据，记录容器停止信息，并从Consul中移除该服务信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Args:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;request:&amp;nbsp;Django请求对象，包含容器停止信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JsonResponse:&amp;nbsp;包含处理结果的JSON响应
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;解析请求数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;self._parse_request_data(request)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;打印容器停止信息，用于日志记录和调试
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;容器关闭上报:&amp;nbsp;{data[&amp;#39;clustername&amp;#39;]},&amp;nbsp;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;{data[&amp;#39;modulename&amp;#39;]},&amp;nbsp;{data[&amp;#39;port&amp;#39;]},&amp;nbsp;{data[&amp;#39;pod_ip&amp;#39;]},&amp;nbsp;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;{data[&amp;#39;reqid&amp;#39;]},&amp;nbsp;{data[&amp;#39;env_type&amp;#39;]},&amp;nbsp;{data[&amp;#39;pod_name&amp;#39;]}&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;从Consul中移除服务条目
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response&amp;nbsp;=&amp;nbsp;ConsulClient.remove_service_entry(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;#39;env_type&amp;#39;],&amp;nbsp;data[&amp;#39;reqid&amp;#39;],&amp;nbsp;data[&amp;#39;clustername&amp;#39;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;#39;pod_ip&amp;#39;],&amp;nbsp;data[&amp;#39;port&amp;#39;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;检查移除操作是否成功
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;response&amp;nbsp;is&amp;nbsp;None:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;JsonResponse(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;#39;status&amp;#39;:&amp;nbsp;&amp;#39;error&amp;#39;,&amp;nbsp;&amp;#39;message&amp;#39;:&amp;nbsp;&amp;#39;移除服务条目失败&amp;#39;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status=500
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;操作成功完成
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;JsonResponse({&amp;#39;status&amp;#39;:&amp;nbsp;&amp;#39;success&amp;#39;,&amp;nbsp;&amp;#39;message&amp;#39;:&amp;nbsp;&amp;#39;数据已接收并处理&amp;#39;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;json.JSONDecodeError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;处理JSON解析错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;JsonResponse(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;#39;status&amp;#39;:&amp;nbsp;&amp;#39;error&amp;#39;,&amp;nbsp;&amp;#39;message&amp;#39;:&amp;nbsp;&amp;#39;无效的JSON数据&amp;#39;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status=400
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;捕获所有未预料到的异常
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;StopView中发生未预期错误:&amp;nbsp;{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;JsonResponse(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;#39;status&amp;#39;:&amp;nbsp;&amp;#39;error&amp;#39;,&amp;nbsp;&amp;#39;message&amp;#39;:&amp;nbsp;&amp;#39;发生未预期的错误&amp;#39;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;status=500
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&lt;/pre&gt;&lt;p&gt;#然后你找一个你的需求容器把上报信息接口添加到yaml文件中然后apply一下,然后等容器完整重启后,delete pod一下可以看看consul中得信息是否发生变化&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;容器关闭上报:&amp;nbsp;上报信息
10:42:10]&amp;nbsp;&amp;quot;POST&amp;nbsp;/docker/report/stop/&amp;nbsp;HTTP/1.1&amp;quot;&amp;nbsp;200&amp;nbsp;84
容器启动上报:&amp;nbsp;
10:42:20]&amp;nbsp;&amp;quot;POST&amp;nbsp;/docker/report/start/&amp;nbsp;HTTP/1.1&amp;quot;&amp;nbsp;200&amp;nbsp;84&lt;/pre&gt;&lt;p&gt;#从接口的输出可以看出来单个Pod发生delete事件的时候是先触发关闭钩子再触发新容器的启动钩子&lt;/p&gt;&lt;p&gt;#我们的demo代码中已经在启动接口中增加了清空整个目录的操作,所以使用上面提到的第二种方法只启动上报已经可以实现我们需要的效果,但是接口只能实现一次是吧,如果失败了呢你不能把所有的pod都重启一遍然后让重新上报吧,所以最好还要有一个兜底的功能。&lt;/p&gt;&lt;p&gt;#那么这个兜底功能如何实现呢,我说个最简单的方法：&lt;br/&gt;首先我们需要拿到各个环境在consul中的信息,比如需求环境：&lt;br/&gt;# curl http://127.0.0.1:8500/v1/kv/upstreams/offline/demand/?keys|grep 192.168&amp;nbsp; &amp;nbsp;#这样环境/需求号/集群名称/集群的IP:端口我们都能得到了&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/22384/dianshang_http_develop/192.168.1.187:30660&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/22384/dianshang_tcp_develop/192.168.1.252:30660&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;upstreams/offline/demand/25568/dianshang_http_develop/192.168.1.187:3066&amp;quot;&lt;/pre&gt;&lt;p&gt;# kubectl get pods -n demand -o wide&amp;nbsp; &amp;nbsp;#这样当前需求环境运行的集群/需求以及对应的podip我们就拿到了,然后写个程序跟consul中的信息每分钟一核准不就行了&lt;/p&gt;&lt;p&gt;&lt;strong style=&quot;color: rgb(68, 85, 102); font-family: 微软雅黑, Arial; text-wrap: wrap; background-color: rgb(254, 254, 254);&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;text-decoration-line: none; color: rgb(58, 110, 165); white-space-collapse: preserve; background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;二、nginx使用Consul转发请求&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.1 需求域名配置&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://blog.51niux.com/?id=322&quot; _src=&quot;https://blog.51niux.com/?id=322&quot;&gt;https://blog.51niux.com/?id=322&lt;/a&gt;&amp;nbsp; &amp;nbsp;这个文章中的内容就用上了哦&lt;/p&gt;&lt;p&gt;# vim&amp;nbsp; /opt/soft/nginx/conf.d/集群名-docker-25298.test.com.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;include&amp;nbsp;/opt/soft/nginx/conf.d/location/集群名-docker-25298.test.com/*.location_upstream.conf;
server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;......
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/opt/soft/nginx/conf.d/location/集群名-docker-25298.test.com/*.location.conf;
}&lt;/pre&gt;&lt;p&gt;#注意了,为什么需要两个include,为什么把upstream.conf引用放到http区域呢而不是单纯的include一个*.conf？因为你要都放到server{}区域是要有下面得报错的:&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;nginx:&amp;nbsp;[emerg]&amp;nbsp;&amp;quot;upstream&amp;quot;&amp;nbsp;directive&amp;nbsp;is&amp;nbsp;not&amp;nbsp;allowed&amp;nbsp;here&amp;nbsp;in&amp;nbsp;/opt/soft/nginx/conf.d/location/模块名-docker-25298.test.com/模块名-docker-25298.test.com.location_upstream.conf:1
nginx:&amp;nbsp;configuration&amp;nbsp;file&amp;nbsp;/opt/soft/nginx/main-conf/nginx.conf&amp;nbsp;test&amp;nbsp;failed&lt;/pre&gt;&lt;p&gt;# vim /opt/soft/nginx/conf.d/location/集群名-docker-25298.test.com/集群名-docker-25298.test.com.location.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_next_upstream&amp;nbsp;http_502&amp;nbsp;&amp;nbsp;error&amp;nbsp;timeout&amp;nbsp;invalid_header;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;http://集群名-docker-25298_pool;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;Host&amp;nbsp;$host;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-For&amp;nbsp;$remote_addr;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Real-IP&amp;nbsp;$remote_addr;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-Proto&amp;nbsp;&amp;nbsp;$scheme;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/pre&gt;&lt;p&gt;# vim /opt/soft/nginx/conf.d/location/集群名-docker-25298.test.com/集群名-docker-25298.test.com.location_upstream.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;集群名-docker-25298_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;server&amp;nbsp;127.0.0.1:11111&amp;nbsp;down;
&amp;nbsp;&amp;nbsp;upsync&amp;nbsp;127.0.0.1:8500/v1/kv/upstreams/offline/demand/25298/集群名&amp;nbsp;upsync_timeout=5s&amp;nbsp;upsync_interval=500ms&amp;nbsp;upsync_type=consul&amp;nbsp;strong_dependency=off;
&amp;nbsp;&amp;nbsp;upsync_dump_path&amp;nbsp;/data/nginxconf/location/集群名-offline-25298.conf;
&amp;nbsp;&amp;nbsp;include&amp;nbsp;/data/nginxconf/location/集群名-offline-25298*.conf;
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx -s reload&amp;nbsp; #可以自行用测试域名访问一下看看请求是不是到了consul里面存储的后端IP:端口,可以的这里就不粘贴测试结果了&lt;/p&gt;&lt;p&gt;#上面只是一个location是吧,但是生产环境中我们有时候一个域名有多个location,这怎么实现呢,其实道理是相同的,一般默认我们都是所有的请求都给location /,但是如果你有其他location呢,你这里就依次类推在比如&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;/opt/soft/nginx/conf.d/location/集群名-docker-25298.test.com/下面创建关于其他的location.conf和upstream.conf文件就可以了。好的那么第二个问题来了,我怎么知道这个域名在创建需求的时候需要额外创建其他的location呢?这就设计到mysql存储数据了,通常呢我们一般有一个模版域名,比如stable域名,我们新增的lcation都通过web页面这上面创建location对应的集群,比如点击添加最少需要(域名/模块名|集群名/location)这三个信息,然后也可以在域名的右侧点击查看可以看到(模块名/集群名/后端端口号/location/申请人/申请时间)这些大体的信息&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#这样就可以实现给一个域名添加除/以外的其他指定的location,这些location就可以作用到nginx的配置文件中了,你的请求也就会被location路由了。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#我们参照上面的例子再做一个stable环境的,要实现一个网关域名转发:1.如果来源IP绑定了需求就优先走需求域名,如果没有需求域名就走稳定环境域名但是是需求的tag&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;text-wrap-mode: wrap; background-color: #B2A2C7;&quot;&gt;2.2 网关数据表创建&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#先创建一个mysql表记录来源IP和需求号的对应关系(当然你可以丰富表字段比如user-agent、添加人、需求描述、添加时间、是否生效等)：&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;CREATE&amp;nbsp;TABLE&amp;nbsp;`on_demand`&amp;nbsp;(
&amp;nbsp;&amp;nbsp;`id`&amp;nbsp;int(11)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;AUTO_INCREMENT&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;自增序号&amp;#39;,
&amp;nbsp;&amp;nbsp;`source_ip`&amp;nbsp;varchar(45)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;用户来源IP（如&amp;nbsp;192.168.1.131）&amp;#39;,
&amp;nbsp;&amp;nbsp;`demand_number`&amp;nbsp;varchar(50)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;需求号（stable/25228）&amp;#39;,
&amp;nbsp;&amp;nbsp;`user`&amp;nbsp;varchar(50)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;操作用户&amp;#39;,&amp;nbsp;&amp;nbsp;--&amp;nbsp;新增user字段，补充合理注释
&amp;nbsp;&amp;nbsp;`create_time`&amp;nbsp;datetime&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;DEFAULT&amp;nbsp;CURRENT_TIMESTAMP&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;创建时间&amp;#39;,&amp;nbsp;&amp;nbsp;--&amp;nbsp;增加默认值更实用
&amp;nbsp;&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;(`id`),
&amp;nbsp;&amp;nbsp;UNIQUE&amp;nbsp;KEY&amp;nbsp;`uk_source_ip`&amp;nbsp;(`source_ip`),&amp;nbsp;&amp;nbsp;--&amp;nbsp;--&amp;nbsp;添加唯一约束，确保一个来源IP只能对应一个需求编号
&amp;nbsp;&amp;nbsp;KEY&amp;nbsp;`idx_ip_demand`&amp;nbsp;(`source_ip`,`demand_number`)&amp;nbsp;&amp;nbsp;--&amp;nbsp;联合索引，提升查询效率
)&amp;nbsp;ENGINE=InnoDB&amp;nbsp;AUTO_INCREMENT=6&amp;nbsp;DEFAULT&amp;nbsp;CHARSET=utf8mb4&amp;nbsp;COLLATE=utf8mb4_general_ci&amp;nbsp;COMMENT=&amp;#39;IP与环境映射表&amp;#39;;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;#然后再创建一下网关表,我们这样涉及,现有一个线下环境的网关表,线下网关绑定location,沙箱网关跟稳定网关做关联,这样location就保持一致了&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;CREATE&amp;nbsp;TABLE&amp;nbsp;`offline_gateway`&amp;nbsp;(
&amp;nbsp;&amp;nbsp;`id`&amp;nbsp;bigint(20)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;AUTO_INCREMENT&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;主键ID&amp;#39;,
&amp;nbsp;&amp;nbsp;`domain_name`&amp;nbsp;varchar(255)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;线下网关域名（如&amp;nbsp;offline.user.test.com）&amp;#39;,
&amp;nbsp;&amp;nbsp;`network_type`&amp;nbsp;varchar(10)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;网络类型（intranet-内网/extranet-外网）&amp;#39;,
&amp;nbsp;&amp;nbsp;`applicant`&amp;nbsp;varchar(50)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;申请人（创建者）&amp;#39;,
&amp;nbsp;&amp;nbsp;`create_time`&amp;nbsp;datetime&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;DEFAULT&amp;nbsp;current_timestamp()&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;创建时间&amp;#39;,
&amp;nbsp;&amp;nbsp;`update_time`&amp;nbsp;datetime&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;DEFAULT&amp;nbsp;current_timestamp()&amp;nbsp;ON&amp;nbsp;UPDATE&amp;nbsp;current_timestamp()&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;更新时间&amp;#39;,
&amp;nbsp;&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;(`id`),
&amp;nbsp;&amp;nbsp;UNIQUE&amp;nbsp;KEY&amp;nbsp;`uk_offline_domain`&amp;nbsp;(`domain_name`)&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;线下域名唯一，避免重复&amp;#39;
)&amp;nbsp;ENGINE=InnoDB&amp;nbsp;AUTO_INCREMENT=4&amp;nbsp;DEFAULT&amp;nbsp;CHARSET=utf8mb4&amp;nbsp;COLLATE=utf8mb4_general_ci&amp;nbsp;COMMENT=&amp;#39;线下网关模板表（存储线下网关基础信息，作为沙箱的配置来源）&amp;#39;;

CREATE&amp;nbsp;TABLE&amp;nbsp;`offline_location`&amp;nbsp;(
&amp;nbsp;&amp;nbsp;`id`&amp;nbsp;bigint(20)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;AUTO_INCREMENT&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;主键ID&amp;#39;,
&amp;nbsp;&amp;nbsp;`offline_gateway_id`&amp;nbsp;bigint(20)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;关联的线下网关ID&amp;#39;,
&amp;nbsp;&amp;nbsp;`module_name`&amp;nbsp;varchar(100)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;所属模块&amp;#39;,
&amp;nbsp;&amp;nbsp;`cluster_name`&amp;nbsp;varchar(255)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;对应的集群名称（如&amp;nbsp;http_user_service）&amp;#39;,
&amp;nbsp;&amp;nbsp;`location_path`&amp;nbsp;varchar(255)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;location路径（如&amp;nbsp;/user/login）&amp;#39;,
&amp;nbsp;&amp;nbsp;`rewrite_rule`&amp;nbsp;varchar(500)&amp;nbsp;DEFAULT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;重写规则（如&amp;nbsp;/user/login/(.*)&amp;nbsp;/auth/$1）&amp;#39;,
&amp;nbsp;&amp;nbsp;`applicant`&amp;nbsp;varchar(50)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;申请人（创建者）&amp;#39;,
&amp;nbsp;&amp;nbsp;`create_time`&amp;nbsp;datetime&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;DEFAULT&amp;nbsp;current_timestamp()&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;创建时间&amp;#39;,
&amp;nbsp;&amp;nbsp;`update_time`&amp;nbsp;datetime&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;DEFAULT&amp;nbsp;current_timestamp()&amp;nbsp;ON&amp;nbsp;UPDATE&amp;nbsp;current_timestamp()&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;更新时间&amp;#39;,
&amp;nbsp;&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;(`id`),
&amp;nbsp;&amp;nbsp;UNIQUE&amp;nbsp;KEY&amp;nbsp;`uk_offline_loc`&amp;nbsp;(`offline_gateway_id`,`location_path`),
&amp;nbsp;&amp;nbsp;CONSTRAINT&amp;nbsp;`offline_location_ibfk_1`&amp;nbsp;FOREIGN&amp;nbsp;KEY&amp;nbsp;(`offline_gateway_id`)&amp;nbsp;REFERENCES&amp;nbsp;`offline_gateway`&amp;nbsp;(`id`)&amp;nbsp;ON&amp;nbsp;DELETE&amp;nbsp;CASCADE
)&amp;nbsp;ENGINE=InnoDB&amp;nbsp;AUTO_INCREMENT=8&amp;nbsp;DEFAULT&amp;nbsp;CHARSET=utf8mb4&amp;nbsp;COLLATE=utf8mb4_general_ci&amp;nbsp;COMMENT=&amp;#39;线下网关的location配置表（沙箱网关直接复用此配置）&amp;#39;;

CREATE&amp;nbsp;TABLE&amp;nbsp;`mirror_gateway`&amp;nbsp;(
&amp;nbsp;&amp;nbsp;`id`&amp;nbsp;bigint(20)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;AUTO_INCREMENT&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;主键ID&amp;#39;,
&amp;nbsp;&amp;nbsp;`domain_name`&amp;nbsp;varchar(255)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;沙箱网关域名（如&amp;nbsp;mirror1.user.test.com）&amp;#39;,
&amp;nbsp;&amp;nbsp;`offline_gateway_id`&amp;nbsp;bigint(20)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;绑定的线下网关ID（1个沙箱仅绑定1个线下）&amp;#39;,
&amp;nbsp;&amp;nbsp;`network_type`&amp;nbsp;varchar(10)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;网络类型（通常与线下一致）&amp;#39;,
&amp;nbsp;&amp;nbsp;`applicant`&amp;nbsp;varchar(50)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;申请人（创建者）&amp;#39;,
&amp;nbsp;&amp;nbsp;`create_time`&amp;nbsp;datetime&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;DEFAULT&amp;nbsp;current_timestamp()&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;创建时间&amp;#39;,
&amp;nbsp;&amp;nbsp;`update_time`&amp;nbsp;datetime&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;DEFAULT&amp;nbsp;current_timestamp()&amp;nbsp;ON&amp;nbsp;UPDATE&amp;nbsp;current_timestamp()&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;更新时间&amp;#39;,
&amp;nbsp;&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;(`id`),
&amp;nbsp;&amp;nbsp;UNIQUE&amp;nbsp;KEY&amp;nbsp;`uk_mirror_domain`&amp;nbsp;(`domain_name`),
&amp;nbsp;&amp;nbsp;UNIQUE&amp;nbsp;KEY&amp;nbsp;`uk_mirror_offline`&amp;nbsp;(`domain_name`,`offline_gateway_id`),
&amp;nbsp;&amp;nbsp;KEY&amp;nbsp;`offline_gateway_id`&amp;nbsp;(`offline_gateway_id`),
&amp;nbsp;&amp;nbsp;CONSTRAINT&amp;nbsp;`mirror_gateway_ibfk_1`&amp;nbsp;FOREIGN&amp;nbsp;KEY&amp;nbsp;(`offline_gateway_id`)&amp;nbsp;REFERENCES&amp;nbsp;`offline_gateway`&amp;nbsp;(`id`)
)&amp;nbsp;ENGINE=InnoDB&amp;nbsp;AUTO_INCREMENT=6&amp;nbsp;DEFAULT&amp;nbsp;CHARSET=utf8mb4&amp;nbsp;COLLATE=utf8mb4_general_ci&amp;nbsp;COMMENT=&amp;#39;沙箱网关表（与线下网关映射，1个沙箱绑定1个线下，1个线下可绑定多个沙箱）&amp;#39;;&lt;/pre&gt;&lt;p&gt;#下面我们插入一些测试数据查看一下效果：&lt;br/&gt;&amp;gt; select * from offline_gateway;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/10/202510111760173318402647.png&quot; alt=&quot;image.png&quot; width=&quot;573&quot; height=&quot;130&quot; style=&quot;width: 573px; height: 130px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;&amp;gt; select * from offline_location;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/10/202510111760171645732761.png&quot; alt=&quot;image.png&quot; width=&quot;902&quot; height=&quot;188&quot; style=&quot;width: 902px; height: 188px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;&amp;gt; select * from mirror_gateway;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/10/202510111760171713702592.png&quot; alt=&quot;image.png&quot; width=&quot;783&quot; height=&quot;171&quot; style=&quot;width: 783px; height: 171px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#可以看到我们的表结构大概就是这样的,让测试网关去关联location对应的集群,我们要环境一致嘛,所以沙箱环境就以测试网关信息由模版就可以了,那么沙箱环境怎么知道自己关联了哪些location呢？&lt;/p&gt;&lt;p&gt;&amp;gt; SELECT&amp;nbsp; &amp;nbsp; m.domain_name AS 沙箱域名,&amp;nbsp; &amp;nbsp;o.domain_name AS 线下域名,&amp;nbsp; &amp;nbsp;l.location_path,&amp;nbsp; &amp;nbsp;l.cluster_name,&amp;nbsp; &amp;nbsp;l.rewrite_rule FROM mirror_gateway m JOIN offline_gateway o ON m.offline_gateway_id = o.id JOIN offline_location l ON o.id = l.offline_gateway_id WHERE m.domain_name = &amp;#39;user-mirror-1.test.com&amp;#39;;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/10/202510111760172774154059.png&quot; alt=&quot;image.png&quot; width=&quot;1002&quot; height=&quot;105&quot; style=&quot;width: 1002px; height: 105px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;&amp;gt; SELECT&amp;nbsp; &amp;nbsp; m.domain_name AS 沙箱域名,&amp;nbsp; &amp;nbsp;o.domain_name AS 线下域名,&amp;nbsp; &amp;nbsp;l.location_path,&amp;nbsp; &amp;nbsp;l.cluster_name,&amp;nbsp; &amp;nbsp;l.rewrite_rule,&amp;nbsp; &amp;nbsp;l.module_name,&amp;nbsp; &amp;nbsp;m.network_type FROM mirror_gateway m JOIN offline_gateway o ON m.offline_gateway_id = o.id JOIN offline_location l ON o.id = l.offline_gateway_id ORDER BY m.domain_name, l.location_path;&amp;nbsp; &amp;nbsp; #这是查询所有沙箱域名跟线下域名对应的location关系的sql语句&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&amp;gt; SELECT&amp;nbsp; &amp;nbsp; o.domain_name AS 线下域名,&amp;nbsp; &amp;nbsp;l.location_path AS location路径,&amp;nbsp; &amp;nbsp;l.module_name AS 模块名称,&amp;nbsp; &amp;nbsp;l.cluster_name AS 集群名称,&amp;nbsp; &amp;nbsp;l.rewrite_rule AS 重写规则,&amp;nbsp; &amp;nbsp;l.applicant AS 配置申请人,&amp;nbsp; &amp;nbsp;l.create_time AS 配置创建时间 FROM offline_gateway o&amp;nbsp; JOIN offline_location l ON o.id = l.offline_gateway_id&amp;nbsp; WHERE o.domain_name = &amp;#39;user-offline.test.com&amp;#39;&amp;nbsp; ORDER BY l.location_path;&amp;nbsp; #单个线下网关域名对应的location信息&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&amp;gt; SELECT&amp;nbsp; &amp;nbsp; o.domain_name AS 线下域名,&amp;nbsp; &amp;nbsp;l.location_path AS location路径,&amp;nbsp; &amp;nbsp;l.module_name AS 模块名称,&amp;nbsp; &amp;nbsp;l.cluster_name AS 集群名称,&amp;nbsp; &amp;nbsp;l.rewrite_rule AS 重写规则 FROM offline_gateway o JOIN offline_location l ON o.id = l.offline_gateway_id ORDER BY o.domain_name, l.location_path;&amp;nbsp; #所有的location信息&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&amp;gt; SELECT&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;o.domain_name AS 线下域名,&amp;nbsp; &amp;nbsp; &amp;nbsp; l.location_path AS location路径,&amp;nbsp; &amp;nbsp;l.cluster_name AS 集群名称,&amp;nbsp; &amp;nbsp; &amp;nbsp; GROUP_CONCAT(DISTINCT m.domain_name) AS 关联的沙箱域名 FROM offline_location l&amp;nbsp; JOIN offline_gateway o ON l.offline_gateway_id = o.id&amp;nbsp; LEFT JOIN mirror_gateway m ON o.id = m.offline_gateway_id&amp;nbsp; WHERE l.cluster_name = &amp;#39;http_order_service&amp;#39;&amp;nbsp; GROUP BY o.domain_name, l.location_path, l.cluster_name&amp;nbsp; ORDER BY o.domain_name, l.location_path;&amp;nbsp; #查询单个模块集群&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&amp;gt; SELECT&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;l.cluster_name AS 集群名称,&amp;nbsp; &amp;nbsp; &amp;nbsp; GROUP_CONCAT(DISTINCT o.domain_name ORDER BY o.domain_name) AS 关联的线下域名,&amp;nbsp; &amp;nbsp; &amp;nbsp; GROUP_CONCAT(DISTINCT CONCAT(o.domain_name, &amp;#39;:&amp;#39;, l.location_path) ORDER BY o.domain_name, l.location_path) AS 线下域名与location路径,&amp;nbsp; &amp;nbsp; &amp;nbsp; GROUP_CONCAT(DISTINCT m.domain_name ORDER BY m.domain_name) AS 关联的沙箱域名 FROM offline_location l&amp;nbsp; JOIN offline_gateway o ON l.offline_gateway_id = o.id&amp;nbsp; LEFT JOIN mirror_gateway m ON o.id = m.offline_gateway_id&amp;nbsp; GROUP BY l.cluster_name&amp;nbsp; ORDER BY l.cluster_name;&lt;/p&gt;&lt;p&gt;#上面的sql语句是查询所有的集群关联的线下域名以及location信息&lt;/p&gt;&lt;p&gt;#好了通过上面的sql语句基本可以满足我们需求了,包括程序去创建nginx的配置文件和平台的查询操作,当然配置文件的更新比如增删改,就变为平台操作触发式的了&lt;/p&gt;&lt;p&gt;&lt;strong style=&quot;color: rgb(68, 85, 102); font-family: 微软雅黑, Arial; text-wrap: wrap; background-color: rgb(254, 254, 254);&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;text-decoration-line: none; color: rgb(58, 110, 165); white-space-collapse: preserve; background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h3 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;2.3 需求域名数据表创建&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#这个需求域名就是各种需求域名创建时候的记录信息,主要是为了web平台关联展示用的&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;CREATE&amp;nbsp;TABLE&amp;nbsp;`environment_domain`&amp;nbsp;(
&amp;nbsp;&amp;nbsp;`id`&amp;nbsp;bigint(20)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;AUTO_INCREMENT&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;主键ID&amp;#39;,
&amp;nbsp;&amp;nbsp;`domain_name`&amp;nbsp;varchar(255)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;域名全称&amp;#39;,
&amp;nbsp;&amp;nbsp;`module_name`&amp;nbsp;varchar(100)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;模块名称&amp;#39;,
&amp;nbsp;&amp;nbsp;`cluster_name`&amp;nbsp;varchar(255)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;集群名称&amp;#39;,
&amp;nbsp;&amp;nbsp;`environment`&amp;nbsp;varchar(20)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;环境标识（offline/mirror）&amp;#39;,
&amp;nbsp;&amp;nbsp;`requirement_id`&amp;nbsp;varchar(50)&amp;nbsp;DEFAULT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;需求编号（用于批量下线）&amp;#39;,
&amp;nbsp;&amp;nbsp;`env_type`&amp;nbsp;varchar(20)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;环境类型：stable-稳定环境，demand-需求环境&amp;#39;,
&amp;nbsp;&amp;nbsp;`network_type`&amp;nbsp;varchar(10)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;网络类型：intranet-内网，extranet-外网&amp;#39;,
&amp;nbsp;&amp;nbsp;`description`&amp;nbsp;varchar(500)&amp;nbsp;DEFAULT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;域名描述&amp;#39;,
&amp;nbsp;&amp;nbsp;`applicant`&amp;nbsp;varchar(50)&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;申请人（创建者）&amp;#39;,
&amp;nbsp;&amp;nbsp;`create_time`&amp;nbsp;datetime&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;DEFAULT&amp;nbsp;current_timestamp(),
&amp;nbsp;&amp;nbsp;`update_time`&amp;nbsp;datetime&amp;nbsp;NOT&amp;nbsp;NULL&amp;nbsp;DEFAULT&amp;nbsp;current_timestamp()&amp;nbsp;ON&amp;nbsp;UPDATE&amp;nbsp;current_timestamp(),
&amp;nbsp;&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;(`id`),
&amp;nbsp;&amp;nbsp;UNIQUE&amp;nbsp;KEY&amp;nbsp;`uk_domain_name`&amp;nbsp;(`domain_name`),
&amp;nbsp;&amp;nbsp;KEY&amp;nbsp;`idx_requirement_id`&amp;nbsp;(`requirement_id`)&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;用于需求下线时批量查询&amp;#39;,
&amp;nbsp;&amp;nbsp;KEY&amp;nbsp;`idx_domain_applicant`&amp;nbsp;(`applicant`)&amp;nbsp;COMMENT&amp;nbsp;&amp;#39;用于按申请人查询域名&amp;#39;
)&amp;nbsp;ENGINE=InnoDB&amp;nbsp;AUTO_INCREMENT=13&amp;nbsp;DEFAULT&amp;nbsp;CHARSET=utf8mb4&amp;nbsp;COLLATE=utf8mb4_general_ci&amp;nbsp;COMMENT=&amp;#39;需求环境域名表（含申请人信息）&amp;#39;;&lt;/pre&gt;&lt;p&gt;&amp;gt; select domain_name,module_name,cluster_name,environment,requirement_id,env_type,network_type from environment_domain order by environment,env_type desc;&amp;nbsp; #可以看下示例数据&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/10/202510111760174378700196.png&quot; alt=&quot;image.png&quot; width=&quot;989&quot; height=&quot;227&quot; style=&quot;width: 989px; height: 227px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#可以看到域名的命名规格就是:模块名-环境-需求.域名后缀,我们默认的需求环境当然是内网类型了啊,因为请求主要依赖于网关转发吗,但是也难免会有某个集群的域名需要进行三方联调,那么我们直接把网关域名开成公网访问显然是不合适的(因为网关域名作为内网测试入口一旦公网就是基本走稳定环境了),当然细节层面还是要基于现状来的,这套主要是为了自动化的按需需求,有些满足不了的需求可以手工创建一个域名随着需求生命周期维护。&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;&lt;/p&gt;</description><pubDate>Mon, 18 Aug 2025 17:43:12 +0800</pubDate></item><item><title>Nginx结合Lua实现弹窗二次验证(三)</title><link>https://blog.51niux.com/?id=325</link><description>&lt;p&gt;承接上文,上文我们只显示了基于用户秘钥字符串的生成和白名单的功能,也算对二次认证有了个初步的了解,下面我们来一个完整的示例。&lt;/p&gt;&lt;p&gt;下面我们通过每一个程序文件的介绍来深入理解我们需要实现什么功能,如何去实现的。&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;一、二次验证代码介绍&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.1&amp;nbsp;config.lua&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;这个配置文件是认证系统的核心配置，负责定义数据库连接、认证策略、Redis 配置、会话管理等关键参数。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;#vi /usr/local/nginx/conf/auth/config.lua&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host&amp;nbsp;=&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port&amp;nbsp;=&amp;nbsp;3306,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;&amp;quot;auth_db&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;&amp;quot;auth&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;auth123&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_packet_size&amp;nbsp;=&amp;nbsp;1024&amp;nbsp;*&amp;nbsp;1024,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_idle_timeout&amp;nbsp;=&amp;nbsp;30000,&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接池空闲超时时间
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pool_size&amp;nbsp;=&amp;nbsp;100,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接池大小
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;白名单配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file&amp;nbsp;=&amp;nbsp;&amp;quot;/usr/local/nginx/conf/auth/ip_whitelist.txt&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reload_interval&amp;nbsp;=&amp;nbsp;86400,&amp;nbsp;&amp;nbsp;--&amp;nbsp;24小时重新加载
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;认证配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auth&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_attempts&amp;nbsp;=&amp;nbsp;3,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;最大尝试次数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ban_time&amp;nbsp;=&amp;nbsp;1200,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;封禁时间(秒)：20分钟
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;attempt_ttl&amp;nbsp;=&amp;nbsp;300,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;尝试计数有效期(秒)：5分钟
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;code_length&amp;nbsp;=&amp;nbsp;6,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证码长度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;secret_length&amp;nbsp;=&amp;nbsp;32,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;秘钥长度
	&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;totp_time_step&amp;nbsp;=&amp;nbsp;30,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;TOTP时间步长(秒)：30秒
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default_secret_key&amp;nbsp;=&amp;nbsp;&amp;quot;604264e9fd0315c5cbe873106db78d7051d739894095867c4c2a9cbc05bfdf86&amp;quot;,&amp;nbsp;&amp;nbsp;--&amp;nbsp;全局默认密钥
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;Redis配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;redis&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host&amp;nbsp;=&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port&amp;nbsp;=&amp;nbsp;6379,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;nil,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout&amp;nbsp;=&amp;nbsp;1000,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接超时(ms)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keepalive&amp;nbsp;=&amp;nbsp;10000,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接池保持时间(ms)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pool_size&amp;nbsp;=&amp;nbsp;100,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接池大小
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user_secret_ttl&amp;nbsp;=&amp;nbsp;3600,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;用户秘钥缓存时间(秒)：1小时
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;会话配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;session&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie_name&amp;nbsp;=&amp;nbsp;&amp;quot;auth_session&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie_domain&amp;nbsp;=&amp;nbsp;&amp;quot;.test.com&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie_path&amp;nbsp;=&amp;nbsp;&amp;quot;/&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie_secure&amp;nbsp;=&amp;nbsp;false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie_http_only&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;expire_time&amp;nbsp;=&amp;nbsp;86400,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;会话有效期(秒)：1天
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cross_domain&amp;nbsp;=&amp;nbsp;false,&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置为true如果是跨域应用
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local_cache_offset&amp;nbsp;=&amp;nbsp;10,&amp;nbsp;&amp;nbsp;--&amp;nbsp;本地缓存比Redis提前过期的秒数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;全局免认证URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;skip_auth_global&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[&amp;quot;/health&amp;quot;]&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[&amp;quot;/metrics&amp;quot;]&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[&amp;quot;/static/&amp;quot;]&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[&amp;quot;/favicon.ico&amp;quot;]&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;域名级免认证URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;skip_auth_domain&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[&amp;quot;lua.test.com&amp;quot;]&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[&amp;quot;/auth/login&amp;quot;]&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[&amp;quot;/auth/logout&amp;quot;]&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[&amp;quot;api.test.com&amp;quot;]&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[&amp;quot;/public/&amp;quot;]&amp;nbsp;=&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;模板路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;templates&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auth_popup&amp;nbsp;=&amp;nbsp;&amp;quot;/usr/local/nginx/conf/auth/templates/auth_popup.html&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;日志配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;level&amp;nbsp;=&amp;nbsp;&amp;quot;info&amp;quot;,&amp;nbsp;&amp;nbsp;--&amp;nbsp;全局日志级别
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_file&amp;nbsp;=&amp;nbsp;&amp;quot;/opt/log/nginx/auth_access.log&amp;quot;,&amp;nbsp;&amp;nbsp;--&amp;nbsp;正常访问日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_file&amp;nbsp;=&amp;nbsp;&amp;quot;/opt/log/nginx/auth_error.log&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;错误日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

return&amp;nbsp;_M&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;&lt;/span&gt;#通过配置文件我们可以看到我们做了一个灵活的设置,那就是我们可能指定就算启用了这个lua文件,但是有些域名可能有很多location,有些location是不需要认证的,这就是域名级别的路径白名单,我们也有一些全局的,比如favicon.ico浏览器小图标,这就是全局的白名单。&lt;/p&gt;&lt;p&gt;#前面我们的前端页面是写到lua文件中的,现在呢我们将lua前端文件从lua代码中剥离了出来放到了templates目录下面,更规范修改更灵活。&lt;/p&gt;&lt;p&gt;#然后呢我们是希望认证日志跟nginx本身的日志文件区分开,默认都写到了error.log里面,我们是想成功/失败的认证日志记录到我们指定日志文件便于排查。&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.2&amp;nbsp;log_utils.lua&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;这是一个日志工具模块文件,为了规范的记录系统日志。亮点介绍：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;上下文适配：避免非请求阶段调用请求阶段API导致的错误
安全健壮：通过pcall捕获所有可能的异常，确保日志操作不影响主流程
格式统一：标准化日志内容，便于后续分析和监控
配置驱动：日志路径、级别通过config.lua统一管理,便于维护&lt;/pre&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/log_utils.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;1.模块初始化与依赖引入
local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;)&amp;nbsp;&amp;nbsp;--引入全局配置(日志路径、级别等）
--&amp;nbsp;为啥要local&amp;nbsp;ngx&amp;nbsp;=&amp;nbsp;ngx这种呢?主要是性能优化：减少全局变量查找开销/代码健壮性：避免全局变量污染/代码可读性：明确依赖关系
--&amp;nbsp;Lua访问局部变量的速度远快于全局变量(约快30%)。对于高频调用的模块(如ngx、os)，通过定义局部变量可以显著提升性能。
local&amp;nbsp;ngx&amp;nbsp;=&amp;nbsp;ngx&amp;nbsp;&amp;nbsp;--Nginx&amp;nbsp;Lua&amp;nbsp;模块(提供请求上下文信息)
local&amp;nbsp;os&amp;nbsp;=&amp;nbsp;os&amp;nbsp;&amp;nbsp;--操作系统模块(获取时间戳）
local&amp;nbsp;io&amp;nbsp;=&amp;nbsp;io&amp;nbsp;&amp;nbsp;--输入输出模块(文件操作)

local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}&amp;nbsp;--定义模块对象,用于导出函数

--&amp;nbsp;2.上下文判断：区分请求/非请求阶段
--&amp;nbsp;Nginx运行阶段分为请求阶段(处理HTTP请求)和非请求阶段(如启动、配置加载)，两者可调用的API不同(例如ngx.var仅在请求阶段可用)
--&amp;nbsp;判断是否处于请求上下文
local&amp;nbsp;function&amp;nbsp;is_request_context()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;尝试访问ngx.var.request_id(仅请求阶段存在),失败则为非请求阶段,pcall捕获可能的错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;success,&amp;nbsp;_&amp;nbsp;=&amp;nbsp;pcall(function()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ngx.var.request_id&amp;nbsp;&amp;nbsp;--&amp;nbsp;尝试访问请求ID，非请求阶段会报错
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;success
end

--&amp;nbsp;3.安全获取客户端IP
--&amp;nbsp;获取客户端IP,客户端IP可能来自X-Forwarded-For(代理场景)、X-Real-IP或直接连接的remote_addr,需兼容非请求阶段
local&amp;nbsp;function&amp;nbsp;get_client_ip_safe()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;is_request_context()&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;N/A&amp;quot;&amp;nbsp;&amp;nbsp;--非请求阶段无客户端IP,返回默认值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;仅在请求上下文时调用&amp;nbsp;ngx.req.get_headers()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;ngx.req.get_headers()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;client_ip&amp;nbsp;=&amp;nbsp;headers[&amp;quot;X-Forwarded-For&amp;quot;]&amp;nbsp;or&amp;nbsp;&amp;nbsp;--优先取代理传递的IP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers[&amp;quot;X-Real-IP&amp;quot;]&amp;nbsp;or&amp;nbsp;--次优先取真实IP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.var.remote_addr&amp;nbsp;or&amp;nbsp;&amp;quot;unknown&amp;quot;&amp;nbsp;--最后取直接连接IP&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--处理&amp;nbsp;X-Forwarded-For多IP场景(格式:&amp;quot;ip1,&amp;nbsp;ip2,&amp;nbsp;...&amp;quot;，取第一个)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(client_ip)&amp;nbsp;==&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;and&amp;nbsp;string.find(client_ip,&amp;nbsp;&amp;quot;,&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;client_ip&amp;nbsp;=&amp;nbsp;string.sub(client_ip,&amp;nbsp;1,&amp;nbsp;string.find(client_ip,&amp;nbsp;&amp;quot;,&amp;quot;)&amp;nbsp;-&amp;nbsp;1)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;client_ip
end

--&amp;nbsp;4.安全获取请求&amp;nbsp;ID
--&amp;nbsp;请求ID(request_id)Nginx为每个请求生成的唯一标识,用于追踪请求链路,仅在请求阶段可用。
local&amp;nbsp;function&amp;nbsp;get_request_id_safe()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;is_request_context()&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;N/A&amp;quot;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ngx.var.request_id&amp;nbsp;or&amp;nbsp;&amp;quot;unknown&amp;quot;&amp;nbsp;&amp;nbsp;--返回请求ID，默认&amp;quot;unknown&amp;quot;
end

--&amp;nbsp;5.日志格式化:统一日志格式
--&amp;nbsp;格式化日志:确保包含时间戳、级别、客户端&amp;nbsp;IP、请求&amp;nbsp;ID、具体信息，便于日志分析工具解析。
local&amp;nbsp;function&amp;nbsp;format_log(level,&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;client_ip&amp;nbsp;=&amp;nbsp;get_client_ip_safe()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;timestamp&amp;nbsp;=&amp;nbsp;os.date(&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;)&amp;nbsp;--&amp;nbsp;时间戳(年-月-日&amp;nbsp;时:分:秒)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;request_id&amp;nbsp;=&amp;nbsp;get_request_id_safe()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;拼接日志字符串：[时间]&amp;nbsp;[级别]&amp;nbsp;[IP]&amp;nbsp;[请求ID]&amp;nbsp;信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;string.format(&amp;quot;[%s]&amp;nbsp;[%s]&amp;nbsp;[%s]&amp;nbsp;[%s]&amp;nbsp;%s&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timestamp,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;level,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;client_ip,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;request_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tostring(message)&amp;nbsp;&amp;nbsp;--&amp;nbsp;确保message是字符串类型
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
end

--&amp;nbsp;安全格式化日志（纯非请求阶段专用）
local&amp;nbsp;function&amp;nbsp;format_log_safe(level,&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;timestamp&amp;nbsp;=&amp;nbsp;os.date(&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;string.format(&amp;quot;[%s]&amp;nbsp;[%s]&amp;nbsp;[N/A]&amp;nbsp;[N/A]&amp;nbsp;%s&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timestamp,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;level,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tostring(message)&amp;nbsp;&amp;nbsp;--&amp;nbsp;确保message是字符串类型
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
end

--&amp;nbsp;6.日志写入:安全操作文件
--&amp;nbsp;负责将格式化后的日志写入配置文件指定的路径,包含错误处理(如文件无法打开时降级打印到控制台)
local&amp;nbsp;function&amp;nbsp;write_log(file_path,&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;参数校验
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(file_path)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;or&amp;nbsp;file_path&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;[LOG_ERROR]&amp;nbsp;日志文件路径不能为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;尝试打开日志文件(&amp;quot;a&amp;quot;表示追加模式)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;file,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;io.open(file_path,&amp;nbsp;&amp;quot;a&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;file&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;[LOG_ERROR]&amp;nbsp;无法打开日志文件:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;file_path&amp;nbsp;..&amp;nbsp;&amp;quot;,&amp;nbsp;错误:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;(err&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;[FALLBACK]&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;安全写入日志(使用pcall捕获写入过程中的错误)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;write_err&amp;nbsp;=&amp;nbsp;pcall(function()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file:write(message&amp;nbsp;..&amp;nbsp;&amp;quot;\n&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;写入日志并换行
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file:flush()&amp;nbsp;&amp;nbsp;--&amp;nbsp;强制刷新到磁盘(避免缓存导致日志丢失)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file:close()&amp;nbsp;&amp;nbsp;--&amp;nbsp;无论成功与否都关闭文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理写入错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;[LOG_ERROR]&amp;nbsp;写入日志失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;(write_err&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true&amp;nbsp;--&amp;nbsp;写入成功
end

--&amp;nbsp;7.&amp;nbsp;日志级别控制:按配置输出
--&amp;nbsp;根据config.log.level控制不同级别的日志是否输出，避免冗余日志。

--&amp;nbsp;调试日志(自动适配上下文)
function&amp;nbsp;_M.debug(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;要注意~=在lua中是不等于的比较运算符,跟其他语音中的!=类似
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;config.log.level&amp;nbsp;~=&amp;nbsp;&amp;quot;debug&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false&amp;nbsp;&amp;nbsp;--&amp;nbsp;日志级别不匹配,不输出
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;根据上下文选择日志格式,写入访问日志文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_msg&amp;nbsp;=&amp;nbsp;is_request_context()&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;and&amp;nbsp;format_log(&amp;quot;DEBUG&amp;quot;,&amp;nbsp;message)&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;or&amp;nbsp;format_log_safe(&amp;quot;DEBUG&amp;quot;,&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;write_log(config.log.access_file,&amp;nbsp;log_msg)
end

--&amp;nbsp;信息日志(自动适配上下文)
function&amp;nbsp;_M.info(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;config.log.level&amp;nbsp;~=&amp;nbsp;&amp;quot;debug&amp;quot;&amp;nbsp;and&amp;nbsp;config.log.level&amp;nbsp;~=&amp;nbsp;&amp;quot;info&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_msg&amp;nbsp;=&amp;nbsp;is_request_context()&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;and&amp;nbsp;format_log(&amp;quot;INFO&amp;quot;,&amp;nbsp;message)&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;or&amp;nbsp;format_log_safe(&amp;quot;INFO&amp;quot;,&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;write_log(config.log.access_file,&amp;nbsp;log_msg)
end

--&amp;nbsp;警告日志(自动适配上下文)
function&amp;nbsp;_M.warn(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_msg&amp;nbsp;=&amp;nbsp;is_request_context()&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;and&amp;nbsp;format_log(&amp;quot;WARN&amp;quot;,&amp;nbsp;message)&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;or&amp;nbsp;format_log_safe(&amp;quot;WARN&amp;quot;,&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;write_log(config.log.error_file,&amp;nbsp;log_msg)
end

--&amp;nbsp;错误日志(自动适配上下文)
function&amp;nbsp;_M.error(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_msg&amp;nbsp;=&amp;nbsp;is_request_context()&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;and&amp;nbsp;format_log(&amp;quot;ERROR&amp;quot;,&amp;nbsp;message)&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;or&amp;nbsp;format_log_safe(&amp;quot;ERROR&amp;quot;,&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;write_log(config.log.error_file,&amp;nbsp;log_msg)
end

--&amp;nbsp;8.&amp;nbsp;兼容旧版本：提供安全日志方法
--&amp;nbsp;为兼容可能存在的旧代码调用,保留info_safe和error_safe别名。
function&amp;nbsp;_M.info_safe(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;_M.info(message)&amp;nbsp;--&amp;nbsp;等同于&amp;nbsp;info&amp;nbsp;方法
end

function&amp;nbsp;_M.error_safe(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;_M.error(message)&amp;nbsp;--&amp;nbsp;等同于&amp;nbsp;error&amp;nbsp;方法
end

return&amp;nbsp;_M&amp;nbsp;&amp;nbsp;--&amp;nbsp;导出模块,供其他脚本通过require(&amp;quot;log_utils&amp;quot;)调用&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.3&amp;nbsp;db_utils.lua&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;该文件是数据库操作模块，基于 resty.mysql 库封装了MySQL数据库的常用操作，主要用于认证系统中用户信息、会话数据的存储与查询。其核心功能包括：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;1.安全连接MySQL数据库并管理连接池
2.提供防SQL注入的查询方法
3.初始化数据库表结构(用户表、会话表)
4.封装用户密钥查询等业务相关的数据库操作&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/db_utils.lua&amp;nbsp; #这个文件不多做介绍了,前面有介绍&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;1.模块依赖与初始化
local&amp;nbsp;mysql&amp;nbsp;=&amp;nbsp;require(&amp;quot;resty.mysql&amp;quot;)
local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;).db
local&amp;nbsp;log&amp;nbsp;=&amp;nbsp;require(&amp;quot;log_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用新的日志模块

local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}

--&amp;nbsp;2.&amp;nbsp;数据库连接管理
--&amp;nbsp;连接数据库并设置超时，使用连接池减少频繁创建连接的开销
function&amp;nbsp;_M.connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;db,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;mysql:new()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;db&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;创建MySQL实例失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;创建MySQL实例失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置超时
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db:set_timeout(1000)&amp;nbsp;&amp;nbsp;--&amp;nbsp;1秒超时
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接数据库
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate&amp;nbsp;=&amp;nbsp;db:connect({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host&amp;nbsp;=&amp;nbsp;config.host,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port&amp;nbsp;=&amp;nbsp;config.port,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;config.database,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;config.user,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;config.password,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_packet_size&amp;nbsp;=&amp;nbsp;config.max_packet_size
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;error_msg&amp;nbsp;=&amp;nbsp;string.format(&amp;quot;连接数据库失败:&amp;nbsp;%s,&amp;nbsp;错误码:&amp;nbsp;%d,&amp;nbsp;SQL状态:&amp;nbsp;%s&amp;quot;,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(error_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;error_msg
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;数据库连接成功&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;db
end

--&amp;nbsp;3.安全执行SQL查询
--&amp;nbsp;执行SQL语句并自动将连接放回连接池，避免连接泄漏。
function&amp;nbsp;_M.execute_query(sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;执行SQL查询:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;db,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;db&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;获取数据库连接失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate&amp;nbsp;=&amp;nbsp;db:query(sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将连接放回连接池
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db:set_keepalive(config.max_idle_timeout,&amp;nbsp;config.pool_size)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;error_msg&amp;nbsp;=&amp;nbsp;string.format(&amp;quot;执行SQL失败:&amp;nbsp;%s,&amp;nbsp;错误码:&amp;nbsp;%d,&amp;nbsp;SQL状态:&amp;nbsp;%s&amp;quot;,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(error_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;error_msg
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;res
end

--&amp;nbsp;4.防SQL注入：字符串转义
--&amp;nbsp;对用户输入的字符串进行转义(如单引号替换为双引号)，避免恶意SQL注入。
function&amp;nbsp;_M.escape_literal(str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;str&amp;nbsp;==&amp;nbsp;nil&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;NULL&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;示例:若用户输入user&amp;#39;&amp;nbsp;OR&amp;nbsp;&amp;#39;1&amp;#39;=&amp;#39;1，转义后变为&amp;nbsp;&amp;#39;user&amp;#39;&amp;#39;&amp;nbsp;OR&amp;nbsp;&amp;#39;&amp;#39;1&amp;#39;&amp;#39;=&amp;#39;&amp;#39;1&amp;#39;,避免注入攻击。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(str)&amp;nbsp;==&amp;nbsp;&amp;quot;boolean&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;str&amp;nbsp;and&amp;nbsp;&amp;quot;1&amp;quot;&amp;nbsp;or&amp;nbsp;&amp;quot;0&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(str)&amp;nbsp;==&amp;nbsp;&amp;quot;number&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;tostring(str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str&amp;nbsp;=&amp;nbsp;tostring(str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str&amp;nbsp;=&amp;nbsp;string.gsub(str,&amp;nbsp;&amp;quot;\\&amp;quot;,&amp;nbsp;&amp;quot;\\\\&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str&amp;nbsp;=&amp;nbsp;string.gsub(str,&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;,&amp;nbsp;&amp;quot;&amp;#39;&amp;#39;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;&amp;nbsp;..&amp;nbsp;str&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;
end

--&amp;nbsp;5.数据库表初始化
--&amp;nbsp;创建users(用户表)和sessions(会话表)，确保系统启动时表结构存在。
function&amp;nbsp;_M.init_tables()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;开始初始化数据库表&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;创建用户表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;create_users_sql&amp;nbsp;=&amp;nbsp;[[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;users&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;INT&amp;nbsp;AUTO_INCREMENT&amp;nbsp;PRIMARY&amp;nbsp;KEY,&amp;nbsp;--&amp;nbsp;用户名(唯一)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;VARCHAR(50)&amp;nbsp;UNIQUE&amp;nbsp;NOT&amp;nbsp;NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otp_secret&amp;nbsp;VARCHAR(32)&amp;nbsp;NOT&amp;nbsp;NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;login_attempts&amp;nbsp;INT&amp;nbsp;DEFAULT&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_attempt_time&amp;nbsp;DATETIME,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;created_at&amp;nbsp;DATETIME&amp;nbsp;DEFAULT&amp;nbsp;CURRENT_TIMESTAMP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.execute_query(create_users_sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;创建用户表失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;&amp;quot;创建用户表失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;用户表创建成功&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;创建会话表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;create_sessions_sql&amp;nbsp;=&amp;nbsp;[[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;sessions&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;session_id&amp;nbsp;VARCHAR(64)&amp;nbsp;PRIMARY&amp;nbsp;KEY,&amp;nbsp;--&amp;nbsp;会话ID(唯一)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user_id&amp;nbsp;INT&amp;nbsp;NOT&amp;nbsp;NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;expires_at&amp;nbsp;DATETIME&amp;nbsp;NOT&amp;nbsp;NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;created_at&amp;nbsp;DATETIME&amp;nbsp;DEFAULT&amp;nbsp;CURRENT_TIMESTAMP,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FOREIGN&amp;nbsp;KEY&amp;nbsp;(user_id)&amp;nbsp;REFERENCES&amp;nbsp;users(id)&amp;nbsp;--&amp;nbsp;外键关联用户表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.execute_query(create_sessions_sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;创建会话表失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;&amp;quot;创建会话表失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;会话表创建成功&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end

--&amp;nbsp;6.业务方法:获取用户密钥
--&amp;nbsp;查询指定用户名对应的OTP密钥(用于TOTP验证码验证）
function&amp;nbsp;_M.get_user_secret(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;开始获取用户秘钥:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;安全转义用户名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;escaped_username&amp;nbsp;=&amp;nbsp;_M.escape_literal(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;构建SQL查询
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;sql&amp;nbsp;=&amp;nbsp;string.format([[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT&amp;nbsp;otp_secret&amp;nbsp;FROM&amp;nbsp;users&amp;nbsp;WHERE&amp;nbsp;username&amp;nbsp;=&amp;nbsp;%s]],&amp;nbsp;escaped_username)&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用转义后的用户名

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;执行查询
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.execute_query(sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;记录查询错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;err&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;查询用户秘钥失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理结果(用户不存在则返回&amp;nbsp;nil)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;or&amp;nbsp;#res&amp;nbsp;==&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;用户不存在，用户名:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;返回用户密钥
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;成功获取用户秘钥:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;res[1][&amp;quot;otp_secret&amp;quot;]&amp;nbsp;&amp;nbsp;--&amp;nbsp;取第一条结果的otp_secret字段
end

return&amp;nbsp;_M&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;text-decoration-line: none; color: rgb(58, 110, 165); white-space: pre-wrap; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/h3&gt;&lt;h3&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.4 修改Nginx增加共享内存和白名单初始化&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# vi /usr/local/nginx/conf/nginx.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#gzip&amp;nbsp;&amp;nbsp;on;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;共享内存定义
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_shared_dict&amp;nbsp;ip_whitelist_cache&amp;nbsp;5m;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_shared_dict&amp;nbsp;session_cache&amp;nbsp;100m;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;定义一个自定义变量用于存储自定义标识（内存中）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_shared_dict&amp;nbsp;custom_request_id&amp;nbsp;10m;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Lua模块路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_package_path&amp;nbsp;&amp;quot;/usr/local/lua_core/lib/lua/?.lua;/usr/local/nginx/conf/auth/?.lua;;&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;白名单初始化（修正：移除直接执行的初始化，改为显式调用）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init_by_lua_file&amp;nbsp;/usr/local/nginx/conf/auth/init_by_lua.lua;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;worker进程初始化（修正：使用完整的init_worker.lua文件）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init_worker_by_lua_file&amp;nbsp;/usr/local/nginx/conf/auth/init_worker.lua;&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.5&amp;nbsp;redis_utils.lua&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/redis_utils.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;第一部分:&amp;nbsp;基础工具与连接管理
--&amp;nbsp;1.1.模块依赖与初始化
local&amp;nbsp;redis&amp;nbsp;=&amp;nbsp;require(&amp;quot;resty.redis&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;引入&amp;nbsp;OpenResty&amp;nbsp;Redis&amp;nbsp;客户端
local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;引入全局配置（包含&amp;nbsp;Redis&amp;nbsp;连接信息等）
local&amp;nbsp;log&amp;nbsp;=&amp;nbsp;require(&amp;quot;log_utils&amp;quot;)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;引入日志工具

local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}&amp;nbsp;&amp;nbsp;--&amp;nbsp;定义模块表，用于导出公共方法
--&amp;nbsp;1.2.安全日志处理函数
--&amp;nbsp;作用：处理空值，统一显示为&amp;nbsp;[空值]/通过正则替换非数字、字母、标点和空格的字符为?,避免日志被特殊字符破坏
local&amp;nbsp;function&amp;nbsp;safe_log_value(value)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;value&amp;nbsp;or&amp;nbsp;value&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;[空值]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;过滤不可见字符（如控制字符），防止日志格式混乱
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;^在字符类中表示&amp;quot;取反&amp;quot;,%w表示字母和数字,%p表示标点符号,这就是匹配除了字母、数字、标点符号和空格之外的所有字符替换为?
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;tostring(value):gsub(&amp;quot;[^%w%p&amp;nbsp;]&amp;quot;,&amp;nbsp;&amp;quot;?&amp;quot;)
end
--&amp;nbsp;1.3.Redis连接管理函数
local&amp;nbsp;function&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red&amp;nbsp;=&amp;nbsp;redis:new()&amp;nbsp;&amp;nbsp;--&amp;nbsp;创建&amp;nbsp;Redis&amp;nbsp;客户端实例
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:set_timeout(config.redis.timeout&amp;nbsp;or&amp;nbsp;1000)&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置超时时间（默认1秒）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接&amp;nbsp;Redis&amp;nbsp;服务器（使用配置的&amp;nbsp;host&amp;nbsp;和&amp;nbsp;port，默认本地&amp;nbsp;127.0.0.1:6379）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:connect(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config.redis.host&amp;nbsp;or&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config.redis.port&amp;nbsp;or&amp;nbsp;6379
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接失败处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;[Redis连接]&amp;nbsp;失败，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;host:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(config.redis.host)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;port:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(config.redis.port)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;error:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(err)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;选择数据库（默认0号库）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;db&amp;nbsp;=&amp;nbsp;config.redis.db&amp;nbsp;or&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:select(db)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;[Redis操作]&amp;nbsp;选择数据库失败，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;db:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(db)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;error:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(err)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()&amp;nbsp;&amp;nbsp;--&amp;nbsp;失败时关闭连接释放资源
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;[Redis连接]&amp;nbsp;成功，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;host:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(config.redis.host)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;port:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(config.redis.port)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;db:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(db)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;red,&amp;nbsp;nil
end

--&amp;nbsp;第二部分：用户会话管理
--&amp;nbsp;2.1.会话查询（多级缓存机制）
--&amp;nbsp;设计点：多级缓存(本地缓存+Redis减少网络开销)/数据双重检验，防止脏数据污染/域名隔离(通过cookie_domain区分不同域名的会话)
--&amp;nbsp;从配置中获取会话相关参数（默认空表避免&amp;nbsp;nil&amp;nbsp;错误）
local&amp;nbsp;session_conf&amp;nbsp;=&amp;nbsp;config.session&amp;nbsp;or&amp;nbsp;{}
function&amp;nbsp;_M.get_user_session(session_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;session_id&amp;nbsp;or&amp;nbsp;session_id&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[会话查询]&amp;nbsp;session_id为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;构建缓存键（格式：session:域名:session_id，支持多域名隔离）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cache_key&amp;nbsp;=&amp;nbsp;&amp;quot;session:&amp;quot;&amp;nbsp;..&amp;nbsp;(session_conf.cookie_domain&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;..&amp;nbsp;&amp;quot;:&amp;quot;&amp;nbsp;..&amp;nbsp;session_id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第一步：检查本地缓存（ngx.shared.session_cache，内存级缓存）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;username&amp;nbsp;=&amp;nbsp;ngx.shared.session_cache&amp;nbsp;and&amp;nbsp;ngx.shared.session_cache:get(cache_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;username&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;获取剩余有效期，便于监控缓存状态
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;remaining_time&amp;nbsp;=&amp;nbsp;ngx.shared.session_cache:ttl(cache_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;[会话查询]&amp;nbsp;命中本地缓存，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;session_id:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(session_id)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;剩余有效期:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;remaining_time&amp;nbsp;..&amp;nbsp;&amp;quot;秒&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;数据有效性校验：防止缓存脏数据（如非字符串类型或包含非法标识）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(username)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;or&amp;nbsp;string.find(username,&amp;nbsp;&amp;quot;userdata:&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(&amp;quot;[会话查询]&amp;nbsp;本地缓存数据无效，已删除&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.session_cache:delete(cache_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;username&amp;nbsp;&amp;nbsp;--&amp;nbsp;本地缓存命中直接返回
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第二步：本地缓存未命中，查询&amp;nbsp;Redis
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[会话查询]&amp;nbsp;本地缓存未命中，查询Redis&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;red&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[会话查询]&amp;nbsp;Redis连接失败：&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;从&amp;nbsp;Redis&amp;nbsp;获取会话数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;username,&amp;nbsp;redis_err&amp;nbsp;=&amp;nbsp;red:get(cache_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()&amp;nbsp;&amp;nbsp;--&amp;nbsp;及时关闭连接释放资源
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理&amp;nbsp;Redis&amp;nbsp;查询错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;redis_err&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[会话查询]&amp;nbsp;Redis查询失败：&amp;quot;&amp;nbsp;..&amp;nbsp;redis_err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第三步：验证&amp;nbsp;Redis&amp;nbsp;数据并同步到本地缓存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;username&amp;nbsp;and&amp;nbsp;username&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;校验数据格式（同本地缓存逻辑）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(username)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;or&amp;nbsp;string.find(username,&amp;nbsp;&amp;quot;userdata:&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(&amp;quot;[会话查询]&amp;nbsp;Redis数据无效，已删除&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:del(cache_key)&amp;nbsp;&amp;nbsp;--&amp;nbsp;清理脏数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;同步到本地缓存（默认有效期300秒，可通过配置修改）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ngx.shared.session_cache&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.session_cache:set(cache_key,&amp;nbsp;username,&amp;nbsp;session_conf.cache_time&amp;nbsp;or&amp;nbsp;300)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[本地缓存-写入]&amp;nbsp;成功&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;username
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[会话查询]&amp;nbsp;Redis未命中&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
end
--&amp;nbsp;2.2.会话创建（双向同步机制）
function&amp;nbsp;_M.create_user_session(session_id,&amp;nbsp;username,&amp;nbsp;expire_seconds)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;参数校验：必填参数缺失直接返回失败
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;session_id&amp;nbsp;or&amp;nbsp;not&amp;nbsp;username&amp;nbsp;or&amp;nbsp;not&amp;nbsp;expire_seconds&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[会话创建]&amp;nbsp;参数缺失&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;数据格式校验：拒绝无效用户名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(username)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;or&amp;nbsp;string.find(username,&amp;nbsp;&amp;quot;userdata:&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[会话创建]&amp;nbsp;用户名格式无效，拒绝创建&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cache_key&amp;nbsp;=&amp;nbsp;&amp;quot;session:&amp;quot;&amp;nbsp;..&amp;nbsp;(session_conf.cookie_domain&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;..&amp;nbsp;&amp;quot;:&amp;quot;&amp;nbsp;..&amp;nbsp;session_id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第一步：写入&amp;nbsp;Redis（带过期时间）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;red&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[会话创建]&amp;nbsp;Redis连接失败：&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用&amp;nbsp;setex&amp;nbsp;原子操作：同时设置值和过期时间
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;set_err&amp;nbsp;=&amp;nbsp;red:setex(cache_key,&amp;nbsp;expire_seconds,&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[会话创建]&amp;nbsp;Redis写入失败：&amp;quot;&amp;nbsp;..&amp;nbsp;set_err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第二步：同步到本地缓存（有效期更短，避免与&amp;nbsp;Redis&amp;nbsp;数据不一致）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ngx.shared.session_cache&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;local_cache_time&amp;nbsp;=&amp;nbsp;math.min(expire_seconds,&amp;nbsp;session_conf.cache_time&amp;nbsp;or&amp;nbsp;300)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.session_cache:set(cache_key,&amp;nbsp;username,&amp;nbsp;local_cache_time)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[会话创建]&amp;nbsp;本地缓存同步成功，有效期：&amp;quot;&amp;nbsp;..&amp;nbsp;local_cache_time&amp;nbsp;..&amp;nbsp;&amp;quot;秒&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[会话创建]&amp;nbsp;成功，有效期：&amp;quot;&amp;nbsp;..&amp;nbsp;expire_seconds&amp;nbsp;..&amp;nbsp;&amp;quot;秒&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end
--&amp;nbsp;2.3.&amp;nbsp;本地缓存专用查询函数
function&amp;nbsp;_M.get_session_from_cache(session_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;session_id&amp;nbsp;or&amp;nbsp;session_id&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cache_key&amp;nbsp;=&amp;nbsp;&amp;quot;session:&amp;quot;&amp;nbsp;..&amp;nbsp;(session_conf.cookie_domain&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;..&amp;nbsp;&amp;quot;:&amp;quot;&amp;nbsp;..&amp;nbsp;session_id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;username&amp;nbsp;=&amp;nbsp;ngx.shared.session_cache&amp;nbsp;and&amp;nbsp;ngx.shared.session_cache:get(cache_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;username&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;同&amp;nbsp;get_user_session&amp;nbsp;的数据校验逻辑
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;remaining_time&amp;nbsp;=&amp;nbsp;ngx.shared.session_cache:ttl(cache_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[本地缓存-读取]&amp;nbsp;命中，剩余有效期：&amp;quot;&amp;nbsp;..&amp;nbsp;remaining_time&amp;nbsp;..&amp;nbsp;&amp;quot;秒&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(username)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;or&amp;nbsp;string.find(username,&amp;nbsp;&amp;quot;userdata:&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(&amp;quot;[本地缓存-读取]&amp;nbsp;数据无效，已删除&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.session_cache:delete(cache_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;username
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[本地缓存-读取]&amp;nbsp;未命中&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
end

--&amp;nbsp;第三部分：TOTP&amp;nbsp;密钥管理
--&amp;nbsp;3.1.获取用户&amp;nbsp;TOTP&amp;nbsp;密钥
function&amp;nbsp;_M.get_user_secret(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;username&amp;nbsp;or&amp;nbsp;username&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[获取密钥]&amp;nbsp;用户名为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;username_empty&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;key&amp;nbsp;=&amp;nbsp;&amp;quot;user_secret:&amp;quot;&amp;nbsp;..&amp;nbsp;username&amp;nbsp;&amp;nbsp;--&amp;nbsp;密钥存储键格式：user_secret:用户名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;red&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[获取密钥]&amp;nbsp;Redis连接失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;redis_connect_failed&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;从&amp;nbsp;Redis&amp;nbsp;获取密钥
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;secret,&amp;nbsp;redis_err&amp;nbsp;=&amp;nbsp;red:get(key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;redis_err&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[获取密钥]&amp;nbsp;Redis查询失败：&amp;quot;&amp;nbsp;..&amp;nbsp;redis_err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;redis_query_failed&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;明确区分&amp;quot;用户不存在&amp;quot;和&amp;quot;查询成功但无数据&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;secret&amp;nbsp;==&amp;nbsp;nil&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[获取密钥]&amp;nbsp;用户不存在：&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;user_not_found&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[获取密钥]&amp;nbsp;成功：&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;secret,&amp;nbsp;nil
end
--&amp;nbsp;3.2.设置用户TOTP密钥
function&amp;nbsp;_M.set_user_secret(username,&amp;nbsp;secret)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;username&amp;nbsp;or&amp;nbsp;not&amp;nbsp;secret&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[设置密钥]&amp;nbsp;参数缺失&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;key&amp;nbsp;=&amp;nbsp;&amp;quot;user_secret:&amp;quot;&amp;nbsp;..&amp;nbsp;username
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;red&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[设置密钥]&amp;nbsp;Redis连接失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;长期存储（1年有效期），适合TOTP密钥的持久化需求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;redis_err&amp;nbsp;=&amp;nbsp;red:setex(key,&amp;nbsp;365&amp;nbsp;*&amp;nbsp;24&amp;nbsp;*&amp;nbsp;3600,&amp;nbsp;secret)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[设置密钥]&amp;nbsp;Redis写入失败：&amp;quot;&amp;nbsp;..&amp;nbsp;redis_err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[设置密钥]&amp;nbsp;成功：&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end

--&amp;nbsp;第四部分：认证安全控制
--&amp;nbsp;4.1.认证尝试次数记录与自动封禁
local&amp;nbsp;auth_conf&amp;nbsp;=&amp;nbsp;config.auth&amp;nbsp;or&amp;nbsp;{}&amp;nbsp;&amp;nbsp;--&amp;nbsp;认证配置（最大尝试次数、封禁时间等）

function&amp;nbsp;_M.record_attempt(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;username&amp;nbsp;or&amp;nbsp;username&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[记录尝试]&amp;nbsp;用户名为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;key&amp;nbsp;=&amp;nbsp;&amp;quot;auth_attempts:&amp;quot;&amp;nbsp;..&amp;nbsp;username&amp;nbsp;&amp;nbsp;--&amp;nbsp;尝试次数存储键
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;max_attempts&amp;nbsp;=&amp;nbsp;auth_conf.max_attempts&amp;nbsp;or&amp;nbsp;5&amp;nbsp;&amp;nbsp;--&amp;nbsp;默认最大5次尝试
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ban_time&amp;nbsp;=&amp;nbsp;auth_conf.ban_time&amp;nbsp;or&amp;nbsp;1200&amp;nbsp;&amp;nbsp;--&amp;nbsp;默认封禁20分钟（1200秒）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;red&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[记录尝试]&amp;nbsp;Redis连接失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;先检查用户是否已被封禁
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;is_banned,&amp;nbsp;redis_err&amp;nbsp;=&amp;nbsp;red:get(&amp;quot;user_banned:&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;redis_err&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[记录尝试]&amp;nbsp;检查封禁状态失败：&amp;quot;&amp;nbsp;..&amp;nbsp;redis_err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;is_banned&amp;nbsp;and&amp;nbsp;is_banned&amp;nbsp;==&amp;nbsp;&amp;quot;1&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(&amp;quot;[记录尝试]&amp;nbsp;用户已被封禁：&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;0&amp;nbsp;&amp;nbsp;--&amp;nbsp;已封禁返回0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;自增尝试次数（原子操作，支持并发场景）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;attempts,&amp;nbsp;redis_err&amp;nbsp;=&amp;nbsp;red:incr(key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;redis_err&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[记录尝试]&amp;nbsp;自增失败：&amp;quot;&amp;nbsp;..&amp;nbsp;redis_err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;首次记录时设置过期时间（与封禁时间一致）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;attempts&amp;nbsp;==&amp;nbsp;1&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:expire(key,&amp;nbsp;ban_time)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;达到最大尝试次数时自动封禁
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;attempts&amp;nbsp;&amp;gt;=&amp;nbsp;max_attempts&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:setex(&amp;quot;user_banned:&amp;quot;&amp;nbsp;..&amp;nbsp;username,&amp;nbsp;ban_time,&amp;nbsp;&amp;quot;1&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;封禁标记
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(&amp;quot;[记录尝试]&amp;nbsp;达到最大次数，已封禁：&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;[记录尝试]&amp;nbsp;成功，剩余次数：&amp;quot;&amp;nbsp;..&amp;nbsp;(max_attempts&amp;nbsp;-&amp;nbsp;attempts)&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;，用户：&amp;quot;&amp;nbsp;..&amp;nbsp;username
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;max_attempts&amp;nbsp;-&amp;nbsp;attempts&amp;nbsp;&amp;nbsp;--&amp;nbsp;返回剩余尝试次数
end
--&amp;nbsp;4.2.辅助封禁管理函数
--&amp;nbsp;重置认证尝试次数（如登录成功后调用）
function&amp;nbsp;_M.reset_attempts(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;username&amp;nbsp;or&amp;nbsp;username&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[重置尝试]&amp;nbsp;用户名为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;key&amp;nbsp;=&amp;nbsp;&amp;quot;auth_attempts:&amp;quot;&amp;nbsp;..&amp;nbsp;username
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;red&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[重置尝试]&amp;nbsp;Redis连接失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;删除尝试次数记录和封禁标记
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:del(key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:del(&amp;quot;user_banned:&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[重置尝试]&amp;nbsp;成功：&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end

--&amp;nbsp;检查用户是否被封禁
function&amp;nbsp;_M.is_user_banned(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;username&amp;nbsp;or&amp;nbsp;username&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[检查封禁]&amp;nbsp;用户名为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;key&amp;nbsp;=&amp;nbsp;&amp;quot;user_banned:&amp;quot;&amp;nbsp;..&amp;nbsp;username
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;red&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[检查封禁]&amp;nbsp;Redis连接失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;is_banned&amp;nbsp;=&amp;nbsp;red:get(key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[检查封禁]&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;(is_banned&amp;nbsp;and&amp;nbsp;&amp;quot;已封禁&amp;quot;&amp;nbsp;or&amp;nbsp;&amp;quot;未封禁&amp;quot;)&amp;nbsp;..&amp;nbsp;&amp;quot;：&amp;quot;&amp;nbsp;..&amp;nbsp;username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;is_banned&amp;nbsp;and&amp;nbsp;is_banned&amp;nbsp;==&amp;nbsp;&amp;quot;1&amp;quot;&amp;nbsp;&amp;nbsp;--&amp;nbsp;明确返回布尔值
end

--&amp;nbsp;手动封禁用户（支持自定义封禁时间）
function&amp;nbsp;_M.ban_user(username,&amp;nbsp;ban_time)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;username&amp;nbsp;or&amp;nbsp;username&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[封禁用户]&amp;nbsp;用户名为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;封禁时间默认使用配置的ban_time（20分钟），支持自定义
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ban_time&amp;nbsp;=&amp;nbsp;ban_time&amp;nbsp;or&amp;nbsp;(auth_conf.ban_time&amp;nbsp;or&amp;nbsp;1200)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;key&amp;nbsp;=&amp;nbsp;&amp;quot;user_banned:&amp;quot;&amp;nbsp;..&amp;nbsp;username&amp;nbsp;&amp;nbsp;--&amp;nbsp;封禁标记存储键
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;red&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;[封禁用户]&amp;nbsp;Redis连接失败，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;error:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(err)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;写入封禁标记（值为&amp;quot;1&amp;quot;，带过期时间）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;redis_err&amp;nbsp;=&amp;nbsp;red:setex(key,&amp;nbsp;ban_time,&amp;nbsp;&amp;quot;1&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;[封禁用户]&amp;nbsp;Redis操作失败，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;error:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(redis_err)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;[封禁用户]&amp;nbsp;成功，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;封禁时间:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(ban_time)&amp;nbsp;..&amp;nbsp;&amp;quot;秒]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end

--&amp;nbsp;第五部分:无效会话清理
function&amp;nbsp;_M.clean_invalid_sessions()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接Redis
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;red,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;red&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[清理无效会话]&amp;nbsp;Redis连接失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cursor&amp;nbsp;=&amp;nbsp;&amp;quot;0&amp;quot;&amp;nbsp;&amp;nbsp;--&amp;nbsp;游标初始值（用于SCAN命令分页）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;deleted&amp;nbsp;=&amp;nbsp;0&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;记录删除的无效会话数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;processed&amp;nbsp;=&amp;nbsp;0&amp;nbsp;--&amp;nbsp;记录处理的总会话数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[清理无效会话]&amp;nbsp;开始扫描会话数据...&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;循环扫描所有会话键（使用SCAN避免阻塞Redis）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;&amp;nbsp;repeat...until是一种循环结构，类似于其他语言中的&amp;nbsp;do...while&amp;nbsp;循环。它的特点是先执行循环体，再判断条件，因此循环体至少会执行一次
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;repeat
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;扫描匹配&amp;quot;session:*&amp;quot;的键，每次返回部分结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;安全扫描：使用SCAN命令替代KEYS，避免一次性扫描大量键导致Redis阻塞
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:scan(cursor,&amp;nbsp;&amp;quot;MATCH&amp;quot;,&amp;nbsp;&amp;quot;session:*&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;[清理无效会话]&amp;nbsp;Redis扫描失败：&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(err))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor&amp;nbsp;=&amp;nbsp;res[1]&amp;nbsp;&amp;nbsp;--&amp;nbsp;更新游标（用于下一次扫描）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;keys&amp;nbsp;=&amp;nbsp;res[2]&amp;nbsp;&amp;nbsp;--&amp;nbsp;当前批次的键列表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;processed&amp;nbsp;=&amp;nbsp;processed&amp;nbsp;+&amp;nbsp;#keys&amp;nbsp;&amp;nbsp;--&amp;nbsp;累计处理数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查每个会话键的值是否有效
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;_,&amp;nbsp;key&amp;nbsp;in&amp;nbsp;ipairs(keys)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;username,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:get(key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;无效判定：非字符串类型或包含&amp;quot;userdata:&amp;quot;（脏数据标识）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;username&amp;nbsp;and&amp;nbsp;(type(username)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;or&amp;nbsp;string.find(username,&amp;nbsp;&amp;quot;userdata:&amp;quot;))&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:del(key)&amp;nbsp;&amp;nbsp;--&amp;nbsp;删除无效会话
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;deleted&amp;nbsp;=&amp;nbsp;deleted&amp;nbsp;+&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[清理无效会话]&amp;nbsp;已删除无效会话:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;until&amp;nbsp;cursor&amp;nbsp;==&amp;nbsp;&amp;quot;0&amp;quot;&amp;nbsp;&amp;nbsp;--&amp;nbsp;游标为0表示扫描完成
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;red:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;[清理无效会话]&amp;nbsp;完成，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;共处理&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;processed&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;个会话，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;删除&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;deleted&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;个无效会话&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
return&amp;nbsp;_M&amp;nbsp;&amp;nbsp;--&amp;nbsp;导出模块表，供其他脚本调用&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.6&amp;nbsp;totp_utils.lua&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/totp_utils.lua&lt;/p&gt;&lt;p&gt;#该文件是TOTP(时间同步验证码)工具模块，基于RFC6238标准实现了TOTP验证码的生成与验证功能。TOTP 广泛用于双因素认证(2FA)，其核心原理是通过密钥和当前时间生成短期有效的验证码。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;1.模块初始化与常量定义
local&amp;nbsp;bit32&amp;nbsp;=&amp;nbsp;require(&amp;quot;bit&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;位操作库（用于哈希计算）
local&amp;nbsp;log&amp;nbsp;=&amp;nbsp;require(&amp;quot;log_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;日志工具

local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}&amp;nbsp;&amp;nbsp;--&amp;nbsp;模块对象

--&amp;nbsp;Base32&amp;nbsp;字符集（遵循&amp;nbsp;RFC&amp;nbsp;4648&amp;nbsp;标准）
--&amp;nbsp;包含大写字母、小写字母（用于兼容）和数字&amp;nbsp;2-7
local&amp;nbsp;BASE32_CHARS&amp;nbsp;=&amp;nbsp;&amp;quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz234567&amp;quot;
local&amp;nbsp;BASE32_UPPER&amp;nbsp;=&amp;nbsp;string.upper(BASE32_CHARS)&amp;nbsp;&amp;nbsp;--&amp;nbsp;大写字符集（用于解码基准）
local&amp;nbsp;BASE32_LOWER&amp;nbsp;=&amp;nbsp;string.lower(BASE32_CHARS)&amp;nbsp;&amp;nbsp;--&amp;nbsp;小写字符集（用于兼容）
local&amp;nbsp;BASE32_PAD&amp;nbsp;=&amp;nbsp;&amp;quot;=&amp;quot;&amp;nbsp;&amp;nbsp;--&amp;nbsp;Base32&amp;nbsp;填充符

--&amp;nbsp;2.&amp;nbsp;Base32解码(TOTP&amp;nbsp;密钥解码)
--&amp;nbsp;TOTP密钥通常以Base32编码存储(如JBSWY3DPEHPK3PXP),需解码为原始字节才能用于加密。
function&amp;nbsp;_M.base32_decode(base32_str,&amp;nbsp;allow_lowercase)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;参数校验：必须为字符串
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(base32_str)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;base32_decode:&amp;nbsp;非法参数类型，期望字符串&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;INVALID_PARAM_TYPE&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;清理输入：移除空格和填充符（=）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cleaned&amp;nbsp;=&amp;nbsp;base32_str:gsub(&amp;quot;%s+&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;):gsub(BASE32_PAD,&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;空输入处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#cleaned&amp;nbsp;==&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;base32_decode:&amp;nbsp;空输入字符串&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;EMPTY_INPUT&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;非法字符校验
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;valid_chars&amp;nbsp;=&amp;nbsp;allow_lowercase&amp;nbsp;and&amp;nbsp;BASE32_CHARS&amp;nbsp;or&amp;nbsp;BASE32_UPPER&amp;nbsp;&amp;nbsp;--&amp;nbsp;允许小写时放宽校验
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;#cleaned&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;char&amp;nbsp;=&amp;nbsp;cleaned:sub(i,&amp;nbsp;i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;valid_chars:find(char,&amp;nbsp;1,&amp;nbsp;true)&amp;nbsp;==&amp;nbsp;nil&amp;nbsp;then&amp;nbsp;&amp;nbsp;--&amp;nbsp;精确匹配字符
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;base32_decode:&amp;nbsp;发现非法字符&amp;nbsp;&amp;#39;&amp;quot;,&amp;nbsp;char,&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;INVALID_CHAR&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;其实避免麻烦正式环境就直接字母生成的时候就全大写字母就好了,就没有转义兼容方面的问题了
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;统一转为大写（确保解码逻辑一致）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cleaned&amp;nbsp;=&amp;nbsp;string.upper(cleaned)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解码核心逻辑：将&amp;nbsp;Base32&amp;nbsp;字符转为字节流
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;result&amp;nbsp;=&amp;nbsp;{}&amp;nbsp;&amp;nbsp;--&amp;nbsp;存储解码后的字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;bits&amp;nbsp;=&amp;nbsp;0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;累计位数（Base32&amp;nbsp;每字符&amp;nbsp;5&amp;nbsp;位）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;value&amp;nbsp;=&amp;nbsp;0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;累计数值（用于拼接字节）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;#cleaned&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;char&amp;nbsp;=&amp;nbsp;cleaned:sub(i,&amp;nbsp;i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;pos&amp;nbsp;=&amp;nbsp;BASE32_UPPER:find(char,&amp;nbsp;1,&amp;nbsp;true)&amp;nbsp;&amp;nbsp;--&amp;nbsp;查找字符在大写集的位置（1-32）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;pos&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;base32_decode:&amp;nbsp;字符集查找失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;CHARSET_LOOKUP_FAILED&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;val&amp;nbsp;=&amp;nbsp;pos&amp;nbsp;-&amp;nbsp;1&amp;nbsp;&amp;nbsp;--&amp;nbsp;转为&amp;nbsp;0-31&amp;nbsp;的数值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;累计&amp;nbsp;5&amp;nbsp;位，凑满&amp;nbsp;8&amp;nbsp;位则生成一个字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value&amp;nbsp;=&amp;nbsp;(value&amp;nbsp;*&amp;nbsp;32)&amp;nbsp;+&amp;nbsp;val&amp;nbsp;&amp;nbsp;--&amp;nbsp;左移&amp;nbsp;5&amp;nbsp;位，加上新值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bits&amp;nbsp;=&amp;nbsp;bits&amp;nbsp;+&amp;nbsp;5
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;当累计位数&amp;nbsp;&amp;gt;=8&amp;nbsp;时，提取一个字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;bits&amp;nbsp;&amp;gt;=&amp;nbsp;8&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte&amp;nbsp;=&amp;nbsp;math.floor(value&amp;nbsp;/&amp;nbsp;(2&amp;nbsp;^&amp;nbsp;(bits&amp;nbsp;-&amp;nbsp;8)))&amp;nbsp;&amp;nbsp;--&amp;nbsp;取高&amp;nbsp;8&amp;nbsp;位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(result,&amp;nbsp;string.char(byte))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value&amp;nbsp;=&amp;nbsp;value&amp;nbsp;%&amp;nbsp;(2&amp;nbsp;^&amp;nbsp;(bits&amp;nbsp;-&amp;nbsp;8))&amp;nbsp;&amp;nbsp;--&amp;nbsp;保留剩余位数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bits&amp;nbsp;=&amp;nbsp;bits&amp;nbsp;-&amp;nbsp;8
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理剩余不足&amp;nbsp;8&amp;nbsp;位的情况（补&amp;nbsp;0&amp;nbsp;凑整）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;bits&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte&amp;nbsp;=&amp;nbsp;math.floor(value&amp;nbsp;/&amp;nbsp;(2&amp;nbsp;^&amp;nbsp;(8&amp;nbsp;-&amp;nbsp;bits)))&amp;nbsp;&amp;nbsp;--&amp;nbsp;补&amp;nbsp;0&amp;nbsp;后取&amp;nbsp;8&amp;nbsp;位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(result,&amp;nbsp;string.char(byte))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;table.concat(result),&amp;nbsp;nil&amp;nbsp;&amp;nbsp;--&amp;nbsp;返回解码后的字节字符串
end

--&amp;nbsp;3.&amp;nbsp;SHA-1哈希算法(TOTP&amp;nbsp;加密基础)
--&amp;nbsp;SHA-1用于生成消息摘要，是HMAC-SHA1的基础。
--&amp;nbsp;左旋转函数（SHA-1&amp;nbsp;核心操作）
local&amp;nbsp;function&amp;nbsp;rotl32(n,&amp;nbsp;b)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;b&amp;nbsp;=&amp;nbsp;b&amp;nbsp;%&amp;nbsp;32&amp;nbsp;&amp;nbsp;--&amp;nbsp;旋转位数取模（防止溢出）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;左旋转&amp;nbsp;b&amp;nbsp;位，溢出部分补到右侧
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;bit32.band(bit32.lshift(n,&amp;nbsp;b),&amp;nbsp;0xFFFFFFFF)&amp;nbsp;+&amp;nbsp;bit32.rshift(n,&amp;nbsp;32&amp;nbsp;-&amp;nbsp;b)
end

--&amp;nbsp;消息填充（SHA-1&amp;nbsp;要求消息长度为&amp;nbsp;512&amp;nbsp;位的倍数）
local&amp;nbsp;function&amp;nbsp;pad_message(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;length_bits&amp;nbsp;=&amp;nbsp;#message&amp;nbsp;*&amp;nbsp;8&amp;nbsp;&amp;nbsp;--&amp;nbsp;消息长度（位）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;length_bits&amp;nbsp;&amp;gt;&amp;nbsp;0x7FFFFFFFFFFFFFFF&amp;nbsp;then&amp;nbsp;&amp;nbsp;--&amp;nbsp;超过&amp;nbsp;64&amp;nbsp;位表示范围
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error(&amp;quot;Message&amp;nbsp;too&amp;nbsp;long&amp;nbsp;for&amp;nbsp;SHA-1&amp;nbsp;padding&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;步骤1：添加&amp;nbsp;0x80&amp;nbsp;标记
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;padded&amp;nbsp;=&amp;nbsp;message&amp;nbsp;..&amp;nbsp;string.char(0x80)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;步骤2：补&amp;nbsp;0&amp;nbsp;直到长度&amp;nbsp;≡&amp;nbsp;448&amp;nbsp;mod&amp;nbsp;512（留出&amp;nbsp;64&amp;nbsp;位存长度）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;(#padded&amp;nbsp;*&amp;nbsp;8)&amp;nbsp;%&amp;nbsp;512&amp;nbsp;~=&amp;nbsp;448&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;padded&amp;nbsp;=&amp;nbsp;padded&amp;nbsp;..&amp;nbsp;string.char(0x00)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;步骤3：添加&amp;nbsp;64&amp;nbsp;位长度（大端序）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;length_bytes&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;7,&amp;nbsp;0,&amp;nbsp;-1&amp;nbsp;do&amp;nbsp;&amp;nbsp;--&amp;nbsp;从高位到低位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;bit32.band(math.floor(length_bits&amp;nbsp;/&amp;nbsp;(256&amp;nbsp;^&amp;nbsp;i)),&amp;nbsp;0xFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(length_bytes,&amp;nbsp;string.char(byte_val))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;padded&amp;nbsp;..&amp;nbsp;table.concat(length_bytes)
end

--&amp;nbsp;将&amp;nbsp;64&amp;nbsp;字节消息块转为&amp;nbsp;16&amp;nbsp;个&amp;nbsp;32&amp;nbsp;位字（SHA-1&amp;nbsp;处理单位）
local&amp;nbsp;function&amp;nbsp;words_from_block(block)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;words&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;j&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;16&amp;nbsp;do&amp;nbsp;&amp;nbsp;--&amp;nbsp;64字节&amp;nbsp;=&amp;nbsp;16&amp;nbsp;*&amp;nbsp;4字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;start&amp;nbsp;=&amp;nbsp;(j&amp;nbsp;-&amp;nbsp;1)&amp;nbsp;*&amp;nbsp;4&amp;nbsp;+&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;end_pos&amp;nbsp;=&amp;nbsp;start&amp;nbsp;+&amp;nbsp;3
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;按大端序拼接&amp;nbsp;4&amp;nbsp;字节为&amp;nbsp;32&amp;nbsp;位字
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte1,&amp;nbsp;byte2,&amp;nbsp;byte3,&amp;nbsp;byte4&amp;nbsp;=&amp;nbsp;block:byte(start,&amp;nbsp;end_pos)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;byte2&amp;nbsp;=&amp;nbsp;byte2&amp;nbsp;or&amp;nbsp;0&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理不足&amp;nbsp;4&amp;nbsp;字节的情况
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;byte3&amp;nbsp;=&amp;nbsp;byte3&amp;nbsp;or&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;byte4&amp;nbsp;=&amp;nbsp;byte4&amp;nbsp;or&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;words[j]&amp;nbsp;=&amp;nbsp;bit32.bor(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(byte1,&amp;nbsp;24),&amp;nbsp;&amp;nbsp;--&amp;nbsp;最高位字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(byte2,&amp;nbsp;16),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(byte3,&amp;nbsp;8),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;byte4&amp;nbsp;&amp;nbsp;--&amp;nbsp;最低位字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;words
end

--&amp;nbsp;SHA-1&amp;nbsp;哈希函数
function&amp;nbsp;_M.sha1(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;初始化哈希值（FIPS&amp;nbsp;180-4&amp;nbsp;标准）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H0&amp;nbsp;=&amp;nbsp;0x67452301
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H1&amp;nbsp;=&amp;nbsp;0xEFCDAB89
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H2&amp;nbsp;=&amp;nbsp;0x98BADCFE
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H3&amp;nbsp;=&amp;nbsp;0x10325476
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H4&amp;nbsp;=&amp;nbsp;0xC3D2E1F0

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;消息填充
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;padded&amp;nbsp;=&amp;nbsp;pad_message(message)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;分块处理（每块&amp;nbsp;512&amp;nbsp;位&amp;nbsp;=&amp;nbsp;64&amp;nbsp;字节）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;#padded,&amp;nbsp;64&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;block&amp;nbsp;=&amp;nbsp;padded:sub(i,&amp;nbsp;i&amp;nbsp;+&amp;nbsp;63)&amp;nbsp;&amp;nbsp;--&amp;nbsp;取&amp;nbsp;64&amp;nbsp;字节块
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;words&amp;nbsp;=&amp;nbsp;words_from_block(block)&amp;nbsp;&amp;nbsp;--&amp;nbsp;转为&amp;nbsp;16&amp;nbsp;个&amp;nbsp;32&amp;nbsp;位字
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;扩展为&amp;nbsp;80&amp;nbsp;个&amp;nbsp;32&amp;nbsp;位字
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;t&amp;nbsp;=&amp;nbsp;17,&amp;nbsp;80&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第&amp;nbsp;t&amp;nbsp;个字&amp;nbsp;=&amp;nbsp;前&amp;nbsp;t-3、t-8、t-14、t-16&amp;nbsp;个字异或后左旋转&amp;nbsp;1&amp;nbsp;位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;word&amp;nbsp;=&amp;nbsp;bit32.bxor(words[t&amp;nbsp;-&amp;nbsp;3],&amp;nbsp;words[t&amp;nbsp;-&amp;nbsp;8],&amp;nbsp;words[t&amp;nbsp;-&amp;nbsp;14],&amp;nbsp;words[t&amp;nbsp;-&amp;nbsp;16])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;words[t]&amp;nbsp;=&amp;nbsp;rotl32(word,&amp;nbsp;1)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;主循环：更新哈希值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;A,&amp;nbsp;B,&amp;nbsp;C,&amp;nbsp;D,&amp;nbsp;E&amp;nbsp;=&amp;nbsp;H0,&amp;nbsp;H1,&amp;nbsp;H2,&amp;nbsp;H3,&amp;nbsp;H4&amp;nbsp;&amp;nbsp;--&amp;nbsp;临时变量
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;t&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;80&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;temp,&amp;nbsp;f,&amp;nbsp;k&amp;nbsp;&amp;nbsp;--&amp;nbsp;f&amp;nbsp;为函数，k&amp;nbsp;为常量（分&amp;nbsp;4&amp;nbsp;轮）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;t_idx&amp;nbsp;=&amp;nbsp;t&amp;nbsp;-&amp;nbsp;1&amp;nbsp;&amp;nbsp;--&amp;nbsp;0-79
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;t_idx&amp;nbsp;&amp;lt;&amp;nbsp;20&amp;nbsp;then&amp;nbsp;&amp;nbsp;--&amp;nbsp;第&amp;nbsp;1&amp;nbsp;轮（0-19）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;nbsp;=&amp;nbsp;bit32.bor(bit32.band(B,&amp;nbsp;C),&amp;nbsp;bit32.band(bit32.bnot(B),&amp;nbsp;D))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k&amp;nbsp;=&amp;nbsp;0x5A827999
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elseif&amp;nbsp;t_idx&amp;nbsp;&amp;lt;&amp;nbsp;40&amp;nbsp;then&amp;nbsp;&amp;nbsp;--&amp;nbsp;第&amp;nbsp;2&amp;nbsp;轮（20-39）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;nbsp;=&amp;nbsp;bit32.bxor(B,&amp;nbsp;C,&amp;nbsp;D)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k&amp;nbsp;=&amp;nbsp;0x6ED9EBA1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elseif&amp;nbsp;t_idx&amp;nbsp;&amp;lt;&amp;nbsp;60&amp;nbsp;then&amp;nbsp;&amp;nbsp;--&amp;nbsp;第&amp;nbsp;3&amp;nbsp;轮（40-59）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;nbsp;=&amp;nbsp;bit32.bor(bit32.band(B,&amp;nbsp;C),&amp;nbsp;bit32.band(B,&amp;nbsp;D),&amp;nbsp;bit32.band(C,&amp;nbsp;D))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k&amp;nbsp;=&amp;nbsp;0x8F1BBCDC
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&amp;nbsp;&amp;nbsp;--&amp;nbsp;第&amp;nbsp;4&amp;nbsp;轮（60-79）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;nbsp;=&amp;nbsp;bit32.bxor(B,&amp;nbsp;C,&amp;nbsp;D)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;k&amp;nbsp;=&amp;nbsp;0xCA62C1D6
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;核心计算：A&amp;nbsp;=&amp;nbsp;(A&amp;nbsp;左旋&amp;nbsp;5&amp;nbsp;位)&amp;nbsp;+&amp;nbsp;f&amp;nbsp;+&amp;nbsp;E&amp;nbsp;+&amp;nbsp;第&amp;nbsp;t&amp;nbsp;个字&amp;nbsp;+&amp;nbsp;k
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;temp&amp;nbsp;=&amp;nbsp;bit32.band(rotl32(A,&amp;nbsp;5)&amp;nbsp;+&amp;nbsp;f&amp;nbsp;+&amp;nbsp;E&amp;nbsp;+&amp;nbsp;words[t]&amp;nbsp;+&amp;nbsp;k,&amp;nbsp;0xFFFFFFFF)&amp;nbsp;&amp;nbsp;--&amp;nbsp;限制为&amp;nbsp;32&amp;nbsp;位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;寄存器移位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;E&amp;nbsp;=&amp;nbsp;D
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;D&amp;nbsp;=&amp;nbsp;C
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;C&amp;nbsp;=&amp;nbsp;rotl32(B,&amp;nbsp;30)&amp;nbsp;&amp;nbsp;--&amp;nbsp;B&amp;nbsp;左旋&amp;nbsp;30&amp;nbsp;位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;B&amp;nbsp;=&amp;nbsp;A
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A&amp;nbsp;=&amp;nbsp;temp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;累加哈希值（更新全局哈希值）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H0&amp;nbsp;=&amp;nbsp;bit32.band(H0&amp;nbsp;+&amp;nbsp;A,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H1&amp;nbsp;=&amp;nbsp;bit32.band(H1&amp;nbsp;+&amp;nbsp;B,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H2&amp;nbsp;=&amp;nbsp;bit32.band(H2&amp;nbsp;+&amp;nbsp;C,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H3&amp;nbsp;=&amp;nbsp;bit32.band(H3&amp;nbsp;+&amp;nbsp;D,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H4&amp;nbsp;=&amp;nbsp;bit32.band(H4&amp;nbsp;+&amp;nbsp;E,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;转换哈希值为字节字符串（每个哈希值&amp;nbsp;4&amp;nbsp;字节，共&amp;nbsp;20&amp;nbsp;字节）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;hash_bytes&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i,&amp;nbsp;h&amp;nbsp;in&amp;nbsp;ipairs({H0,&amp;nbsp;H1,&amp;nbsp;H2,&amp;nbsp;H3,&amp;nbsp;H4})&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;j&amp;nbsp;=&amp;nbsp;3,&amp;nbsp;0,&amp;nbsp;-1&amp;nbsp;do&amp;nbsp;&amp;nbsp;--&amp;nbsp;大端序
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;bit32.band(bit32.rshift(h,&amp;nbsp;j&amp;nbsp;*&amp;nbsp;8),&amp;nbsp;0xFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(hash_bytes,&amp;nbsp;string.char(byte_val))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;table.concat(hash_bytes)
end

--&amp;nbsp;4.HMAC-SHA1算法(TOTP核心)
--&amp;nbsp;HMAC是基于哈希的消息认证码,TOTP通过HMAC-SHA1(密钥，时间步)生成验证码。
function&amp;nbsp;_M.hmac_sha1(key,&amp;nbsp;data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;参数校验
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(key)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;or&amp;nbsp;type(data)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;hmac_sha1:&amp;nbsp;非法参数类型&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;INVALID_PARAM_TYPE&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理密钥：若长度&amp;nbsp;&amp;gt;64&amp;nbsp;字节，先用&amp;nbsp;SHA-1&amp;nbsp;压缩为&amp;nbsp;20&amp;nbsp;字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#key&amp;nbsp;&amp;gt;&amp;nbsp;64&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;_M.sha1(key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;填充密钥到&amp;nbsp;64&amp;nbsp;字节（HMAC&amp;nbsp;要求）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;key&amp;nbsp;..&amp;nbsp;string.rep(&amp;quot;\0&amp;quot;,&amp;nbsp;64&amp;nbsp;-&amp;nbsp;#key)&amp;nbsp;&amp;nbsp;--&amp;nbsp;不足部分补&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;生成内键（与&amp;nbsp;0x36&amp;nbsp;异或）和外键（与&amp;nbsp;0x5C&amp;nbsp;异或）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;inner_key&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;outer_key&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;64&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;key:byte(i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(inner_key,&amp;nbsp;string.char(bit32.bxor(byte_val,&amp;nbsp;0x36)))&amp;nbsp;&amp;nbsp;--&amp;nbsp;内键异或&amp;nbsp;0x36
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(outer_key,&amp;nbsp;string.char(bit32.bxor(byte_val,&amp;nbsp;0x5C)))&amp;nbsp;&amp;nbsp;--&amp;nbsp;外键异或&amp;nbsp;0x5C
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;inner_key_str&amp;nbsp;=&amp;nbsp;table.concat(inner_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;outer_key_str&amp;nbsp;=&amp;nbsp;table.concat(outer_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算&amp;nbsp;HMAC&amp;nbsp;值：HMAC&amp;nbsp;=&amp;nbsp;SHA1(外键&amp;nbsp;+&amp;nbsp;SHA1(内键&amp;nbsp;+&amp;nbsp;数据))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;inner_hash&amp;nbsp;=&amp;nbsp;_M.sha1(inner_key_str&amp;nbsp;..&amp;nbsp;data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;inner_hash&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;INNER_HASH_FAILED&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;outer_hash&amp;nbsp;=&amp;nbsp;_M.sha1(outer_key_str&amp;nbsp;..&amp;nbsp;inner_hash)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;outer_hash,&amp;nbsp;nil
end

--&amp;nbsp;5.TOTP&amp;nbsp;验证码生成与验证
--&amp;nbsp;5.1生成TOTP验证码
function&amp;nbsp;_M.generate_totp(base32_secret,&amp;nbsp;time_step,&amp;nbsp;digits,&amp;nbsp;allow_lowercase)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;参数默认值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time_step&amp;nbsp;=&amp;nbsp;time_step&amp;nbsp;or&amp;nbsp;30&amp;nbsp;&amp;nbsp;--&amp;nbsp;时间步长（默认30秒）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;digits&amp;nbsp;=&amp;nbsp;digits&amp;nbsp;or&amp;nbsp;6&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证码位数（默认6位）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;allow_lowercase&amp;nbsp;=&amp;nbsp;allow_lowercase&amp;nbsp;or&amp;nbsp;true&amp;nbsp;&amp;nbsp;--&amp;nbsp;允许小写密钥
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;参数校验
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(base32_secret)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;or&amp;nbsp;base32_secret&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;generate_totp:&amp;nbsp;非法密钥&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;INVALID_SECRET&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;time_step&amp;nbsp;&amp;lt;=&amp;nbsp;0&amp;nbsp;or&amp;nbsp;digits&amp;nbsp;&amp;lt;&amp;nbsp;6&amp;nbsp;or&amp;nbsp;digits&amp;nbsp;&amp;gt;&amp;nbsp;8&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;generate_totp:&amp;nbsp;非法参数范围&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;INVALID_PARAMS&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解码&amp;nbsp;Base32&amp;nbsp;密钥
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;secret,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.base32_decode(base32_secret,&amp;nbsp;allow_lowercase)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;secret&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;generate_totp:&amp;nbsp;密钥解码失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算当前时间步（counter&amp;nbsp;=&amp;nbsp;当前时间&amp;nbsp;/&amp;nbsp;时间步长，取整数）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;current_time&amp;nbsp;=&amp;nbsp;os.time()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;counter&amp;nbsp;=&amp;nbsp;math.floor(current_time&amp;nbsp;/&amp;nbsp;time_step)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将&amp;nbsp;counter&amp;nbsp;转为&amp;nbsp;8&amp;nbsp;字节大端序（TOTP&amp;nbsp;要求）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;counter_bytes&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;7,&amp;nbsp;0,&amp;nbsp;-1&amp;nbsp;do&amp;nbsp;&amp;nbsp;--&amp;nbsp;从高位到低位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;bit32.band(math.floor(counter&amp;nbsp;/&amp;nbsp;(256&amp;nbsp;^&amp;nbsp;i)),&amp;nbsp;0xFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;counter_bytes[#counter_bytes&amp;nbsp;+&amp;nbsp;1]&amp;nbsp;=&amp;nbsp;string.char(byte_val)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;counter_str&amp;nbsp;=&amp;nbsp;table.concat(counter_bytes)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算&amp;nbsp;HMAC-SHA1(密钥,&amp;nbsp;counter)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;hash,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.hmac_sha1(secret,&amp;nbsp;counter_str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;hash&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;generate_totp:&amp;nbsp;HMAC&amp;nbsp;计算失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;动态截断（DT，取哈希值的一部分生成数字）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;offset&amp;nbsp;=&amp;nbsp;bit32.band(hash:byte(20),&amp;nbsp;0x0F)&amp;nbsp;&amp;nbsp;--&amp;nbsp;取第20字节的低4位作为偏移量
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;从偏移量开始取&amp;nbsp;4&amp;nbsp;字节，最高位设为&amp;nbsp;0（避免符号问题）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;binary_code&amp;nbsp;=&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;4&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;hash:byte(offset&amp;nbsp;+&amp;nbsp;i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;binary_code&amp;nbsp;=&amp;nbsp;bit32.bor(binary_code,&amp;nbsp;bit32.lshift(byte_val,&amp;nbsp;(4&amp;nbsp;-&amp;nbsp;i)&amp;nbsp;*&amp;nbsp;8))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;binary_code&amp;nbsp;=&amp;nbsp;bit32.band&amp;nbsp;(binary_code,&amp;nbsp;0x7FFFFFFF)&amp;nbsp;--&amp;nbsp;清除最高位，确保为正数

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算验证码（取模&amp;nbsp;10^digits，不足位数补&amp;nbsp;0）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;otp&amp;nbsp;=&amp;nbsp;binary_code&amp;nbsp;%&amp;nbsp;(10&amp;nbsp;^&amp;nbsp;digits)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;string.format&amp;nbsp;(&amp;quot;%0&amp;quot;&amp;nbsp;..&amp;nbsp;digits&amp;nbsp;..&amp;nbsp;&amp;quot;d&amp;quot;,&amp;nbsp;otp),&amp;nbsp;nil
end

--&amp;nbsp;5.2验证TOTP验证码*&amp;nbsp;
--&amp;nbsp;支持时间窗口容错（默认前后各1个时间步，共3个窗口），解决网络延迟导致的验证失败问题。&amp;nbsp;&amp;nbsp;
function&amp;nbsp;_M.verify_totp(base32_secret,&amp;nbsp;code,&amp;nbsp;time_step,&amp;nbsp;digits,&amp;nbsp;window,&amp;nbsp;allow_lowercase)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;参数默认值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time_step&amp;nbsp;=&amp;nbsp;time_step&amp;nbsp;or&amp;nbsp;30
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;digits&amp;nbsp;=&amp;nbsp;digits&amp;nbsp;or&amp;nbsp;6
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;window&amp;nbsp;=&amp;nbsp;window&amp;nbsp;or&amp;nbsp;1&amp;nbsp;&amp;nbsp;--&amp;nbsp;时间窗口（默认±1个时间步）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;allow_lowercase&amp;nbsp;=&amp;nbsp;allow_lowercase&amp;nbsp;or&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证码格式校验（必须为数字）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(code)&amp;nbsp;~=&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;or&amp;nbsp;not&amp;nbsp;code:match(&amp;quot;^%d+$&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;verify_totp:&amp;nbsp;验证码格式非法&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;&amp;quot;INVALID_CODE_FORMAT&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算当前时间步
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;current_time&amp;nbsp;=&amp;nbsp;os.time()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;current_counter&amp;nbsp;=&amp;nbsp;math.floor(current_time&amp;nbsp;/&amp;nbsp;time_step)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证时间窗口内的所有可能时间步（如&amp;nbsp;window=1&amp;nbsp;则验证&amp;nbsp;current_counter-1、current_counter、current_counter+1）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;-window,&amp;nbsp;window&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;counter_try&amp;nbsp;=&amp;nbsp;current_counter&amp;nbsp;+&amp;nbsp;i
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;生成对应时间步的验证码
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;otp,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.generate_totp(base32_secret,&amp;nbsp;time_step,&amp;nbsp;digits,&amp;nbsp;allow_lowercase)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;otp&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;verify_totp:&amp;nbsp;生成验证码失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;安全比较验证码（防止时序攻击）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;_M.safe_compare(otp,&amp;nbsp;code)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;verify_totp:&amp;nbsp;验证成功，时间步=&amp;quot;,&amp;nbsp;counter_try)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true,&amp;nbsp;&amp;quot;VALID&amp;quot;,&amp;nbsp;counter_try
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;verify_totp:&amp;nbsp;所有时间窗口验证失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;&amp;quot;INVALID&amp;quot;,&amp;nbsp;current_counter
end
--&amp;nbsp;6.安全辅助函数
--&amp;nbsp;6.1安全比较(防止时序攻击)
--&amp;nbsp;普通字符串比较（如&amp;nbsp;==）可能因比较时长泄露字符串差异信息，安全比较确保无论匹配与否，耗时一致。
function&amp;nbsp;_M.safe_compare(a,&amp;nbsp;b)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#a&amp;nbsp;~=&amp;nbsp;#b&amp;nbsp;then&amp;nbsp;&amp;nbsp;--&amp;nbsp;长度不同直接返回&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;result&amp;nbsp;=&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;逐字节比较，累计差异（即使中途发现差异，仍会比较完所有字节）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;#a&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;result&amp;nbsp;+&amp;nbsp;(string.byte(a,&amp;nbsp;i)&amp;nbsp;~=&amp;nbsp;string.byte(b,&amp;nbsp;i)&amp;nbsp;and&amp;nbsp;1&amp;nbsp;or&amp;nbsp;0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result&amp;nbsp;==&amp;nbsp;0&amp;nbsp;&amp;nbsp;--&amp;nbsp;无差异则返回&amp;nbsp;true
end
--&amp;nbsp;6.2生成Base32密钥(用于初始化用户TOTP密钥)
function&amp;nbsp;_M.generate_secret(length,&amp;nbsp;use_lowercase)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;length&amp;nbsp;=&amp;nbsp;length&amp;nbsp;or&amp;nbsp;16&amp;nbsp;&amp;nbsp;--&amp;nbsp;密钥长度（默认16字节，128位）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;use_lowercase&amp;nbsp;=&amp;nbsp;use_lowercase&amp;nbsp;or&amp;nbsp;true&amp;nbsp;&amp;nbsp;--&amp;nbsp;生成小写密钥（兼容常见工具）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;生成随机字节（优先使用&amp;nbsp;Nginx&amp;nbsp;共享内存的随机源）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;bytes&amp;nbsp;=&amp;nbsp;ngx.shared.random&amp;nbsp;and&amp;nbsp;ngx.shared.random:bytes(length,&amp;nbsp;true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;bytes&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;备选方案：使用&amp;nbsp;math.random（安全性较低，适合测试）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bytes&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;length&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bytes&amp;nbsp;=&amp;nbsp;bytes&amp;nbsp;..&amp;nbsp;string.char(math.random(0,&amp;nbsp;255))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;编码为&amp;nbsp;Base32
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;encoded,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.base32_encode(bytes,&amp;nbsp;use_lowercase)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;encoded&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error(&amp;quot;生成密钥失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;(err&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;encoded
end
--&amp;nbsp;6.3Base32编码(用于生成密钥)
function&amp;nbsp;_M.base32_encode(input,&amp;nbsp;use_lowercase)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;input&amp;nbsp;or&amp;nbsp;#input&amp;nbsp;==&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;EMPTY_INPUT&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;转换输入为字节数组
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;bytes&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;#input&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bytes[i]&amp;nbsp;=&amp;nbsp;string.byte(input,&amp;nbsp;i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算输出长度（每5字节输入对应8字节Base32输出）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;len&amp;nbsp;=&amp;nbsp;#bytes
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;rem&amp;nbsp;=&amp;nbsp;len&amp;nbsp;%&amp;nbsp;5
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;out_len&amp;nbsp;=&amp;nbsp;((len&amp;nbsp;-&amp;nbsp;rem)&amp;nbsp;/&amp;nbsp;5)&amp;nbsp;*&amp;nbsp;8
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;rem&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out_len&amp;nbsp;=&amp;nbsp;out_len&amp;nbsp;+&amp;nbsp;8&amp;nbsp;&amp;nbsp;--&amp;nbsp;不足5字节的部分也按8字符处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;编码核心逻辑
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;output&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;按5字节块处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0,&amp;nbsp;len&amp;nbsp;-&amp;nbsp;5,&amp;nbsp;5&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;chunk&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;j&amp;nbsp;=&amp;nbsp;0,&amp;nbsp;4&amp;nbsp;do&amp;nbsp;&amp;nbsp;--&amp;nbsp;取5字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;i&amp;nbsp;+&amp;nbsp;j&amp;nbsp;&amp;lt;&amp;nbsp;len&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chunk[j&amp;nbsp;+&amp;nbsp;1]&amp;nbsp;=&amp;nbsp;bytes[i&amp;nbsp;+&amp;nbsp;j&amp;nbsp;+&amp;nbsp;1]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chunk[j&amp;nbsp;+&amp;nbsp;1]&amp;nbsp;=&amp;nbsp;0&amp;nbsp;&amp;nbsp;--&amp;nbsp;补0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将5字节（40位）拆分为8个5位值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;value&amp;nbsp;=&amp;nbsp;bit32.bor(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(chunk[1],&amp;nbsp;32),&amp;nbsp;&amp;nbsp;--&amp;nbsp;第1字节左移32位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(chunk[2],&amp;nbsp;24),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(chunk[3],&amp;nbsp;16),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(chunk[4],&amp;nbsp;8),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chunk[5]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;提取8个5位值，映射到Base32字符
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;j&amp;nbsp;=&amp;nbsp;0,&amp;nbsp;7&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;idx&amp;nbsp;=&amp;nbsp;bit32.band(bit32.rshift(value,&amp;nbsp;(7&amp;nbsp;-&amp;nbsp;j)&amp;nbsp;*&amp;nbsp;5),&amp;nbsp;0x1F)&amp;nbsp;+&amp;nbsp;1&amp;nbsp;&amp;nbsp;--&amp;nbsp;1-32
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;char&amp;nbsp;=&amp;nbsp;string.sub(BASE32_UPPER,&amp;nbsp;idx,&amp;nbsp;idx)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;转为小写（如果需要）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;use_lowercase&amp;nbsp;and&amp;nbsp;idx&amp;nbsp;&amp;lt;=&amp;nbsp;26&amp;nbsp;then&amp;nbsp;&amp;nbsp;--&amp;nbsp;字母部分（A-Z）转为小写
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;char&amp;nbsp;=&amp;nbsp;string.lower(char)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;output&amp;nbsp;=&amp;nbsp;output&amp;nbsp;..&amp;nbsp;char
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;添加填充符（=）确保输出长度为8的倍数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;rem&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;output&amp;nbsp;=&amp;nbsp;output&amp;nbsp;..&amp;nbsp;string.rep(BASE32_PAD,&amp;nbsp;8&amp;nbsp;-&amp;nbsp;((len&amp;nbsp;*&amp;nbsp;8)&amp;nbsp;%&amp;nbsp;40)&amp;nbsp;/&amp;nbsp;5)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;output,&amp;nbsp;nil
end
--&amp;nbsp;6.&amp;nbsp;模块导出
return&amp;nbsp;_M&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;text-decoration-line: none; color: rgb(58, 110, 165); white-space: pre-wrap; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/h3&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.7&amp;nbsp;ip_utils.lua&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#该文件是IP地址校验工具模块，主要用于检查客户端IP是否在预定义的白名单中。支持两种匹配模式(精确匹配:直接判断IP是否存在于白名单/CIDR匹配:判断IP是否属于某个网段(如192.168.1.0/24))&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;# vi /usr/local/nginx/conf/auth/ip_utils.lua&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;1.&amp;nbsp;模块初始化与依赖
local&amp;nbsp;whitelist_cache&amp;nbsp;=&amp;nbsp;ngx.shared.ip_whitelist_cache&amp;nbsp;&amp;nbsp;--&amp;nbsp;Nginx&amp;nbsp;共享内存缓存
local&amp;nbsp;log&amp;nbsp;=&amp;nbsp;require(&amp;quot;log_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;日志工具
local&amp;nbsp;bit&amp;nbsp;=&amp;nbsp;require(&amp;quot;bit&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;位操作库（用于&amp;nbsp;CIDR&amp;nbsp;计算）

local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}&amp;nbsp;&amp;nbsp;--&amp;nbsp;模块对象

--&amp;nbsp;主校验函数:检查IP是否在白名单
function&amp;nbsp;_M.check_ip_whitelist(ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;高效缓存机制,使用Nginx共享内存缓存白名单，避免重复读取配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查白名单是否已初始化（缓存中应有&amp;nbsp;&amp;quot;initialized&amp;quot;&amp;nbsp;标记）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;whitelist_cache:get(&amp;quot;initialized&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;白名单未初始化&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;步骤1：精确匹配
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;先精确匹配再CIDR匹配，减少不必要的计算
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;whitelist_cache:get(&amp;quot;entry:&amp;quot;&amp;nbsp;..&amp;nbsp;ip)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.debug(&amp;quot;IP在白名单中（精确匹配）:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;步骤2：CIDR匹配（遍历所有&amp;nbsp;CIDR&amp;nbsp;格式的白名单条目）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;批量获取缓存键(get_keys)，提升CIDR匹配效率
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;keys&amp;nbsp;=&amp;nbsp;whitelist_cache:get_keys(1000)&amp;nbsp;&amp;nbsp;--&amp;nbsp;获取缓存中的前1000个键
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;_,&amp;nbsp;key&amp;nbsp;in&amp;nbsp;ipairs(keys)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;key:find(&amp;quot;entry:&amp;quot;,&amp;nbsp;1,&amp;nbsp;true)&amp;nbsp;==&amp;nbsp;1&amp;nbsp;then&amp;nbsp;&amp;nbsp;--&amp;nbsp;只处理以&amp;nbsp;&amp;quot;entry:&amp;quot;&amp;nbsp;开头的键
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cidr&amp;nbsp;=&amp;nbsp;key:sub(7)&amp;nbsp;&amp;nbsp;--&amp;nbsp;提取实际的&amp;nbsp;IP/CIDR
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;cidr:find(&amp;quot;/&amp;quot;)&amp;nbsp;and&amp;nbsp;_M.ip_in_cidr(ip,&amp;nbsp;cidr)&amp;nbsp;then&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查是否为&amp;nbsp;CIDR&amp;nbsp;格式并调用匹配函数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.debug(&amp;quot;IP在白名单中（CIDR匹配）:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;in&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;cidr)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;未匹配任何白名单条目
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
end

--&amp;nbsp;3.CIDR&amp;nbsp;匹配核心函数
--&amp;nbsp;判断一个IP是否属于某个CIDR网段(如192.168.1.0/24)
function&amp;nbsp;_M.ip_in_cidr(ip,&amp;nbsp;cidr)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解析输入IP为四个十进制数（如&amp;nbsp;&amp;quot;192.168.1.1&amp;quot;&amp;nbsp;→&amp;nbsp;{192,&amp;nbsp;168,&amp;nbsp;1,&amp;nbsp;1}）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip_octets&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;octet&amp;nbsp;in&amp;nbsp;ip:gmatch(&amp;quot;%d+&amp;quot;)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(ip_octets,&amp;nbsp;tonumber(octet))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#ip_octets&amp;nbsp;~=&amp;nbsp;4&amp;nbsp;then&amp;nbsp;return&amp;nbsp;false&amp;nbsp;end&amp;nbsp;&amp;nbsp;--&amp;nbsp;非合法IPv4地址
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将IP转换为32位整数（用于位运算）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;function&amp;nbsp;ip_to_int(octets)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;octets[1]&amp;nbsp;*&amp;nbsp;256^3&amp;nbsp;+&amp;nbsp;octets[2]&amp;nbsp;*&amp;nbsp;256^2&amp;nbsp;+&amp;nbsp;octets[3]&amp;nbsp;*&amp;nbsp;256&amp;nbsp;+&amp;nbsp;octets[4]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip_int&amp;nbsp;=&amp;nbsp;ip_to_int(ip_octets)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解析CIDR格式（如&amp;nbsp;&amp;quot;192.168.1.0/24&amp;quot;&amp;nbsp;→&amp;nbsp;base_ip=&amp;quot;192.168.1.0&amp;quot;,&amp;nbsp;mask=24）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;base_ip,&amp;nbsp;mask&amp;nbsp;=&amp;nbsp;cidr:match(&amp;quot;^([%d.]+)/(%d+)$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;base_ip&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ip&amp;nbsp;==&amp;nbsp;cidr&amp;nbsp;&amp;nbsp;--&amp;nbsp;非CIDR格式（如纯IP），直接精确匹配
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解析基础IP（CIDR中的网络地址部分）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;base_octets&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;octet&amp;nbsp;in&amp;nbsp;base_ip:gmatch(&amp;quot;%d+&amp;quot;)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(base_octets,&amp;nbsp;tonumber(octet))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#base_octets&amp;nbsp;~=&amp;nbsp;4&amp;nbsp;then&amp;nbsp;return&amp;nbsp;false&amp;nbsp;end&amp;nbsp;&amp;nbsp;--&amp;nbsp;非合法IPv4地址
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;base_int&amp;nbsp;=&amp;nbsp;ip_to_int(base_octets)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mask&amp;nbsp;=&amp;nbsp;tonumber(mask)&amp;nbsp;&amp;nbsp;--&amp;nbsp;CIDR掩码位数（如24）&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;通过位运算(bit.band、bit.lshift)实现高效网段匹配&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算32位掩码（如&amp;nbsp;/24&amp;nbsp;→&amp;nbsp;255.255.255.0&amp;nbsp;→&amp;nbsp;0xFFFFFF00）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;mask_int&amp;nbsp;=&amp;nbsp;mask&amp;nbsp;==&amp;nbsp;0&amp;nbsp;and&amp;nbsp;0&amp;nbsp;or&amp;nbsp;bit.lshift(0xFFFFFFFF,&amp;nbsp;32&amp;nbsp;-&amp;nbsp;mask)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;判断IP是否在CIDR网段内：
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;1.&amp;nbsp;将IP和基础IP分别与掩码做AND运算
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;2.&amp;nbsp;比较结果是否相同（相同则表示在同一网段）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;bit.band(ip_int,&amp;nbsp;mask_int)&amp;nbsp;==&amp;nbsp;bit.band(base_int,&amp;nbsp;mask_int)
end

return&amp;nbsp;_M&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.8&amp;nbsp;init_whitelist.lua&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#该文件是IP白名单加载模块，负责从配置文件读取IP白名单规则，并将其加载到Nginx共享内存中&lt;/p&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/init_whitelist.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;全局配置模块（包含白名单文件路径）
local&amp;nbsp;whitelist_cache&amp;nbsp;=&amp;nbsp;ngx.shared.ip_whitelist_cache&amp;nbsp;&amp;nbsp;--&amp;nbsp;Nginx共享内存
local&amp;nbsp;log&amp;nbsp;=&amp;nbsp;require(&amp;quot;log_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;日志工具
local&amp;nbsp;ip_utils&amp;nbsp;=&amp;nbsp;require(&amp;quot;ip_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;IP处理工具（此处未直接使用，但可能用于验证）

local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}&amp;nbsp;&amp;nbsp;--&amp;nbsp;模块对象
--&amp;nbsp;2.&amp;nbsp;白名单加载函数
function&amp;nbsp;_M.load_whitelist()&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;加载IP白名单...&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;步骤1：打开白名单文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;file,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;io.open(config.whitelist.file,&amp;nbsp;&amp;quot;r&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;file&amp;nbsp;then&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;无法打开白名单文件:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;步骤2：清空现有缓存（避免旧规则残留）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:flush_all()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;步骤3：逐行读取文件并加载到缓存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;count&amp;nbsp;=&amp;nbsp;0&amp;nbsp;&amp;nbsp;--&amp;nbsp;记录加载的规则数量
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;line&amp;nbsp;in&amp;nbsp;file:lines()&amp;nbsp;do&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;line&amp;nbsp;=&amp;nbsp;line:gsub(&amp;quot;^%s+&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;):gsub(&amp;quot;%s+$&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;去除首尾空格
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;跳过空行和注释（以#开头的行）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;line&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;and&amp;nbsp;not&amp;nbsp;line:find(&amp;quot;^#&amp;quot;)&amp;nbsp;then&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将IP/CIDR存入缓存，键格式为&amp;nbsp;&amp;quot;entry:IP/CIDR&amp;quot;，值为true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;共享内存高效存储：支持多进程共享/每条规则以entry:IP/CIDR为键，避免键冲突并便于后续查询
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:set(&amp;quot;entry:&amp;quot;&amp;nbsp;..&amp;nbsp;line,&amp;nbsp;true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;count&amp;nbsp;=&amp;nbsp;count&amp;nbsp;+&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file:close()&amp;nbsp;&amp;nbsp;--&amp;nbsp;关闭文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;步骤4：存储元数据（标记初始化状态和统计信息）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:set(&amp;quot;initialized&amp;quot;,&amp;nbsp;true)&amp;nbsp;&amp;nbsp;--&amp;nbsp;标记白名单已初始化
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:set(&amp;quot;last_loaded&amp;quot;,&amp;nbsp;ngx.time())&amp;nbsp;&amp;nbsp;--&amp;nbsp;记录加载时间戳,便于监控配置更新频率
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:set(&amp;quot;count&amp;quot;,&amp;nbsp;count)&amp;nbsp;&amp;nbsp;--&amp;nbsp;记录规则总数,用于运行时校验
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;白名单加载完成，共&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;count&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;条记录&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end

return&amp;nbsp;_M&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;&lt;/span&gt;# echo &amp;quot;127.0.0.1&amp;quot; &amp;gt;&amp;gt; /usr/local/nginx/conf/auth/ip_whitelist.txt&amp;nbsp; &amp;nbsp;#创建一个白名单文件防止启动报错&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.9 init_by_lua.lua&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#该文件是Nginx启动时的初始化脚本，其核心功能是在Nginx启动阶段加载IP白名单配置到共享内存，并标记初始化状态，为后续请求的 IP 校验做准备。&lt;/p&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/init_by_lua.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;加载白名单初始化模块
--&amp;nbsp;模块化设计:引入独立模块，将白名单加载逻辑与初始化入口分离，提高可维护性。
local&amp;nbsp;init_whitelist&amp;nbsp;=&amp;nbsp;require(&amp;quot;init_whitelist&amp;quot;)

--&amp;nbsp;执行白名单加载，返回布尔值表示成功/失败
local&amp;nbsp;success&amp;nbsp;=&amp;nbsp;init_whitelist.load_whitelist()

--&amp;nbsp;根据加载结果设置状态并记录日志
if&amp;nbsp;not&amp;nbsp;success&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;白名单初始化失败，系统将拒绝所有请求&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置初始化标记为&amp;nbsp;false（关键！后续请求校验会拒绝所有IP）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;共享内存缓存:使用ngx.shared.ip_whitelist_cache存储白名单数据，这是Nginx进程间共享的内存区域，所有工作进程均可访问。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.ip_whitelist_cache:set(&amp;quot;initialized&amp;quot;,&amp;nbsp;false)
else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.INFO,&amp;nbsp;&amp;quot;白名单初始化成功&amp;quot;)
end&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #B2A2C7;&quot;&gt;1.10&amp;nbsp;init_worker.lua&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;#&lt;/span&gt;该文件是 Nginx 工作进程初始化脚本，其核心功能是：在每个Nginx 工作进程启动时执行初始化任务、&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;实现IP白名单的定时自动刷新机制、&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;通过分布式锁保证多进程&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#该脚本与 init_by_lua.lua 互补，前者负责全局初始化，后者负责工作进程级别的初始化和定时任务&lt;/p&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/init_worker.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;全局配置
local&amp;nbsp;whitelist_cache&amp;nbsp;=&amp;nbsp;ngx.shared.ip_whitelist_cache&amp;nbsp;&amp;nbsp;--&amp;nbsp;共享内存缓存
local&amp;nbsp;log&amp;nbsp;=&amp;nbsp;require(&amp;quot;log_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;日志工具
local&amp;nbsp;MAX_RETRIES&amp;nbsp;=&amp;nbsp;3&amp;nbsp;&amp;nbsp;--&amp;nbsp;最大重试次数

--&amp;nbsp;2.安全加载模块工具函数
--&amp;nbsp;作用：使用&amp;nbsp;pcall&amp;nbsp;安全加载&amp;nbsp;Lua&amp;nbsp;模块，捕获并记录加载异常，避免因模块缺失导致进程崩溃。
local&amp;nbsp;function&amp;nbsp;safe_require(module_name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;status,&amp;nbsp;result&amp;nbsp;=&amp;nbsp;pcall(require,&amp;nbsp;module_name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;status&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;模块加载失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;module_name&amp;nbsp;..&amp;nbsp;&amp;quot;,&amp;nbsp;错误:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;result)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result
end

--&amp;nbsp;3.白名单重载函数
--&amp;nbsp;核心逻辑：检查是否达到配置的刷新间隔、通过共享内存锁(reload_lock)实现分布式锁机制、仅获取锁的进程执行白名单刷新，避免多进程重复操作
local&amp;nbsp;function&amp;nbsp;reload_whitelist()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;init_whitelist&amp;nbsp;=&amp;nbsp;safe_require(&amp;quot;init_whitelist&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;init_whitelist&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;now&amp;nbsp;=&amp;nbsp;ngx.time()&amp;nbsp;&amp;nbsp;--&amp;nbsp;当前时间戳
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;last_load_time&amp;nbsp;=&amp;nbsp;whitelist_cache:get(&amp;quot;last_loaded&amp;quot;)&amp;nbsp;or&amp;nbsp;0&amp;nbsp;&amp;nbsp;--&amp;nbsp;上次加载时间
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;判断是否达到刷新间隔（配置中定义，如&amp;nbsp;300&amp;nbsp;秒）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;now&amp;nbsp;-&amp;nbsp;last_load_time&amp;nbsp;&amp;gt;=&amp;nbsp;config.whitelist.reload_interval&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;亮点1:分布式锁机制：通过whitelist_cache:add(&amp;quot;reload_lock&amp;quot;)实现跨进程同步使用、锁有效期(30秒)防止死锁
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;whitelist_cache:add(&amp;quot;reload_lock&amp;quot;,&amp;nbsp;true,&amp;nbsp;30)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;工作进程&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;ngx.worker.pid()&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;重新加载白名单&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;success&amp;nbsp;=&amp;nbsp;init_whitelist.load_whitelist()&amp;nbsp;&amp;nbsp;--&amp;nbsp;调用实际加载函数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:delete(&amp;quot;reload_lock&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;释放锁
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;success
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elseif&amp;nbsp;err&amp;nbsp;~=&amp;nbsp;&amp;quot;exists&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;获取白名单加载锁失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true&amp;nbsp;&amp;nbsp;--&amp;nbsp;时间未到或锁被占用，视为成功
end

--&amp;nbsp;4.定时器初始化函数
--&amp;nbsp;关键点：使用ngx.timer.every创建周期性定时器\配置刷新间隔动态调整、处理premature标志，避免定时器提前终止时的异常
local&amp;nbsp;function&amp;nbsp;init_timers()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;亮点2：定时自动刷新：基于ngx.timer.every的非阻塞定时器，不影响正常请求处理、刷新间隔可配置，支持动态调整
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;ngx.timer.every(config.whitelist.reload_interval,&amp;nbsp;function(premature)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;premature&amp;nbsp;then&amp;nbsp;return&amp;nbsp;end&amp;nbsp;&amp;nbsp;--&amp;nbsp;定时器提前终止时退出
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reload_whitelist()&amp;nbsp;&amp;nbsp;--&amp;nbsp;执行白名单刷新
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;创建白名单定时任务失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;白名单定时任务创建成功，间隔:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;config.whitelist.reload_interval&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;秒&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end

--&amp;nbsp;5.工作进程初始化主函数
--&amp;nbsp;重试机制：首次加载白名单失败时自动重试(最多3次)、每次重试间隔0.5秒,提高初始化成功率
local&amp;nbsp;function&amp;nbsp;init_worker()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;工作进程初始化开始&amp;nbsp;(PID:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;ngx.worker.pid()&amp;nbsp;..&amp;nbsp;&amp;quot;)&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;首次加载白名单（带重试机制）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;retries&amp;nbsp;=&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;success&amp;nbsp;=&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;retries&amp;nbsp;&amp;lt;&amp;nbsp;MAX_RETRIES&amp;nbsp;and&amp;nbsp;not&amp;nbsp;success&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;success&amp;nbsp;=&amp;nbsp;reload_whitelist()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;retries&amp;nbsp;=&amp;nbsp;retries&amp;nbsp;+&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;success&amp;nbsp;then&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.sleep(0.5)&amp;nbsp;&amp;nbsp;--&amp;nbsp;失败时等待&amp;nbsp;0.5&amp;nbsp;秒后重试
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;success&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;白名单首次加载失败，尝试次数:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;MAX_RETRIES)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;初始化定时器
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init_timers()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;工作进程初始化完成&amp;nbsp;(PID:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;ngx.worker.pid()&amp;nbsp;..&amp;nbsp;&amp;quot;)&amp;quot;)
end

--&amp;nbsp;6.安全执行初始化
local&amp;nbsp;status,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;pcall(init_worker)
if&amp;nbsp;not&amp;nbsp;status&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;工作进程初始化异常:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err)
end&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #B2A2C7;&quot;&gt;1.11&amp;nbsp;auth.lua&lt;/span&gt;&lt;br/&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;#该文件是认证网关核心模块，负责对客户端请求进行多层级认证(IP 白名单、会话验证、TOTP 验证码），并实现免认证URL控制、会话管理、安全防护等功能。作为 Nginx 请求处理链路的关键环节，它决定请求是否被允许访问后端服务。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;# vi /usr/local/nginx/conf/auth/auth.lua&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;第一部分:模块依赖与初始化
local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;全局配置（含免认证规则、会话设置等）
local&amp;nbsp;redis_utils&amp;nbsp;=&amp;nbsp;require(&amp;quot;redis_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;Redis操作工具（会话存储、用户状态）
local&amp;nbsp;db_utils&amp;nbsp;=&amp;nbsp;require(&amp;quot;db_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;数据库工具（用户密钥查询）
local&amp;nbsp;ip_utils&amp;nbsp;=&amp;nbsp;require(&amp;quot;ip_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;IP白名单校验工具
local&amp;nbsp;totp_utils&amp;nbsp;=&amp;nbsp;require(&amp;quot;totp_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;TOTP验证码生成与验证
local&amp;nbsp;log&amp;nbsp;=&amp;nbsp;require(&amp;quot;log_utils&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;日志工具

--&amp;nbsp;第二部分:基础工具函数
--&amp;nbsp;2.1.&amp;nbsp;安全日志处理
local&amp;nbsp;function&amp;nbsp;safe_log_value(value)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;value&amp;nbsp;or&amp;nbsp;value&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;[空值]&amp;quot;&amp;nbsp;&amp;nbsp;--&amp;nbsp;明确标记空值，避免日志显示空白
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;过滤不可见字符（如控制字符、特殊符号），防止日志格式混乱
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;tostring(value):gsub(&amp;quot;[^%w%p&amp;nbsp;]&amp;quot;,&amp;nbsp;&amp;quot;?&amp;quot;)
end
--&amp;nbsp;2.2.免认证URL检查
--&amp;nbsp;2.2.1&amp;nbsp;免认证规则匹配逻辑
local&amp;nbsp;function&amp;nbsp;is_skip_auth(url,&amp;nbsp;skip_rules)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;精确匹配（URL完全一致）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;skip_rules[url]&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;前缀匹配（规则以&amp;quot;/&amp;quot;结尾时，匹配所有以此为前缀的URL）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;pattern,&amp;nbsp;_&amp;nbsp;in&amp;nbsp;pairs(skip_rules)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;string.sub(pattern,&amp;nbsp;-1)&amp;nbsp;==&amp;nbsp;&amp;quot;/&amp;quot;&amp;nbsp;and&amp;nbsp;string.sub(url,&amp;nbsp;1,&amp;nbsp;#pattern)&amp;nbsp;==&amp;nbsp;pattern&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
end

--&amp;nbsp;2.2.2&amp;nbsp;免认证检查入口
local&amp;nbsp;function&amp;nbsp;check_skip_auth()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;current_url&amp;nbsp;=&amp;nbsp;ngx.var.uri&amp;nbsp;&amp;nbsp;--&amp;nbsp;当前请求URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;current_host&amp;nbsp;=&amp;nbsp;ngx.var.host&amp;nbsp;&amp;nbsp;--&amp;nbsp;当前请求域名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_prefix&amp;nbsp;=&amp;nbsp;&amp;quot;[免认证检查]&amp;nbsp;&amp;quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查全局免认证URL（所有域名生效）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;config.skip_auth_global&amp;nbsp;and&amp;nbsp;type(config.skip_auth_global)&amp;nbsp;==&amp;nbsp;&amp;quot;table&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;is_skip_auth(current_url,&amp;nbsp;config.skip_auth_global)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;全局免认证URL，允许访问（url:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(current_url)&amp;nbsp;..&amp;nbsp;&amp;quot;）&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查域名级免认证URL（仅当前域名生效）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;config.skip_auth_domain&amp;nbsp;and&amp;nbsp;type(config.skip_auth_domain)&amp;nbsp;==&amp;nbsp;&amp;quot;table&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;domain_rules&amp;nbsp;=&amp;nbsp;config.skip_auth_domain[current_host]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;domain_rules&amp;nbsp;and&amp;nbsp;type(domain_rules)&amp;nbsp;==&amp;nbsp;&amp;quot;table&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;is_skip_auth(current_url,&amp;nbsp;domain_rules)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;域名级免认证URL，允许访问（host:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(current_host)&amp;nbsp;..&amp;nbsp;&amp;quot;,&amp;nbsp;url:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(current_url)&amp;nbsp;..&amp;nbsp;&amp;quot;）&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false&amp;nbsp;&amp;nbsp;--&amp;nbsp;需要认证
end
--&amp;nbsp;2.3.&amp;nbsp;客户端&amp;nbsp;IP&amp;nbsp;获取
local&amp;nbsp;function&amp;nbsp;get_client_ip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip&amp;nbsp;=&amp;nbsp;ngx.var.remote_addr&amp;nbsp;&amp;nbsp;--&amp;nbsp;直接客户端IP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;xff&amp;nbsp;=&amp;nbsp;ngx.var.http_x_forwarded_for&amp;nbsp;&amp;nbsp;--&amp;nbsp;代理链IP列表（X-Forwarded-For）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;xff&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;取最原始客户端IP（X-Forwarded-For格式：client_ip,&amp;nbsp;proxy1_ip,&amp;nbsp;proxy2_ip）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;first_ip&amp;nbsp;=&amp;nbsp;string.match(xff,&amp;nbsp;&amp;quot;^([^,]+)&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ip&amp;nbsp;=&amp;nbsp;first_ip&amp;nbsp;or&amp;nbsp;ip
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ip
end

--&amp;nbsp;第三部分：认证页面渲染
--&amp;nbsp;3.1&amp;nbsp;HTML模板加载
--&amp;nbsp;从文件系统加载HTML模板，支持通过配置动态指定模板路径，便于前端页面定制
local&amp;nbsp;function&amp;nbsp;load_template(name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;name&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;模板名称为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;&amp;lt;p&amp;gt;系统错误：模板名称为空&amp;lt;/p&amp;gt;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;path&amp;nbsp;=&amp;nbsp;config.templates&amp;nbsp;and&amp;nbsp;config.templates[name]&amp;nbsp;&amp;nbsp;--&amp;nbsp;从配置获取模板路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;path&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;模板路径未配置，模板名称:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;&amp;lt;p&amp;gt;系统错误：模板未配置&amp;lt;/p&amp;gt;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;读取模板文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;file,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;io.open(path,&amp;nbsp;&amp;quot;r&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;file&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;模板加载失败（路径:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;path&amp;nbsp;..&amp;nbsp;&amp;quot;）：&amp;quot;&amp;nbsp;..&amp;nbsp;(err&amp;nbsp;or&amp;nbsp;&amp;quot;未知错误&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;&amp;lt;p&amp;gt;系统错误：模板加载失败&amp;lt;/p&amp;gt;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;content&amp;nbsp;=&amp;nbsp;file:read(&amp;quot;*a&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;content&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;lt;p&amp;gt;系统错误：模板内容为空&amp;lt;/p&amp;gt;&amp;quot;
end
--&amp;nbsp;3.2.&amp;nbsp;认证弹窗渲染
--&amp;nbsp;生成认证弹窗页面，支持动态显示错误信息和剩余尝试次数，同时通过响应头禁用缓存，确保页面实时性。
local&amp;nbsp;function&amp;nbsp;show_auth_popup(redirect_url,&amp;nbsp;error_msg,&amp;nbsp;remaining_attempts)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_msg&amp;nbsp;=&amp;nbsp;error_msg&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;remaining_attempts&amp;nbsp;=&amp;nbsp;remaining_attempts&amp;nbsp;or&amp;nbsp;(config.auth&amp;nbsp;and&amp;nbsp;config.auth.max_attempts&amp;nbsp;or&amp;nbsp;3)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;错误信息显示控制（有错误时显示，否则隐藏）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;display&amp;nbsp;=&amp;nbsp;error_msg&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;and&amp;nbsp;&amp;quot;block&amp;quot;&amp;nbsp;or&amp;nbsp;&amp;quot;none&amp;quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;template&amp;nbsp;=&amp;nbsp;load_template(&amp;quot;auth_popup&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;加载弹窗模板
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;替换模板变量（将动态内容注入HTML）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template&amp;nbsp;=&amp;nbsp;string.gsub(template,&amp;nbsp;&amp;quot;{redirect_url}&amp;quot;,&amp;nbsp;redirect_url&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template&amp;nbsp;=&amp;nbsp;string.gsub(template,&amp;nbsp;&amp;quot;{error_msg}&amp;quot;,&amp;nbsp;error_msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template&amp;nbsp;=&amp;nbsp;string.gsub(template,&amp;nbsp;&amp;quot;{error_display}&amp;quot;,&amp;nbsp;display)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template&amp;nbsp;=&amp;nbsp;string.gsub(template,&amp;nbsp;&amp;quot;{remaining_attempts}&amp;quot;,&amp;nbsp;tostring(remaining_attempts))

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置响应头（禁用缓存、指定HTML类型）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Content-Type&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;text/html;&amp;nbsp;charset=utf-8&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Cache-Control&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;no-store,&amp;nbsp;no-cache,&amp;nbsp;must-revalidate&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Pragma&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;no-cache&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Expires&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;0&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(template)&amp;nbsp;&amp;nbsp;--&amp;nbsp;输出HTML内容
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
end

--&amp;nbsp;第四部分：用户状态管理
--&amp;nbsp;4.1.封禁状态检查
--&amp;nbsp;作用：检查用户是否因多次认证失败被封禁，为登录限流提供支持
local&amp;nbsp;function&amp;nbsp;check_ban_status(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;username&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;&amp;quot;用户名不能为空&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;调用redis_utils查询封禁状态（封装Redis操作）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;is_banned&amp;nbsp;=&amp;nbsp;redis_utils.is_user_banned(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;is_banned&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true,&amp;nbsp;&amp;quot;账号已封禁，请20分钟后重试&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;nil
end
--&amp;nbsp;4.2.认证尝试次数记录
--&amp;nbsp;核心机制：通过Redis的INCR命令原子记录失败次数，达到阈值时自动封禁用户，防止暴力破解。
local&amp;nbsp;function&amp;nbsp;record_attempt(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;username&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;记录尝试次数失败：用户名为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(config.auth&amp;nbsp;and&amp;nbsp;config.auth.max_attempts&amp;nbsp;or&amp;nbsp;3)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;调用redis_utils记录失败次数（封装Redis自增操作）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;attempts&amp;nbsp;=&amp;nbsp;redis_utils.record_attempt(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;attempts&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;记录尝试次数失败，用户:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(config.auth&amp;nbsp;and&amp;nbsp;config.auth.max_attempts&amp;nbsp;or&amp;nbsp;3)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;达到最大尝试次数时自动封禁
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;attempts&amp;nbsp;&amp;lt;=&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;redis_utils.ban_user(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(&amp;quot;用户尝试次数超限，已封禁:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;attempts
end

--&amp;nbsp;第五部分：Cookie操作(会话管理核心)
--&amp;nbsp;5.1.&amp;nbsp;Cookie&amp;nbsp;读取
local&amp;nbsp;function&amp;nbsp;get_cookie(name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;name&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;获取Cookie失败：Cookie名称为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cookie_header&amp;nbsp;=&amp;nbsp;ngx.var.http_cookie&amp;nbsp;&amp;nbsp;--&amp;nbsp;原始Cookie头
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;请求Cookie头:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(cookie_header))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;cookie_header&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;未找到Cookie头&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解析Cookie（按&amp;quot;;&amp;quot;分割，提取目标Cookie）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;values&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;cookie&amp;nbsp;in&amp;nbsp;string.gmatch(cookie_header,&amp;nbsp;&amp;quot;[^;]+&amp;quot;)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;key,&amp;nbsp;value&amp;nbsp;=&amp;nbsp;cookie:match(&amp;quot;^%s*(.-)%s*=%s*(.-)%s*$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;key&amp;nbsp;and&amp;nbsp;key&amp;nbsp;==&amp;nbsp;name&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(values,&amp;nbsp;value&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理多值Cookie（取第一个）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#values&amp;nbsp;==&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;未找到指定Cookie:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elseif&amp;nbsp;#values&amp;nbsp;&amp;gt;&amp;nbsp;1&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(&amp;quot;发现&amp;quot;&amp;nbsp;..&amp;nbsp;#values&amp;nbsp;..&amp;nbsp;&amp;quot;个同名Cookie（名称:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;name&amp;nbsp;..&amp;nbsp;&amp;quot;），使用第一个值&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;values[1]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;清洗Cookie值（仅保留字母数字，避免注入风险）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;clean_value&amp;nbsp;=&amp;nbsp;string.gsub(values[1],&amp;nbsp;&amp;quot;%s+&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;找到Cookie:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;name&amp;nbsp;..&amp;nbsp;&amp;quot;（清洗后:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(clean_value)&amp;nbsp;..&amp;nbsp;&amp;quot;）&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;clean_value
end
--&amp;nbsp;5.2.&amp;nbsp;Cookie&amp;nbsp;设置
local&amp;nbsp;function&amp;nbsp;set_cookie(name,&amp;nbsp;value,&amp;nbsp;expire_seconds)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;name&amp;nbsp;or&amp;nbsp;not&amp;nbsp;value&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;设置Cookie失败：名称或值为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;从配置读取Cookie属性（带默认值）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;session_conf&amp;nbsp;=&amp;nbsp;config.session&amp;nbsp;or&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;domain&amp;nbsp;=&amp;nbsp;session_conf.cookie_domain&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;path&amp;nbsp;=&amp;nbsp;session_conf.cookie_path&amp;nbsp;or&amp;nbsp;&amp;quot;/&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;secure&amp;nbsp;=&amp;nbsp;session_conf.cookie_secure&amp;nbsp;or&amp;nbsp;false&amp;nbsp;&amp;nbsp;--&amp;nbsp;HTTPS时启用Secure标记
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;http_only&amp;nbsp;=&amp;nbsp;session_conf.cookie_http_only&amp;nbsp;or&amp;nbsp;true&amp;nbsp;&amp;nbsp;--&amp;nbsp;防止JS读取

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;清洗会话ID（仅保留字母数字，避免注入风险）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;safe_value&amp;nbsp;=&amp;nbsp;string.gsub(value,&amp;nbsp;&amp;quot;[^a-zA-Z0-9]&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;safe_value&amp;nbsp;~=&amp;nbsp;value&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(&amp;quot;会话ID包含特殊字符，已清洗:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(value)&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;→&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_value)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value&amp;nbsp;=&amp;nbsp;safe_value
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;构建Cookie字符串
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cookie&amp;nbsp;=&amp;nbsp;string.format(&amp;quot;%s=%s&amp;quot;,&amp;nbsp;name,&amp;nbsp;value)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;添加domain（支持子域名共享）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;domain&amp;nbsp;and&amp;nbsp;domain&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;string.match(domain,&amp;nbsp;&amp;quot;^%.&amp;quot;)&amp;nbsp;and&amp;nbsp;domain&amp;nbsp;~=&amp;nbsp;ngx.var.host&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;domain&amp;nbsp;=&amp;nbsp;&amp;quot;.&amp;quot;&amp;nbsp;..&amp;nbsp;domain&amp;nbsp;&amp;nbsp;--&amp;nbsp;自动补全前缀点（如&amp;nbsp;&amp;quot;example.com&amp;quot;&amp;nbsp;→&amp;nbsp;&amp;quot;.example.com&amp;quot;）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie&amp;nbsp;=&amp;nbsp;cookie&amp;nbsp;..&amp;nbsp;&amp;quot;;&amp;nbsp;Domain=&amp;quot;&amp;nbsp;..&amp;nbsp;domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;添加路径和过期时间
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie&amp;nbsp;=&amp;nbsp;cookie&amp;nbsp;..&amp;nbsp;&amp;quot;;&amp;nbsp;Path=&amp;quot;&amp;nbsp;..&amp;nbsp;path
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;expire_seconds&amp;nbsp;and&amp;nbsp;expire_seconds&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;expire_time&amp;nbsp;=&amp;nbsp;ngx.cookie_time(ngx.time()&amp;nbsp;+&amp;nbsp;expire_seconds)&amp;nbsp;&amp;nbsp;--&amp;nbsp;转换为Cookie时间格式
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie&amp;nbsp;=&amp;nbsp;cookie&amp;nbsp;..&amp;nbsp;&amp;quot;;&amp;nbsp;Expires=&amp;quot;&amp;nbsp;..&amp;nbsp;expire_time&amp;nbsp;..&amp;nbsp;&amp;quot;;&amp;nbsp;Max-Age=&amp;quot;&amp;nbsp;..&amp;nbsp;expire_seconds
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;安全属性
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;secure&amp;nbsp;and&amp;nbsp;ngx.var.scheme&amp;nbsp;==&amp;nbsp;&amp;quot;https&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie&amp;nbsp;=&amp;nbsp;cookie&amp;nbsp;..&amp;nbsp;&amp;quot;;&amp;nbsp;Secure&amp;quot;&amp;nbsp;&amp;nbsp;--&amp;nbsp;仅HTTPS传输
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;http_only&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie&amp;nbsp;=&amp;nbsp;cookie&amp;nbsp;..&amp;nbsp;&amp;quot;;&amp;nbsp;HttpOnly&amp;quot;&amp;nbsp;&amp;nbsp;--&amp;nbsp;禁止JS访问
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;SameSite属性（防CSRF）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie&amp;nbsp;=&amp;nbsp;cookie&amp;nbsp;..&amp;nbsp;&amp;quot;;&amp;nbsp;SameSite=&amp;quot;&amp;nbsp;..&amp;nbsp;(secure&amp;nbsp;and&amp;nbsp;&amp;quot;None&amp;quot;&amp;nbsp;or&amp;nbsp;&amp;quot;Lax&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Set-Cookie&amp;quot;]&amp;nbsp;=&amp;nbsp;cookie
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;设置Cookie:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;cookie)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;value
end
--&amp;nbsp;5.3.&amp;nbsp;Cookie删除
--&amp;nbsp;通过多路径/多域名组合删除&amp;nbsp;Cookie，确保在各种配置下都能彻底清除无效会话，避免残留的无效Cookie导致认证异常
local&amp;nbsp;function&amp;nbsp;delete_cookie(name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;name&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(&amp;quot;删除Cookie失败：名称为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;session_conf&amp;nbsp;=&amp;nbsp;config.session&amp;nbsp;or&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;domain&amp;nbsp;=&amp;nbsp;session_conf.cookie_domain&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;path&amp;nbsp;=&amp;nbsp;session_conf.cookie_path&amp;nbsp;or&amp;nbsp;&amp;quot;/&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;构建删除Cookie（过期时间设为过去）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cookie&amp;nbsp;=&amp;nbsp;string.format(&amp;quot;%s=deleted;&amp;nbsp;Path=%s;&amp;nbsp;Expires=Thu,&amp;nbsp;01&amp;nbsp;Jan&amp;nbsp;1970&amp;nbsp;00:00:00&amp;nbsp;GMT&amp;quot;,&amp;nbsp;name,&amp;nbsp;path)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;domain&amp;nbsp;and&amp;nbsp;domain&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;string.match(domain,&amp;nbsp;&amp;quot;^%.&amp;quot;)&amp;nbsp;and&amp;nbsp;domain&amp;nbsp;~=&amp;nbsp;ngx.var.host&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;domain&amp;nbsp;=&amp;nbsp;&amp;quot;.&amp;quot;&amp;nbsp;..&amp;nbsp;domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie&amp;nbsp;=&amp;nbsp;cookie&amp;nbsp;..&amp;nbsp;&amp;quot;;&amp;nbsp;Domain=&amp;quot;&amp;nbsp;..&amp;nbsp;domain
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;关键修复：覆盖所有可能的Cookie路径/域名组合（避免残留）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cookies&amp;nbsp;=&amp;nbsp;{cookie}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;添加根路径Cookie删除（防止路径不匹配）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;path&amp;nbsp;~=&amp;nbsp;&amp;quot;/&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(cookies,&amp;nbsp;string.format(&amp;quot;%s=deleted;&amp;nbsp;Path=/;&amp;nbsp;Expires=Thu,&amp;nbsp;01&amp;nbsp;Jan&amp;nbsp;1970&amp;nbsp;00:00:00&amp;nbsp;GMT;&amp;nbsp;Domain=%s&amp;quot;,&amp;nbsp;name,&amp;nbsp;domain))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;添加无域名Cookie删除（兼容不同域名配置）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(cookies,&amp;nbsp;string.format(&amp;quot;%s=deleted;&amp;nbsp;Path=%s;&amp;nbsp;Expires=Thu,&amp;nbsp;01&amp;nbsp;Jan&amp;nbsp;1970&amp;nbsp;00:00:00&amp;nbsp;GMT&amp;quot;,&amp;nbsp;name,&amp;nbsp;path))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Set-Cookie&amp;quot;]&amp;nbsp;=&amp;nbsp;cookies
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;删除Cookie:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;table.concat(cookies,&amp;nbsp;&amp;quot;;&amp;nbsp;&amp;quot;))
end

--&amp;nbsp;第六部分：主认证流程（核心逻辑）
local&amp;nbsp;function&amp;nbsp;authenticate()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;client_ip&amp;nbsp;=&amp;nbsp;get_client_ip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_prefix&amp;nbsp;=&amp;nbsp;&amp;quot;IP:&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;nbsp;-&amp;nbsp;&amp;quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;6.1.&amp;nbsp;全局禁用缓存（避免认证页面被缓存）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Cache-Control&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;no-store,&amp;nbsp;no-cache,&amp;nbsp;must-revalidate&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Pragma&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;no-cache&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Expires&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;0&amp;quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;6.2.&amp;nbsp;免认证URL检查（优先级最高）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;check_skip_auth()&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;URL在免认证列表中，允许访问&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;6.3.&amp;nbsp;IP白名单检查（跳过认证）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ip_utils.check_ip_whitelist(client_ip)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;IP在白名单中，允许访问&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;6.4.&amp;nbsp;会话验证（从Cookie获取session_id）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;session_conf&amp;nbsp;=&amp;nbsp;config.session&amp;nbsp;or&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;session_id&amp;nbsp;=&amp;nbsp;get_cookie(session_conf.cookie_name&amp;nbsp;or&amp;nbsp;&amp;quot;session_id&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;当前会话ID:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(session_id))

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;session_id&amp;nbsp;and&amp;nbsp;session_id&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;调用redis_utils统一处理会话验证（封装本地缓存+Redis查询）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;username&amp;nbsp;=&amp;nbsp;redis_utils.get_user_session(session_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证用户名有效性（防脏数据）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;username&amp;nbsp;and&amp;nbsp;type(username)&amp;nbsp;==&amp;nbsp;&amp;quot;string&amp;quot;&amp;nbsp;and&amp;nbsp;username&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;and&amp;nbsp;not&amp;nbsp;string.find(username,&amp;nbsp;&amp;quot;userdata:&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;会话有效，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;session_id:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(session_id)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;用户:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;会话无效（用户名无效），清理资源，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;session_id:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(session_id)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;用户名:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;会话无效：强制删除Cookie并清理本地缓存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;delete_cookie(session_conf.cookie_name&amp;nbsp;or&amp;nbsp;&amp;quot;session_id&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;额外清理本地缓存（双重保障）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ngx.shared.session_cache&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cache_key&amp;nbsp;=&amp;nbsp;&amp;quot;session:&amp;quot;&amp;nbsp;..&amp;nbsp;(session_conf.cookie_domain&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;..&amp;nbsp;&amp;quot;:&amp;quot;&amp;nbsp;..&amp;nbsp;session_id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.session_cache:delete(cache_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(&amp;quot;[本地缓存-清理]&amp;nbsp;主动删除无效会话（session_id:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(session_id)&amp;nbsp;..&amp;nbsp;&amp;quot;]）&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;未获取到有效session_id（session_id为空或空白）&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;6.5.&amp;nbsp;处理POST认证请求（提交用户名和验证码）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ngx.var.request_method&amp;nbsp;==&amp;nbsp;&amp;quot;POST&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.req.read_body()&amp;nbsp;&amp;nbsp;--&amp;nbsp;读取POST请求体
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;args&amp;nbsp;=&amp;nbsp;ngx.req.get_post_args()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;username&amp;nbsp;=&amp;nbsp;args.username
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;code&amp;nbsp;=&amp;nbsp;args.code
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;redirect_url&amp;nbsp;=&amp;nbsp;args.redirect_url&amp;nbsp;or&amp;nbsp;ngx.var.request_uri

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;参数校验：用户名和验证码不能为空
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;username&amp;nbsp;or&amp;nbsp;not&amp;nbsp;code&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;参数缺失，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;code:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(code)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;show_auth_popup(redirect_url,&amp;nbsp;&amp;quot;请输入用户名和验证码&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查用户是否被封禁
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;is_banned,&amp;nbsp;ban_msg&amp;nbsp;=&amp;nbsp;check_ban_status(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;is_banned&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;用户已封禁，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;封禁原因:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(ban_msg)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;show_auth_popup(redirect_url,&amp;nbsp;ban_msg,&amp;nbsp;0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;获取用户TOTP密钥（Redis优先，数据库兜底）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;secret,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;redis_utils.get_user_secret(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;明确判断用户不存在（Redis无记录）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;secret&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;err&amp;nbsp;==&amp;nbsp;&amp;quot;user_not_found&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;用户不存在（Redis无记录），&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;获取用户密钥失败，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;错误:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(err)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;attempts&amp;nbsp;=&amp;nbsp;record_attempt(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;show_auth_popup(redirect_url,&amp;nbsp;&amp;quot;用户不存在&amp;quot;,&amp;nbsp;attempts)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;过滤无效密钥（如null/空值）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;secret&amp;nbsp;==&amp;nbsp;&amp;quot;null&amp;quot;&amp;nbsp;or&amp;nbsp;secret&amp;nbsp;==&amp;nbsp;&amp;quot;NULL&amp;quot;&amp;nbsp;or&amp;nbsp;secret&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;用户密钥无效，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(secret)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;attempts&amp;nbsp;=&amp;nbsp;record_attempt(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;show_auth_popup(redirect_url,&amp;nbsp;&amp;quot;用户状态异常，请联系管理员&amp;quot;,&amp;nbsp;attempts)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;数据库兜底查询（确保数据一致性）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;db_secret&amp;nbsp;=&amp;nbsp;db_utils.get_user_secret(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;db_secret&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;用户不存在（数据库无记录），&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;清理Redis无效记录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;redis_utils.set_user_secret(username,&amp;nbsp;nil)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;attempts&amp;nbsp;=&amp;nbsp;record_attempt(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;show_auth_popup(redirect_url,&amp;nbsp;&amp;quot;用户不存在&amp;quot;,&amp;nbsp;attempts)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;同步数据库中的密钥到Redis（双向一致性保障）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;db_secret&amp;nbsp;and&amp;nbsp;db_secret&amp;nbsp;~=&amp;nbsp;secret&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;更新Redis中的用户密钥，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;redis_utils.set_user_secret(username,&amp;nbsp;db_secret)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;secret&amp;nbsp;=&amp;nbsp;db_secret
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证TOTP验证码
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;开始验证TOTP，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;auth_conf&amp;nbsp;=&amp;nbsp;config.auth&amp;nbsp;or&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;is_valid,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;totp_utils.verify_totp(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;secret,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;code,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auth_conf.totp_time_step&amp;nbsp;or&amp;nbsp;30,&amp;nbsp;&amp;nbsp;--&amp;nbsp;时间步长（秒）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auth_conf.code_length&amp;nbsp;or&amp;nbsp;6&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证码长度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;is_valid&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;认证成功：创建新会话
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;redis_utils.reset_attempts(username)&amp;nbsp;&amp;nbsp;--&amp;nbsp;重置失败次数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;生成随机会话ID（包含时间戳和随机数，确保唯一性）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;new_session_id&amp;nbsp;=&amp;nbsp;ngx.md5(username&amp;nbsp;..&amp;nbsp;ngx.time()&amp;nbsp;..&amp;nbsp;math.random()&amp;nbsp;..&amp;nbsp;client_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用redis_utils统一创建会话
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;expire_seconds&amp;nbsp;=&amp;nbsp;session_conf.expire_time&amp;nbsp;or&amp;nbsp;3600
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;create_ok&amp;nbsp;=&amp;nbsp;redis_utils.create_user_session(new_session_id,&amp;nbsp;username,&amp;nbsp;expire_seconds)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;create_ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.error(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;会话创建失败，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;session_id:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(new_session_id)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;show_auth_popup(redirect_url,&amp;nbsp;&amp;quot;系统错误：会话创建失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置会话Cookie（与Redis有效期一致）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set_cookie(session_conf.cookie_name&amp;nbsp;or&amp;nbsp;&amp;quot;session_id&amp;quot;,&amp;nbsp;new_session_id,&amp;nbsp;expire_seconds)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;重定向到认证前的URL
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;认证成功，重定向到:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(redirect_url)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;session_id:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(new_session_id)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Location&amp;quot;]&amp;nbsp;=&amp;nbsp;redirect_url
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(302)&amp;nbsp;&amp;nbsp;--&amp;nbsp;执行重定向
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;认证失败：记录尝试次数并提示错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;attempts&amp;nbsp;=&amp;nbsp;record_attempt(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.warn(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;TOTP验证失败，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;username:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(username)&amp;nbsp;..&amp;nbsp;&amp;quot;],&amp;nbsp;&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;剩余次数:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;attempts
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;error_msg&amp;nbsp;=&amp;nbsp;&amp;quot;验证码错误&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;attempts&amp;nbsp;&amp;lt;=&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_msg&amp;nbsp;=&amp;nbsp;&amp;quot;尝试次数过多，账号已被封禁20分钟&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;show_auth_popup(redirect_url,&amp;nbsp;error_msg,&amp;nbsp;attempts)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;6.6.&amp;nbsp;显示认证弹窗（GET请求或认证失败时）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log.info(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_prefix&amp;nbsp;..&amp;nbsp;&amp;quot;显示认证弹窗，&amp;quot;&amp;nbsp;..
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;当前URL:&amp;nbsp;[&amp;quot;&amp;nbsp;..&amp;nbsp;safe_log_value(ngx.var.request_uri)&amp;nbsp;..&amp;nbsp;&amp;quot;]&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;show_auth_popup(ngx.var.request_uri)
end

--&amp;nbsp;第七部分：认证结果处理&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
--&amp;nbsp;执行认证流程并返回结果
local&amp;nbsp;auth_result&amp;nbsp;=&amp;nbsp;authenticate()
if&amp;nbsp;not&amp;nbsp;auth_result&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.HTTP_UNAUTHORIZED)&amp;nbsp;&amp;nbsp;--&amp;nbsp;认证失败，返回401
else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.DECLINED)&amp;nbsp;&amp;nbsp;--&amp;nbsp;认证成功，继续后续处理
end&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;text-decoration-line: none; color: rgb(58, 110, 165); white-space: pre-wrap; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/h3&gt;&lt;h3&gt;&lt;span style=&quot;color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; text-wrap-mode: wrap; background-color: #FFFFFF;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.12&amp;nbsp;auth_popup.html&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# mkdir /usr/local/nginx/conf/auth/templates&lt;/p&gt;&lt;p&gt;#&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;text-wrap-mode: wrap; color: rgba(0, 0, 0, 0.85); font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 16px; background-color: #FFFFFF;&quot;&gt;该文件是认证系统的前端弹窗模板，用于展示用户登录界面。当用户访问需要认证的资源且尚未登录时，系统会加载此模板生成一个全屏遮罩层，要求用户输入用户名和TOTP动态验证码进行身份验证。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/templates/auth_popup.html&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;lt;!DOCTYPE&amp;nbsp;html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;title&amp;gt;身份验证&amp;lt;/title&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;style&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/*&amp;nbsp;全屏遮罩层：覆盖整个页面，半透明黑色背景，居中显示认证框&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.auth-overlay&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;position:&amp;nbsp;fixed;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;top:&amp;nbsp;0;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;left:&amp;nbsp;0;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:&amp;nbsp;100%;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;height:&amp;nbsp;100%;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background:&amp;nbsp;rgba(0,0,0,0.5);&amp;nbsp;/*&amp;nbsp;半透明黑色，阻止用户操作底层页面&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;display:&amp;nbsp;flex;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;justify-content:&amp;nbsp;center;&amp;nbsp;/*&amp;nbsp;水平居中&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;align-items:&amp;nbsp;center;&amp;nbsp;/*&amp;nbsp;垂直居中&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;z-index:&amp;nbsp;9999;&amp;nbsp;/*&amp;nbsp;确保遮罩层在最上层&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/*&amp;nbsp;认证框：白色背景，带阴影和圆角，固定宽度&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.auth-box&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background:&amp;nbsp;white;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;padding:&amp;nbsp;20px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;border-radius:&amp;nbsp;5px;&amp;nbsp;/*&amp;nbsp;圆角边框&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:&amp;nbsp;300px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;box-shadow:&amp;nbsp;0&amp;nbsp;0&amp;nbsp;10px&amp;nbsp;rgba(0,0,0,0.3);&amp;nbsp;/*&amp;nbsp;阴影效果增强层次感&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/*&amp;nbsp;错误提示区域：红色文字，由后端控制显示/隐藏&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.auth-error&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color:&amp;nbsp;red;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;margin-bottom:&amp;nbsp;10px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;display:&amp;nbsp;{error_display};&amp;nbsp;/*&amp;nbsp;{error_display}由后端替换为block/none&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/*&amp;nbsp;安全提示：灰色小字，告知用户封禁规则&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.auth-note&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color:&amp;nbsp;#666;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-size:&amp;nbsp;12px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;margin-bottom:&amp;nbsp;15px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/*&amp;nbsp;表单样式：输入框和按钮占满宽度&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.auth-form&amp;nbsp;input&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:&amp;nbsp;100%;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;padding:&amp;nbsp;8px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;margin-bottom:&amp;nbsp;10px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;box-sizing:&amp;nbsp;border-box;&amp;nbsp;/*&amp;nbsp;确保padding不影响总宽度&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/*&amp;nbsp;提交按钮：绿色背景，白色文字，圆角&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.auth-form&amp;nbsp;button&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:&amp;nbsp;100%;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;padding:&amp;nbsp;10px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background:&amp;nbsp;#4CAF50;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color:&amp;nbsp;white;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;border:&amp;nbsp;none;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;border-radius:&amp;nbsp;3px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor:&amp;nbsp;pointer;&amp;nbsp;/*&amp;nbsp;鼠标悬停显示手型&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/*&amp;nbsp;剩余尝试次数：橙色文字，提示用户剩余机会&amp;nbsp;*/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.auth-attempts&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color:&amp;nbsp;orange;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;margin-top:&amp;nbsp;10px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-size:&amp;nbsp;14px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;全屏遮罩层：阻止用户操作页面其他内容&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;auth-overlay&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;认证框：包含所有表单元素&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;auth-box&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;h3&amp;gt;系统认证&amp;lt;/h3&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;错误提示区域：显示后端返回的错误信息（如验证码错误）&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;auth-error&amp;quot;&amp;nbsp;id=&amp;quot;error-message&amp;quot;&amp;gt;{error_msg}&amp;lt;/div&amp;gt;&amp;nbsp;&amp;lt;!--&amp;nbsp;{error_msg}由后端替换为具体错误文本&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;安全提示文本：告知用户连续错误的后果&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;auth-note&amp;quot;&amp;gt;提示：连续错误3次，账号将被临时锁定20分钟&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;认证表单：提交用户名和验证码到后端&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;form&amp;nbsp;class=&amp;quot;auth-form&amp;quot;&amp;nbsp;method=&amp;quot;post&amp;quot;&amp;nbsp;action=&amp;quot;{redirect_url}&amp;quot;&amp;gt;&amp;nbsp;&amp;lt;!--&amp;nbsp;{redirect_url}由后端替换为原始请求URL&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;用户名输入框：必填，使用localStorage保存&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;input&amp;nbsp;type=&amp;quot;text&amp;quot;&amp;nbsp;name=&amp;quot;username&amp;quot;&amp;nbsp;placeholder=&amp;quot;用户名&amp;quot;&amp;nbsp;required&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;验证码输入框：必填，6位动态码&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;input&amp;nbsp;type=&amp;quot;text&amp;quot;&amp;nbsp;name=&amp;quot;code&amp;quot;&amp;nbsp;placeholder=&amp;quot;6位动态验证码&amp;quot;&amp;nbsp;required&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;隐藏字段：保存原始URL，用于认证成功后重定向&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;input&amp;nbsp;type=&amp;quot;hidden&amp;quot;&amp;nbsp;name=&amp;quot;redirect_url&amp;quot;&amp;nbsp;value=&amp;quot;{redirect_url}&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;提交按钮：触发表单提交到后端&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;button&amp;nbsp;type=&amp;quot;submit&amp;quot;&amp;gt;验证&amp;lt;/button&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/form&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--&amp;nbsp;剩余尝试次数：显示后端计算的剩余次数&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;auth-attempts&amp;quot;&amp;gt;剩余尝试次数:&amp;nbsp;{remaining_attempts}&amp;lt;/div&amp;gt;&amp;nbsp;&amp;lt;!--&amp;nbsp;{remaining_attempts}由后端替换为数字&amp;nbsp;--&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;script&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;页面加载完成后执行：优化用户体验
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;document.addEventListener(&amp;#39;DOMContentLoaded&amp;#39;,&amp;nbsp;function()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;获取用户名和验证码输入框元素
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;usernameInput&amp;nbsp;=&amp;nbsp;document.querySelector(&amp;#39;input[name=&amp;quot;username&amp;quot;]&amp;#39;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;codeInput&amp;nbsp;=&amp;nbsp;document.querySelector(&amp;#39;input[name=&amp;quot;code&amp;quot;]&amp;#39;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;从localStorage读取上次保存的用户名并自动填充
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;savedUsername&amp;nbsp;=&amp;nbsp;localStorage.getItem(&amp;#39;auth_username&amp;#39;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(savedUsername)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;usernameInput.value&amp;nbsp;=&amp;nbsp;savedUsername;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;每次加载页面时清空验证码输入框（避免重复提交旧验证码）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;codeInput.value&amp;nbsp;=&amp;nbsp;&amp;#39;&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;监听用户名输入变化，实时保存到localStorage（避免用户重复输入）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;usernameInput.addEventListener(&amp;#39;input&amp;#39;,&amp;nbsp;function()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;localStorage.setItem(&amp;#39;auth_username&amp;#39;,&amp;nbsp;this.value);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;二、验证测试&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;2.1 IP不在白名单中登录弹窗&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#vi&amp;nbsp;/usr/local/nginx/conf/conf.d/lua.test.com.conf&amp;nbsp; &amp;nbsp;#配置一个最简单的域名进行测试,记得自己配置下hosts文件哦&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name&amp;nbsp;lua.test.com;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root&amp;nbsp;/opt/web;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;charset&amp;nbsp;utf-8;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_log&amp;nbsp;/usr/local/nginx/logs/error.log&amp;nbsp;debug;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_by_lua_file&amp;nbsp;/usr/local/nginx/conf/auth/auth.lua;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root&amp;nbsp;/opt/web;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;index&amp;nbsp;index.html;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;确保不干扰Cookie
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;Cookie&amp;nbsp;$http_cookie;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;#vi&amp;nbsp;/opt/web/index.html&amp;nbsp; #最简单的测试首页&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;lt;!DOCTYPE&amp;nbsp;html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;title&amp;gt;Hello&amp;nbsp;World&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;h1&amp;gt;Hello,&amp;nbsp;World!&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;p&gt;#浏览器访问&lt;a href=&quot;http://lua.test.com/index.html&quot; _src=&quot;http://lua.test.com/index.html&quot;&gt;http://lua.test.com/index.html&lt;/a&gt; &lt;/p&gt;&lt;p&gt;# tail -f /usr/local/nginx/logs/error.log&amp;nbsp; #从错误日志可以看到缺少授权&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[error]&amp;nbsp;588087#0:&amp;nbsp;*3945&amp;nbsp;attempt&amp;nbsp;to&amp;nbsp;set&amp;nbsp;status&amp;nbsp;401&amp;nbsp;via&amp;nbsp;ngx.exit&amp;nbsp;after&amp;nbsp;sending&amp;nbsp;out&amp;nbsp;the&amp;nbsp;response&amp;nbsp;status&amp;nbsp;200,&amp;nbsp;
client:&amp;nbsp;192.168.1.101,&amp;nbsp;server:&amp;nbsp;lua.test.com,&amp;nbsp;request:&amp;nbsp;&amp;quot;POST&amp;nbsp;/index.html&amp;nbsp;HTTP/1.1&amp;quot;,&amp;nbsp;
host:&amp;nbsp;&amp;quot;lua.test.com&amp;quot;,&amp;nbsp;referrer:&amp;nbsp;&amp;quot;http://lua.test.com/index.html&amp;quot;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/07/202507231753267613662128.png&quot; alt=&quot;image.png&quot; width=&quot;411&quot; height=&quot;293&quot; style=&quot;width: 411px; height: 293px;&quot;/&gt; &amp;nbsp;&lt;/p&gt;&lt;p&gt;#上图为故意输错验证码可以看到有错误提示&lt;/p&gt;&lt;p&gt;# tail -f /opt/log/nginx/auth_error.log&amp;nbsp; #看看错误日志怎么体现的呢？&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[ERROR]&amp;nbsp;[192.168.1.101]&amp;nbsp;[1b90880fecccc634198dfa86824295d2]&amp;nbsp;[获取密钥]&amp;nbsp;Redis返回非字符串类型:&amp;nbsp;
[WARN]&amp;nbsp;[192.168.1.101]&amp;nbsp;[19dd9cb762b43c17096c689ac39260e0]&amp;nbsp;IP:192.168.1.101&amp;nbsp;-&amp;nbsp;TOTP验证失败（username:&amp;nbsp;test3,&amp;nbsp;剩余尝试次数:&amp;nbsp;2）
[ERROR]&amp;nbsp;[192.168.1.101]&amp;nbsp;[6302ec80939f6e311ae7aa1edd30a8f2]&amp;nbsp;[获取密钥]&amp;nbsp;Redis返回非字符串类型:&amp;nbsp;
[WARN]&amp;nbsp;[192.168.1.101]&amp;nbsp;[a5136d3ddf1ddd619c1ca5af523e7e48]&amp;nbsp;IP:192.168.1.101&amp;nbsp;-&amp;nbsp;TOTP验证失败（username:&amp;nbsp;test1,&amp;nbsp;剩余尝试次数:&amp;nbsp;2）
[ERROR]&amp;nbsp;[192.168.1.101]&amp;nbsp;[33885994750b49fa74e8182796c3d349]&amp;nbsp;[获取密钥]&amp;nbsp;Redis返回非字符串类型:&amp;nbsp;
[WARN]&amp;nbsp;[192.168.1.101]&amp;nbsp;[72cf3b0a64321ca26f77f63d19c42c39]&amp;nbsp;IP:192.168.1.101&amp;nbsp;-&amp;nbsp;用户不存在（username:&amp;nbsp;test12313）&lt;/pre&gt;&lt;p&gt;# tail -f /opt/log/nginx/auth_access.log&amp;nbsp; #可以看到完整的认证过程,这里就不粘贴日志了&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/07/202507231753267809662690.png&quot; alt=&quot;image.png&quot;/&gt;&lt;br/&gt;#也可以自行查看redis内的key信息变化跟代码里面的逻辑一一对应哈&lt;/p&gt;&lt;p&gt;#然后我们输入正确的验证码,再看下变化,这里就不截图了哈,hello world页面也没啥好截图的。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# tail -f /opt/log/nginx/auth_access.log&amp;nbsp; #可以看看日志最后的输出&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[8de87d8836845f5ffd70d9776bafd7e5]&amp;nbsp;IP:192.168.1.101&amp;nbsp;-&amp;nbsp;认证成功，重定向到:&amp;nbsp;/index.html
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[245518fd33b5eb2adf168ffb531219ee]&amp;nbsp;请求Cookie头:&amp;nbsp;auth_session=15d4063c6ef40e65d9d35a590155e541
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[147fd95be0607f4952edf94d78ebf495]&amp;nbsp;找到Cookie:&amp;nbsp;auth_session（清洗后:&amp;nbsp;15d4063c6ef40e65d9d35a590155e541）
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[4e036267e7ec4429748630c463853cc6]&amp;nbsp;IP:192.168.1.101&amp;nbsp;-&amp;nbsp;当前会话ID:&amp;nbsp;15d4063c6ef40e65d9d35a590155e541
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[7e34c6f7aebafcd4f0d1623450320c03]&amp;nbsp;[本地缓存-读取]&amp;nbsp;命中（session_id:&amp;nbsp;15d4063c6ef40e65d9d35a590155e541）
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[416f8fd60145319091b5a8b7a488a72b]&amp;nbsp;IP:192.168.1.101&amp;nbsp;-&amp;nbsp;本地缓存会话有效，用户:&amp;nbsp;test3&lt;/pre&gt;&lt;p&gt;#127.0.0.1:6379&amp;gt; get session:.test.com:15d4063c6ef40e65d9d35a590155e541&amp;nbsp; #现在redis中也有一个以固定格式命名的key&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;quot;test3&amp;quot;&lt;/pre&gt;&lt;p&gt;#现在你在反复刷新页面,浏览器也不会再有弹窗认证了,可以看到cookie已经存储到浏览器中了：&lt;br/&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/07/202507231753268262620871.png&quot; alt=&quot;image.png&quot; width=&quot;884&quot; height=&quot;110&quot; style=&quot;width: 884px; height: 110px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# tail -f /opt/log/nginx/auth_access.log&amp;nbsp; #那么日志中怎么体现的呢？&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[453edb8fc44ac3a8cf0ca6f753bfa9b9]&amp;nbsp;请求Cookie头:&amp;nbsp;auth_session=15d4063c6ef40e65d9d35a590155e541
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[9d974c9d8ada2092942e941a205f9751]&amp;nbsp;找到Cookie:&amp;nbsp;auth_session（清洗后:&amp;nbsp;15d4063c6ef40e65d9d35a590155e541）
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[69e8896b5171232d8feb13ccba2c5026]&amp;nbsp;IP:192.168.1.101&amp;nbsp;-&amp;nbsp;当前会话ID:&amp;nbsp;15d4063c6ef40e65d9d35a590155e541
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[ab5dcb76450c19e1a9af413fcec13ab6]&amp;nbsp;[本地缓存-读取]&amp;nbsp;命中（session_id:&amp;nbsp;15d4063c6ef40e65d9d35a590155e541）
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[3545864b2b5a1c4627d8ec1f3c896f9f]&amp;nbsp;IP:192.168.1.101&amp;nbsp;-&amp;nbsp;本地缓存会话有效，用户:&amp;nbsp;test3&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;2.2 验证锁定状态&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/07/202507241753340878896017.png&quot; alt=&quot;image.png&quot; width=&quot;418&quot; height=&quot;324&quot; style=&quot;width: 418px; height: 324px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#故意输错三次验证码就会进入封禁状态&lt;br/&gt;# tail -f /opt/log/nginx/auth_access.log&amp;nbsp; #我们看看日志怎么显示的&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[5401fe6bb3378ce4007ad43e24e0191f]&amp;nbsp;IP:192.168.1.101&amp;nbsp;-&amp;nbsp;开始验证TOTP，username:&amp;nbsp;[test1]
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[7f738a71d6186657b0bbc884c796ff34]&amp;nbsp;verify_totp:&amp;nbsp;所有时间窗口验证失败
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[1b0bdbe5124b9830ac7392d3c6da5505]&amp;nbsp;[Redis连接]&amp;nbsp;成功，host:&amp;nbsp;[127.0.0.1],&amp;nbsp;port:&amp;nbsp;[6379],&amp;nbsp;db:&amp;nbsp;[0]
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[be3e9ed357cd56cb26c9c7e6cc56965c]&amp;nbsp;[Redis连接]&amp;nbsp;成功，host:&amp;nbsp;[127.0.0.1],&amp;nbsp;port:&amp;nbsp;[6379],&amp;nbsp;db:&amp;nbsp;[0]
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[7b847c78ca94d76c60aa1d0ee996e347]&amp;nbsp;[封禁用户]&amp;nbsp;成功，username:&amp;nbsp;[test1],&amp;nbsp;封禁时间:&amp;nbsp;[1200秒]&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;2.3 手工清理cookie让用户再次认证&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#如果你想让一个用户重新认证,或者某些用户重新认证怎么搞呢？&lt;/p&gt;&lt;p&gt;127.0.0.1:6379&amp;gt; del session:.test.com:365439d445da4afa5a2957829980262c&amp;nbsp; &amp;nbsp;#删掉redis中的cookie&lt;/p&gt;&lt;p&gt;#然后nginx -s reload一下吗,切记reload是不行的,因为reload只是加载配置这些,共享缓存区内的数据是不是清理的,想要把共享缓存区里面的cookie缓存也清理掉应该怎么做呢,最简单粗暴的方法就是restart nginx,但是线上环境那会让你动不动就restart，最靠谱的方案就是写个小工具小脚本或者小页面,把redis和nginx共享缓存中的此用户的cookie键都删掉。&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;2.4 免认证url访问&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#我们有些静态的url比如图片之类的是不需要验证的,我们前面也做了此类设计,下面简单验证一下&lt;/p&gt;&lt;p&gt;#浏览器访问一下&amp;nbsp; &lt;a href=&quot;http://lua.test.com/static/1.html&quot; _src=&quot;http://lua.test.com/static/1.html&quot;&gt;http://lua.test.com/static/1.html&lt;/a&gt;&amp;nbsp; &amp;nbsp;#因为没有所以是404页面哈,但是只要没有认证弹窗就说明调过认证了&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# tail -f /opt/log/nginx/auth_access.log&amp;nbsp; #我们看看日志怎么显示的&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[cda930e363f8b9034dfccef8d41ebd67]&amp;nbsp;[免认证检查]&amp;nbsp;全局免认证URL，允许访问（url:&amp;nbsp;/static/1.html）
[INFO]&amp;nbsp;[192.168.1.101]&amp;nbsp;[8ad2d26a34d4e544d89bade39dc94665]&amp;nbsp;IP:192.168.1.101-&amp;nbsp;URL在免认证列表中，允许访问&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;&lt;/p&gt;</description><pubDate>Fri, 18 Jul 2025 16:44:06 +0800</pubDate></item><item><title>Nginx结合Lua实现二次验证(二)</title><link>https://blog.51niux.com/?id=324</link><description>&lt;p&gt;好了紧跟上文,上一篇&amp;nbsp;&lt;a href=&quot;https://blog.51niux.com/?id=323&quot; _src=&quot;https://blog.51niux.com/?id=323&quot;&gt;https://blog.51niux.com/?id=323&lt;/a&gt;&amp;nbsp; 我们已经详细的了解了Nginx结合lua的一些简单用法,下面我们用一个现实中比较常见的例子来继续了解。&lt;/p&gt;&lt;p&gt;#我先描述一下场景啊,二次验证已经非常的司空见惯了啊,比如你登录阿里云腾讯云这些需要绑定MFA并且每次登录的时候都需要输入一个基于时间的6位数字,当然这种方式也是企业内部也是被广泛使用的,比如我们有些系统由于场景需要比如我们有很多分城市,需要公网访问一些个别的系统,你想除了账号密码认证外,还想结合每个人的身份分配一个唯一的秘钥字符串,以此生成一个6位验证数字,只有登录通过后才能1天内使用(当然也可以通过阿里云的sase等一些安全产品解决公网访问的问题)。&lt;/p&gt;&lt;p&gt;#比如你现在就是内网访问,但是你还是想多一层验证,比如每次登录堡垒机等跟权限相关的系统,除了账号密码外,还是想让用手机上的mfa验证一下(每个人都有内部账号,每个账号都会入职的时候分配一个唯一的字符串,用于结合当前时间产生一个变化只有30秒生命周期的的6位数字)。&lt;/p&gt;&lt;p&gt;#好了上面是一个实际场景的大概描述,下面就让我们一步步实现它吧&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;一、先用脚本产生一个固定字符串的随机验证&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;#vi&amp;nbsp;totp_generator.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;--&amp;nbsp;导入bit32库，用于位操作，位操作就像搭积木，我们可以把数字拆成二进制的小块，然后移动或组合它们
--&amp;nbsp;Lua5.1中需要使用bit库，Lua5.2+可以直接使用bit32
local&amp;nbsp;bit32&amp;nbsp;=&amp;nbsp;require(&amp;quot;bit&amp;quot;)

--&amp;nbsp;Base32解码函数（阿里云MFA密钥通常为Base32编码）
--&amp;nbsp;Base32就像是一种密码本，用32个字母和数字表示二进制数据
local&amp;nbsp;function&amp;nbsp;base32_decode(base32_str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;移除所有空格并转为大写,就像整理信件一样，我们先去掉多余的空格，并且统一字母大小写
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base32_str&amp;nbsp;=&amp;nbsp;base32_str:gsub(&amp;quot;%s&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;):upper()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查输入长度,如果没有内容，就像空信封一样，我们无法处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#base32_str&amp;nbsp;==&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error(&amp;quot;Base32字符串不能为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查是否有填充字符&amp;#39;=&amp;#39;,Base32编码会用&amp;#39;=&amp;#39;符号填充，就像拼图最后用空白块补齐一样
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;padding&amp;nbsp;=&amp;nbsp;base32_str:match(&amp;quot;=+$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;padding&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;移除字符串末尾的填充字符,从1开始,#base32_str:获取原字符串的长度,#padding:获取填充字符子串的长度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;相减后得到的是不包含填充字符的字符串的结束位置,整体效果是：保留原字符串从开头到填充字符之前的部分
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base32_str&amp;nbsp;=&amp;nbsp;base32_str:sub(1,&amp;nbsp;#base32_str&amp;nbsp;-&amp;nbsp;#padding)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证输入长度（必须是8的倍数），Base32的规则是每8个字符代表5个原始数据字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#base32_str&amp;nbsp;%&amp;nbsp;8&amp;nbsp;~=&amp;nbsp;0&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error(&amp;quot;Base32字符串长度必须是8的倍数&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;Base32字符集定义，这是Base32编码的&amp;quot;密码本&amp;quot;，每个字母和数字对应一个0-31的数字
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;base32_chars&amp;nbsp;=&amp;nbsp;&amp;quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ234567&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;result&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;存储解码后的结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;bits&amp;nbsp;=&amp;nbsp;0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;累积的位数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;value&amp;nbsp;=&amp;nbsp;0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;累积的值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;逐字符解码,一个字母一个字母地&amp;quot;翻译&amp;quot;Base32字符串
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;#base32_str&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;c&amp;nbsp;=&amp;nbsp;base32_str:sub(i,&amp;nbsp;i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证字符是否有效,如果在密码本里找不到这个字符，就说明是无效的.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;c&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error(&amp;quot;Base32字符串包含空字符&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将字符映射到对应的值（0-31）,就像查字典一样，找到字符在密码本中的位置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;digit&amp;nbsp;=&amp;nbsp;base32_chars:find(c,&amp;nbsp;1,&amp;nbsp;true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;digit&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error(&amp;quot;无效的Base32字符:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;c)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;digit&amp;nbsp;=&amp;nbsp;digit&amp;nbsp;-&amp;nbsp;1&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;减1是因为Lua的索引从1开始，而我们需要从0开始&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;累积位值，每个Base32字符代表5位二进制数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value&amp;nbsp;=&amp;nbsp;bit32.lshift(value,&amp;nbsp;5)&amp;nbsp;+&amp;nbsp;digit
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bits&amp;nbsp;=&amp;nbsp;bits&amp;nbsp;+&amp;nbsp;5&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;增加5位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;当累积了至少8位时，我们可以生成一个字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;bits&amp;nbsp;&amp;gt;=&amp;nbsp;8&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bits&amp;nbsp;=&amp;nbsp;bits&amp;nbsp;-&amp;nbsp;8&amp;nbsp;--&amp;nbsp;用掉8位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;提取最左边的8位（相当于取整钱）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;bit32.rshift(value,&amp;nbsp;bits)将多余的位右移去掉,bit32.band(...,&amp;nbsp;0xFF)只保留低8位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;result&amp;nbsp;..&amp;nbsp;string.char(bit32.band(bit32.rshift(value,&amp;nbsp;bits),&amp;nbsp;0xFF))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;返回解码后的二进制数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result
end

--&amp;nbsp;SHA-1的常量值,SHA-1算法中的四个固定常量
local&amp;nbsp;K&amp;nbsp;=&amp;nbsp;{0x5A827999,&amp;nbsp;0x6ED9EBA1,&amp;nbsp;0x8F1BBCDC,&amp;nbsp;0xCA62C1D6}

--&amp;nbsp;左旋转函数,将一个32位数字向左旋转指定的位数
local&amp;nbsp;function&amp;nbsp;rotl32(n,&amp;nbsp;b)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;先将n左移b位，然后将移出的部分放到右边
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;例如：rotl32(0001,&amp;nbsp;1)&amp;nbsp;=&amp;nbsp;0010&amp;nbsp;(左移)&amp;nbsp;+&amp;nbsp;0000&amp;nbsp;(右移)&amp;nbsp;=&amp;nbsp;0010
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;bit32.band(bit32.lshift(n,&amp;nbsp;b),&amp;nbsp;0xFFFFFFFF)&amp;nbsp;+&amp;nbsp;bit32.rshift(n,&amp;nbsp;32&amp;nbsp;-&amp;nbsp;b)
end

--&amp;nbsp;将消息填充到512比特的倍数,SHA-1要求输入是512位(64字节)的倍数,就像把信折成特定大小才能放进信封
local&amp;nbsp;function&amp;nbsp;pad_message(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;length_bits&amp;nbsp;=&amp;nbsp;#message&amp;nbsp;*&amp;nbsp;8&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算消息总位数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;message&amp;nbsp;=&amp;nbsp;message&amp;nbsp;..&amp;nbsp;string.char(0x80)&amp;nbsp;--&amp;nbsp;添加0x80字节(10000000)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;填充0x00，直到长度为512比特的倍数减64比特,最后64位要留给原始消息的长度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;(#message&amp;nbsp;*&amp;nbsp;8)&amp;nbsp;%&amp;nbsp;512&amp;nbsp;~=&amp;nbsp;448&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;message&amp;nbsp;=&amp;nbsp;message&amp;nbsp;..&amp;nbsp;string.char(0x00)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;添加原始长度（64位，大端序）,就像在信封上标注信的原始大小
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;length_bytes&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;7,&amp;nbsp;0,&amp;nbsp;-1&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;从高位到低位，依次提取长度的每8位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;bit32.band(math.floor(length_bits&amp;nbsp;/&amp;nbsp;(256&amp;nbsp;^&amp;nbsp;i)),&amp;nbsp;0xFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(length_bytes,&amp;nbsp;string.char(byte_val))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;message&amp;nbsp;=&amp;nbsp;message&amp;nbsp;..&amp;nbsp;table.concat(length_bytes)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;message
end

--&amp;nbsp;将512比特的消息块转换为16个32位字，把大消息分成小块处理
local&amp;nbsp;function&amp;nbsp;words_from_block(block)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;words&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;j&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;16&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;每4个字节组成一个32位字
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte1,&amp;nbsp;byte2,&amp;nbsp;byte3,&amp;nbsp;byte4&amp;nbsp;=&amp;nbsp;block:byte((j&amp;nbsp;-&amp;nbsp;1)&amp;nbsp;*&amp;nbsp;4&amp;nbsp;+&amp;nbsp;1,&amp;nbsp;(j&amp;nbsp;-&amp;nbsp;1)&amp;nbsp;*&amp;nbsp;4&amp;nbsp;+&amp;nbsp;4)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;byte2&amp;nbsp;=&amp;nbsp;byte2&amp;nbsp;or&amp;nbsp;0&amp;nbsp;&amp;nbsp;--&amp;nbsp;如果不足4个字节，用0填充
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;byte3&amp;nbsp;=&amp;nbsp;byte3&amp;nbsp;or&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;byte4&amp;nbsp;=&amp;nbsp;byte4&amp;nbsp;or&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将4个字节合并成一个32位整数，就像把4个小积木拼成一个大积木
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;words[j]&amp;nbsp;=&amp;nbsp;bit32.bor(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(byte1,&amp;nbsp;24),&amp;nbsp;--&amp;nbsp;第一个字节移到最高位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(byte2,&amp;nbsp;16),&amp;nbsp;--&amp;nbsp;第二个字节移到次高位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bit32.lshift(byte3,&amp;nbsp;8),&amp;nbsp;&amp;nbsp;--&amp;nbsp;第三个字节移到次低位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;byte4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;第四个字节在最低位
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;words
end

--&amp;nbsp;SHA-1主函数，对消息进行哈希处理，生成160位的哈希值，就像给消息做一个&amp;quot;指纹&amp;quot;
function&amp;nbsp;sha1(message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;初始哈希值，SHA-1算法定义的5个初始常量，就像拼图的5个角块
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H0&amp;nbsp;=&amp;nbsp;0x67452301
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H1&amp;nbsp;=&amp;nbsp;0xEFCDAB89
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H2&amp;nbsp;=&amp;nbsp;0x98BADCFE
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H3&amp;nbsp;=&amp;nbsp;0x10325476
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;H4&amp;nbsp;=&amp;nbsp;0xC3D2E1F0

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;填充消息，确保消息长度是512位的倍数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;message&amp;nbsp;=&amp;nbsp;pad_message(message)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理每个512比特块，把消息分成多个小块依次处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;#message,&amp;nbsp;64&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;block&amp;nbsp;=&amp;nbsp;message:sub(i,&amp;nbsp;i&amp;nbsp;+&amp;nbsp;63)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;words&amp;nbsp;=&amp;nbsp;words_from_block(block)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;扩展到80个字，从16个字扩展到80个字，增加数据的复杂性
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;t&amp;nbsp;=&amp;nbsp;17,&amp;nbsp;80&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;word&amp;nbsp;=&amp;nbsp;bit32.bxor(words[t&amp;nbsp;-&amp;nbsp;3],&amp;nbsp;words[t&amp;nbsp;-&amp;nbsp;8],&amp;nbsp;words[t&amp;nbsp;-&amp;nbsp;14],&amp;nbsp;words[t&amp;nbsp;-&amp;nbsp;16])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;words[t]&amp;nbsp;=&amp;nbsp;rotl32(word,&amp;nbsp;1)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;初始化工作变量，5个工作变量，用于临时存储计算结果
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;A,&amp;nbsp;B,&amp;nbsp;C,&amp;nbsp;D,&amp;nbsp;E&amp;nbsp;=&amp;nbsp;H0,&amp;nbsp;H1,&amp;nbsp;H2,&amp;nbsp;H3,&amp;nbsp;H4

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;主循环&amp;nbsp;-&amp;nbsp;80轮处理，对每个扩展后的字进行复杂的位运算
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;t&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;80&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;temp,&amp;nbsp;f
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;k_index&amp;nbsp;=&amp;nbsp;math.floor((t&amp;nbsp;-&amp;nbsp;1)&amp;nbsp;/&amp;nbsp;20)&amp;nbsp;+&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;根据当前轮数选择不同的逻辑函数，就像根据不同的步骤做不同的动作
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;t&amp;nbsp;&amp;lt;=&amp;nbsp;20&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;逻辑函数1：(B&amp;nbsp;AND&amp;nbsp;C)&amp;nbsp;OR&amp;nbsp;(NOT&amp;nbsp;B&amp;nbsp;AND&amp;nbsp;D)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;nbsp;=&amp;nbsp;bit32.bor(bit32.band(B,&amp;nbsp;C),&amp;nbsp;bit32.band(bit32.bnot(B),&amp;nbsp;D))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elseif&amp;nbsp;t&amp;nbsp;&amp;lt;=&amp;nbsp;40&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;逻辑函数2：B&amp;nbsp;XOR&amp;nbsp;C&amp;nbsp;XOR&amp;nbsp;D
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;nbsp;=&amp;nbsp;bit32.bxor(B,&amp;nbsp;C,&amp;nbsp;D)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elseif&amp;nbsp;t&amp;nbsp;&amp;lt;=&amp;nbsp;60&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;逻辑函数3：(B&amp;nbsp;AND&amp;nbsp;C)&amp;nbsp;OR&amp;nbsp;(B&amp;nbsp;AND&amp;nbsp;D)&amp;nbsp;OR&amp;nbsp;(C&amp;nbsp;AND&amp;nbsp;D)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;nbsp;=&amp;nbsp;bit32.bor(bit32.band(B,&amp;nbsp;C),&amp;nbsp;bit32.band(B,&amp;nbsp;D),&amp;nbsp;bit32.band(C,&amp;nbsp;D))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;逻辑函数4：B&amp;nbsp;XOR&amp;nbsp;C&amp;nbsp;XOR&amp;nbsp;D
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;nbsp;=&amp;nbsp;bit32.bxor(B,&amp;nbsp;C,&amp;nbsp;D)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算临时值，结合左旋转、逻辑函数、常量和当前字进行计算
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;temp&amp;nbsp;=&amp;nbsp;bit32.band(rotl32(A,&amp;nbsp;5)&amp;nbsp;+&amp;nbsp;f&amp;nbsp;+&amp;nbsp;E&amp;nbsp;+&amp;nbsp;words[t]&amp;nbsp;+&amp;nbsp;K[k_index],&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;循环移位工作变量，就像5个人站成一圈，依次传递任务
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;E&amp;nbsp;=&amp;nbsp;D
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;D&amp;nbsp;=&amp;nbsp;C
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;C&amp;nbsp;=&amp;nbsp;rotl32(B,&amp;nbsp;30)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;B&amp;nbsp;=&amp;nbsp;A
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;A&amp;nbsp;=&amp;nbsp;temp
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;更新哈希值，将这一轮的计算结果累加到最终哈希值上
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H0&amp;nbsp;=&amp;nbsp;bit32.band(H0&amp;nbsp;+&amp;nbsp;A,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H1&amp;nbsp;=&amp;nbsp;bit32.band(H1&amp;nbsp;+&amp;nbsp;B,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H2&amp;nbsp;=&amp;nbsp;bit32.band(H2&amp;nbsp;+&amp;nbsp;C,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H3&amp;nbsp;=&amp;nbsp;bit32.band(H3&amp;nbsp;+&amp;nbsp;D,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;H4&amp;nbsp;=&amp;nbsp;bit32.band(H4&amp;nbsp;+&amp;nbsp;E,&amp;nbsp;0xFFFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;返回最终的哈希值（二进制格式），将5个32位整数转换为20字节的二进制数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;hash_bytes&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i,&amp;nbsp;h&amp;nbsp;in&amp;nbsp;ipairs({H0,&amp;nbsp;H1,&amp;nbsp;H2,&amp;nbsp;H3,&amp;nbsp;H4})&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;j&amp;nbsp;=&amp;nbsp;3,&amp;nbsp;0,&amp;nbsp;-1&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;从高位到低位，提取每个整数的4个字节
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;bit32.band(bit32.rshift(h,&amp;nbsp;j&amp;nbsp;*&amp;nbsp;8),&amp;nbsp;0xFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(hash_bytes,&amp;nbsp;string.char(byte_val))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;table.concat(hash_bytes)
end

--&amp;nbsp;定义HMAC-SHA1函数，基于SHA-1的密钥哈希消息认证码，就像给消息加了一把带密码的锁
local&amp;nbsp;function&amp;nbsp;hmac_sha1(key,&amp;nbsp;data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;如果密钥长度超过64字节，先进行SHA-1哈希，确保密钥长度不超过块大小
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#key&amp;nbsp;&amp;gt;&amp;nbsp;64&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;sha1(key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将密钥填充到64字节，就像把钥匙磨成特定形状
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;key&amp;nbsp;..&amp;nbsp;string.rep(&amp;quot;\0&amp;quot;,&amp;nbsp;64&amp;nbsp;-&amp;nbsp;#key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;内部和外部密钥，创建两个不同的密钥变体
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;inner_key&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;outer_key&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;对密钥的每个字节进行处理，内部密钥：与0x36异或外部密钥：与0x5C异或
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;64&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;key:byte(i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(inner_key,&amp;nbsp;string.char(bit32.bxor(byte_val,&amp;nbsp;0x36)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(outer_key,&amp;nbsp;string.char(bit32.bxor(byte_val,&amp;nbsp;0x5C)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inner_key&amp;nbsp;=&amp;nbsp;table.concat(inner_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;outer_key&amp;nbsp;=&amp;nbsp;table.concat(outer_key)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算内部哈希，先用内部密钥和数据计算哈希
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;inner_hash&amp;nbsp;=&amp;nbsp;sha1(inner_key&amp;nbsp;..&amp;nbsp;data)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算外部哈希，再用外部密钥和内部哈希结果计算最终哈希
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;sha1(outer_key&amp;nbsp;..&amp;nbsp;inner_hash)
end

--&amp;nbsp;定义TOTP函数，基于时间的一次性密码算法，就像一个随时间变化的密码锁
local&amp;nbsp;function&amp;nbsp;totp(secret,&amp;nbsp;time_step,&amp;nbsp;digits)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;获取当前时间戳，记录从1970年1月1日到现在的秒数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;current_time&amp;nbsp;=&amp;nbsp;os.time()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算时间步长，将时间分成固定长度的&amp;quot;块&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;counter&amp;nbsp;=&amp;nbsp;math.floor(current_time&amp;nbsp;/&amp;nbsp;time_step)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将计数器转换为8字节的大端序字节数组，就像把数字翻译成计算机能理解的语言
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;counter_bytes&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;7,&amp;nbsp;0,&amp;nbsp;-1&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;bit32.band(math.floor(counter&amp;nbsp;/&amp;nbsp;(256&amp;nbsp;^&amp;nbsp;i)),&amp;nbsp;0xFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;counter_bytes[#counter_bytes&amp;nbsp;+&amp;nbsp;1]&amp;nbsp;=&amp;nbsp;string.char(byte_val)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;counter_bytes&amp;nbsp;=&amp;nbsp;table.concat(counter_bytes)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;计算HMAC-SHA1，使用密钥和计数器生成哈希值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;hash&amp;nbsp;=&amp;nbsp;hmac_sha1(secret,&amp;nbsp;counter_bytes)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;动态偏移量（从哈希结果的最后一个字节的低4位获取），就像从密码锁中随机选择一个位置开始
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;offset&amp;nbsp;=&amp;nbsp;bit32.band(hash:byte(20),&amp;nbsp;0x0F)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;提取4字节数据（从offset位置开始），从哈希结果中提取一部分作为基础
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;binary_code&amp;nbsp;=&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;4&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;byte_val&amp;nbsp;=&amp;nbsp;hash:byte(offset&amp;nbsp;+&amp;nbsp;i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;binary_code&amp;nbsp;=&amp;nbsp;bit32.bor(binary_code,&amp;nbsp;bit32.lshift(byte_val,&amp;nbsp;(4&amp;nbsp;-&amp;nbsp;i)&amp;nbsp;*&amp;nbsp;8))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;移除最高位并取模，确保结果是一个正整数，并限制位数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;binary_code&amp;nbsp;=&amp;nbsp;bit32.band(binary_code,&amp;nbsp;0x7FFFFFFF)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;otp&amp;nbsp;=&amp;nbsp;binary_code&amp;nbsp;%&amp;nbsp;(10&amp;nbsp;^&amp;nbsp;digits)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;格式化为固定长度的字符串，就像把密码格式化成固定位数的数字
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;string.format(&amp;quot;%0&amp;quot;&amp;nbsp;..&amp;nbsp;digits&amp;nbsp;..&amp;nbsp;&amp;quot;d&amp;quot;,&amp;nbsp;otp)
end

--&amp;nbsp;时间同步检查（可选），确保客户端和服务器的时间一致，就像校准两个钟表
local&amp;nbsp;function&amp;nbsp;check_time_synchronization(server_time_url)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;实际应用中，应从服务器获取时间戳进行比对，这里仅为示例，实际实现需要HTTP请求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;警告：时间同步检查功能未实现，建议与服务器时间进行比对&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end

--&amp;nbsp;主函数，程序的入口点
local&amp;nbsp;function&amp;nbsp;main()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;阿里云MFA密钥（Base32编码），这是你在阿里云设置MFA时得到的密钥
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;base32_secret&amp;nbsp;=&amp;nbsp;&amp;quot;zABCDEghijklmnopqrstuvwxyzABCDEF&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解码Base32密钥，将密钥从&amp;quot;密码本语言&amp;quot;翻译成计算机能理解的二进制
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;secret&amp;nbsp;=&amp;nbsp;base32_decode(base32_secret)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;TOTP参数，时间步长：每30秒生成一个新密码，验证码位数：生成6位数字密码
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;time_step&amp;nbsp;=&amp;nbsp;30&amp;nbsp;&amp;nbsp;--&amp;nbsp;步长为30秒
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;digits&amp;nbsp;=&amp;nbsp;6&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证码位数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查时间同步，确保你的电脑时间准确
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;check_time_synchronization()&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;生成当前验证码
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;基于当前时间和密钥计算出一次性密码
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;otp&amp;nbsp;=&amp;nbsp;totp(secret,&amp;nbsp;time_step,&amp;nbsp;digits)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;当前时间戳:&amp;quot;,&amp;nbsp;os.time())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;TOTP验证码:&amp;quot;,&amp;nbsp;otp)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;错误：本地时间与服务器时间不同步，请调整系统时间&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
end

--&amp;nbsp;执行主函数，启动整个程序
main()&lt;/pre&gt;&lt;p&gt;&lt;br class=&quot;container-Vdm5p7 wrapper-_7axQ_ undefined&quot; style=&quot;-webkit-font-smoothing: antialiased; box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); content: &amp;quot;&amp;quot;; display: block; font-size: var(--md-box-paragraph-spacing); margin: 1em; overflow-anchor: auto; font-family: Inter, -apple-system, BlinkMacSystemFont, &amp;quot;Segoe UI&amp;quot;, &amp;quot;SF Pro SC&amp;quot;, &amp;quot;SF Pro Display&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; text-wrap-mode: wrap; background-color: rgb(249, 250, 251);&quot;/&gt;&lt;/p&gt;&lt;p&gt;# /usr/local/luajit/bin/luajit totp_generator.lua&amp;nbsp; &amp;nbsp;#手工执行一下(用手机阿里云或者谷歌验证器绑一下zABCDEghijklmnopqrstuvwxyzABCDEF核对一下)&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;警告：时间同步检查功能未实现，建议与服务器时间进行比对
当前时间戳:	1749723585
TOTP验证码:	097080&lt;/pre&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;二、用标准目录形式为用户产生密钥串&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;#上面的例子我们已经可以用单一的秘钥生成了6位验证码,现实场景呢,一般也不会让用户自行生成秘钥,一般都是系统自动分配的唯一秘钥串,字符串长度呢就是8的倍数。你像阿里面向用户比较多所以它比较长64位,我们就给内部员工使用不需要那么长16位就可以了。&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.1 准备mysql环境&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#这里我们假设用户注册后的用户信息是存储到数据库中的(很多时候新员工入职的时候所有信息会存在一个指定的库表中,然后其他功能再依此延伸,你比如有个字段会自动创建一个16位的秘钥字符串,而我们就需要用户字母名称和秘钥串这两个字段,不管是定时任务式的还是触发式的很多时候我们会把这个信息存储到文本或者redis缓存中便于加载)。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;MariaDB&amp;nbsp;[(none)]&amp;gt;&amp;nbsp;create&amp;nbsp;database&amp;nbsp;auth_db;
MariaDB&amp;nbsp;[(none)]&amp;gt;&amp;nbsp;grant&amp;nbsp;all&amp;nbsp;privileges&amp;nbsp;on&amp;nbsp;auth_db.*&amp;nbsp;to&amp;nbsp;&amp;#39;auth&amp;#39;@&amp;#39;127.0.0.1&amp;#39;&amp;nbsp;identified&amp;nbsp;by&amp;nbsp;&amp;#39;auth123&amp;#39;;
MariaDB&amp;nbsp;[(none)]&amp;gt;&amp;nbsp;flush&amp;nbsp;privileges;&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.2 准备相关lua文件&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# mkdir /usr/local/nginx/conf/auth&lt;/p&gt;&lt;p&gt;# vim /usr/local/nginx/conf/auth/config.lua&amp;nbsp; &amp;nbsp;#创建配置文件&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;系统配置信息,创建了一个局部变量名为config的表,表Lua中最常用的数据结构，类似于其他语言中的字典、哈希表或对象
local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;数据库配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host&amp;nbsp;=&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;,&amp;nbsp;--&amp;nbsp;MySQL服务器地址
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port&amp;nbsp;=&amp;nbsp;3306,&amp;nbsp;&amp;nbsp;--&amp;nbsp;端口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;&amp;quot;auth_db&amp;quot;,&amp;nbsp;--&amp;nbsp;数据库名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;&amp;quot;auth&amp;quot;,&amp;nbsp;&amp;nbsp;--&amp;nbsp;用户名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;auth123&amp;quot;,&amp;nbsp;--&amp;nbsp;密码
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_idle_timeout&amp;nbsp;=&amp;nbsp;10000,&amp;nbsp;--&amp;nbsp;连接池空闲超时(毫秒)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pool_size&amp;nbsp;=&amp;nbsp;100&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接池大小
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;二次验证配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otp&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;code_length&amp;nbsp;=&amp;nbsp;6,&amp;nbsp;--&amp;nbsp;验证码长度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time_step&amp;nbsp;=&amp;nbsp;30,&amp;nbsp;&amp;nbsp;--&amp;nbsp;时间步长(秒)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;window_size&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证窗口(前后允许的时间步数)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;secret_length&amp;nbsp;=&amp;nbsp;16&amp;nbsp;--&amp;nbsp;密钥长度(字节)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;登录限制配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;login_attempts&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_attempts&amp;nbsp;=&amp;nbsp;5,&amp;nbsp;--&amp;nbsp;最大尝试次数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lockout_time&amp;nbsp;=&amp;nbsp;60&amp;nbsp;--&amp;nbsp;锁定时间(秒)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;会话配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;session&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;expires_in&amp;nbsp;=&amp;nbsp;86400,&amp;nbsp;--&amp;nbsp;会话有效期(秒)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookie_name&amp;nbsp;=&amp;nbsp;&amp;quot;auth_token&amp;quot;&amp;nbsp;--&amp;nbsp;Cookie名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;IP白名单配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_file&amp;nbsp;=&amp;nbsp;&amp;quot;/usr/local/nginx/conf/auth/ip_whitelist.txt&amp;quot;,&amp;nbsp;&amp;nbsp;--&amp;nbsp;白名单文件路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;reload_interval&amp;nbsp;=&amp;nbsp;3600,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;路径配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;login_path&amp;nbsp;=&amp;nbsp;&amp;quot;/verify&amp;quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;登录页面路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;register_path&amp;nbsp;=&amp;nbsp;&amp;quot;/register&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;注册页面路径
}
--&amp;nbsp;return语句用于将模块的内容导出，使其他模块可以通过&amp;nbsp;require函数加载和使用这些内容,将这个配置表导出为模块的公共接口
return&amp;nbsp;config&lt;/pre&gt;&lt;p&gt;# vim /usr/local/nginx/conf/auth/db.lua&amp;nbsp; #创建数据库操作文件&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;数据库操作模块
local&amp;nbsp;mysql&amp;nbsp;=&amp;nbsp;require(&amp;quot;resty.mysql&amp;quot;)
local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;).db

local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}

--&amp;nbsp;创建数据库连接
function&amp;nbsp;_M.connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;db,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;mysql:new()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;db&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;&amp;quot;创建MySQL实例失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置超时
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db:set_timeout(1000)&amp;nbsp;&amp;nbsp;--&amp;nbsp;1秒超时
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;连接数据库
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate&amp;nbsp;=&amp;nbsp;db:connect({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host&amp;nbsp;=&amp;nbsp;config.host,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port&amp;nbsp;=&amp;nbsp;config.port,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;config.database,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;config.user,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;config.password,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_packet_size&amp;nbsp;=&amp;nbsp;config.max_packet_size
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;string.format(&amp;quot;连接数据库失败:&amp;nbsp;%s,&amp;nbsp;错误码:&amp;nbsp;%d,&amp;nbsp;SQL状态:&amp;nbsp;%s&amp;quot;,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;db
end

--&amp;nbsp;安全执行SQL查询
--&amp;nbsp;_M是模块的命名空间，代表当前模块,execute_query是函数名,sql是传入的SQL查询语句，字符串类型
function&amp;nbsp;_M.execute_query(sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;调用模块内部的connect函数创建数据库连接,如果连接失败(db为nil)，返回nil和错误信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;db,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.connect()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;db&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;执行查询，db:query(sql)是resty.mysql库提供的执行SQL查询的方法,其中errno:MySQL错误码(失败时),sqlstate:SQL状态码(失败时)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate&amp;nbsp;=&amp;nbsp;db:query(sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将连接放回连接池,config.max_idle_timeout:连接在池中保持空闲的最长时间(ms),config.pool_size:连接池的最大大小
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db:set_keepalive(config.max_idle_timeout,&amp;nbsp;config.pool_size)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;如果查询失败（res为nil），返回nil和格式化的错误信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil,&amp;nbsp;string.format(&amp;quot;执行SQL失败:&amp;nbsp;%s,&amp;nbsp;错误码:&amp;nbsp;%d,&amp;nbsp;SQL状态:&amp;nbsp;%s&amp;quot;,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;如果查询成功，返回查询结果res
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;res
end

--&amp;nbsp;安全转义SQL字符串（防止SQL注入），将任意类型的值转换为适合在SQL语句中使用的安全字符串表示形式
function&amp;nbsp;_M.escape_literal(str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;在SQL中，nil对应NULL关键字，返回字符串&amp;quot;NULL&amp;quot;（不带引号），使其能直接在SQL中作为空值使用
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;str&amp;nbsp;==&amp;nbsp;nil&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;NULL&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理布尔值,SQL中通常使用1和0表示布尔值true和false,将布尔值转换为对应的数字字符串
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(str)&amp;nbsp;==&amp;nbsp;&amp;quot;boolean&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;str&amp;nbsp;and&amp;nbsp;&amp;quot;1&amp;quot;&amp;nbsp;or&amp;nbsp;&amp;quot;0&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理数字,直接将数字转换为字符串，保持其数值表示
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;type(str)&amp;nbsp;==&amp;nbsp;&amp;quot;number&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;tostring(str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理字符串确保输入值被视为字符串，用单引号将转义后的字符串包围，形成合法的SQL字符串字面量
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str&amp;nbsp;=&amp;nbsp;tostring(str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;转义单引号和反斜杠
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str&amp;nbsp;=&amp;nbsp;string.gsub(str,&amp;nbsp;&amp;quot;\\&amp;quot;,&amp;nbsp;&amp;quot;\\\\&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str&amp;nbsp;=&amp;nbsp;string.gsub(str,&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;,&amp;nbsp;&amp;quot;&amp;#39;&amp;#39;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;&amp;nbsp;..&amp;nbsp;str&amp;nbsp;..&amp;nbsp;&amp;quot;&amp;#39;&amp;quot;
end

--&amp;nbsp;初始化数据库表
function&amp;nbsp;_M.init_tables()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;创建用户表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;create_users_sql&amp;nbsp;=&amp;nbsp;[[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;users&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;INT&amp;nbsp;AUTO_INCREMENT&amp;nbsp;PRIMARY&amp;nbsp;KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;VARCHAR(50)&amp;nbsp;UNIQUE&amp;nbsp;NOT&amp;nbsp;NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otp_secret&amp;nbsp;VARCHAR(32)&amp;nbsp;NOT&amp;nbsp;NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;login_attempts&amp;nbsp;INT&amp;nbsp;DEFAULT&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_attempt_time&amp;nbsp;DATETIME,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;created_at&amp;nbsp;DATETIME&amp;nbsp;DEFAULT&amp;nbsp;CURRENT_TIMESTAMP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.execute_query(create_users_sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;&amp;quot;创建用户表失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;创建会话表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;create_sessions_sql&amp;nbsp;=&amp;nbsp;[[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;sessions&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;session_id&amp;nbsp;VARCHAR(64)&amp;nbsp;PRIMARY&amp;nbsp;KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user_id&amp;nbsp;INT&amp;nbsp;NOT&amp;nbsp;NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;expires_at&amp;nbsp;DATETIME&amp;nbsp;NOT&amp;nbsp;NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;created_at&amp;nbsp;DATETIME&amp;nbsp;DEFAULT&amp;nbsp;CURRENT_TIMESTAMP,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FOREIGN&amp;nbsp;KEY&amp;nbsp;(user_id)&amp;nbsp;REFERENCES&amp;nbsp;users(id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;_M.execute_query(create_sessions_sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;&amp;quot;创建会话表失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end
return&amp;nbsp;_M&lt;/pre&gt;&lt;p&gt;# vim /usr/local/nginx/conf/auth/utils.lua&amp;nbsp; #&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;工具函数模块，提供通用功能的文件&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;)

local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}
--&amp;nbsp;定义模块的公共函数generate_random_string，接受一个参数length表示要生成的随机字符串长度&amp;nbsp;
function&amp;nbsp;_M.generate_random_string(length)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;初始化空字符串result用于存储生成的随机字符串
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;result&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;定义字符集chars，包含所有可能被使用的字符(大小写字母和数字)。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;chars&amp;nbsp;=&amp;nbsp;&amp;quot;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&amp;quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;通过执行系统命令echo&amp;nbsp;$$获取当前进程&amp;nbsp;ID。io.popen()打开一个管道执行命令，read()读取输出，tonumber()转换为数字。如果获取失败则默认为0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;pid&amp;nbsp;=&amp;nbsp;tonumber(io.popen(&amp;quot;echo&amp;nbsp;$$&amp;quot;):read())&amp;nbsp;or&amp;nbsp;0

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用时间和进程ID组合作为种子,结合进程ID可以避免同一秒内生成相同的随机序列
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;math.randomseed(os.time()&amp;nbsp;+&amp;nbsp;pid)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;预调用math.random()三次，丢弃结果。这是Lua中提高随机数质量的常见做法，因为首次调用往往不够随机。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;_&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;3&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;math.random()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;ngx.log(ngx.INFO,&amp;nbsp;&amp;quot;进入生成验证码阶段&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;上面是打印一下内容,在排错阶段很有用,在开发阶段多加一些输出,可以知道代码在哪一步出错了,需要就打开注释
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;循环length次，每次生成一个1到字符集长度之间的随机索引
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;i&amp;nbsp;=&amp;nbsp;1,&amp;nbsp;length&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;只调用一次math.random，获取单个字符
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;random_index&amp;nbsp;=&amp;nbsp;math.random(1,&amp;nbsp;#chars)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用sub()方法从字符集中提取对应位置的单个字符，并追加到result中
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;result&amp;nbsp;..&amp;nbsp;chars:sub(random_index,&amp;nbsp;random_index)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;返回生成的随机字符串
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result
end

--&amp;nbsp;解析HTTP请求参数
function&amp;nbsp;_M.parse_args()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;args&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解析GET参数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ngx.var.request_method&amp;nbsp;==&amp;nbsp;&amp;quot;GET&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;query&amp;nbsp;=&amp;nbsp;ngx.var.query_string
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;query&amp;nbsp;and&amp;nbsp;query&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;k,&amp;nbsp;v&amp;nbsp;in&amp;nbsp;string.gmatch(query,&amp;nbsp;&amp;quot;([^&amp;amp;=]+)=([^&amp;amp;=]*)&amp;quot;)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;args[k]&amp;nbsp;=&amp;nbsp;ngx.unescape_uri(v)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解析POST参数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elseif&amp;nbsp;ngx.var.request_method&amp;nbsp;==&amp;nbsp;&amp;quot;POST&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.req.read_body()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;post_data&amp;nbsp;=&amp;nbsp;ngx.req.get_post_data()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;post_data&amp;nbsp;and&amp;nbsp;post_data&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;k,&amp;nbsp;v&amp;nbsp;in&amp;nbsp;string.gmatch(post_data,&amp;nbsp;&amp;quot;([^&amp;amp;=]+)=([^&amp;amp;=]*)&amp;quot;)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;args[k]&amp;nbsp;=&amp;nbsp;ngx.unescape_uri(v)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;args
end

--&amp;nbsp;检查字符串是否为空
function&amp;nbsp;_M.is_empty(str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;str&amp;nbsp;==&amp;nbsp;nil&amp;nbsp;or&amp;nbsp;str&amp;nbsp;==&amp;nbsp;&amp;quot;&amp;quot;
end
--&amp;nbsp;将模块表_M作为结果返回，使外部代码可以访问其公共函数
return&amp;nbsp;_M&lt;/pre&gt;&lt;p&gt;# vim /usr/local/nginx/conf/auth/otp.lua #注册用户产生随机密钥的文件&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;导入三个外部模块
local&amp;nbsp;db&amp;nbsp;=&amp;nbsp;require(&amp;quot;db&amp;quot;)
local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;)
local&amp;nbsp;utils&amp;nbsp;=&amp;nbsp;require(&amp;quot;utils&amp;quot;)

local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}

--&amp;nbsp;定义generate_secret函数，用于生成&amp;nbsp;OTP&amp;nbsp;密钥
function&amp;nbsp;_M.generate_secret()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;调用utils模块的generate_random_string函数，生成长度为config.otp.secret_length的随机字符串
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;utils.generate_random_string(config.otp.secret_length)
end

--&amp;nbsp;注册新用户（生成OTP密钥）
function&amp;nbsp;_M.register_user(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;首先调用generate_secret生成OTP密钥
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;otp_secret&amp;nbsp;=&amp;nbsp;_M.generate_secret()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证生成的密钥长度是否超过&amp;nbsp;32&amp;nbsp;个字符（假设数据库字段类型为&amp;nbsp;VARCHAR&amp;nbsp;(32)）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#otp_secret&amp;nbsp;&amp;gt;&amp;nbsp;32&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--如果超过则截断为前&amp;nbsp;32&amp;nbsp;个字符，防止数据库插入错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otp_secret&amp;nbsp;=&amp;nbsp;string.sub(otp_secret,&amp;nbsp;1,&amp;nbsp;32)&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;ngx.log(ngx.INFO,&amp;quot;otp_secret:&amp;quot;,otp_secret)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;构建SQL插入语句，将用户名和OTP密钥存入users表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;sql&amp;nbsp;=&amp;nbsp;string.format([[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT&amp;nbsp;INTO&amp;nbsp;users&amp;nbsp;(username,&amp;nbsp;otp_secret)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VALUES&amp;nbsp;(%s,&amp;nbsp;%s)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用db.escape_literal对输入进行转义，防止SQL注入攻击
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db.escape_literal(username),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db.escape_literal(otp_secret)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;执行SQL插入语句
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;res,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;db.execute_query(sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false,&amp;nbsp;nil,&amp;nbsp;&amp;quot;注册失败:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;err
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true,&amp;nbsp;otp_secret,&amp;nbsp;&amp;quot;注册成功&amp;quot;
end

return&amp;nbsp;_M&lt;/pre&gt;&lt;p&gt;#vim&amp;nbsp;/usr/local/nginx/conf/auth/main.lua&amp;nbsp; #主程序入口文件&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;ngx是OpenResty提供的全局对象，用于访问Nginx&amp;nbsp;API
local&amp;nbsp;ngx&amp;nbsp;=&amp;nbsp;ngx
local&amp;nbsp;otp&amp;nbsp;=&amp;nbsp;require(&amp;quot;otp&amp;quot;)
local&amp;nbsp;db&amp;nbsp;=&amp;nbsp;require(&amp;quot;db&amp;quot;)
local&amp;nbsp;utils&amp;nbsp;=&amp;nbsp;require(&amp;quot;utils&amp;quot;)
local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;)

--&amp;nbsp;调用db.init_tables()初始化数据库表(如果不存在),失败时返回500错误并终止请求处理
local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;db.init_tables()
if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;数据库连接失败:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.status&amp;nbsp;=&amp;nbsp;500
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;数据库初始化失败:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

--&amp;nbsp;显示登录弹窗
local&amp;nbsp;function&amp;nbsp;show_login_popup(error_msg,&amp;nbsp;original_url)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_msg&amp;nbsp;=&amp;nbsp;error_msg&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;original_url&amp;nbsp;=&amp;nbsp;original_url&amp;nbsp;or&amp;nbsp;ngx.var.request_uri
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;创建HTML模板
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;html&amp;nbsp;=&amp;nbsp;[[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!DOCTYPE&amp;nbsp;html&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;html&amp;nbsp;lang=&amp;quot;zh-CN&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;head&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;meta&amp;nbsp;charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;title&amp;gt;二次验证&amp;lt;/title&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;style&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;body&amp;nbsp;{&amp;nbsp;font-family:&amp;nbsp;Arial,&amp;nbsp;sans-serif;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.overlay&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;position:&amp;nbsp;fixed;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;top:&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;left:&amp;nbsp;0;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:&amp;nbsp;100%;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;height:&amp;nbsp;100%;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color:&amp;nbsp;rgba(0,0,0,0.5);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;display:&amp;nbsp;flex;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;justify-content:&amp;nbsp;center;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;align-items:&amp;nbsp;center;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;z-index:&amp;nbsp;9999;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.popup&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color:&amp;nbsp;white;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;padding:&amp;nbsp;20px;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;border-radius:&amp;nbsp;5px;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;box-shadow:&amp;nbsp;0&amp;nbsp;0&amp;nbsp;10px&amp;nbsp;rgba(0,0,0,0.3);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max-width:&amp;nbsp;400px;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:&amp;nbsp;100%;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;animation:&amp;nbsp;fadeIn&amp;nbsp;0.3s&amp;nbsp;ease-in-out;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@keyframes&amp;nbsp;fadeIn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;from&amp;nbsp;{&amp;nbsp;opacity:&amp;nbsp;0;&amp;nbsp;transform:&amp;nbsp;scale(0.9);&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;to&amp;nbsp;{&amp;nbsp;opacity:&amp;nbsp;1;&amp;nbsp;transform:&amp;nbsp;scale(1);&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.form-group&amp;nbsp;{&amp;nbsp;margin-bottom:&amp;nbsp;15px;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.form-group&amp;nbsp;label&amp;nbsp;{&amp;nbsp;display:&amp;nbsp;block;&amp;nbsp;margin-bottom:&amp;nbsp;5px;&amp;nbsp;font-weight:&amp;nbsp;bold;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.form-group&amp;nbsp;input&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:&amp;nbsp;100%;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;padding:&amp;nbsp;10px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;border:&amp;nbsp;1px&amp;nbsp;solid&amp;nbsp;#ddd;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;border-radius:&amp;nbsp;4px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;box-sizing:&amp;nbsp;border-box;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;button&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color:&amp;nbsp;#4CAF50;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color:&amp;nbsp;white;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;padding:&amp;nbsp;10px&amp;nbsp;15px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;border:&amp;nbsp;none;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;border-radius:&amp;nbsp;4px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor:&amp;nbsp;pointer;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:&amp;nbsp;100%;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-size:&amp;nbsp;16px;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;button:hover&amp;nbsp;{&amp;nbsp;background-color:&amp;nbsp;#45a049;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.error&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color:&amp;nbsp;red;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;margin-bottom:&amp;nbsp;10px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;padding:&amp;nbsp;8px;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color:&amp;nbsp;#ffebee;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;border-radius:&amp;nbsp;4px;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.info&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;margin-top:&amp;nbsp;15px;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-size:&amp;nbsp;14px;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color:&amp;nbsp;#666;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/style&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/head&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;body&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;overlay&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;popup&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;h2&amp;gt;二次验证&amp;lt;/h2&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{{error_message}}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;p&amp;gt;您需要进行二次验证才能访问此资源&amp;lt;/p&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;form&amp;nbsp;method=&amp;quot;post&amp;quot;&amp;nbsp;action=&amp;quot;{{action_url}}&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;input&amp;nbsp;type=&amp;quot;hidden&amp;quot;&amp;nbsp;name=&amp;quot;original_url&amp;quot;&amp;nbsp;value=&amp;quot;{{original_url}}&amp;quot;&amp;nbsp;/&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;form-group&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;label&amp;nbsp;for=&amp;quot;username&amp;quot;&amp;gt;用户名:&amp;lt;/label&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;input&amp;nbsp;type=&amp;quot;text&amp;quot;&amp;nbsp;id=&amp;quot;username&amp;quot;&amp;nbsp;name=&amp;quot;username&amp;quot;&amp;nbsp;required&amp;nbsp;placeholder=&amp;quot;输入您的用户名&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;form-group&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;label&amp;nbsp;for=&amp;quot;code&amp;quot;&amp;gt;二次验证码:&amp;lt;/label&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;input&amp;nbsp;type=&amp;quot;text&amp;quot;&amp;nbsp;id=&amp;quot;code&amp;quot;&amp;nbsp;name=&amp;quot;code&amp;quot;&amp;nbsp;required&amp;nbsp;placeholder=&amp;quot;输入6位数字验证码&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;button&amp;nbsp;type=&amp;quot;submit&amp;quot;&amp;gt;验证并继续&amp;lt;/button&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/form&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;info&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;p&amp;gt;提示:&amp;nbsp;验证码每30秒更新一次&amp;lt;/p&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;p&amp;gt;测试用注册:&amp;nbsp;&amp;lt;a&amp;nbsp;href=&amp;quot;/register?username=test&amp;quot;&amp;gt;/register?username=test&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/body&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/html&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;安全替换函数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;function&amp;nbsp;safe_replace(html,&amp;nbsp;placeholder,&amp;nbsp;value)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;确保替换值中的特殊字符被正确处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value&amp;nbsp;=&amp;nbsp;string.gsub(value,&amp;nbsp;&amp;quot;%%&amp;quot;,&amp;nbsp;&amp;quot;%%%%&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;string.gsub(html,&amp;nbsp;placeholder,&amp;nbsp;value)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用安全替换函数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;html&amp;nbsp;=&amp;nbsp;safe_replace(html,&amp;nbsp;&amp;quot;{{error_message}}&amp;quot;,&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_msg&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;and&amp;nbsp;string.format(&amp;#39;&amp;lt;div&amp;nbsp;class=&amp;quot;error&amp;quot;&amp;gt;%s&amp;lt;/div&amp;gt;&amp;#39;,&amp;nbsp;error_msg)&amp;nbsp;or&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;html&amp;nbsp;=&amp;nbsp;safe_replace(html,&amp;nbsp;&amp;quot;{{original_url}}&amp;quot;,&amp;nbsp;ngx.escape_uri(original_url))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;html&amp;nbsp;=&amp;nbsp;safe_replace(html,&amp;nbsp;&amp;quot;{{action_url}}&amp;quot;,&amp;nbsp;ngx.var.request_uri)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header.content_type&amp;nbsp;=&amp;nbsp;&amp;quot;text/html;&amp;nbsp;charset=utf-8&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(html)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.HTTP_OK)
end

--&amp;nbsp;处理注册请求(测试用)
if&amp;nbsp;ngx.var.uri&amp;nbsp;==&amp;nbsp;config.register_path&amp;nbsp;and&amp;nbsp;ngx.req.get_method()&amp;nbsp;==&amp;nbsp;&amp;quot;GET&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;args&amp;nbsp;=&amp;nbsp;ngx.req.get_uri_args()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;username&amp;nbsp;=&amp;nbsp;args.username
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;utils.is_empty(username)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header.content_type&amp;nbsp;=&amp;nbsp;&amp;quot;text/plain;&amp;nbsp;charset=utf-8&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.status&amp;nbsp;=&amp;nbsp;400
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;请提供用户名&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.HTTP_BAD_REQUEST)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;注册新用户,调用otp.register_user生成OTP密钥并存储到数据库
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ok,&amp;nbsp;otp_secret,&amp;nbsp;message&amp;nbsp;=&amp;nbsp;otp.register_user(username)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;成功时返回包含用户和OTP&amp;nbsp;密钥的HTML页面
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置Content-Type为text/html，确保浏览器正确解析HTML
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header.content_type&amp;nbsp;=&amp;nbsp;&amp;quot;text/html;&amp;nbsp;charset=utf-8&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say([[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!DOCTYPE&amp;nbsp;html&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;html&amp;nbsp;lang=&amp;quot;zh-CN&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;head&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;meta&amp;nbsp;charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;title&amp;gt;注册成功&amp;lt;/title&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;style&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;body&amp;nbsp;{&amp;nbsp;font-family:&amp;nbsp;Arial,&amp;nbsp;sans-serif;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.container&amp;nbsp;{&amp;nbsp;max-width:&amp;nbsp;600px;&amp;nbsp;margin:&amp;nbsp;0&amp;nbsp;auto;&amp;nbsp;padding:&amp;nbsp;20px;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.success&amp;nbsp;{&amp;nbsp;color:&amp;nbsp;green;&amp;nbsp;font-weight:&amp;nbsp;bold;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.back-link&amp;nbsp;{&amp;nbsp;display:&amp;nbsp;inline-block;&amp;nbsp;margin-top:&amp;nbsp;20px;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/style&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/head&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;body&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;nbsp;class=&amp;quot;container&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;h2&amp;gt;注册成功&amp;lt;/h2&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;p&amp;nbsp;class=&amp;quot;success&amp;quot;&amp;gt;用户名:&amp;nbsp;]]&amp;nbsp;..&amp;nbsp;username&amp;nbsp;..&amp;nbsp;[[&amp;lt;/p&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;p&amp;nbsp;class=&amp;quot;success&amp;quot;&amp;gt;OTP密钥:&amp;nbsp;]]&amp;nbsp;..&amp;nbsp;otp_secret&amp;nbsp;..&amp;nbsp;[[&amp;lt;/p&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;p&amp;gt;请使用此密钥在Google&amp;nbsp;Authenticator中生成验证码&amp;lt;/p&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;a&amp;nbsp;href=&amp;quot;/verify&amp;quot;&amp;nbsp;class=&amp;quot;back-link&amp;quot;&amp;gt;返回登录页面&amp;lt;/a&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/body&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/html&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header.content_type&amp;nbsp;=&amp;nbsp;&amp;quot;text/plain;&amp;nbsp;charset=utf-8&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.status&amp;nbsp;=&amp;nbsp;500
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;注册失败:&amp;nbsp;&amp;quot;,&amp;nbsp;message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;处理完注册请求后，直接退出，不再执行后续代码
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.HTTP_OK)
end

--&amp;nbsp;显示登录弹窗
show_login_popup(nil,&amp;nbsp;ngx.var.request_uri)&lt;/pre&gt;&lt;p&gt;# vim /usr/local/nginx/conf/nginx.conf&amp;nbsp; #nginx主配置文件配置&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#gzip&amp;nbsp;&amp;nbsp;on;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_package_path&amp;nbsp;&amp;quot;/usr/local/lua_core/lib/lua/?.lua;/usr/local/nginx/conf/auth/?.lua;;&amp;quot;;
......
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/usr/local/nginx/conf/conf.d/*.conf;&lt;/pre&gt;&lt;p&gt;# vim /usr/local/nginx/conf/conf.d/lua.test.com.conf&amp;nbsp; #配置一个测试域名,然后本地电脑配置hosts&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name&amp;nbsp;lua.test.com;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root&amp;nbsp;/opt/web;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;charset&amp;nbsp;utf-8;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_log&amp;nbsp;/usr/local/nginx/logs/error.log&amp;nbsp;debug;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;主应用入口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default_type&amp;nbsp;&amp;#39;text/plain&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;指定&amp;nbsp;Lua&amp;nbsp;处理程序
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_file&amp;nbsp;/usr/local/nginx/conf/auth/main.lua;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;~&amp;nbsp;^/favicon.ico$&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;404;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/opt/www/favicon;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# /usr/local/nginx/sbin/nginx -s reload&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;2.3 访问验证一下&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;先注册一个test用户：http://lua.test.com/register?username=test&amp;nbsp; &amp;nbsp;#如果已经存在test用户了,web页面会提示：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;注册失败:&amp;nbsp;注册失败:&amp;nbsp;执行SQL失败:&amp;nbsp;Duplicate&amp;nbsp;entry&amp;nbsp;&amp;#39;test&amp;#39;&amp;nbsp;for&amp;nbsp;key&amp;nbsp;&amp;#39;username&amp;#39;,&amp;nbsp;错误码:&amp;nbsp;1062,&amp;nbsp;SQL状态:&amp;nbsp;23000&lt;/pre&gt;&lt;p&gt;再来注册一个不存在的用户：http://lua.test.com/register?username=test1&amp;nbsp; #下面是web页面显示效果&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/06/202506171750150899117106.png&quot; alt=&quot;image.png&quot; width=&quot;462&quot; height=&quot;210&quot; style=&quot;width: 462px; height: 210px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;下面我们点击下返回登录页面进入验证页面(http://lua.test.com/verify),现在访问任何页面都会页面弹窗需要登录验证除了注册页面,如下图：&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/06/202506241750749567497486.png&quot; alt=&quot;image.png&quot; width=&quot;657&quot; height=&quot;388&quot; style=&quot;width: 657px; height: 388px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;三、增加一个IP白名单的功能&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;#如果我们有一些固定的出口IP,使用者不希望每天都进行一次登录验证,或者当程序验证有问题频繁弹出验证框的时候,IP白名单功能就很有用了。&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;text-wrap-mode: wrap; background-color: #CCC1D9;&quot;&gt;3.1 代码编写&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# vim /usr/local/nginx/conf/auth/utils.lua&amp;nbsp; #增加IP处理逻辑&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;日志函数
function&amp;nbsp;_M.log(level,&amp;nbsp;msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;分级日志：支持&amp;nbsp;debug、info、warn、error&amp;nbsp;四种日志级别&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_levels&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debug&amp;nbsp;=&amp;nbsp;ngx.DEBUG,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;info&amp;nbsp;=&amp;nbsp;ngx.INFO,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;warn&amp;nbsp;=&amp;nbsp;ngx.WARN,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error&amp;nbsp;=&amp;nbsp;ngx.ERR
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;默认级别：若传入的日志级别不合法，默认使用info级别
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;log_level&amp;nbsp;=&amp;nbsp;log_levels[level]&amp;nbsp;or&amp;nbsp;log_levels.info
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--日志前缀：所有日志都会自动添加&amp;nbsp;[auth]&amp;nbsp;前缀，方便在&amp;nbsp;Nginx&amp;nbsp;日志中快速定位和过滤
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(log_level,&amp;nbsp;&amp;quot;[auth]&amp;nbsp;&amp;quot;,&amp;nbsp;msg)
end

--&amp;nbsp;读取文件内容
function&amp;nbsp;_M.read_file(path)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用io.open(path,&amp;nbsp;&amp;quot;r&amp;quot;)&amp;nbsp;以只读模式打开文件,若文件打开失败返回nil并记录错误
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;file&amp;nbsp;=&amp;nbsp;io.open(path,&amp;nbsp;&amp;quot;r&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;file&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_M.log(&amp;quot;error&amp;quot;,&amp;nbsp;&amp;quot;Failed&amp;nbsp;to&amp;nbsp;open&amp;nbsp;file:&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;path)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;nil
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用file:read(&amp;quot;*all&amp;quot;)&amp;nbsp;一次性读取整个文件内容，适合小文件。大文件可能导致内存溢出。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;若处理大文件（如超过&amp;nbsp;100MB），建议改用循环读取（如&amp;nbsp;file:read(4096)）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;可以使用pcall包裹读取逻辑，防止文件读取过程中出错导致Lua脚本崩溃
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;content&amp;nbsp;=&amp;nbsp;file:read(&amp;quot;*all&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;无论文件读取是否成功，file:close()&amp;nbsp;都会执行（在当前实现中隐含正确关闭，更健壮的写法可使用&amp;nbsp;finally&amp;nbsp;确保关闭）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;content
end

--&amp;nbsp;检查IP是否在CIDR范围内
function&amp;nbsp;_M.ip_in_cidr(ip,&amp;nbsp;cidr)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip_parts&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用&amp;nbsp;ip:gmatch(&amp;quot;%d+&amp;quot;)&amp;nbsp;提取IP地址的四个数字部分，解析IP地址为四部分数字
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;part&amp;nbsp;in&amp;nbsp;ip:gmatch(&amp;quot;%d+&amp;quot;)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(ip_parts,&amp;nbsp;tonumber(part))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#ip_parts&amp;nbsp;~=&amp;nbsp;4&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解析CIDR格式&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cidr_ip,&amp;nbsp;cidr_mask&amp;nbsp;=&amp;nbsp;cidr:match(&amp;quot;^([^/]+)/(%d+)$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;cidr_ip&amp;nbsp;or&amp;nbsp;not&amp;nbsp;cidr_mask&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;若不是CIDR格式，直接比较IP是否相等&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ip&amp;nbsp;==&amp;nbsp;cidr
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解析CIDR中的IP部分
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cidr_parts&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;part&amp;nbsp;in&amp;nbsp;cidr_ip:gmatch(&amp;quot;%d+&amp;quot;)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table.insert(cidr_parts,&amp;nbsp;tonumber(part))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;#cidr_parts&amp;nbsp;~=&amp;nbsp;4&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;验证掩码合法性
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cidr_mask&amp;nbsp;=&amp;nbsp;tonumber(cidr_mask)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;cidr_mask&amp;nbsp;&amp;lt;&amp;nbsp;0&amp;nbsp;or&amp;nbsp;cidr_mask&amp;nbsp;&amp;gt;&amp;nbsp;32&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将IP转换为32位整数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip_num&amp;nbsp;=&amp;nbsp;(ip_parts[1]&amp;nbsp;*&amp;nbsp;2^24)&amp;nbsp;+&amp;nbsp;(ip_parts[2]&amp;nbsp;*&amp;nbsp;2^16)&amp;nbsp;+&amp;nbsp;(ip_parts[3]&amp;nbsp;*&amp;nbsp;2^8)&amp;nbsp;+&amp;nbsp;ip_parts[4]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cidr_num&amp;nbsp;=&amp;nbsp;(cidr_parts[1]&amp;nbsp;*&amp;nbsp;2^24)&amp;nbsp;+&amp;nbsp;(cidr_parts[2]&amp;nbsp;*&amp;nbsp;2^16)&amp;nbsp;+&amp;nbsp;(cidr_parts[3]&amp;nbsp;*&amp;nbsp;2^8)&amp;nbsp;+&amp;nbsp;cidr_parts[4]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;mask&amp;nbsp;=&amp;nbsp;bit.lshift(0xFFFFFFFF,&amp;nbsp;32&amp;nbsp;-&amp;nbsp;cidr_mask)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;通过按位与运算判断IP是否在CIDR范围内
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;bit.band(ip_num,&amp;nbsp;mask)&amp;nbsp;==&amp;nbsp;bit.band(cidr_num,&amp;nbsp;mask)
end&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#vim&amp;nbsp;/usr/local/nginx/conf/auth/main.lua&amp;nbsp; #主程序入口文件,增加一些代码,代码分拆到文件中指定的位置哈&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;获取客户端IP
local&amp;nbsp;function&amp;nbsp;get_client_ip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip&amp;nbsp;=&amp;nbsp;ngx.var.remote_addr
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查代理头
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ngx.var.http_x_forwarded_for&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ip&amp;nbsp;=&amp;nbsp;ngx.var.http_x_forwarded_for:match(&amp;quot;^([^,]+)&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ip
end

--&amp;nbsp;检查IP是否在白名单中
local&amp;nbsp;function&amp;nbsp;check_ip_whitelist(ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;whitelist_file&amp;nbsp;=&amp;nbsp;config.whitelist_file
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;content&amp;nbsp;=&amp;nbsp;utils.read_file(whitelist_file)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;content&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.WARN,&amp;nbsp;&amp;quot;白名单文件不存在或无法读取:&amp;nbsp;&amp;quot;,&amp;nbsp;whitelist_file)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查IP是否在白名单中
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;line&amp;nbsp;in&amp;nbsp;content:gmatch(&amp;quot;[^\r\n]+&amp;quot;)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;whitelisted_ip&amp;nbsp;=&amp;nbsp;line:match(&amp;quot;^%s*(.-)%s*$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;whitelisted_ip&amp;nbsp;and&amp;nbsp;whitelisted_ip&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;utils.ip_in_cidr(ip,&amp;nbsp;whitelisted_ip)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
end
--&amp;nbsp;主逻辑开始
local&amp;nbsp;client_ip&amp;nbsp;=&amp;nbsp;get_client_ip()
ngx.log(ngx.INFO,&amp;nbsp;&amp;quot;客户端IP:&amp;nbsp;&amp;quot;,&amp;nbsp;client_ip)

--&amp;nbsp;检查IP白名单
if&amp;nbsp;check_ip_whitelist(client_ip)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.INFO,&amp;nbsp;&amp;quot;IP在白名单中，跳过验证:&amp;nbsp;&amp;quot;,&amp;nbsp;client_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;return&amp;nbsp;&amp;nbsp;--&amp;nbsp;继续处理原始请求,return后面一定要跟ngx.exit(ngx.DECLINED),不然你访问什么都是200状态码但是没内容
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;声明当前阶段处理完成，让Nginx继续后续处理
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ngx.exit(ngx.DECLINED)
end&lt;/pre&gt;&lt;p&gt;#这里我们要着重了解一下单纯的rerurn、return ngx.exit(ngx.DECLINED)、return ngx.exec(ngx.var.uri)的区别：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;return的作用：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14px; text-wrap-mode: nowrap;&quot;&gt;终止当前 Lua 代码块的执行：当遇到 return 时，Lua 脚本会立即退出当前函数或代码块，但 不会通知 Nginx 请求处理状态。适用场景：&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14px; text-wrap-mode: nowrap;&quot;&gt;在 if-else 分支中提前结束逻辑，继续执行后续 Lua 代码，&lt;/span&gt;&lt;span style=&quot;text-wrap-mode: nowrap;&quot;&gt;当需要返回值给调用者时（但在 Nginx 处理阶段中很少使用）。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: nowrap;&quot;&gt;当Nginx 处于 content_by_lua_file 阶段（负责生成响应内容）。由于没有显式生成内容或转发请求，Nginx 最终返回一个空响应（状态码 200，但内容为空）。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;text-wrap-mode: nowrap;&quot;&gt;return ngx.exit(ngx.DECLINED) 的作用：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;显式通知 Nginx 当前阶段处理完成：ngx.DECLINED 是一个特殊状态码，表示 “我不处理这个请求，交给后续模块/阶段继续处理”。适用场景：&lt;/p&gt;&lt;p&gt;在access 阶段(如 access_by_lua_file)中，用于跳过当前验证逻辑，让 Nginx 继续处理请求(如寻找静态文件、执行 proxy_pass等)当需要将请求流程控制权交还给 Nginx 时。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;ngx.exec(ngx.var.uri) 的作用：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;强制 Nginx 重新路由请求，从头开始处理当前 URI（包括执行 index 指令、寻找静态文件等）。&lt;/p&gt;&lt;p&gt;适用于 content_by_lua_file 场景，确保请求被正确转发到后续处理流程。&lt;/p&gt;&lt;p&gt;#echo &amp;quot;客户端IP&amp;quot; &amp;gt;&amp;gt;/usr/local/nginx/conf/auth/ip_whitelist.txt&amp;nbsp; #把你要加的IP添加到此白名单文件中,一个IP或者网段单独一行&lt;span style=&quot;text-wrap-mode: nowrap;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# vim /opt/web/index.html&amp;nbsp; &amp;nbsp;#再来一个最简单的测试页面&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;哈哈少年,恭喜你通过了考验!&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;简单测试：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;浏览器分别访问：http://lua.test.com/index.html和http://lua.test.com/dasdasdas&amp;nbsp; (自己验证结果哈,就不截图了,会分别显示内容和404)&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/p&gt;&lt;h3 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;3.2 分层设计&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#上面得读取文件的例子简单,而且只要修改完白名单文件就能立即生效,但是问题嘛就是不适合线上场景,线上的请求量大频繁的i/o开销影响性能。&lt;/p&gt;&lt;p&gt;# 我们初步这么设计,Nginx一启动就添加白名单中的内容到内存中,server区域有一个全局的Ip白名单认证通过就放行,不通过再流转到location的cookie认证。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/init_whitelist.lua&amp;nbsp; &amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;config&amp;nbsp;=&amp;nbsp;require(&amp;quot;config&amp;quot;)

local&amp;nbsp;whitelist_cache&amp;nbsp;=&amp;nbsp;ngx.shared.ip_whitelist_cache

--&amp;nbsp;工具函数：检查IP是否在CIDR范围内
local&amp;nbsp;function&amp;nbsp;ip_in_cidr(ip,&amp;nbsp;cidr)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;若属于标准CIDR格式（像&amp;quot;192.168.1.0/24&amp;quot;这样），就会把IP地址和CIDR网络地址都转换为32位整数，然后借助位运算来判断该IP是否在这个网络范围内
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;_,&amp;nbsp;_,&amp;nbsp;base_ip,&amp;nbsp;bits&amp;nbsp;=&amp;nbsp;string.find(cidr,&amp;nbsp;&amp;quot;^(%d+%.%d+%.%d+%.%d+)/(%d+)$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;base_ip&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ip&amp;nbsp;==&amp;nbsp;cidr&amp;nbsp;&amp;nbsp;--&amp;nbsp;不是CIDR格式，直接精确匹配
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;function&amp;nbsp;ip_to_int(ip_str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;a,&amp;nbsp;b,&amp;nbsp;c,&amp;nbsp;d&amp;nbsp;=&amp;nbsp;string.match(ip_str,&amp;nbsp;&amp;quot;^(%d+)%.(%d+)%.(%d+)%.(%d+)$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(a&amp;nbsp;*&amp;nbsp;2^24)&amp;nbsp;+&amp;nbsp;(b&amp;nbsp;*&amp;nbsp;2^16)&amp;nbsp;+&amp;nbsp;(c&amp;nbsp;*&amp;nbsp;2^8)&amp;nbsp;+&amp;nbsp;d
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip_num&amp;nbsp;=&amp;nbsp;ip_to_int(ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;base_num&amp;nbsp;=&amp;nbsp;ip_to_int(base_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;mask&amp;nbsp;=&amp;nbsp;bit32.lshift(0xFFFFFFFF,&amp;nbsp;32&amp;nbsp;-&amp;nbsp;tonumber(bits))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;bit32.band(ip_num,&amp;nbsp;mask)&amp;nbsp;==&amp;nbsp;bit32.band(base_num,&amp;nbsp;mask)
end

--&amp;nbsp;加载白名单到共享字典
local&amp;nbsp;function&amp;nbsp;load_whitelist()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;file,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;io.open(config.whitelist_file,&amp;nbsp;&amp;quot;r&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;file&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;无法打开白名单文件:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;content&amp;nbsp;=&amp;nbsp;file:read(&amp;quot;*a&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file:close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;清空现有缓存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:flush_all()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;解析并存储白名单条目
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;count&amp;nbsp;=&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;line&amp;nbsp;in&amp;nbsp;string.gmatch(content,&amp;nbsp;&amp;quot;[^\r\n]+&amp;quot;)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;line&amp;nbsp;=&amp;nbsp;string.gsub(line,&amp;nbsp;&amp;quot;^%s*(.-)%s*$&amp;quot;,&amp;nbsp;&amp;quot;%1&amp;quot;)&amp;nbsp;&amp;nbsp;--&amp;nbsp;去除首尾空格
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;line&amp;nbsp;~=&amp;nbsp;&amp;quot;&amp;quot;&amp;nbsp;and&amp;nbsp;not&amp;nbsp;string.find(line,&amp;nbsp;&amp;quot;^#&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;直接将IP/CIDR作为键存入共享字典
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:set(&amp;quot;entry:&amp;quot;&amp;nbsp;..&amp;nbsp;line,&amp;nbsp;true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;count&amp;nbsp;=&amp;nbsp;count&amp;nbsp;+&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;存储元数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:set(&amp;quot;last_loaded&amp;quot;,&amp;nbsp;ngx.time())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:set(&amp;quot;count&amp;quot;,&amp;nbsp;count)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.INFO,&amp;nbsp;&amp;quot;加载白名单完成，共&amp;nbsp;&amp;quot;,&amp;nbsp;count,&amp;nbsp;&amp;quot;&amp;nbsp;条记录&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
end

--&amp;nbsp;导出加载函数供工作进程调用
local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}
_M.load_whitelist&amp;nbsp;=&amp;nbsp;load_whitelist

--&amp;nbsp;初始化执行（仅加载一次，不创建定时器）
local&amp;nbsp;success&amp;nbsp;=&amp;nbsp;load_whitelist()
if&amp;nbsp;not&amp;nbsp;success&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;白名单初始化失败，拒绝所有请求&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:set(&amp;quot;initialized&amp;quot;,&amp;nbsp;false)
else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;whitelist_cache:set(&amp;quot;initialized&amp;quot;,&amp;nbsp;true)
end

return&amp;nbsp;_M&lt;/pre&gt;&lt;p&gt;# vi /usr/local/nginx/conf/nginx.conf #定义共享内存加载启动初始化程序文件&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;lua_package_path&amp;nbsp;&amp;quot;/usr/local/lua_core/lib/lua/?.lua;/usr/local/nginx/conf/auth/?.lua;;&amp;quot;;
#&amp;nbsp;定义共享内存（根据白名单大小调整，1MB约可存储5万条IP）&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
lua_shared_dict&amp;nbsp;ip_whitelist_cache&amp;nbsp;5m;
#&amp;nbsp;Nginx启动时执行初始化脚本
init_by_lua_file&amp;nbsp;/usr/local/nginx/conf/auth/init_whitelist.lua;&lt;/pre&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/ip_utils.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{}

--&amp;nbsp;检查IP是否在CIDR范围内
function&amp;nbsp;_M.ip_in_cidr(ip,&amp;nbsp;cidr)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;_,&amp;nbsp;_,&amp;nbsp;base_ip,&amp;nbsp;bits&amp;nbsp;=&amp;nbsp;string.find(cidr,&amp;nbsp;&amp;quot;^(%d+%.%d+%.%d+%.%d+)/(%d+)$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;base_ip&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ip&amp;nbsp;==&amp;nbsp;cidr
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;function&amp;nbsp;ip_to_int(ip_str)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;a,&amp;nbsp;b,&amp;nbsp;c,&amp;nbsp;d&amp;nbsp;=&amp;nbsp;string.match(ip_str,&amp;nbsp;&amp;quot;^(%d+)%.(%d+)%.(%d+)%.(%d+)$&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(a&amp;nbsp;*&amp;nbsp;2^24)&amp;nbsp;+&amp;nbsp;(b&amp;nbsp;*&amp;nbsp;2^16)&amp;nbsp;+&amp;nbsp;(c&amp;nbsp;*&amp;nbsp;2^8)&amp;nbsp;+&amp;nbsp;d
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip_num&amp;nbsp;=&amp;nbsp;ip_to_int(ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;base_num&amp;nbsp;=&amp;nbsp;ip_to_int(base_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;mask&amp;nbsp;=&amp;nbsp;bit32.lshift(0xFFFFFFFF,&amp;nbsp;32&amp;nbsp;-&amp;nbsp;tonumber(bits))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;bit32.band(ip_num,&amp;nbsp;mask)&amp;nbsp;==&amp;nbsp;bit32.band(base_num,&amp;nbsp;mask)
end

return&amp;nbsp;_M&lt;/pre&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/shared_state.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;_M&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is_whitelisted&amp;nbsp;=&amp;nbsp;false
}

function&amp;nbsp;_M.set_whitelisted(status)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_M.is_whitelisted&amp;nbsp;=&amp;nbsp;status
end

function&amp;nbsp;_M.get_whitelisted()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;_M.is_whitelisted
end

return&amp;nbsp;_M&lt;/pre&gt;&lt;p&gt;# vi /usr/local/nginx/conf/auth/check_whitelist.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;shared_state&amp;nbsp;=&amp;nbsp;require(&amp;quot;shared_state&amp;quot;)

--&amp;nbsp;check_whitelist.lua
local&amp;nbsp;whitelist_cache&amp;nbsp;=&amp;nbsp;ngx.shared.ip_whitelist_cache

--&amp;nbsp;获取客户端IP
local&amp;nbsp;function&amp;nbsp;get_client_ip()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip&amp;nbsp;=&amp;nbsp;ngx.var.remote_addr
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;xff&amp;nbsp;=&amp;nbsp;ngx.var.http_x_forwarded_for
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;xff&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;从XFF头中提取出第一个IP地址，以此作为客户端的真实&amp;nbsp;IP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;first_ip&amp;nbsp;=&amp;nbsp;string.match(xff,&amp;nbsp;&amp;quot;^([^,]+)&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;first_ip&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ip&amp;nbsp;=&amp;nbsp;first_ip
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ip
end

--&amp;nbsp;检查IP是否在白名单中
local&amp;nbsp;function&amp;nbsp;check_ip_whitelist(ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;initialized&amp;nbsp;=&amp;nbsp;whitelist_cache:get(&amp;quot;initialized&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;先确认白名单是否已经初始化，如果没有初始化，就会拒绝所有请求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;initialized&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;白名单未初始化，拒绝请求&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;先检查精确匹配
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;whitelist_cache:get(&amp;quot;entry:&amp;quot;&amp;nbsp;..&amp;nbsp;ip)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;若精确匹配不通过，就会遍历所有CIDR格式的条目，使用的ip_in_cidr函数进行网络范围匹配
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;max_count&amp;nbsp;=&amp;nbsp;10000&amp;nbsp;&amp;nbsp;--&amp;nbsp;限制最大扫描数量，为了防止遍历操作消耗过多资源
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;keys&amp;nbsp;=&amp;nbsp;whitelist_cache:get_keys(max_count)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;_,&amp;nbsp;key&amp;nbsp;in&amp;nbsp;ipairs(keys)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;string.sub(key,&amp;nbsp;1,&amp;nbsp;6)&amp;nbsp;==&amp;nbsp;&amp;quot;entry:&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;cidr&amp;nbsp;=&amp;nbsp;string.sub(key,&amp;nbsp;7)&amp;nbsp;&amp;nbsp;--&amp;nbsp;提取实际IP/CIDR
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;跳过精确IP（已检查过）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;string.find(cidr,&amp;nbsp;&amp;quot;/&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ip_in_cidr&amp;nbsp;=&amp;nbsp;require&amp;nbsp;&amp;quot;ip_utils&amp;quot;.ip_in_cidr
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ip_in_cidr(ip,&amp;nbsp;cidr)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;false
end

--&amp;nbsp;主逻辑
local&amp;nbsp;client_ip&amp;nbsp;=&amp;nbsp;get_client_ip()
ngx.log(ngx.INFO,&amp;nbsp;&amp;quot;客户端IP:&amp;nbsp;&amp;quot;,&amp;nbsp;client_ip)

if&amp;nbsp;check_ip_whitelist(client_ip)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;ngx.log(ngx.INFO,&amp;nbsp;&amp;quot;IP在白名单中，允许访问:&amp;nbsp;&amp;quot;,&amp;nbsp;client_ip)
&amp;nbsp;&amp;nbsp;shared_state.set_whitelisted(true)&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置共享状态
&amp;nbsp;&amp;nbsp;return&amp;nbsp;ngx.exit(ngx.DECLINED)
else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;继续处理请求，让后续模块显示登录弹窗
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.WARN,&amp;nbsp;&amp;quot;IP不在白名单中，走弹窗认证逻辑:&amp;nbsp;&amp;quot;,&amp;nbsp;client_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;ngx.log(ngx.WARN,&amp;nbsp;&amp;quot;IP不在白名单中，拒绝访问:&amp;nbsp;&amp;quot;,&amp;nbsp;client_ip)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;return&amp;nbsp;ngx.exit(ngx.HTTP_FORBIDDEN)&amp;nbsp;&amp;nbsp;--如果想不在白名单中就直接返回403就开启这两行
end&lt;/pre&gt;&lt;p&gt;#把3.1加的IP白名单的函数去掉,这里就不再展示注释代码了(不注释的话就算你走过了全局的IP白名单认证还会卡在main.lua这里)&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# vi /usr/local/nginx/conf/auth/main.lua&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;开头添加导入
local&amp;nbsp;shared_state&amp;nbsp;=&amp;nbsp;require(&amp;quot;shared_state&amp;quot;)
local&amp;nbsp;is_whitelisted&amp;nbsp;=&amp;nbsp;shared_state.get_whitelisted()
--&amp;nbsp;在底部添加
if&amp;nbsp;is_whitelisted&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;ngx.exit(ngx.DECLINED)
end

--&amp;nbsp;显示登录弹窗
show_login_popup(nil,&amp;nbsp;ngx.var.request_uri)&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;# vim /usr/local/nginx/conf/conf.d/lua.test.com.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name&amp;nbsp;lua.test.com;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root&amp;nbsp;/opt/web;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;charset&amp;nbsp;utf-8;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_log&amp;nbsp;/usr/local/nginx/logs/error.log&amp;nbsp;debug;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;在access阶段进行IP白名单检查
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_by_lua_file&amp;nbsp;/usr/local/nginx/conf/auth/check_whitelist.lua;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default_type&amp;nbsp;&amp;#39;text/plain&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_file&amp;nbsp;/usr/local/nginx/conf/auth/main.lua;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;index&amp;nbsp;index.html;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;允许访问注册接口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;=&amp;nbsp;/register&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default_type&amp;nbsp;&amp;#39;text/plain&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_file&amp;nbsp;/usr/local/nginx/conf/auth/main.lua;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;~&amp;nbsp;^/favicon.ico$&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;404;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/opt/www/favicon;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# /usr/local/nginx/sbin/nginx -s reload&amp;nbsp; &amp;nbsp;#可以通过ip_whitelist.txt中修改ip来验证效果,这里就不再截图演示了&lt;/p&gt;&lt;p&gt;#上面是通过Lua模块共享状态的方式,仅在单次请求生命周期内有效，适合简单场景&lt;/p&gt;&lt;p&gt;#下面还有通过通过请求头传递白名单状态：ngx.var.is_whitelisted = &amp;quot;1&amp;quot;/&amp;quot;0&amp;quot;的方式，还有使用共享字典存储临时认证状态的方式,可跨请求持久化，适合需要更复杂认证的场景：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;nbsp;--&amp;nbsp;check_whitelist.lua中为白名单IP创建临时认证标记
&amp;nbsp;--在共享字典中标记该IP已认证（有效期1小时）
local&amp;nbsp;auth_cache&amp;nbsp;=&amp;nbsp;ngx.shared.auth_cache
auth_cache:set(&amp;quot;ip_auth:&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip,&amp;nbsp;true,&amp;nbsp;3600)
--&amp;nbsp;在main.lua中引用
local&amp;nbsp;auth_cache&amp;nbsp;=&amp;nbsp;ngx.shared.auth_cache
local&amp;nbsp;client_ip&amp;nbsp;=&amp;nbsp;get_client_ip()
local&amp;nbsp;is_ip_whitelisted&amp;nbsp;=&amp;nbsp;auth_cache:get(&amp;quot;ip_auth:&amp;quot;&amp;nbsp;..&amp;nbsp;client_ip)&lt;/pre&gt;</description><pubDate>Wed, 11 Jun 2025 18:43:09 +0800</pubDate></item><item><title>Nginx结合Lua基础知识(一)</title><link>https://blog.51niux.com/?id=323</link><description>&lt;p&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;一、Nginx+Lua简单使用&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.1 简单方式直接使用openresty&lt;/span&gt;&lt;/h3&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;1.1.1&amp;nbsp;openresty安装&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;#官网：https://openresty.org/en/&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;#&amp;nbsp;yum install -y readline-devel pcre-devel openssl-devel&lt;/p&gt;&lt;p&gt;#&amp;nbsp;wget &lt;a href=&quot;https://openresty.org/download/openresty-1.27.1.1.tar.gz&quot; _src=&quot;https://openresty.org/download/openresty-1.27.1.1.tar.gz&quot;&gt;https://openresty.org/download/openresty-1.27.1.1.tar.gz&lt;/a&gt; &lt;/p&gt;&lt;p&gt;# tar xf openresty-1.27.1.1.tar.gz&lt;/p&gt;&lt;p&gt;#&amp;nbsp;cd openresty-1.27.1.1/&lt;/p&gt;&lt;p&gt;#&amp;nbsp;./configure --prefix=/opt/soft/openresty --with-luajit --with-http_stub_status_module --with-pcre --with-pcre-jit&lt;/p&gt;&lt;p&gt;# gmake &amp;amp;&amp;amp; gmake install&lt;/p&gt;&lt;p&gt;# vim /opt/soft/openresty/nginx/conf/nginx.conf&amp;nbsp; #在server区域中加上这么一句调用lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/hello&amp;nbsp;{
&amp;nbsp;&amp;nbsp;default_type&amp;nbsp;text/html;
&amp;nbsp;&amp;nbsp;content_by_lua&amp;nbsp;&amp;#39;ngx.say(&amp;quot;hello,lua&amp;nbsp;scripts&amp;quot;)&amp;#39;;
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/openresty/nginx/sbin/nginx&lt;/p&gt;&lt;p&gt;# curl&amp;nbsp; 127.0.0.1/hello&amp;nbsp; &amp;nbsp;#直接curl调用测试一下会看到有内容数据,说明lua调用成功&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;hello,lua&amp;nbsp;scripts&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.2 nginx编译安装支持lua&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;安装luajit&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# wget&amp;nbsp; https://codeload.github.com/openresty/luajit2/tar.gz/refs/tags/v2.1-20250117.tar.gz&amp;nbsp;&lt;/p&gt;&lt;p&gt;# &lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;tar xf v2.1-20250117.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# cd&amp;nbsp;luajit2-2.1-20250117&lt;/p&gt;&lt;p&gt;# make install PREFIX=/usr/local/luajit&lt;/p&gt;&lt;p&gt;# vim&amp;nbsp;/etc/profile.d/lua.sh&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;export&amp;nbsp;LUAJIT_LIB=/usr/local/luajit/lib
export&amp;nbsp;LUAJIT_INC=/usr/local/luajit/include/luajit-2.1&lt;/pre&gt;&lt;p&gt;# source /etc/profile.d/lua.sh&lt;/p&gt;&lt;p&gt;# echo &amp;quot;/usr/local/luajit/lib/&amp;quot; &amp;gt;&amp;gt;/etc/ld.so.conf.d/lua.conf&lt;/p&gt;&lt;p&gt;#&amp;nbsp;ldconfig&lt;/p&gt;&lt;p&gt;#首先安装2.1版本的lua,因为我们是用OpenResty得lua-resty-core会提示需要luajit2.1,下面为报错提醒[注意但是不影响nginx启动,所以大家常用的&lt;a href=&quot;https://codeload.github.com/LuaJIT/LuaJIT/tar.gz/refs/tags/v2.0.4&quot; _src=&quot;https://codeload.github.com/LuaJIT/LuaJIT/tar.gz/refs/tags/v2.0.4&quot;&gt;https://codeload.github.com/LuaJIT/LuaJIT/tar.gz/refs/tags/v2.0.4&lt;/a&gt;&amp;nbsp;还是可以用的 ]：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;nginx:&amp;nbsp;[alert]&amp;nbsp;detected&amp;nbsp;a&amp;nbsp;LuaJIT&amp;nbsp;version&amp;nbsp;which&amp;nbsp;is&amp;nbsp;not&amp;nbsp;OpenResty&amp;#39;s;&amp;nbsp;many&amp;nbsp;optimizations&amp;nbsp;will&amp;nbsp;be&amp;nbsp;disabled&amp;nbsp;and&amp;nbsp;performance&amp;nbsp;will&amp;nbsp;be&amp;nbsp;compromised&amp;nbsp;
(see&amp;nbsp;https://github.com/openresty/luajit2&amp;nbsp;for&amp;nbsp;OpenResty&amp;#39;s&amp;nbsp;LuaJIT&amp;nbsp;or,&amp;nbsp;even&amp;nbsp;better,&amp;nbsp;consider&amp;nbsp;using&amp;nbsp;the&amp;nbsp;OpenResty&amp;nbsp;releases&amp;nbsp;from&amp;nbsp;https://openresty.org/en/download.html)
nginx:&amp;nbsp;[alert]&amp;nbsp;[lua]&amp;nbsp;base.lua:42:&amp;nbsp;use&amp;nbsp;of&amp;nbsp;lua-resty-core&amp;nbsp;with&amp;nbsp;LuaJIT&amp;nbsp;2.0&amp;nbsp;is&amp;nbsp;not&amp;nbsp;recommended;&amp;nbsp;use&amp;nbsp;LuaJIT&amp;nbsp;2.1+&amp;nbsp;instead
nginx:&amp;nbsp;[alert]&amp;nbsp;[lua]&amp;nbsp;lrucache.lua:25:&amp;nbsp;use&amp;nbsp;of&amp;nbsp;lua-resty-lrucache&amp;nbsp;with&amp;nbsp;LuaJIT&amp;nbsp;2.0&amp;nbsp;is&amp;nbsp;not&amp;nbsp;recommended;&amp;nbsp;use&amp;nbsp;LuaJIT&amp;nbsp;2.1+&amp;nbsp;instead&lt;/pre&gt;&lt;p&gt;#然后你说我安装官网的&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/LuaJIT/LuaJIT/archive/refs/tags/v2.1.ROLLING.tar.gz&quot; _src=&quot;https://github.com/LuaJIT/LuaJIT/archive/refs/tags/v2.1.ROLLING.tar.gz&quot; style=&quot;text-wrap-mode: wrap;&quot;&gt;https://github.com/LuaJIT/LuaJIT/archive/refs/tags/v2.1.ROLLING.tar.gz&lt;/a&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&amp;nbsp; 好了吧,但是还是会有提醒依旧不会影响你服务启动,下面提醒：&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;nginx:&amp;nbsp;[alert]&amp;nbsp;detected&amp;nbsp;a&amp;nbsp;LuaJIT&amp;nbsp;version&amp;nbsp;which&amp;nbsp;is&amp;nbsp;not&amp;nbsp;OpenResty&amp;#39;s;&amp;nbsp;many&amp;nbsp;optimizations&amp;nbsp;will&amp;nbsp;be&amp;nbsp;disabled&amp;nbsp;and&amp;nbsp;performance&amp;nbsp;will&amp;nbsp;be&amp;nbsp;compromised&amp;nbsp;
(see&amp;nbsp;https://github.com/openresty/luajit2&amp;nbsp;for&amp;nbsp;OpenResty&amp;#39;s&amp;nbsp;LuaJIT&amp;nbsp;or,&amp;nbsp;even&amp;nbsp;better,&amp;nbsp;consider&amp;nbsp;using&amp;nbsp;the&amp;nbsp;OpenResty&amp;nbsp;releases&amp;nbsp;from&amp;nbsp;https://openresty.org/en/download.html)&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;安装需要的lua模块&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#wget&amp;nbsp;https://codeload.github.com/openresty/lua-nginx-module/tar.gz/refs/tags/v0.10.28.tar.gz&lt;/p&gt;&lt;p&gt;#tar xf&amp;nbsp;lua-nginx-module-0.10.28.tar.gz&lt;/p&gt;&lt;p&gt;#wget&amp;nbsp;https://codeload.github.com/openresty/lua-resty-core/tar.gz/refs/tags/v0.1.31.tar.gz&lt;/p&gt;&lt;p&gt;#tar xf&amp;nbsp;lua-resty-core-0.1.31.tar.gz&lt;/p&gt;&lt;p&gt;#cd&amp;nbsp;lua-resty-core-0.1.31&lt;/p&gt;&lt;p&gt;#make install PREFIX=/usr/local/lua_core&lt;/p&gt;&lt;p&gt;#wget https://codeload.github.com/openresty/lua-resty-lrucache/tar.gz/refs/tags/v0.15.tar.gz&lt;/p&gt;&lt;p&gt;#tar xf&amp;nbsp;lua-resty-lrucache-0.15.tar.gz&lt;/p&gt;&lt;p&gt;#cd&amp;nbsp;lua-resty-lrucache&lt;/p&gt;&lt;p&gt;#make install PREFIX=/usr/local/lua_core&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;安装nginx并配置lua进行测试&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# yum install pcre pcre-devel openssl openssl-devel gd gd-devel -y&lt;/p&gt;&lt;p&gt;#wget https://nginx.org/download/nginx-1.28.0.tar.gz&lt;/p&gt;&lt;p&gt;#tar xf&amp;nbsp;nginx-1.28.0.tar.gz&lt;/p&gt;&lt;p&gt;# cd nginx-1.28.0/&lt;/p&gt;&lt;p&gt;#./configure --prefix=/opt/soft/nginx --sbin-path=/opt/soft/nginx/sbin/nginx --conf-path=/opt/soft/nginx/main-conf/nginx.conf --error-log-path=/opt/log/nginx/error.log --http-log-path=/opt/log/nginx/access.log --pid-path=/opt/soft/nginx/run/nginx.pid --lock-path=/opt/soft/nginx/run/nginx.lock --user=work --group=work --http-client-body-temp-path=/opt/soft/nginx/cache/client_temp --http-proxy-temp-path=/opt/soft/nginx/cache/proxy_temp --http-fastcgi-temp-path=/opt/soft/nginx/cache/fastcgi_temp --http-uwsgi-temp-path=/opt/soft/nginx/cache/uwsgi_tmp --http-scgi-temp-path=/opt/soft/nginx/cache/scgi_temp --with-http_v2_module --with-http_stub_status_module --with-http_ssl_module --with-http_realip_module --with-http_sub_module --with-http_gzip_static_module --with-pcre --with-http_addition_module --with-http_image_filter_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_gzip_static_module --with-file-aio --with-http_random_index_module --with-ld-opt=-Wl,-rpath,/usr/local/luajit/lib --add-module=/opt/soft/package/nginx_lua/lua-nginx-module-0.10.28&lt;/p&gt;&lt;p&gt;#make -j 8&lt;/p&gt;&lt;p&gt;#make install&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# vim /opt/soft/nginx/main-conf/nginx.conf&amp;nbsp; #增加一条lua测试location&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/lua&amp;nbsp;{&amp;nbsp;default_type&amp;nbsp;&amp;#39;text/plain&amp;#39;;&amp;nbsp;content_by_lua&amp;nbsp;&amp;#39;ngx.say(&amp;quot;hello,&amp;nbsp;lua&amp;quot;)&amp;#39;;&amp;nbsp;}

location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;root&amp;nbsp;&amp;nbsp;&amp;nbsp;html;
&amp;nbsp;&amp;nbsp;index&amp;nbsp;&amp;nbsp;index.html&amp;nbsp;index.htm;
}&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;#mkdir /opt/soft/nginx/cache&lt;/p&gt;&lt;p&gt;#/opt/soft/nginx/sbin/nginx -t&amp;nbsp; #测试是没有报错的所以语法没问题&lt;/p&gt;&lt;p&gt;#/opt/soft/nginx/sbin/nginx&amp;nbsp; #报错如下：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;nginx:&amp;nbsp;[alert]&amp;nbsp;failed&amp;nbsp;to&amp;nbsp;load&amp;nbsp;the&amp;nbsp;&amp;#39;resty.core&amp;#39;&amp;nbsp;module&amp;nbsp;(https://github.com/openresty/lua-resty-core);&amp;nbsp;
ensure&amp;nbsp;you&amp;nbsp;are&amp;nbsp;using&amp;nbsp;an&amp;nbsp;OpenResty&amp;nbsp;release&amp;nbsp;from&amp;nbsp;https://openresty.org/en/download.html&amp;nbsp;(reason:&amp;nbsp;module&amp;nbsp;&amp;#39;resty.core&amp;#39;&amp;nbsp;not&amp;nbsp;found:
	no&amp;nbsp;field&amp;nbsp;package.preload[&amp;#39;resty.core&amp;#39;]
	no&amp;nbsp;file&amp;nbsp;&amp;#39;./resty/core.lua&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/luajit/share/luajit-2.1/resty/core.lua&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/share/lua/5.1/resty/core.lua&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/share/lua/5.1/resty/core/init.lua&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/luajit/share/lua/5.1/resty/core.lua&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/luajit/share/lua/5.1/resty/core/init.lua&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;./resty/core.so&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/lib/lua/5.1/resty/core.so&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/luajit/lib/lua/5.1/resty/core.so&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/lib/lua/5.1/loadall.so&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;./resty.so&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/lib/lua/5.1/resty.so&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/luajit/lib/lua/5.1/resty.so&amp;#39;
	no&amp;nbsp;file&amp;nbsp;&amp;#39;/usr/local/lib/lua/5.1/loadall.so&amp;#39;)&amp;nbsp;in&amp;nbsp;/opt/soft/nginx/main-conf/nginx.conf:119&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;# vim /opt/soft/nginx/main-conf/nginx.conf&amp;nbsp; &amp;nbsp;#开启引用前面编译的lua模块&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#gzip&amp;nbsp;&amp;nbsp;on;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#就是增加下面这一段
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lua_package_path&amp;nbsp;&amp;quot;/usr/local/lua_core/lib/lua/?.lua;;&amp;quot;;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; &amp;nbsp;#启动没有再报错&lt;/p&gt;&lt;p&gt;&amp;nbsp;# curl&amp;nbsp; 127.0.0.1/lua&amp;nbsp; &amp;nbsp;#测试输出正常&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;hello,&amp;nbsp;lua&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.3&amp;nbsp;nginx调用lua的简单介绍&lt;/span&gt;&lt;/h3&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;lua在nginx的指令执行顺序：&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;图片链接：&lt;a href=&quot;https://github.com/openresty/lua-nginx-module/blob/master/doc/images/lua_nginx_modules_directives.drawio.png&quot; _src=&quot;https://github.com/openresty/lua-nginx-module/blob/master/doc/images/lua_nginx_modules_directives.drawio.png&quot;&gt;https://github.com/openresty/lua-nginx-module/blob/master/doc/images/lua_nginx_modules_directives.drawio.png&lt;/a&gt;&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;我们顺着上面的流程图把&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space-collapse: preserve; background-color: #FFFFFF;&quot;&gt;贯穿整个 Nginx生命周期的Lua 脚本执行链路,也就是Nginx Lua 模块提供了一系列钩子函数（hook),允许我们在 Nginx 的不同阶段执行 Lua 脚本介绍一下。以下是这些钩子的主要作用和调用顺序：&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#一、初始化阶段
init_by_lua钩子函数&amp;nbsp;#全局初始化阶段,这是Nginx启动时最早的一个阶段,发生在主进程(master&amp;nbsp;process)启动期间,且只执行一次。
init_worker_by_lua钩子函数&amp;nbsp;#Worker初始化阶段,在每个worker进程启动时执行,每个worker&amp;nbsp;进程都会独立运行一次,初始化的内容仅对当前&amp;nbsp;worker进程有效

#二、TLS(传输层安全协议)相关的钩子函数主要用于处理与TLS&amp;nbsp;握手、证书动态加载以及客户端认证等相关的逻辑
ssl_client_hello_by_lua&amp;nbsp;#TLS客户端握手阶段,在收到客户端的TLS&amp;nbsp;ClientHello消息时触发,发生在SSL/TLS握手的最早阶段
ssl_certificate_by_lua&amp;nbsp;#TLS证书动态加载阶段,在TLS握手期间,当客户端请求连接时触发。发生在SSL/TLS握手之前
ssl_session_fetch_by_lua&amp;nbsp;#TLS会话恢复阶段,在TLS握手期间，尝试恢复之前保存的TLS会话时触发。发生在SSL/TLS握手之前
ssl_session_store_by_lua&amp;nbsp;#TLS会话存储阶段,在TLS握手完成后,将新的TLS会话保存到外部存储时触发

#三、HTTP&amp;nbsp;level阶段,Nginx处理HTTP请求的各个阶段
server_rewrite_by_lua&amp;nbsp;#这个阶段主要用于URL重写和复杂的逻辑处理，如访问控制或动态内容生成
set_by_lua&amp;nbsp;#允许通过Lua脚本计算并设置变量值。它通常用于实现复杂的逻辑处理,并返回结果,在请求的rewrite和access阶段之前完成⁠⁣&amp;nbsp;
rewrite_by_lua&amp;nbsp;#用于在Nginx的rewrite阶段执行Lua脚本，主要用于URL重写和转发等功能
access_by_lua&amp;nbsp;#用于在访问阶段执行用户自定义的Lua代码,在请求到达后端服务前进行权限检查,可以用来做权限管理/动态路由/限流控制/黑名单机制
content_by_lua&amp;nbsp;#处理请求内容并生成响应⁠⁣
balancer_by_lua&amp;nbsp;#在Nginx的负载均衡阶段动态选择上游服务器,它主要用于实现复杂的、自定义的负载均衡逻辑，例如基于请求参数、哈希算法或动态服务发现来选择后端服务器。
header_filter_by_lua&amp;nbsp;#允许用户在HTTP响应头发送给客户端之前执行自定义Lua脚本。这一阶段的主要用途是修改或设置响应头
body_filter_by_lua&amp;nbsp;#用于在响应体发送到客户端之前对其进行修改。它允许开发者以流式处理的方式操作响应内容
log_filter_by_lua&amp;nbsp;#允许使用Lua脚本在日志记录阶段执行自定义逻辑，可用于统计分析或添加额外信息到日志中

#四、Worker进程退出阶段
exit_worker_by_lua&amp;nbsp;#当Nginx&amp;nbsp;Worker进程需要关闭时(例如服务器重启、重新加载配置或正常关闭时)，这一阶段适合用于清理资源、记录日志或通知外部系统等操作⁠⁣&lt;/pre&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;Nginx调⽤ Lua 模块指令,Nginx的可插拔模块加载执⾏：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;set_by_lua&amp;nbsp;set_by_lua_file&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#设置Nginx变量,可以实现负载的赋值逻辑&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
access_by_lua&amp;nbsp;access_by_lua_file&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#请求访问阶段处理,&amp;nbsp;⽤于访问控制&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
content_by_lua&amp;nbsp;content_by_lua_file&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#内容处理器,&amp;nbsp;接受请求处理并输出响应&lt;/pre&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;Nginx 调⽤ Lua API：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;ngx.var&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#nginx变量&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
ngx.req.get_headers&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#获取请求头&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
ngx.req.get_uri_args&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#获取url请求参数&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
ngx.redirect&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#重定向&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
ngx.print&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#输出响应内容体&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
ngx.say&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#输出响应内容体,最后输出⼀个换⾏符&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
ngx.header&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#输出响应头&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #B2A2C7;&quot;&gt;1.4 lua在nginx中的一些阶段简单用法介绍&lt;/span&gt;&lt;/h3&gt;&lt;h4 style=&quot;text-wrap-mode: wrap; margin: 10px auto; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;1.4.1 初始阶段例子介绍&lt;/span&gt;&lt;/h4&gt;&lt;p style=&quot;margin: 10px auto; text-wrap-mode: wrap; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: #D7E3BC;&quot;&gt;&lt;strong&gt;init_by_lua例子介绍：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;margin: 10px auto; text-wrap-mode: wrap; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;# vim nginx.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--init_by_lua块:在这里我们定义了一个全局变量_G.my_global_variable和一个全局函数_G.say_hello
init_by_lua&amp;nbsp;&amp;#39;
--&amp;nbsp;定义一个全局变量
_G.my_global_variable&amp;nbsp;=&amp;nbsp;&amp;quot;Hello&amp;nbsp;from&amp;nbsp;init_by_lua&amp;quot;
--&amp;nbsp;定义一个全局函数
_G.say_hello&amp;nbsp;=&amp;nbsp;function(name)
&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Hello,&amp;nbsp;&amp;quot;&amp;nbsp;..&amp;nbsp;name&amp;nbsp;..&amp;nbsp;&amp;quot;!&amp;nbsp;This&amp;nbsp;message&amp;nbsp;is&amp;nbsp;from&amp;nbsp;init_by_lua.&amp;quot;
end
&amp;#39;;

server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;......
&amp;nbsp;&amp;nbsp;location&amp;nbsp;/init_by_lua&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;---&amp;nbsp;content_by_lua块:在处理HTTP请求时，我们通过ngx.say输出了这个全局变量和调用了这个全局函数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua&amp;nbsp;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用预加载的全局变量
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(_G.my_global_variable)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;调用预加载的全局函数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;greeting&amp;nbsp;=&amp;nbsp;_G.say_hello(&amp;quot;World&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(greeting)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;;
}&lt;/pre&gt;&lt;p style=&quot;margin: 10px auto; text-wrap-mode: wrap; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;# curl&amp;nbsp; 127.0.0.1/init_by_lua&amp;nbsp; #访问一下查看效果&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Hello&amp;nbsp;from&amp;nbsp;init_by_lua
Hello,&amp;nbsp;World!&amp;nbsp;This&amp;nbsp;message&amp;nbsp;is&amp;nbsp;from&amp;nbsp;init_by_lua.&lt;/pre&gt;&lt;p style=&quot;margin: 10px auto; text-wrap-mode: wrap; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: #D7E3BC;&quot;&gt;&lt;strong&gt;init_worker_by_lua例子介绍：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;color: #111111; font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: #FFFFFF;&quot;&gt;# vim nginx.conf&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;init_worker_by_lua块:当每个worker进程启动时,这段Lua代码会被执行一次。记录了每个worker进程的PID到Nginx的日志中，并且将这个PID存储到了全局变量&amp;nbsp;_G.worker_pid&amp;nbsp;中。
init_worker_by_lua&amp;nbsp;&amp;#39;
&amp;nbsp;&amp;nbsp;--&amp;nbsp;引入lua库,我们使用了ngx.log函数来记录不同级别的日志消息（如ERROR、WARN、INFO）。
&amp;nbsp;&amp;nbsp;local&amp;nbsp;ngx_log&amp;nbsp;=&amp;nbsp;ngx.log
&amp;nbsp;&amp;nbsp;local&amp;nbsp;ngx_ERR&amp;nbsp;=&amp;nbsp;ngx.ERR
&amp;nbsp;&amp;nbsp;local&amp;nbsp;ngx_WARN&amp;nbsp;=&amp;nbsp;ngx.WARN
&amp;nbsp;&amp;nbsp;local&amp;nbsp;ngx_INFO&amp;nbsp;=&amp;nbsp;ngx.INFO

&amp;nbsp;&amp;nbsp;--&amp;nbsp;每个worker进程启动时输出一条日志
&amp;nbsp;&amp;nbsp;ngx_log(ngx_INFO,&amp;nbsp;&amp;quot;Worker&amp;nbsp;process&amp;nbsp;started&amp;nbsp;with&amp;nbsp;pid:&amp;nbsp;&amp;quot;,&amp;nbsp;ngx.worker.pid())

&amp;nbsp;&amp;nbsp;--&amp;nbsp;可以在这里初始化worker级别的一些变量或连接池等
&amp;nbsp;&amp;nbsp;_G.worker_pid&amp;nbsp;=&amp;nbsp;ngx.worker.pid()
&amp;#39;;

server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;location&amp;nbsp;/init_worker_by_lua&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;content_by_lua块:在处理HTTP请求时,可以访问在init_worker_by_lua中初始化的全局变量&amp;nbsp;_G.worker_pid，并将其作为响应的一部分返回给客户端
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua&amp;nbsp;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;在处理请求时可以使用worker级别初始化的数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;This&amp;nbsp;request&amp;nbsp;is&amp;nbsp;handled&amp;nbsp;by&amp;nbsp;worker&amp;nbsp;with&amp;nbsp;pid:&amp;nbsp;&amp;quot;,&amp;nbsp;_G.worker_pid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;;
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;......&amp;nbsp;&amp;nbsp;
}&lt;/pre&gt;&lt;p style=&quot;margin: 10px auto; text-wrap-mode: wrap; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;# curl&amp;nbsp; 127.0.0.1/init_worker_by_lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;This&amp;nbsp;request&amp;nbsp;is&amp;nbsp;handled&amp;nbsp;by&amp;nbsp;worker&amp;nbsp;with&amp;nbsp;pid:&amp;nbsp;2871584&lt;/pre&gt;&lt;h4 style=&quot;text-wrap-mode: wrap; margin: 10px auto; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;1.4.2 HTTP Level阶段介绍&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;&lt;span style=&quot;background-color: #D7E3BC;&quot;&gt;&lt;strong&gt;rewrite_by_lua例子介绍：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#rewrite_by_lua 是早期的语法形式，直接将 Lua 脚本作为字符串传入。rewrite_by_lua_block 是更现代的形式，支持多行脚本编写，可读性更高。&lt;/p&gt;&lt;p&gt;#如果你的 Nginx 版本较新，建议使用 rewrite_by_lua_block，因为它更易读、更易维护。&lt;/p&gt;&lt;p&gt;# vim /opt/soft/nginx/main-conf/nginx.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;rewrite_by_lua&amp;nbsp;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;uri&amp;nbsp;=&amp;nbsp;ngx.var.uri
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;Original&amp;nbsp;URI:&amp;nbsp;&amp;quot;,&amp;nbsp;uri)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;判断是否以&amp;nbsp;/api/v2/&amp;nbsp;开头
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;string.match(uri,&amp;nbsp;&amp;quot;^/api/v2/&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;如果不是，则重写为&amp;nbsp;/api/v2/&amp;nbsp;前缀
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;new_uri&amp;nbsp;=&amp;nbsp;&amp;quot;/api/v2&amp;quot;&amp;nbsp;..&amp;nbsp;uri
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;Rewritten&amp;nbsp;URI:&amp;nbsp;&amp;quot;,&amp;nbsp;new_uri)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.req.set_uri(new_uri,&amp;nbsp;true)&amp;nbsp;--&amp;nbsp;设置新的&amp;nbsp;URI&amp;nbsp;并应用
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;#39;;
&amp;nbsp;&amp;nbsp;#将请求转发到后端服务
&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;http://192.168.1.101;
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; -s reload&lt;/p&gt;&lt;p&gt;# curl 127.0.0.1/api/abc&lt;/p&gt;&lt;p&gt;# tail -f /opt/log/nginx/access.log&amp;nbsp; &amp;nbsp;#当前lua机器查看日志&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;quot;GET&amp;nbsp;/api/abc&amp;nbsp;HTTP/1.1&amp;quot;&amp;nbsp;404&amp;nbsp;153&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;curl/7.61.1&amp;quot;&lt;/pre&gt;&lt;p&gt;# tail -f /usr/local/nginx/logs/access.log&amp;nbsp; #登录后台机器查看请求日志,可以看到url被重写请求过来了&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;quot;GET&amp;nbsp;/api/v2/api/abc&amp;nbsp;HTTP/1.0&amp;quot;&amp;nbsp;404&amp;nbsp;153&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;curl/7.61.1&amp;quot;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #D7E3BC;&quot;&gt;&lt;strong&gt;server_rewrite_by_lua例子介绍：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x:0; --tw-border-spacing-y:0; --tw-translate-x:0; --tw-translate-y:0; --tw-rotate:0; --tw-skew-x:0; --tw-skew-y:0; --tw-scale-x:1; --tw-scale-y:1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness:proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width:0px; --tw-ring-offset-color:#fff; --tw-ring-color:rgba(59,130,246,0.5); --tw-ring-offset-shadow:0 0 #0000; --tw-ring-shadow:0 0 #0000; --tw-shadow:0 0 #0000; --tw-shadow-colored:0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; flex-shrink: 1; min-width: 0px; width: 100%; overflow-wrap: break-word;&quot;&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; line-height: 1.5;&quot;&gt;#例子描述:检查请求的 URI 是否以 &lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; font-weight: 600; line-height: 1.5;&quot;&gt;/api/v2/&lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; line-height: 1.5;&quot;&gt; 开头,&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; line-height: 1.5;&quot;&gt;如果不是，则将原始 URI 拼接到 &lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; font-weight: 600; line-height: 1.5;&quot;&gt;/api/v2&lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; line-height: 1.5;&quot;&gt; 后面形成新的 URI,&lt;/span&gt;将重写后的 URI 应用到当前请求中。&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; height: 14px; display: block;&quot;&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; line-height: 1.5; font-size: 12px;&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;font-size: 14px; border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; line-height: 1.5;&quot;&gt;通过这种方式，可以确保所有请求都以 &lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;font-size: 14px; border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; font-weight: 600; line-height: 1.5;&quot;&gt;/api/v2/&lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;font-size: 14px; border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; line-height: 1.5;&quot;&gt; 开头，从而实现统一的 API 路径管理&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# vim /opt/soft/nginx/main-conf/nginx.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/api&amp;nbsp;{
&amp;nbsp;&amp;nbsp;--&amp;nbsp;因为server_rewrite_by_lua提示不认,所以更换为rewrite_by_lua_block&amp;nbsp;
&amp;nbsp;&amp;nbsp;rewrite_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;Original&amp;nbsp;URI:&amp;nbsp;&amp;quot;,&amp;nbsp;ngx.var.uri)&amp;nbsp;&amp;nbsp;--&amp;nbsp;打印调试日志
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;避免重复嵌套,如果URI不以/api/v2/开头，则进入if&amp;nbsp;块内的逻辑；否则跳过该逻辑
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;string.match(ngx.var.uri,&amp;nbsp;&amp;quot;^/api/v2/&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;这一行的作用是将原始URI拼接到/api/v2后面,形成一个新的&amp;nbsp;URI&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;new_uri&amp;nbsp;=&amp;nbsp;&amp;quot;/api/v2&amp;quot;&amp;nbsp;..&amp;nbsp;ngx.var.uri
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;Rewritten&amp;nbsp;URI:&amp;nbsp;&amp;quot;,&amp;nbsp;new_uri)&amp;nbsp;&amp;nbsp;--&amp;nbsp;打印重写后的&amp;nbsp;URI
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;设置新的URI,并应用重写规则,第二个参数true表示强制更新&amp;nbsp;URI,即使原始&amp;nbsp;URI&amp;nbsp;已经被重写过
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.req.set_uri(new_uri,&amp;nbsp;true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;/span&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; -s reload&lt;/p&gt;&lt;p&gt;# curl 127.0.0.1/api/reource&lt;/p&gt;&lt;p&gt;# tail -f /opt/log/nginx/error.log #因为是例子,所以访问资源不存在,直接看错误日志就行,从例子可看出这个请求是进入到url重写然后再出去重新进入判断然后响应&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[error]&amp;nbsp;168085#0:&amp;nbsp;*730&amp;nbsp;[lua]&amp;nbsp;rewrite_by_lua(nginx.conf:46):2:&amp;nbsp;Original&amp;nbsp;URI:&amp;nbsp;/api/reource,&amp;nbsp;client:&amp;nbsp;127.0.0.1,&amp;nbsp;server:&amp;nbsp;localhost,&amp;nbsp;request:&amp;nbsp;&amp;quot;GET&amp;nbsp;/api/reource&amp;nbsp;HTTP/1.1&amp;quot;,&amp;nbsp;host:&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;
[error]&amp;nbsp;168085#0:&amp;nbsp;*730&amp;nbsp;[lua]&amp;nbsp;rewrite_by_lua(nginx.conf:46):5:&amp;nbsp;Rewritten&amp;nbsp;URI:&amp;nbsp;/api/v2/api/reource,&amp;nbsp;client:&amp;nbsp;127.0.0.1,&amp;nbsp;server:&amp;nbsp;localhost,&amp;nbsp;request:&amp;nbsp;&amp;quot;GET&amp;nbsp;/api/reource&amp;nbsp;HTTP/1.1&amp;quot;,&amp;nbsp;host:&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;
[error]&amp;nbsp;168085#0:&amp;nbsp;*730&amp;nbsp;[lua]&amp;nbsp;rewrite_by_lua(nginx.conf:46):2:&amp;nbsp;Original&amp;nbsp;URI:&amp;nbsp;/api/v2/api/reource,&amp;nbsp;client:&amp;nbsp;127.0.0.1,&amp;nbsp;server:&amp;nbsp;localhost,&amp;nbsp;request:&amp;nbsp;&amp;quot;GET&amp;nbsp;/api/reource&amp;nbsp;HTTP/1.1&amp;quot;,&amp;nbsp;host:&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;
[error]&amp;nbsp;168085#0:&amp;nbsp;*730&amp;nbsp;open()&amp;nbsp;&amp;quot;/opt/soft/nginx/html/api/v2/api/reource&amp;quot;&amp;nbsp;failed&amp;nbsp;(2:&amp;nbsp;No&amp;nbsp;such&amp;nbsp;file&amp;nbsp;or&amp;nbsp;directory),&amp;nbsp;client:&amp;nbsp;127.0.0.1,&amp;nbsp;server:&amp;nbsp;localhost,&amp;nbsp;request:&amp;nbsp;&amp;quot;GET&amp;nbsp;/api/reource&amp;nbsp;HTTP/1.1&amp;quot;,&amp;nbsp;host:&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #D7E3BC;&quot;&gt;&lt;strong&gt;set_by_lua例子介绍：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;margin: 10px auto; text-wrap-mode: wrap; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;&lt;strong&gt;set_by_lua和set_by_lua_file区别介绍：&lt;/strong&gt;&lt;/p&gt;&lt;p style=&quot;margin: 10px auto; text-wrap-mode: wrap; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;set_by_lua 和 set_by_lua_file ：这两个模块都⽤于设置 Nginx 变量。 set_by_lua 通过 inline Lua 代码设置变量的值， set_by_lua_file 则可以通过引⼊ Lua 脚本⽂件来设置变量。这两个模块通常⽤于实现负载的赋值逻辑，如根据请求的 headers 头部信息等进⾏动态变量设置。&lt;/p&gt;&lt;p style=&quot;margin: 10px auto; text-wrap-mode: wrap; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;先来一个set_by_lua的例子：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;~&amp;nbsp;/ftest&amp;nbsp;{
&amp;nbsp;&amp;nbsp;#定义一个&amp;nbsp;Nginx&amp;nbsp;变量&amp;nbsp;$input，其初始值为&amp;nbsp;&amp;quot;Hello,&amp;nbsp;Lua!&amp;quot;
&amp;nbsp;&amp;nbsp;set&amp;nbsp;$input&amp;nbsp;&amp;quot;Hello,Lua!&amp;quot;;
&amp;nbsp;&amp;nbsp;#通过&amp;nbsp;Lua&amp;nbsp;脚本对变量$input使用string.upper&amp;nbsp;函数将字符串转换为大写，并将结果赋值给变量&amp;nbsp;$output
&amp;nbsp;&amp;nbsp;set_by_lua&amp;nbsp;$output&amp;nbsp;&amp;#39;return&amp;nbsp;string.upper(ngx.var.input)&amp;#39;;
&amp;nbsp;&amp;nbsp;return&amp;nbsp;200&amp;nbsp;&amp;quot;Input:&amp;nbsp;$input,&amp;nbsp;Output:&amp;nbsp;$output\n&amp;quot;;
}&lt;/pre&gt;&lt;p style=&quot;margin: 10px auto; text-wrap-mode: wrap; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;# curl&amp;nbsp; 127.0.0.1/ftest&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Input:&amp;nbsp;Hello,Lua!,&amp;nbsp;Output:&amp;nbsp;HELLO,LUA!&lt;/pre&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;strong&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;strong&gt;再来一个set_by_lua_block的例子(动态生成变量):&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/time&amp;nbsp;{
&amp;nbsp;&amp;nbsp;#用于动态生成变量&amp;nbsp;$current_time&amp;nbsp;的值
&amp;nbsp;&amp;nbsp;set_by_lua_block&amp;nbsp;$current_time&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#获取当前时间并格式化为标准的日期时间字符串
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;os.date(&amp;quot;!%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;#将生成的时间作为响应头的一部分返回
&amp;nbsp;&amp;nbsp;add_header&amp;nbsp;X-Current-Time&amp;nbsp;$current_time;
&amp;nbsp;&amp;nbsp;return&amp;nbsp;200&amp;nbsp;&amp;quot;The&amp;nbsp;current&amp;nbsp;time&amp;nbsp;is:&amp;nbsp;$current_time\n&amp;quot;;
}&lt;/pre&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;strong&gt;再来一个set_by_lua_file的简单例子：&lt;/strong&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/multiply&amp;nbsp;{
&amp;nbsp;&amp;nbsp;#&amp;nbsp;定义一个变量&amp;nbsp;$result&amp;nbsp;来存储&amp;nbsp;Lua&amp;nbsp;脚本的返回值
&amp;nbsp;&amp;nbsp;set&amp;nbsp;$result&amp;nbsp;&amp;quot;&amp;quot;;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;调用外部Lua脚本进行计算,将$arg_number(即&amp;nbsp;URL参数number的值)作为参数传递给Lua脚本并将返回值赋值给变量$result
&amp;nbsp;&amp;nbsp;set_by_lua_file&amp;nbsp;$result&amp;nbsp;&amp;#39;/opt/soft/openresty/nginx/conf/lua/multiply.lua&amp;#39;&amp;nbsp;$arg_number;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;将计算结果返回给客户端
&amp;nbsp;&amp;nbsp;return&amp;nbsp;200&amp;nbsp;&amp;quot;Result:&amp;nbsp;$result\n&amp;quot;;
}&lt;/pre&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;# \vi /opt/soft/openresty/nginx/conf/lua/multiply.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;获取第一个参数(即$arg_number的值),并尝试将其转换为数字类型
local&amp;nbsp;number&amp;nbsp;=&amp;nbsp;tonumber(ngx.arg[1])

--&amp;nbsp;如果参数无效或无法转换为数字，则返回错误信息&amp;nbsp;&amp;quot;Invalid&amp;nbsp;input&amp;quot;
if&amp;nbsp;not&amp;nbsp;number&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Invalid&amp;nbsp;input&amp;quot;
end

--&amp;nbsp;将数字乘以2，并将结果转换为字符串类型后返回
return&amp;nbsp;tostring(number&amp;nbsp;*&amp;nbsp;2)&lt;/pre&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;# curl&amp;nbsp; 127.0.0.1//multiply?number=6&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Result:&amp;nbsp;12&lt;/pre&gt;&lt;p style=&quot;text-wrap-mode: wrap;&quot;&gt;# curl&amp;nbsp; 127.0.0.1//multiply?number=dadssa&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Result:&amp;nbsp;Invalid&amp;nbsp;input&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #D7E3BC;&quot;&gt;&lt;strong&gt;access_by_lua_block例子介绍：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#同上,access_by_lua得写法适合简单字符串的形式,access_by_lua_block是后来支持的新特性支持直接编写多行Lua 代码，适合复杂的逻辑场景。更直观、更易维护&lt;/p&gt;&lt;p&gt;#写个简单针对token认证的例子.为了确保所有访问受保护资源的请求都经过身份验证，可以使用 access_by_lua 来实现以下功能&lt;/p&gt;&lt;p&gt;# vim /opt/soft/nginx/main-conf/nginx.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/protected&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;token&amp;nbsp;=&amp;nbsp;ngx.req.get_headers()[&amp;quot;Authorization&amp;quot;]

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查是否有&amp;nbsp;Authorization&amp;nbsp;头部
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;token&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.status&amp;nbsp;=&amp;nbsp;401
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Content-Type&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;application/json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;#39;{&amp;quot;error&amp;quot;:&amp;nbsp;&amp;quot;Unauthorized:&amp;nbsp;Missing&amp;nbsp;token&amp;quot;}&amp;#39;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;ngx.HTTP_UNAUTHORIZED是OpenResty中Nginx&amp;nbsp;Lua模块的一个函数调用,用于立即终止当前请求的处理,并返回指定的&amp;nbsp;HTTP&amp;nbsp;状态码给客户端。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.HTTP_UNAUTHORIZED)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;假设简单的验证逻辑：token&amp;nbsp;必须以&amp;nbsp;&amp;quot;Bearer&amp;quot;&amp;nbsp;开头
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;string.match(token,&amp;nbsp;&amp;quot;^Bearer&amp;quot;)&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.status&amp;nbsp;=&amp;nbsp;402
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;Content-Type&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;application/json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;#39;{&amp;quot;error&amp;quot;:&amp;nbsp;&amp;quot;Unauthorized:&amp;nbsp;Invalid&amp;nbsp;token&amp;nbsp;format&amp;quot;}&amp;#39;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.HTTP_UNAUTHORIZED)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;进一步的验证逻辑可以在这里添加，例如解码&amp;nbsp;JWT&amp;nbsp;并验证签名
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.ERR,&amp;nbsp;&amp;quot;Token&amp;nbsp;is&amp;nbsp;valid,&amp;nbsp;allowing&amp;nbsp;request&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#上面都if判断后没有退出自然就流转到了后端响应接口
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;http://192.168.1.102;
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; -s reload&lt;/p&gt;&lt;p&gt;# curl -I 127.0.0.1/protected&amp;nbsp; #无token请求&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;HTTP/1.1&amp;nbsp;401&amp;nbsp;Unauthorized&lt;/pre&gt;&lt;p&gt;# curl -H &amp;quot;Authorization: tokentest&amp;quot; 127.0.0.1/protected&amp;nbsp; #token不规范请求&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{&amp;quot;error&amp;quot;:&amp;nbsp;&amp;quot;Unauthorized:&amp;nbsp;Invalid&amp;nbsp;token&amp;nbsp;format&amp;quot;}&lt;/pre&gt;&lt;p&gt;# curl -H &amp;quot;Authorization: Bearer aabbccdd&amp;quot; 127.0.0.1/protected&amp;nbsp; &amp;nbsp;#正常请求&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #D7E3BC;&quot;&gt;&lt;strong&gt;content_by_lua_block例子介绍：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#content_by_lua是 OpenResty中的一个指令，用于在 Nginx 的content 阶段嵌入Lua 脚本代码,它通常用来动态生成 HTTP响应内容。&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;假设现在要实现一个 RESTful API，用于返回当前服务器的时间。如果用户提供了有效的 API Token，则返回时间；否则返回 401 Unauthorized 错误。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;# vim /opt/soft/nginx/main-conf/nginx.conf&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/api/time&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;获取请求头中的&amp;nbsp;Authorization&amp;nbsp;字段
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;authorization&amp;nbsp;=&amp;nbsp;ngx.req.get_headers()[&amp;quot;Authorization&amp;quot;]

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查是否提供了有效的&amp;nbsp;Token
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;authorization&amp;nbsp;or&amp;nbsp;authorization&amp;nbsp;~=&amp;nbsp;&amp;quot;Bearer&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.status&amp;nbsp;=&amp;nbsp;ngx.HTTP_UNAUTHORIZED
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;Unauthorized:&amp;nbsp;Please&amp;nbsp;provide&amp;nbsp;a&amp;nbsp;valid&amp;nbsp;token&amp;quot;)&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.exit(ngx.HTTP_UNAUTHORIZED)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;如果通过了身份验证，返回当前服务器时间
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;time&amp;nbsp;=&amp;nbsp;os.date(&amp;quot;!%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;Current&amp;nbsp;server&amp;nbsp;time:&amp;nbsp;&amp;quot;,&amp;nbsp;time)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; -s reload&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# curl -H &amp;quot;Authorization: xxxxyyyy&amp;quot; 127.0.0.1/api/time&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;Unauthorized:&amp;nbsp;Please&amp;nbsp;provide&amp;nbsp;a&amp;nbsp;valid&amp;nbsp;token&lt;/pre&gt;&lt;p&gt;# curl -H &amp;quot;Authorization: Bearer &amp;quot; 127.0.0.1/api/time&amp;nbsp; &amp;nbsp;#注意这是UTC时间,如果想显示北京时区的时间并且服务配置的是北京时区就用local time = os.date(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;)&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Current&amp;nbsp;server&amp;nbsp;time:&amp;nbsp;2025-03-26&amp;nbsp;07:39:10&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #D7E3BC;&quot;&gt;&lt;strong&gt;header_filter_by_lua_block例子介绍:&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# vim /opt/soft/nginx/main-conf/nginx.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;Hello,&amp;nbsp;World!&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;header_filter_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;动态设置&amp;nbsp;X-Custom-Header&amp;nbsp;的值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ngx.var.uri&amp;nbsp;==&amp;nbsp;&amp;quot;/&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;X-Custom-Header&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;This&amp;nbsp;is&amp;nbsp;the&amp;nbsp;root&amp;nbsp;path&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elseif&amp;nbsp;ngx.var.uri&amp;nbsp;==&amp;nbsp;&amp;quot;/about&amp;quot;&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;X-Custom-Header&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;This&amp;nbsp;is&amp;nbsp;the&amp;nbsp;about&amp;nbsp;page&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;X-Custom-Header&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;Unknown&amp;nbsp;path&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
location&amp;nbsp;/about&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;About&amp;nbsp;Page&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;header_filter_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.header[&amp;quot;X-Custom-Header&amp;quot;]&amp;nbsp;=&amp;nbsp;&amp;quot;Custom&amp;nbsp;value&amp;nbsp;for&amp;nbsp;about&amp;nbsp;page&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; -s reload&lt;/p&gt;&lt;p&gt;# curl -I http://localhost/&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;X-Custom-Header:&amp;nbsp;This&amp;nbsp;is&amp;nbsp;the&amp;nbsp;root&amp;nbsp;path&lt;/pre&gt;&lt;p&gt;# curl -I http://localhost/dsada&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;X-Custom-Header:&amp;nbsp;Unknown&amp;nbsp;path&lt;/pre&gt;&lt;p&gt;# curl -I http://localhost/about&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;X-Custom-Header:&amp;nbsp;Custom&amp;nbsp;value&amp;nbsp;for&amp;nbsp;about&amp;nbsp;page&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #D7E3BC;&quot;&gt;&lt;strong&gt;body_filter_by_lua_block例子介绍：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#body_filter_by_lua_block指令&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;允许用户通过 Lua 脚本对HTTP响应体进行修改或处理,主要用途:修改响应内容/分块传输处理/&lt;/span&gt;后处理逻辑(在响应发送给客户端之前，执行一些额外的逻辑。例如，压缩响应内容、加密数据或记录日志）&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#工作原理: ngx.arg[1]: 表示当前块的响应体数据（字符串类型）/ngx.arg[2]: 表示是否为最后一块数据（布尔值）。修改 ngx.arg[1] 的值会影响最终发送给客户端的响应体内容。body_filter_by_lua_block会影响每个请求的响应阶段，确保脚本逻辑尽量简单以避免性能问题&lt;/p&gt;&lt;p&gt;# vim /opt/soft/nginx/main-conf/nginx.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/body&amp;nbsp;{&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;Hello,&amp;nbsp;this&amp;nbsp;is&amp;nbsp;the&amp;nbsp;original&amp;nbsp;response&amp;nbsp;body.&amp;quot;)&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;body_filter_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查是否有剩余的响应数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ngx.arg[2]&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;如果是最后一块数据，则添加注释
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;last_part&amp;nbsp;=&amp;nbsp;ngx.arg[1]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.arg[1]&amp;nbsp;=&amp;nbsp;last_part&amp;nbsp;..&amp;nbsp;&amp;quot;\n&amp;lt;!--&amp;nbsp;This&amp;nbsp;is&amp;nbsp;a&amp;nbsp;dynamic&amp;nbsp;footer&amp;nbsp;added&amp;nbsp;by&amp;nbsp;Lua&amp;nbsp;--&amp;gt;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# curl&amp;nbsp; http://localhost/body&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Hello,&amp;nbsp;this&amp;nbsp;is&amp;nbsp;the&amp;nbsp;original&amp;nbsp;response&amp;nbsp;body.

&amp;lt;!--&amp;nbsp;This&amp;nbsp;is&amp;nbsp;a&amp;nbsp;dynamic&amp;nbsp;footer&amp;nbsp;added&amp;nbsp;by&amp;nbsp;Lua&amp;nbsp;--&amp;gt;&lt;/pre&gt;&lt;h4 style=&quot;text-wrap-mode: wrap; margin: 10px auto; padding: 0px; color: rgb(17, 17, 17); font-family: &amp;quot;PingFang SC&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 13px; background-color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;1.4.3 work进程退出阶段介绍&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;#当我们进程退出的时候,我们想记录一些信息到日志中,这就需要用到exit_worker_by_lua_block&lt;/p&gt;&lt;p&gt;# vim /usr/local/nginx/conf/nginx.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#nginx进程多一点才能测出效果
worker_processes&amp;nbsp;&amp;nbsp;4;
http&amp;nbsp;{
......
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#在每个工作进程启动时初始化一个全局计数器worker_counter
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init_worker_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;ngx.shared是nginx提供的一个共享内存区域，允许多个请求和工作进程之间共享数据。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;worker_counter是我们在共享内存中定义的一个键。如果该键不存在，则初始化为一个空表&amp;nbsp;{}。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.worker_counter&amp;nbsp;=&amp;nbsp;ngx.shared.worker_counter&amp;nbsp;or&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;在共享内存中，我们定义了一个count&amp;nbsp;键，用于记录当前工作进程处理的请求数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;如果count键不存在(即第一次启动)，则初始化为&amp;nbsp;0；否则，将其值加1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.worker_counter.count&amp;nbsp;=&amp;nbsp;(ngx.shared.worker_counter.count&amp;nbsp;or&amp;nbsp;0)&amp;nbsp;+&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#在每个工作进程退出时执行清理操作。在这个例子中，它记录了该工作进程处理的请求数，并清除了计数器。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exit_worker_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;从共享内存中读取当前工作进程处理的请求数，并赋值给变量count
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;count&amp;nbsp;=&amp;nbsp;ngx.shared.worker_counter.count
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;检查count是否存在。如果存在，则继续执行后续逻辑
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;count&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;使用&amp;nbsp;ngx.log函数记录一条日志消息。日志级别为&amp;nbsp;INFO，表示这是一条普通信息日志。日志内容包括工作进程退出的信息以及它处理的总请求数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.log(ngx.INFO,&amp;nbsp;&amp;quot;Worker&amp;nbsp;process&amp;nbsp;exiting.&amp;nbsp;Total&amp;nbsp;requests&amp;nbsp;handled:&amp;nbsp;&amp;quot;,&amp;nbsp;count)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将共享内存中的count键设置为&amp;nbsp;nil，以清理数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.worker_counter.count&amp;nbsp;=&amp;nbsp;nil&amp;nbsp;--&amp;nbsp;清理计数器
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
server&amp;nbsp;{
......
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#每次请求/count路径时，增加请求计数并返回当前计数值。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/count&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_block&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--从共享内存中读取当前工作进程处理的请求数，并赋值给变量count
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;count&amp;nbsp;=&amp;nbsp;ngx.shared.worker_counter.count
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;将共享内存中的count键的值加&amp;nbsp;1，表示处理了一个新的请求
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.shared.worker_counter.count&amp;nbsp;=&amp;nbsp;count&amp;nbsp;+&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;Worker&amp;nbsp;process&amp;nbsp;&amp;nbsp;pid:&amp;nbsp;&amp;quot;,&amp;nbsp;ngx.worker.pid()..&amp;quot;,&amp;quot;..&amp;quot;Hello,&amp;nbsp;worker&amp;nbsp;counter:&amp;nbsp;&amp;quot;,&amp;nbsp;count)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# /usr/local/nginx/sbin/nginx -s reload&lt;/p&gt;&lt;p&gt;# curl 127.0.0.1/count&amp;nbsp; #以此类推,多curl几回&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Worker&amp;nbsp;process&amp;nbsp;&amp;nbsp;pid:&amp;nbsp;3373320,Hello,&amp;nbsp;worker&amp;nbsp;counter:&amp;nbsp;1&lt;/pre&gt;&lt;p&gt;# curl 127.0.0.1/count&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Worker&amp;nbsp;process&amp;nbsp;&amp;nbsp;pid:&amp;nbsp;3373321,Hello,&amp;nbsp;worker&amp;nbsp;counter:&amp;nbsp;1&lt;/pre&gt;&lt;p&gt;# /usr/local/nginx/sbin/nginx -s reload&lt;/p&gt;&lt;p&gt;# tail -100f /usr/local/nginx/logs/error.log&amp;nbsp; &amp;nbsp;#可以看下打印的日志&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[info]&amp;nbsp;3373321#0:&amp;nbsp;[lua]&amp;nbsp;exit_worker_by_lua(nginx.conf:46):5:&amp;nbsp;Worker&amp;nbsp;process&amp;nbsp;exiting.&amp;nbsp;Total&amp;nbsp;requests&amp;nbsp;handled:&amp;nbsp;2
[info]&amp;nbsp;3373322#0:&amp;nbsp;[lua]&amp;nbsp;exit_worker_by_lua(nginx.conf:46):5:&amp;nbsp;Worker&amp;nbsp;process&amp;nbsp;exiting.&amp;nbsp;Total&amp;nbsp;requests&amp;nbsp;handled:&amp;nbsp;2
[info]&amp;nbsp;3373323#0:&amp;nbsp;[lua]&amp;nbsp;exit_worker_by_lua(nginx.conf:46):5:&amp;nbsp;Worker&amp;nbsp;process&amp;nbsp;exiting.&amp;nbsp;Total&amp;nbsp;requests&amp;nbsp;handled:&amp;nbsp;2
[info]&amp;nbsp;3373320#0:&amp;nbsp;[lua]&amp;nbsp;exit_worker_by_lua(nginx.conf:46):5:&amp;nbsp;Worker&amp;nbsp;process&amp;nbsp;exiting.&amp;nbsp;Total&amp;nbsp;requests&amp;nbsp;handled:&amp;nbsp;3&lt;/pre&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;二、lua常用模块使用&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;span style=&quot;text-wrap-mode: wrap;&quot;&gt;#nginx使用到的模块如何使用：https://github.com/openresty/lua-nginx-module#description &lt;/span&gt;&lt;/p&gt;&lt;p&gt;#从过上面的章节我们对lua在nginx各个阶段的使用有了一个初步的了解,但是如果要实现我们的功能还要结合mysql、redis等一些功能来使用&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.1&amp;nbsp;lua-resty-mysql的学习使用&lt;/span&gt;&lt;/h3&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;2.1.1 使用方法学习&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;#https://github.com/openresty/lua-resty-mysql?tab=readme-ov-file#methods&amp;nbsp; &amp;nbsp; #先根据文档把使用方法学习一下&lt;/p&gt;&lt;p&gt;&lt;strong&gt;new：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;语法：&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJWUldrSzlQMGp6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBkYiUyQyUyMGVyciUyMCUzRCUyMG15c3FsJTNBbmV3KCklMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTglQUYlQUQlRTYlQjMlOTUlRUYlQkMlOUFkYiVFRiVCQyU4Q2VyciUzRG15c3FsJTNBbmV3JUVGJUJDJTg4JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMmRiJTJDJTIwZXJyJTIwJTNEJTIwbXlzcWwlM0FuZXcoKSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;db, err = mysql:new() &amp;nbsp; &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;#创建MySQL连接对象。如果失败，则返回nil和一个描述错误的字符串。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlcXhXcVhyd3lCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJjb25uZWN0JTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU4JUJGJTlFJUU2JThFJUE1JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMmNvbm5lY3QlMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;connect：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlcXhXcVhyd3lCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJjb25uZWN0JTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU4JUJGJTlFJUU2JThFJUE1JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMmNvbm5lY3QlMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;语法&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCdzRuNVgybFJlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBvayUyQyUyMGVyciUyQyUyMGVycmNvZGUlMkMlMjBzcWxzdGF0ZSUyMCUzRCUyMGRiJTNBY29ubmVjdChvcHRpb25zKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QW9rJUUzJTgwJTgxZXJyJUUzJTgwJTgxZXJyY29kZSVFMyU4MCU4MXNxbHN0YXRlJTNEZGIlM0Fjb25uZWN0JUVGJUJDJTg4JUU5JTgwJTg5JUU5JUExJUI5JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMiUzQSUyMG9rJTJDJTIwZXJyJTJDJTIwZXJyY29kZSUyQyUyMHNxbHN0YXRlJTIwJTNEJTIwZGIlM0Fjb25uZWN0KG9wdGlvbnMpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;: ok, err, errcode, sqlstate = db:connect(options)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlcXhXcVhyd3lCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJjb25uZWN0JTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU4JUJGJTlFJUU2JThFJUE1JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMmNvbm5lY3QlMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCdzRuNVgybFJlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBvayUyQyUyMGVyciUyQyUyMGVycmNvZGUlMkMlMjBzcWxzdGF0ZSUyMCUzRCUyMGRiJTNBY29ubmVjdChvcHRpb25zKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QW9rJUUzJTgwJTgxZXJyJUUzJTgwJTgxZXJyY29kZSVFMyU4MCU4MXNxbHN0YXRlJTNEZGIlM0Fjb25uZWN0JUVGJUJDJTg4JUU5JTgwJTg5JUU5JUExJUI5JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMiUzQSUyMG9rJTJDJTIwZXJyJTJDJTIwZXJyY29kZSUyQyUyMHNxbHN0YXRlJTIwJTNEJTIwZGIlM0Fjb25uZWN0KG9wdGlvbnMpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#options参数是一个Lua表，其中包含以下键,详细的参照官网文档把&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;set_timeout:&lt;/strong&gt;&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlRVdxOWpENVp6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXRfdGltZW91dCUyMiUyQyUyMmRzdCUyMiUzQSUyMnNldF90aW1lb3V0JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnNldF90aW1lb3V0JTIyJTdEJTVEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCb01RNGpPQUdWJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBkYiUzQXNldF90aW1lb3V0KHRpbWUpJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU4JUFGJUFEJUU2JUIzJTk1JUVGJUJDJTlBZGIlM0FzZXRfdGltZW91dCVFRiVCQyU4OCVFNiU5NyVCNiVFOSU5NyVCNCVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzeW50YXglM0ElMjBkYiUzQXNldF90aW1lb3V0KHRpbWUpJTIyJTdEJTVEJTdEJTVE&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: db:set_timeout(time)&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlcXhXcVhyd3lCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJjb25uZWN0JTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU4JUJGJTlFJUU2JThFJUE1JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMmNvbm5lY3QlMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCdzRuNVgybFJlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBvayUyQyUyMGVyciUyQyUyMGVycmNvZGUlMkMlMjBzcWxzdGF0ZSUyMCUzRCUyMGRiJTNBY29ubmVjdChvcHRpb25zKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QW9rJUUzJTgwJTgxZXJyJUUzJTgwJTgxZXJyY29kZSVFMyU4MCU4MXNxbHN0YXRlJTNEZGIlM0Fjb25uZWN0JUVGJUJDJTg4JUU5JTgwJTg5JUU5JUExJUI5JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMiUzQSUyMG9rJTJDJTIwZXJyJTJDJTIwZXJyY29kZSUyQyUyMHNxbHN0YXRlJTIwJTNEJTIwZGIlM0Fjb25uZWN0KG9wdGlvbnMpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#为后续操作(包括连接方法)设置超时(以毫秒为单位)保护。&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlcXhXcVhyd3lCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJjb25uZWN0JTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU4JUJGJTlFJUU2JThFJUE1JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMmNvbm5lY3QlMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCdzRuNVgybFJlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBvayUyQyUyMGVyciUyQyUyMGVycmNvZGUlMkMlMjBzcWxzdGF0ZSUyMCUzRCUyMGRiJTNBY29ubmVjdChvcHRpb25zKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QW9rJUUzJTgwJTgxZXJyJUUzJTgwJTgxZXJyY29kZSVFMyU4MCU4MXNxbHN0YXRlJTNEZGIlM0Fjb25uZWN0JUVGJUJDJTg4JUU5JTgwJTg5JUU5JUExJUI5JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMiUzQSUyMG9rJTJDJTIwZXJyJTJDJTIwZXJyY29kZSUyQyUyMHNxbHN0YXRlJTIwJTNEJTIwZGIlM0Fjb25uZWN0KG9wdGlvbnMpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlYllKTk5QMzBCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTJDJTIyZHN0JTIyJTNBJTIyc2V0X2tlZXBhbGl2ZSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;set_keepalive：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlYllKTk5QMzBCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTJDJTIyZHN0JTIyJTNBJTIyc2V0X2tlZXBhbGl2ZSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;语法&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCUFhuTU1nRG5CJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBvayUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2V0X2tlZXBhbGl2ZShtYXhfaWRsZV90aW1lb3V0JTJDJTIwcG9vbF9zaXplKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QW9rJUVGJUJDJThDZXJyJTNEZGIlM0FzZXRfa2VlcGFsaXZlJUVGJUJDJTg4bWF4X2lkbGVfdGltZW91dCVFRiVCQyU4Q3Bvb2xfc2l6ZSVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjIlM0ElMjBvayUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2V0X2tlZXBhbGl2ZShtYXhfaWRsZV90aW1lb3V0JTJDJTIwcG9vbF9zaXplKSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;: ok, err = db:set_keepalive(max_idle_timeout, pool_size)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlYllKTk5QMzBCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTJDJTIyZHN0JTIyJTNBJTIyc2V0X2tlZXBhbGl2ZSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCUFhuTU1nRG5CJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBvayUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2V0X2tlZXBhbGl2ZShtYXhfaWRsZV90aW1lb3V0JTJDJTIwcG9vbF9zaXplKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QW9rJUVGJUJDJThDZXJyJTNEZGIlM0FzZXRfa2VlcGFsaXZlJUVGJUJDJTg4bWF4X2lkbGVfdGltZW91dCVFRiVCQyU4Q3Bvb2xfc2l6ZSVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjIlM0ElMjBvayUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2V0X2tlZXBhbGl2ZShtYXhfaWRsZV90aW1lb3V0JTJDJTIwcG9vbF9zaXplKSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#立即将当前MySQL连接放入ngx_lua cosocket连接池。&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;get_reused_times:&lt;/strong&gt;&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJ6bERkTUQwUjR6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJnZXRfcmV1c2VkX3RpbWVzJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU4JThFJUI3JUU1JThGJTk2JUU0JUJEJUJGJUU3JTk0JUE4JUU2JUFDJUExJUU2JTk1JUIwJTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMmdldF9yZXVzZWRfdGltZXMlMjIlN0QlNUQlN0QlMkMlN0IlMjJ0eXBlJTIyJTNBJTIycGFyYWdyYXBoJTIyJTJDJTIyY2hpbGRyZW4lMjIlM0ElNUIlN0IlMjJpZCUyMiUzQSUyMkJEZEx3ZFBPckIlMjIlMkMlMjJwYXJhSWR4JTIyJTNBMiUyQyUyMnNyYyUyMiUzQSUyMnN5bnRheCUzQSUyMHRpbWVzJTJDJTIwZXJyJTIwJTNEJTIwZGIlM0FnZXRfcmV1c2VkX3RpbWVzKCklMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTglQUYlQUQlRTYlQjMlOTUlRUYlQkMlOUF0aW1lcyVFRiVCQyU4Q2VyciUzRGRiJTNBZ2V0X3VzZWRfdGltZXMlRUYlQkMlODglRUYlQkMlODklMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyc3ludGF4JTNBJTIwdGltZXMlMkMlMjBlcnIlMjAlM0QlMjBkYiUzQWdldF9yZXVzZWRfdGltZXMoKSUyMiU3RCU1RCU3RCU1RA==&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: times, err = db:get_reused_times()&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlYllKTk5QMzBCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTJDJTIyZHN0JTIyJTNBJTIyc2V0X2tlZXBhbGl2ZSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCUFhuTU1nRG5CJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBvayUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2V0X2tlZXBhbGl2ZShtYXhfaWRsZV90aW1lb3V0JTJDJTIwcG9vbF9zaXplKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QW9rJUVGJUJDJThDZXJyJTNEZGIlM0FzZXRfa2VlcGFsaXZlJUVGJUJDJTg4bWF4X2lkbGVfdGltZW91dCVFRiVCQyU4Q3Bvb2xfc2l6ZSVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjIlM0ElMjBvayUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2V0X2tlZXBhbGl2ZShtYXhfaWRsZV90aW1lb3V0JTJDJTIwcG9vbF9zaXplKSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#&lt;/span&gt;&lt;/span&gt;此方法返回当前连接的（成功）重用次数。如果发生错误，它将返回nil和一个描述错误的字符串。&lt;/p&gt;&lt;p&gt;#如果当前连接不是来自内置连接池，则此方法始终返回0，即连接从未被重用（尚未）。如果连接来自连接池，则返回值始终为非零。因此，此方法也可用于确定当前连接是否来自池。&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-node=&quot;text&quot;&gt;close:&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCYWowYTdZSkplJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJjbG9zZSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFNSU4NSVCMyUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJjbG9zZSUyMiU3RCU1RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJjaGlsZHJlbiUyMiUzQSU1QiU3QiUyMmlkJTIyJTNBJTIyemdhcjFOTE1FZSUyMiUyQyUyMnBhcmFJZHglMjIlM0ExJTJDJTIyc3JjJTIyJTNBJTIyc3ludGF4JTNBJTIwb2slMkMlMjBlcnIlMjAlM0QlMjBkYiUzQWNsb3NlKCklMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTglQUYlQUQlRTYlQjMlOTUlRUYlQkMlOUFvayVFRiVCQyU4Q2VyciUzRGRiJTNBY2xvc2UlRUYlQkMlODglRUYlQkMlODklMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyc3ludGF4JTNBJTIwb2slMkMlMjBlcnIlMjAlM0QlMjBkYiUzQWNsb3NlKCklMjIlN0QlNUQlN0QlNUQ=&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: ok, err = db:close()&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlYllKTk5QMzBCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTJDJTIyZHN0JTIyJTNBJTIyc2V0X2tlZXBhbGl2ZSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzZXRfa2VlcGFsaXZlJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCUFhuTU1nRG5CJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBvayUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2V0X2tlZXBhbGl2ZShtYXhfaWRsZV90aW1lb3V0JTJDJTIwcG9vbF9zaXplKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QW9rJUVGJUJDJThDZXJyJTNEZGIlM0FzZXRfa2VlcGFsaXZlJUVGJUJDJTg4bWF4X2lkbGVfdGltZW91dCVFRiVCQyU4Q3Bvb2xfc2l6ZSVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjIlM0ElMjBvayUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2V0X2tlZXBhbGl2ZShtYXhfaWRsZV90aW1lb3V0JTJDJTIwcG9vbF9zaXplKSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#&lt;/span&gt;&lt;/span&gt;关闭当前mysql连接并返回状态。如果成功，返回1。如果发生错误，则返回nil，并返回一个描述错误的字符串。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCUFhuTTZucVdCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZW5kX3F1ZXJ5JTIyJTJDJTIyZHN0JTIyJTNBJTIyc2VuZF9xdWVyeSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzZW5kX3F1ZXJ5JTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;send_query：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCUFhuTTZucVdCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZW5kX3F1ZXJ5JTIyJTJDJTIyZHN0JTIyJTNBJTIyc2VuZF9xdWVyeSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzZW5kX3F1ZXJ5JTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;语法：&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCdzRuZ2xuNmxlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBieXRlcyUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2VuZF9xdWVyeShxdWVyeSklMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTglQUYlQUQlRTYlQjMlOTUlRUYlQkMlOUElRTUlQUQlOTclRTglOEElODIlRUYlQkMlOENlcnIlM0RkYiUzQXNlbmRfcXVlcnklRUYlQkMlODhxdWVyeSVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJieXRlcyUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2VuZF9xdWVyeShxdWVyeSklMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;bytes, err = db:send_query(query)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCUFhuTTZucVdCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZW5kX3F1ZXJ5JTIyJTJDJTIyZHN0JTIyJTNBJTIyc2VuZF9xdWVyeSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzZW5kX3F1ZXJ5JTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCdzRuZ2xuNmxlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBieXRlcyUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2VuZF9xdWVyeShxdWVyeSklMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTglQUYlQUQlRTYlQjMlOTUlRUYlQkMlOUElRTUlQUQlOTclRTglOEElODIlRUYlQkMlOENlcnIlM0RkYiUzQXNlbmRfcXVlcnklRUYlQkMlODhxdWVyeSVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJieXRlcyUyQyUyMGVyciUyMCUzRCUyMGRiJTNBc2VuZF9xdWVyeShxdWVyeSklMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#&lt;/span&gt;&lt;/span&gt;将查询发送到远程MySQL服务器，而不等待其回复。返回成功发送的字节，否则返回nil和描述错误的字符串。之后，使用read_result方法读取MySQL的回复。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJ6Z2FyMTNnTXllJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJyZWFkX3Jlc3VsdCUyMiUyQyUyMmRzdCUyMiUzQSUyMnJlYWRfcmVzdWx0JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnJlYWRfcmVzdWx0JTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;read_result:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;语法&lt;/span&gt;: res, err, errcode, sqlstate = db:read_result()&lt;/p&gt;&lt;p&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: res, err, errcode, sqlstate = db:read_result(nrows)&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#读取MySQL服务器返回的一个结果。它返回一个包含所有行的数组。每行包含每个数据字段的键值对。例如：&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Bob&amp;quot;,&amp;nbsp;age&amp;nbsp;=&amp;nbsp;32,&amp;nbsp;phone&amp;nbsp;=&amp;nbsp;ngx.null&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;{&amp;nbsp;name&amp;nbsp;=&amp;nbsp;&amp;quot;Marry&amp;quot;,&amp;nbsp;age&amp;nbsp;=&amp;nbsp;18,&amp;nbsp;phone&amp;nbsp;=&amp;nbsp;&amp;quot;10666372&amp;quot;}
}&lt;/pre&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;#如果发生错误，此方法最多返回4个值：nil、err、errcode和sqlstate。err返回值包含一个描述错误的字符串，errcode返回值包含MySQL错误代码（一个数值），最后，sqlstate返回值包含由5个字符组成的标准SQL错误代码。请注意，如果MySQL不返回errcode和sqlstate，它们可能为nil。&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCWm82eVpiRHdlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJxdWVyeSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFNiU5RiVBNSVFOCVBRiVBMiUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJxdWVyeSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;query：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;语法&lt;/span&gt;: res, err, errcode, sqlstate = db:query(query)&lt;/p&gt;&lt;p&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJ6bERkTUdvd1h6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZXMlMkMlMjBlcnIlMkMlMjBlcnJjb2RlJTJDJTIwc3Fsc3RhdGUlMjAlM0QlMjBkYiUzQXF1ZXJ5KHF1ZXJ5KSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXJlcyVFMyU4MCU4MWVyciVFMyU4MCU4MWVycmNvZGUlRTMlODAlODFzcWxzdGF0ZSUzRGRiJTNBcXVlcnklRUYlQkMlODhxdWVyeSVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzeW50YXglM0ElMjByZXMlMkMlMjBlcnIlMkMlMjBlcnJjb2RlJTJDJTIwc3Fsc3RhdGUlMjAlM0QlMjBkYiUzQXF1ZXJ5KHF1ZXJ5KSUyMiU3RCU1RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJjaGlsZHJlbiUyMiUzQSU1QiU3QiUyMmlkJTIyJTNBJTIyQkRkTHdBUWJMQiUyMiUyQyUyMnBhcmFJZHglMjIlM0EyJTJDJTIyc3JjJTIyJTNBJTIyJTIyJTJDJTIyZHN0JTIyJTNBJTIyJTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMiUyMiU3RCU1RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJjaGlsZHJlbiUyMiUzQSU1QiU3QiUyMmlkJTIyJTNBJTIyZUxiMXdBbmo0eiUyMiUyQyUyMnBhcmFJZHglMjIlM0EzJTJDJTIyc3JjJTIyJTNBJTIyc3ludGF4JTNBJTIwcmVzJTJDJTIwZXJyJTJDJTIwZXJyY29kZSUyQyUyMHNxbHN0YXRlJTIwJTNEJTIwZGIlM0FxdWVyeShxdWVyeSUyQyUyMG5yb3dzKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXJlcyVFRiVCQyU4Q2VyciVFRiVCQyU4Q2VycmNvZGUlRUYlQkMlOENzcWxzdGF0ZSUzRGRiJTNBcXVlcnklRUYlQkMlODhxdWVyeSVFRiVCQyU4Q25yb3dzJUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnN5bnRheCUzQSUyMHJlcyUyQyUyMGVyciUyQyUyMGVycmNvZGUlMkMlMjBzcWxzdGF0ZSUyMCUzRCUyMGRiJTNBcXVlcnkocXVlcnklMkMlMjBucm93cyklMjIlN0QlNUQlN0QlNUQ=&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: res, err, errcode, sqlstate = db:query(query, nrows)&lt;/span&gt;&lt;/div&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCWm82eVpiRHdlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJxdWVyeSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFNiU5RiVBNSVFOCVBRiVBMiUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJxdWVyeSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#这是组合send_query调用和第一个read_result调用的快捷方式。如果成功，应该始终检查err返回值是否再次出现，因为此方法只会调用read_result一次。&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;strong&gt;server_ver:&lt;/strong&gt;&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCNDBSa3dtOTdCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXJ2ZXJfdmVyJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU2JTlDJThEJUU1JThBJUExJUU1JTk5JUE4XyVFNiU5QyU4RCVFNSU4QSVBMSVFNSU5OSVBOCUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzZXJ2ZXJfdmVyJTIyJTdEJTVEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJWQWJaWXBxV2R6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBzdHIlMjAlM0QlMjBkYiUzQXNlcnZlcl92ZXIoKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXN0ciUzRGRiJTNBc2VydmVyX3ZlciVFRiVCQyU4OCVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzeW50YXglM0ElMjBzdHIlMjAlM0QlMjBkYiUzQXNlcnZlcl92ZXIoKSUyMiU3RCU1RCU3RCU1RA==&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: str = db:server_ver()&lt;/span&gt;&lt;/div&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCWm82eVpiRHdlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJxdWVyeSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFNiU5RiVBNSVFOCVBRiVBMiUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJxdWVyeSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#返回MySQL服务器版本字符串，如“5.1.64”。&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCWm82eVpiRHdlJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJxdWVyeSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFNiU5RiVBNSVFOCVBRiVBMiUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJxdWVyeSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlYllKTm54alpCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXRfY29tcGFjdF9hcnJheXMlMjIlMkMlMjJkc3QlMjIlM0ElMjJzZXRfY29tcGFjdF9hcnJheXMlMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyc2V0X2NvbXBhY3RfYXJyYXlzJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;set_compact_arrays:&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;语法：&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCUFhuTWFkd1JCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBkYiUzQXNldF9jb21wYWN0X2FycmF5cyhib29sZWFuKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QWRiJTNBc2V0X2NvbXBhY3RfYXJyYXlzJUVGJUJDJTg4JUU1JUI4JTgzJUU1JUIwJTk0JUU1JTgwJUJDJUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMmRiJTNBc2V0X2NvbXBhY3RfYXJyYXlzKGJvb2xlYW4pJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;db:set_compact_arrays(boolean)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCUFhuTWFkd1JCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjBkYiUzQXNldF9jb21wYWN0X2FycmF5cyhib29sZWFuKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QWRiJTNBc2V0X2NvbXBhY3RfYXJyYXlzJUVGJUJDJTg4JUU1JUI4JTgzJUU1JUIwJTk0JUU1JTgwJUJDJUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMmRiJTNBc2V0X2NvbXBhY3RfYXJyYXlzKGJvb2xlYW4pJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#设置是否对后续查询返回的结果集使用“紧凑数组”结构。&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;2.1.2 环境支持&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;#如果&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt;不想使用 OpenResty，但仍然可以单独安装 &lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; font-weight: 600; line-height: 1.5;&quot;&gt;lua-resty-mysql&lt;/span&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt; 模块。可以通过 LuaRocks 或手动编译安装。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;LuaRocks安装方式：&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;#yum install libtermcap-devel ncurses-devel libevent-devel readline-devel -y&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;#wget https://luarocks.org/releases/luarocks-3.11.1.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;#tar xf luarocks-3.11.1.tar.gz&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;#cd luarocks-3.11.1/&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;#./configure --with-lua=&amp;quot;/usr/local/luajit/&amp;quot; --lua-suffix=&amp;quot;jit&amp;quot;&amp;nbsp; --with-lua-include=/usr/local/luajit/include/luajit-2.1/&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;#make &amp;nbsp;&amp;amp;&amp;amp; make install&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;ic-richtext-list-h&quot; style=&quot;border: 0px; box-sizing: border-box; margin: 0px; padding: 0px; -webkit-tap-highlight-color: ; --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; user-select: text; color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF; line-height: 1.5;&quot;&gt;&lt;span style=&quot;color: #171A1D; font-family: -apple-system, &amp;quot;PingFang SC&amp;quot;, &amp;quot;Hiragino Sans GB&amp;quot;, &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;Segoe UI&amp;quot;, system-ui, Roboto, &amp;quot;Droid Sans&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, sans-serif; white-space: pre-wrap; background-color: #FFFFFF;&quot;&gt;# luarocks --version&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;/usr/local/bin/luarocks&amp;nbsp;3.11.1
LuaRocks&amp;nbsp;main&amp;nbsp;command-line&amp;nbsp;interface&lt;/pre&gt;&lt;p&gt;#luarocks install lua-resty-mysql&amp;nbsp; #因为拉取的是https://github.com/openresty/lua-resty-mysql/失败了就多执行几次&lt;/p&gt;&lt;p&gt;# find /usr/local/|grep mysql|grep resty&amp;nbsp; &amp;nbsp;#可以查看一下是否安装到了指定位置&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;/usr/local/lib/luarocks/rocks-5.1/lua-resty-mysql
/usr/local/lib/luarocks/rocks-5.1/lua-resty-mysql/0.15-0
/usr/local/lib/luarocks/rocks-5.1/lua-resty-mysql/0.15-0/lua-resty-mysql-0.15-0.rockspec
/usr/local/lib/luarocks/rocks-5.1/lua-resty-mysql/0.15-0/doc
/usr/local/lib/luarocks/rocks-5.1/lua-resty-mysql/0.15-0/doc/README.markdown
/usr/local/lib/luarocks/rocks-5.1/lua-resty-mysql/0.15-0/rock_manifest
/usr/local/share/lua/5.1/resty/mysql.lua&lt;/pre&gt;&lt;p&gt;#然后写例子测试一下：&lt;/p&gt;&lt;p&gt;# vim /usr/local/nginx/conf/lua_conf/lua_mysql_script.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;通过Lua的require函数加载了resty.mysql模块，并将其赋值给局部变量mysql。这个模块提供了与&amp;nbsp;MySQL数据库交互的功能
local&amp;nbsp;mysql&amp;nbsp;=&amp;nbsp;require&amp;nbsp;&amp;quot;resty.mysql&amp;quot;

--&amp;nbsp;创建一个新的MySQL连接对象。如果创建失败，err将包含错误信息；否则，db&amp;nbsp;将是新创建的数据库连接对象。
local&amp;nbsp;db,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;mysql:new()
--&amp;nbsp;如果未能成功创建数据库连接对象（即db为nil），则输出错误信息并通过&amp;nbsp;return&amp;nbsp;结束脚本执行。
if&amp;nbsp;not&amp;nbsp;db&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;failed&amp;nbsp;to&amp;nbsp;instantiate&amp;nbsp;mysql:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end

--&amp;nbsp;设置MySQL操作的超时时间，单位为毫秒。这里设置的是1秒。如果操作在1秒内未完成，将抛出超时错误
db:set_timeout(1000)&amp;nbsp;

--&amp;nbsp;使用db:connect方法尝试连接到MySQL数据库服务器
local&amp;nbsp;ok,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate&amp;nbsp;=&amp;nbsp;db:connect{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host&amp;nbsp;=&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;port&amp;nbsp;=&amp;nbsp;3306,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;database&amp;nbsp;=&amp;nbsp;&amp;quot;testdb&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;&amp;quot;root&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;aabbccdd123&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;max_packet_size:&amp;nbsp;允许的最大数据包大小，这里设置为&amp;nbsp;1MB
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_packet_size&amp;nbsp;=&amp;nbsp;1024&amp;nbsp;*&amp;nbsp;1024
}
--&amp;nbsp;如果连接失败(即ok为false),则输出详细的错误信息并结束脚本执行
if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;failed&amp;nbsp;to&amp;nbsp;connect:&amp;nbsp;&amp;quot;,&amp;nbsp;err,&amp;nbsp;&amp;quot;:&amp;nbsp;&amp;quot;,&amp;nbsp;errno,&amp;nbsp;&amp;quot;&amp;nbsp;&amp;quot;,&amp;nbsp;sqlstate)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end

--&amp;nbsp;执行一个SQL查询语句,从test表中选择所有列的第一行记录。如果查询失败，err,&amp;nbsp;errno,和sqlstate将包含错误信息
local&amp;nbsp;res,&amp;nbsp;err,&amp;nbsp;errno,&amp;nbsp;sqlstate&amp;nbsp;=&amp;nbsp;db:query(&amp;quot;SELECT&amp;nbsp;*&amp;nbsp;FROM&amp;nbsp;users&amp;nbsp;LIMIT&amp;nbsp;1&amp;quot;)
if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;bad&amp;nbsp;result:&amp;nbsp;&amp;quot;,&amp;nbsp;err,&amp;nbsp;&amp;quot;:&amp;nbsp;&amp;quot;,&amp;nbsp;errno,&amp;nbsp;&amp;quot;:&amp;nbsp;&amp;quot;,&amp;nbsp;sqlstate)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end

--&amp;nbsp;遍历查询结果res中的每一行，并打印出每行的id和name字段。这里的ipairs是用于遍历数组形式的表
for&amp;nbsp;i,&amp;nbsp;row&amp;nbsp;in&amp;nbsp;ipairs(res)&amp;nbsp;do
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;Row:&amp;nbsp;&amp;quot;,&amp;nbsp;row.id,&amp;nbsp;&amp;quot;&amp;nbsp;-&amp;nbsp;&amp;quot;,&amp;nbsp;row.name)
end

--&amp;nbsp;将当前的数据库连接放入连接池中，以便后续使用。set_keepalive方法的第一个参数是最大空闲时间（毫秒），第二个参数是连接池中的最大连接数
local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;db:set_keepalive(10000,&amp;nbsp;100)
--&amp;nbsp;如果无法将连接放入连接池，则输出错误信息并结束脚本执行。
if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;failed&amp;nbsp;to&amp;nbsp;set&amp;nbsp;keepalive:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end&lt;/pre&gt;&lt;p&gt;# vim /usr/local/nginx/conf/nginx.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;location&amp;nbsp;/test&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_file&amp;nbsp;/usr/local/nginx/conf/lua_conf/lua_mysql_script.lua;
}&lt;/pre&gt;&lt;p&gt;#/usr/local/nginx/sbin/nginx -s reload&lt;/p&gt;&lt;p&gt;# mysql -uroot -p -e &amp;quot;select * from testdb.users;&amp;quot;&amp;nbsp; &amp;nbsp;#查看一下数据库现在的测试信息&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/05/202505081746691789406509.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;# curl 127.0.0.1/test&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Row:&amp;nbsp;1&amp;nbsp;-&amp;nbsp;dage&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;原生源码包的方式:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;#wget https://github.com/openresty/lua-resty-mysql/archive/refs/tags/v0.27.tar.gz&lt;/p&gt;&lt;p&gt;#tar xf v0.27.tar.gz&lt;/p&gt;&lt;p&gt;#cd lua-resty-mysql-0.27/&lt;/p&gt;&lt;p&gt;# /usr/local/luajit/bin/luajit&amp;nbsp; -e &amp;quot;print(package.path)&amp;quot;&amp;nbsp; #查看lua加载的路径&lt;/p&gt;&lt;p&gt;#mkdir /usr/local/luajit/share/lua/5.1/resty/&amp;nbsp; #选取上面查出来的任意路径&lt;/p&gt;&lt;p&gt;#cp lib/resty/mysql.lua&amp;nbsp;&amp;nbsp;/usr/local/luajit/share/lua/5.1/resty/&lt;/p&gt;&lt;p&gt;#wget https://github.com/openresty/lua-resty-string/archive/refs/tags/v0.16.tar.gz&amp;nbsp; #lua-resty-string 模块,它包含了 resty.sha256,如果不安装这个的话会有下面报错,需要加密的模块：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;usr/local/luajit/bin/luajit:&amp;nbsp;/usr/local/luajit/share/luajit-2.1/resty/mysql.lua:5:&amp;nbsp;module&amp;nbsp;&amp;#39;resty.sha256&amp;#39;&amp;nbsp;not&amp;nbsp;found:
	no&amp;nbsp;field&amp;nbsp;package.preload[&amp;#39;resty.sha256&amp;#39;]
	......&lt;/pre&gt;&lt;p&gt;#tar xf v0.16.tar.gz&lt;/p&gt;&lt;p&gt;#cd&amp;nbsp; lua-resty-string-0.16/&lt;/p&gt;&lt;p&gt;#cp lib/resty/*.lua /usr/local/luajit/share/lua/5.1/resty/&amp;nbsp; &amp;nbsp;#至此就可以用上面的lua例子测试一下mysql的连接了&lt;/p&gt;&lt;p&gt;#当然你&lt;strong&gt;还有另一种加载模块的方式&lt;/strong&gt;,你说我不想cp太麻烦我想加载多目录就可以在nginx上面这样配置：&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;--&amp;nbsp;路径之间用分号&amp;nbsp;(;)&amp;nbsp;分隔。双分号&amp;nbsp;(;;)&amp;nbsp;表示保留Lua默认的搜索路径。
lua_package_path&amp;nbsp;&amp;quot;/opt/soft/package/nginx_lua/lua-resty-mysql-0.27/lib/?.lua;/opt/soft/package/nginx_lua/lua-resty-string-0.16/lib/?.lua;/usr/local/lua_core/lib/lua/?.lua;;&amp;quot;;
--&amp;nbsp;当然使用绝对路径太长了,可以把这个lua文件都放到nginx指定的目录下面,然后使用./modules/?.lua;这种相对路径的方式&lt;/pre&gt;&lt;h3 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.2 lua-resty-redis的学习使用&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#一般mysql和redis都结合者使用,所以我们照着上面的方法也学习一下redis的使用&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;2.2.1 使用方法学习&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;#除了小写之外，所有Redis命令都有自己的同名方法。你可以在这里找到Redis命令的完整列表：http://redis.io/commands&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJ6cm84ZFE5WTlCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJuZXclMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTYlOTYlQjAlMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIybmV3JTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;new：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJ6cm84ZFE5WTlCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJuZXclMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTYlOTYlQjAlMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIybmV3JTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;语法&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCWm82a1B4WE1lJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlMkMlMjBlcnIlMjAlM0QlMjByZWRpcyUzQW5ldygpJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU4JUFGJUFEJUU2JUIzJTk1JUVGJUJDJTlBcmVkJUVGJUJDJThDZXJyJTNEcmVkaXMlM0FuZXclRUYlQkMlODglRUYlQkMlODklMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyJTNBJTIwcmVkJTJDJTIwZXJyJTIwJTNEJTIwcmVkaXMlM0FuZXcoKSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;: red, err = redis:new()&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#创建redis对象。如果失败，则返回nil和一个描述错误的字符串。&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-node=&quot;text&quot;&gt;connect:&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: ok, err = red:connect(host, port, options_table?)&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: ok, err = red:connect(&amp;quot;unix:/path/to/unix.sock&amp;quot;, options_table?)&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#&lt;/span&gt;尝试连接到redis服务器正在侦听的远程主机和端口，或redis服务器侦听的本地unix域套接字文件。在实际解析主机名并连接到远程后端之前，此方法将始终在连接池中查找由此方法的先前调用创建的匹配空闲连接。可选的options_table参数是一个Lua表,包含以下键：ssl、ssl_verify、server_name、pool、pool_size、backlog&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlS2Q4eWp2b2xWJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXRfdGltZW91dCUyMiUyQyUyMmRzdCUyMiUzQSUyMnNldF90aW1lb3V0JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnNldF90aW1lb3V0JTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;set_timeout:&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJWN1AyYnBrTnZ6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FzZXRfdGltZW91dCh0aW1lKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXJlZCVFRiVCQyU5QXNldF90aW1lb3V0JUVGJUJDJTg4JUU2JTk3JUI2JUU5JTk3JUI0JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnN5bnRheCUzQSUyMHJlZCUzQXNldF90aW1lb3V0KHRpbWUpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;&quot;&gt;语法&lt;/span&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJWN1AyYnBrTnZ6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FzZXRfdGltZW91dCh0aW1lKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXJlZCVFRiVCQyU5QXNldF90aW1lb3V0JUVGJUJDJTg4JUU2JTk3JUI2JUU5JTk3JUI0JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnN5bnRheCUzQSUyMHJlZCUzQXNldF90aW1lb3V0KHRpbWUpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;: red:set_timeout(time)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJWN1AyYnBrTnZ6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FzZXRfdGltZW91dCh0aW1lKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXJlZCVFRiVCQyU5QXNldF90aW1lb3V0JUVGJUJDJTg4JUU2JTk3JUI2JUU5JTk3JUI0JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnN5bnRheCUzQSUyMHJlZCUzQXNldF90aW1lb3V0KHRpbWUpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#&lt;/span&gt;为后续操作（包括连接方法）设置超时（以毫秒为单位）保护。从该模块的v0.28版本开始，建议使用set_timeouts来支持此方法。&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-node=&quot;text&quot;&gt;set_timeouts:&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJ6eTJrMU5PcVJCJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJzZXRfdGltZW91dHMlMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTglQUUlQkUlRTclQkQlQUUlRTglQjYlODUlRTYlOTclQjYlMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyc2V0X3RpbWVvdXRzJTIyJTdEJTVEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlMmxHWlFOYTR6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FzZXRfdGltZW91dHMoY29ubmVjdF90aW1lb3V0JTJDJTIwc2VuZF90aW1lb3V0JTJDJTIwcmVhZF90aW1lb3V0KSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXJlZCVFRiVCQyU5QSVFOCVBRSVCRSVFNyVCRCVBRSVFOCVCNiU4NSVFNiU5NyVCNiVFRiVCQyU4OCVFOCVCRiU5RSVFNiU4RSVBNSVFOCVCNiU4NSVFNiU5NyVCNiVFMyU4MCU4MSVFNSU4RiU5MSVFOSU4MCU4MSVFOCVCNiU4NSVFNiU5NyVCNiVFMyU4MCU4MSVFOCVBRiVCQiVFNSU4RiU5NiVFOCVCNiU4NSVFNiU5NyVCNiVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FzZXRfdGltZW91dHMoY29ubmVjdF90aW1lb3V0JTJDJTIwc2VuZF90aW1lb3V0JTJDJTIwcmVhZF90aW1lb3V0KSUyMiU3RCU1RCU3RCU1RA==&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: red:set_timeouts(connect_timeout, send_timeout, read_timeout)&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJWN1AyYnBrTnZ6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FzZXRfdGltZW91dCh0aW1lKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXJlZCVFRiVCQyU5QXNldF90aW1lb3V0JUVGJUJDJTg4JUU2JTk3JUI2JUU5JTk3JUI0JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnN5bnRheCUzQSUyMHJlZCUzQXNldF90aW1lb3V0KHRpbWUpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#分别设置后续套接字操作的连接、发送和读取超时阈值（以毫秒为单位）。使用此方法设置超时阈值比set_timeout提供了更多的粒度。因此，最好使用set_timeout而不是set_timeout。v0.28版本中添加了此方法。&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;set_keepalive:&lt;/strong&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: ok, err = red:set_keepalive(max_idle_timeout, pool_size)&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#&lt;/span&gt;立即将当前Redis连接放入ngx_lua cosocket连接池。可以指定连接在池中时的最大空闲超时（以毫秒为单位）以及每个nginx工作进程的池的最大大小。&lt;/p&gt;&lt;p&gt;#如果成功，返回1。如果发生错误，则返回nil，并返回一个描述错误的字符串。&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;strong&gt;get_reused_times:&lt;/strong&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: times, err = red:get_reused_times()&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#&lt;/span&gt;此方法返回当前连接的（成功）重用次数。如果发生错误，它将返回nil和一个描述错误的字符串。&lt;/p&gt;&lt;p&gt;#如果当前连接不是来自内置连接池，则此方法始终返回0，即连接从未被重用（尚未）。如果连接来自连接池，则返回值始终为非零。因此，此方法也可用于确定当前连接是否来自池。&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-node=&quot;text&quot;&gt;close:&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: ok, err = red:close()&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#&lt;/span&gt;关闭当前的redis连接并返回状态。如果成功，返回1。如果发生错误，则返回nil，并返回一个描述错误的字符串。&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-node=&quot;text&quot;&gt;init_pipeline:&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: red:init_pipeline()&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: red:init_pipeline(n)&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#启用redis流水线模式。所有后续对Redis命令方法的调用都将自动缓存，并在调用commit_pipeline方法时一次性发送到服务器，或者通过调用cancel_pipeline来取消。&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-node=&quot;text&quot;&gt;commit_pipeline:&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlSmxXeUE0M296JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJjb21taXRfcGlwZWxpbmUlMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTUlQTclOTQlRTYlODklOTglRTclQUUlQTElRTklODElOTMlMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyY29tbWl0X3BpcGVsaW5lJTIyJTdEJTVEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCWG9Ba2xEM3B6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZXN1bHRzJTJDJTIwZXJyJTIwJTNEJTIwcmVkJTNBY29tbWl0X3BpcGVsaW5lKCklMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTglQUYlQUQlRTYlQjMlOTUlRUYlQkMlOUElRTclQkIlOTMlRTYlOUUlOUMlRUYlQkMlOEMlRTklOTQlOTklRTglQUYlQUYlM0QlRTclQkElQTIlRTglODklQjIlRUYlQkMlOUFjb21taXRfcGlwZWxpbmUlRUYlQkMlODglRUYlQkMlODklMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyc3ludGF4JTNBJTIwcmVzdWx0cyUyQyUyMGVyciUyMCUzRCUyMHJlZCUzQWNvbW1pdF9waXBlbGluZSgpJTIyJTdEJTVEJTdEJTVE&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: results, err = red:commit_pipeline()&lt;/span&gt;&lt;/div&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;#通过在一次运行中将所有缓存的Redis查询提交到远程服务器来退出流水线模式。这些查询的所有回复都将被自动收集，并作为最高级别的大型多批量回复返回。&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-node=&quot;text&quot;&gt;cancel_pipeline：&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJCdjFOcFdPTmt6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJjYW5jZWxfcGlwZWxpbmUlMjIlMkMlMjJkc3QlMjIlM0ElMjJjYW5jZWxfcGlwZWxpbmUlMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1hdGNoZXMlMjIlM0FudWxsJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyY2FuY2VsX3BpcGVsaW5lJTIyJTdEJTVEJTdEJTJDJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJlamtEOWFPRHFWJTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FjYW5jZWxfcGlwZWxpbmUoKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QSVFNyVCQSVBMiVFOCU4OSVCMiVFRiVCQyU5QWNhbmNlbF9waXBlbGluZSVFRiVCQyU4OCVFRiVCQyU4OSUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWF0Y2hlcyUyMiUzQW51bGwlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FjYW5jZWxfcGlwZWxpbmUoKSUyMiU3RCU1RCU3RCUyQyU3QiUyMnR5cGUlMjIlM0ElMjJwYXJhZ3JhcGglMjIlMkMlMjJjaGlsZHJlbiUyMiUzQSU1QiU3QiUyMmlkJTIyJTNBJTIyVk5Oank4MWpiViUyMiUyQyUyMnBhcmFJZHglMjIlM0EyJTJDJTIyc3JjJTIyJTNBJTIyJTIyJTJDJTIyZHN0JTIyJTNBJTIyJTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMiUyMiU3RCU1RCU3RCU1RA==&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: red:cancel_pipeline()&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;span data-slate-zero-width=&quot;n&quot; data-slate-length=&quot;0&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#通过丢弃自上次调用init_pipeline方法以来所有现有的缓存Redis命令来退出流水线模式。&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;strong&gt;hmset:&lt;/strong&gt;&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: res, err = red:hmset(myhash, field1, value1, field2, value2, ...)&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: res, err = red:hmset(myhash, { field1 = value1, field2 = value2, ... })&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#&lt;/span&gt;Redis“hmset”命令的特殊包装器。当只有三个参数（包括“red”对象本身）时，最后一个参数必须是一个包含所有字段/值对的Lua表。&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-node=&quot;text&quot;&gt;array_to_hash:&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: hash = red:array_to_hash(array)&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#&lt;/span&gt;将类似数组的Lua表转换为类似哈希表的辅助函数。此方法首次在v0.11版本中引入。&lt;/p&gt;&lt;div data-slate-node=&quot;element&quot; style=&quot;position: relative;&quot;&gt;&lt;strong&gt;&lt;span data-slate-node=&quot;text&quot;&gt;read_reply:&lt;/span&gt;&lt;/strong&gt;&lt;/div&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;语法: res, err = red:read_reply()&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#正在从redis服务器读取回复。该方法例如对于Redis Pub/Sub API最有用&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;h4 style=&quot;text-wrap-mode: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;2.2.2 环境支持&lt;/span&gt;&lt;/h4&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;&lt;/span&gt;&lt;strong style=&quot;text-wrap-mode: wrap;&quot;&gt;原生源码包的方式:&lt;/strong&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;# wget https://github.com/openresty/lua-resty-redis/archive/refs/tags/v0.29.tar.gz&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;# tar xf v0.29.tar.gz&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;#&amp;nbsp;mkdir /usr/local/luajit/share/lua/5.1/resty&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;# cp lua-resty-redis-0.29/lib/resty/redis.lua&amp;nbsp; /usr/local/luajit/share/lua/5.1/resty/&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#下面举一个redis的set和get的最简单用法&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;position: relative;&quot;&gt;&lt;span data-slate-node=&quot;text&quot;&gt;#vim /opt/soft/nginx/conf/lua_conf/set_redis.lua&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;redis&amp;nbsp;=&amp;nbsp;require&amp;nbsp;&amp;quot;resty.redis&amp;quot;
local&amp;nbsp;red&amp;nbsp;=&amp;nbsp;redis:new()

--&amp;nbsp;连接&amp;nbsp;Redis
red:set_timeout(1000)&amp;nbsp;--&amp;nbsp;超时时间&amp;nbsp;1&amp;nbsp;秒
local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:connect(&amp;quot;127.0.0.1&amp;quot;,&amp;nbsp;6379)
if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;failed&amp;nbsp;to&amp;nbsp;connect:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end

--&amp;nbsp;设置键值对
local&amp;nbsp;res,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:set(&amp;quot;test01&amp;quot;,&amp;nbsp;&amp;quot;hello_redis&amp;quot;)
if&amp;nbsp;not&amp;nbsp;res&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;failed&amp;nbsp;to&amp;nbsp;set&amp;nbsp;test01:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end

ngx.say(&amp;quot;成功设置test01的值为&amp;nbsp;hello_redis&amp;quot;)

--&amp;nbsp;关闭连接
local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:set_keepalive(10000,&amp;nbsp;100)&amp;nbsp;--&amp;nbsp;设置连接池参数
if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;failed&amp;nbsp;to&amp;nbsp;set&amp;nbsp;keepalive:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end&lt;/pre&gt;&lt;p style=&quot;position: relative;&quot;&gt;#vim&amp;nbsp;/opt/soft/nginx/conf/lua_conf/get_redis.lua&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;local&amp;nbsp;redis&amp;nbsp;=&amp;nbsp;require&amp;nbsp;&amp;quot;resty.redis&amp;quot;
local&amp;nbsp;red&amp;nbsp;=&amp;nbsp;redis:new()

--&amp;nbsp;连接&amp;nbsp;Redis
red:set_timeout(1000)&amp;nbsp;--&amp;nbsp;超时时间&amp;nbsp;1&amp;nbsp;秒
local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:connect(&amp;quot;127.0.0.1&amp;quot;,&amp;nbsp;6379)
if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;failed&amp;nbsp;to&amp;nbsp;connect:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end

--&amp;nbsp;获取键值
local&amp;nbsp;value,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:get(&amp;quot;test01&amp;quot;)
if&amp;nbsp;not&amp;nbsp;value&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;failed&amp;nbsp;to&amp;nbsp;get&amp;nbsp;test01:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end

ngx.say(&amp;quot;test01&amp;nbsp;的值是:&amp;nbsp;&amp;quot;,&amp;nbsp;value)

--&amp;nbsp;关闭连接
local&amp;nbsp;ok,&amp;nbsp;err&amp;nbsp;=&amp;nbsp;red:set_keepalive(10000,&amp;nbsp;100)&amp;nbsp;--&amp;nbsp;设置连接池参数
if&amp;nbsp;not&amp;nbsp;ok&amp;nbsp;then
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ngx.say(&amp;quot;failed&amp;nbsp;to&amp;nbsp;set&amp;nbsp;keepalive:&amp;nbsp;&amp;quot;,&amp;nbsp;err)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
end&lt;/pre&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJWN1AyYnBrTnZ6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FzZXRfdGltZW91dCh0aW1lKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXJlZCVFRiVCQyU5QXNldF90aW1lb3V0JUVGJUJDJTg4JUU2JTk3JUI2JUU5JTk3JUI0JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnN5bnRheCUzQSUyMHJlZCUzQXNldF90aW1lb3V0KHRpbWUpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;#vim /opt/soft/nginx/main-conf/nginx.conf&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;......
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/set_redis&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_file&amp;nbsp;/opt/soft/nginx/conf/lua_conf/set_redis.lua;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/get_redis&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content_by_lua_file&amp;nbsp;/opt/soft/nginx/conf/lua_conf/get_redis.lua;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;......
}&lt;/pre&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJWN1AyYnBrTnZ6JTIyJTJDJTIycGFyYUlkeCUyMiUzQTElMkMlMjJzcmMlMjIlM0ElMjJzeW50YXglM0ElMjByZWQlM0FzZXRfdGltZW91dCh0aW1lKSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFOCVBRiVBRCVFNiVCMyU5NSVFRiVCQyU5QXJlZCVFRiVCQyU5QXNldF90aW1lb3V0JUVGJUJDJTg4JUU2JTk3JUI2JUU5JTk3JUI0JUVGJUJDJTg5JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtYXRjaGVzJTIyJTNBbnVsbCUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMnN5bnRheCUzQSUyMHJlZCUzQXNldF90aW1lb3V0KHRpbWUpJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;/span&gt;# /opt/soft/nginx/sbin/nginx -s reload&lt;/p&gt;&lt;p&gt;#下面是测试结果：&lt;/p&gt;&lt;p&gt;# curl 127.0.0.1/set_redis&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;成功设置test01的值为&amp;nbsp;hello_redis&lt;/pre&gt;&lt;p&gt;# curl 127.0.0.1/get_redis&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;test01&amp;nbsp;的值是:&amp;nbsp;hello_redis&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Tue, 25 Mar 2025 17:34:45 +0800</pubDate></item><item><title>Nginx+Consul+Upsync实现动态负载均衡(三)</title><link>https://blog.51niux.com/?id=322</link><description>&lt;p&gt;以往我们都使用的静态后端配置文件的方式,这就导致了每次都需要修改配置文件并reload,每次后端有变动要手工修改配置文件,这时候你想让你的后端动态变化起来,后端的IP变化了比如测试环境的容器IP变化了,你nginx无需reload,请求的后端就改为了最新的后端,那么就是本次文章要介绍的内容了。&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;一、nginx编译并使用Upsync&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.1 nginx编译upsync&lt;/span&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Upsync是微博开源的一个基于nginx实现动态配置的模块,通过拉取consul的上游数据,实现无需重新加载nginx,动态修改后端服务器属性的目录，git地址：&lt;a href=&quot;https://github.com/weibocom/nginx-upsync-module&quot; _src=&quot;https://github.com/weibocom/nginx-upsync-module&quot;&gt;https://github.com/weibocom/nginx-upsync-module&lt;/a&gt; &lt;/p&gt;&lt;p&gt;#cd /opt/soft/package&lt;br/&gt;&lt;/p&gt;&lt;p&gt;#wget https://github.com/weibocom/nginx-upsync-module/archive/master.zip&lt;/p&gt;&lt;p&gt;#unzip master.zip&lt;/p&gt;&lt;p&gt;#cd nginx-1.26.2&amp;nbsp; #nginx的下载和解压就不再演示了&lt;/p&gt;&lt;p&gt;#./configure --prefix=/opt/soft/nginx --sbin-path=/opt/soft/nginx/sbin/nginx --conf-path=/opt/soft/nginx/main-conf/nginx.conf --error-log-path=/opt/log/nginx/error.log --http-log-path=/opt/log/nginx/access.log --pid-path=/opt/soft/nginx/run/nginx.pid --lock-path=/opt/soft/nginx/run/nginx.lock --user=work --group=work --http-client-body-temp-path=/opt/soft/nginx/cache/client_temp --http-proxy-temp-path=/opt/soft/nginx/cache/proxy_temp --http-fastcgi-temp-path=/opt/soft/nginx/cache/fastcgi_temp --http-uwsgi-temp-path=/opt/soft/nginx/cache/uwsgi_tmp --http-scgi-temp-path=/opt/soft/nginx/cache/scgi_temp --with-http_v2_module --with-http_stub_status_module --with-http_ssl_module --with-http_realip_module --with-http_sub_module --with-http_gzip_static_module --with-pcre --with-http_addition_module --with-http_image_filter_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_gzip_static_module --with-file-aio --add-module=/opt/soft/package/nginx-upsync-module-master&lt;/p&gt;&lt;p&gt;#make -j 4&lt;/p&gt;&lt;p&gt;#make install&lt;/p&gt;&lt;p&gt;#mkdir /opt/soft/nginx/cache/&lt;/p&gt;&lt;p&gt;#chown work:work /opt/soft/nginx/cache -R&lt;/p&gt;&lt;p&gt;#vim /opt/soft/nginx/main-conf/nginx.conf&amp;nbsp; #修改配置文件就不多做介绍了,主要增加include:include /opt/soft/nginx/conf.d/*.conf;&lt;/p&gt;&lt;p&gt;# cat /opt/soft/package/nginx-upsync-module-master/README.md&amp;nbsp; &amp;nbsp;#可以看看使用示例这里就不多做介绍了&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.2 nginx使用upsync&lt;/span&gt;&lt;br/&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;试验1(基本配置并拉取什么样的配置信息)：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/02/202502081739012135292323.png&quot; alt=&quot;image.png&quot; width=&quot;249&quot; height=&quot;399&quot; style=&quot;width: 249px; height: 399px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#从上图我们可以看到配置了两种key一种带端口的一种不带端口的,我们看看是如何加载的&lt;/p&gt;&lt;p&gt;# cat /opt/soft/nginx/conf.d/grafana_pool_upstream.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;grafana_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;#server&amp;nbsp;127.0.0.1:8080;
&amp;nbsp;&amp;nbsp;&amp;nbsp;upsync&amp;nbsp;192.168.1.166:8500/v1/kv/offline/upstream/grafana/&amp;nbsp;upsync_timeout=5s&amp;nbsp;upsync_interval=500ms&amp;nbsp;upsync_type=consul&amp;nbsp;strong_dependency=off;
&amp;nbsp;&amp;nbsp;&amp;nbsp;upsync_dump_path&amp;nbsp;/data/nginx/conf/grafana_upstream.conf;
&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;&amp;nbsp;/data/nginx/conf/grafana_upstream.conf;
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; -t&amp;nbsp; &amp;nbsp;#报错很清晰啊,找不到配置文件,不应该啊,不应该自动创建配置文件吗如果目录存在的话&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;nginx:&amp;nbsp;[emerg]&amp;nbsp;open()&amp;nbsp;&amp;quot;/data/nginx/conf/grafana_upstream.conf&amp;quot;&amp;nbsp;failed&amp;nbsp;(2:&amp;nbsp;No&amp;nbsp;such&amp;nbsp;file&amp;nbsp;or&amp;nbsp;directory)&amp;nbsp;in&amp;nbsp;/opt/soft/nginx/conf.d/grafana_pool_upstream.conf:5
nginx:&amp;nbsp;configuration&amp;nbsp;file&amp;nbsp;/opt/soft/nginx/main-conf/nginx.conf&amp;nbsp;test&amp;nbsp;failed&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;# touch /data/nginx/conf/grafana_upstream.conf&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; -t&amp;nbsp; &amp;nbsp;#又有新的错误了,注意我配置文件一直注释着一行呢,就是那里我们打开一下试一试&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;nginx:&amp;nbsp;[emerg]&amp;nbsp;no&amp;nbsp;servers&amp;nbsp;are&amp;nbsp;inside&amp;nbsp;upstream&amp;nbsp;in&amp;nbsp;/opt/soft/nginx/conf.d/grafana_pool_upstream.conf:6
nginx:&amp;nbsp;configuration&amp;nbsp;file&amp;nbsp;/opt/soft/nginx/main-conf/nginx.conf&amp;nbsp;test&amp;nbsp;failed&lt;/pre&gt;&lt;p&gt;# cat /opt/soft/nginx/conf.d/grafana_pool_upstream.conf&amp;nbsp; &amp;nbsp;&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;grafana_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#server这是一个固定格式,配置文件配置上就行
&amp;nbsp;&amp;nbsp;&amp;nbsp;server&amp;nbsp;127.0.0.1:11111&amp;nbsp;down;
&amp;nbsp;&amp;nbsp;&amp;nbsp;#upsync_timeout从consul拉取的超时时间,upsync_interval从consul拉取服务信息的时间间隔,upsync_type指定使用什么类型的服务
&amp;nbsp;&amp;nbsp;&amp;nbsp;#strong_dependency配置nginx在启动时是否强依赖配置服务器,如果配置为on,则拉取配置失败时Nginx启动或者检测就会报错,如果是off就是拉取失败还读取本地配置
&amp;nbsp;&amp;nbsp;&amp;nbsp;upsync&amp;nbsp;192.168.1.166:8500/v1/kv/offline/upstream/grafana/&amp;nbsp;upsync_timeout=5s&amp;nbsp;upsync_interval=500ms&amp;nbsp;upsync_type=consul&amp;nbsp;strong_dependency=off;
&amp;nbsp;&amp;nbsp;&amp;nbsp;#指定从consul拉取的上游服务器后持久化到的文件为止,这样即使consul配置拉取失败,本地有备份依旧会走本地
&amp;nbsp;&amp;nbsp;&amp;nbsp;upsync_dump_path&amp;nbsp;/data/nginx/conf/grafana_upstream.conf;
&amp;nbsp;&amp;nbsp;&amp;nbsp;#加载本地的配置文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/data/nginx/conf/grafana_upstream.conf;
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; -t&amp;nbsp; &amp;nbsp;&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;nginx:&amp;nbsp;the&amp;nbsp;configuration&amp;nbsp;file&amp;nbsp;/opt/soft/nginx/main-conf/nginx.conf&amp;nbsp;syntax&amp;nbsp;is&amp;nbsp;ok
nginx:&amp;nbsp;configuration&amp;nbsp;file&amp;nbsp;/opt/soft/nginx/main-conf/nginx.conf&amp;nbsp;test&amp;nbsp;is&amp;nbsp;successful&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&lt;/p&gt;&lt;p&gt;# cat /data/nginx/conf/grafana_upstream.conf&amp;nbsp; &amp;nbsp;#可以看到创建的文件中有内容了,&lt;span style=&quot;background-color: #FF0000;&quot;&gt;&lt;strong&gt;里面只记录带端口的key信息&lt;/strong&gt;&lt;/span&gt;,权重之类的现在是默认的&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;server&amp;nbsp;192.168.1.166:3000&amp;nbsp;weight=1&amp;nbsp;max_fails=2&amp;nbsp;fail_timeout=10s;
server&amp;nbsp;192.168.1.165:3000&amp;nbsp;weight=1&amp;nbsp;max_fails=2&amp;nbsp;fail_timeout=10s;
server&amp;nbsp;192.168.1.164:3000&amp;nbsp;weight=1&amp;nbsp;max_fails=2&amp;nbsp;fail_timeout=10s;&lt;/pre&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;试验2(自动创建配置文件)：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;#上个例子我们可以看到,本地备份的配置文件是需要手工创建的,不然就会提示加载不到,怎么解决这个问题呢,其中有两种方法,第一种很简单就是这些nginx配置文件肯定也是程序生成的,就是在生成配置文件的时候同时生成include加载的配置文件,第二种也不复杂就是注意命名的唯一,好了下面展示第二种方法。&lt;/p&gt;&lt;p&gt;# rm -f /data/nginx/conf/grafana_upstream.conf&amp;nbsp; &amp;nbsp;#先把我们产生的测试upstream.conf配置文件删掉,确保目录是空的&lt;/p&gt;&lt;p&gt;# cat grafana_pool_upstream.conf&amp;nbsp; &amp;nbsp;#就是用include *正则配置文件的方式,所以要确保前缀是唯一的&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;grafana_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;server&amp;nbsp;127.0.0.1:11111&amp;nbsp;down;
&amp;nbsp;&amp;nbsp;&amp;nbsp;upsync&amp;nbsp;192.168.1.166:8500/v1/kv/offline/upstream/grafana/&amp;nbsp;upsync_timeout=5s&amp;nbsp;upsync_interval=500ms&amp;nbsp;upsync_type=consul&amp;nbsp;strong_dependency=off;
&amp;nbsp;&amp;nbsp;&amp;nbsp;upsync_dump_path&amp;nbsp;/data/nginx/conf/grafana_upstream.conf;
&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;&amp;nbsp;/data/nginx/conf/grafana_*.conf;
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; -t&amp;nbsp; &amp;nbsp;#可自行验证,是没问题的&lt;/p&gt;&lt;p&gt;&lt;strong style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;试验3(测试一下把nginx配置文件中的consul服务停掉,请求是否会读取本地)：&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这里的试验方式呢,就是写个for循环一直curl然后你通过nginx的请求日志看看后端的生效方式：&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;试验3.1&lt;/span&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;我们将offline/upstream/grafana/下面的key清空,或者估计配置错offline/upstream/grafana123/,或者你配置的consul服务端连接不上的时候,这时候你重启nginx会发现不报错,因为获取不到新的key信息,所以/data/nginx/conf/grafana_upstream.conf中保留的是最后一份能获取到信息的后端配置内容,当然将/data/nginx/conf/grafana_upstream.conf删除掉之后再重启nginx是灌不进来任何信息的。&lt;/p&gt;&lt;p&gt;但是啊,虽然&lt;span style=&quot;text-wrap: wrap;&quot;&gt;grafana_upstream.conf是空的,因为获取不到具体的后端配置,这时候我们配置的127.0.0.1那个就登场了,如果换成一个优雅的报错页面是不是就更好了。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/02/202502121739344550443207.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #B7DDE8;&quot;&gt;试验3.2&lt;/span&gt;&amp;nbsp;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;承接上面的例子,现在我们请求走的是127.0.0.1,这时候我们往grafana下面增加一个key,你会发现请求就到了新的key上面,也就是能获取到后端就走指定的后端,没获取到后端再走默认的server。&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap; background-color: #B7DDE8;&quot;&gt;试验3.3&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;承接上面的例子,现在的grafana_upstream.conf已经有一个后端了,我们现在把nginx配置的consul关闭掉,看看请求会不会走本地的配置文件去转发,答案是依旧可以。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap; background-color: #B7DDE8;&quot;&gt;试验3.4&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;承接上面的例子,我们把nginx重启一下,让其直接加载后端配置文件,我们看看curl的效果,直接说结果,你会发现并不是完全请求后端配置文件中的后端,偶尔会出现请求127.0.0.1不通再跳转到新后端的情况,也不是一直出现,是间隔一段时间出现一次&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;&amp;nbsp;HTTP/1.1&amp;quot;&amp;nbsp;301&amp;nbsp;234&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;curl/7.29.0&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;0.001&amp;nbsp;127.0.0.1:11111,&amp;nbsp;192.168.1.228:80&amp;nbsp;502,&amp;nbsp;301&amp;nbsp;0.001,&amp;nbsp;0.000
&amp;nbsp;HTTP/1.1&amp;quot;&amp;nbsp;301&amp;nbsp;234&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;curl/7.29.0&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;0.001&amp;nbsp;192.168.1.228:80&amp;nbsp;301&amp;nbsp;0.001
&amp;nbsp;......
&amp;nbsp;HTTP/1.1&amp;quot;&amp;nbsp;301&amp;nbsp;234&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;curl/7.29.0&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;0.001&amp;nbsp;127.0.0.1:11111,&amp;nbsp;192.168.1.228:80&amp;nbsp;502,&amp;nbsp;301&amp;nbsp;0.000,&amp;nbsp;0.000&lt;/pre&gt;&lt;p&gt;这时候我们将nginx连接的consul恢复,然后增加一个新key,然后查看下请求方式,可以看到后端开始交替轮询了：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;HTTP/1.1&amp;quot;&amp;nbsp;301&amp;nbsp;234&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;curl/7.29.0&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;0.000&amp;nbsp;192.168.1.228:80&amp;nbsp;301&amp;nbsp;0.000
HTTP/1.1&amp;quot;&amp;nbsp;404&amp;nbsp;170&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;curl/7.29.0&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;0.001&amp;nbsp;192.168.1.230:80&amp;nbsp;404&amp;nbsp;0.001
HTTP/1.1&amp;quot;&amp;nbsp;301&amp;nbsp;234&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;curl/7.29.0&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;0.001&amp;nbsp;192.168.1.228:80&amp;nbsp;301&amp;nbsp;0.001
HTTP/1.1&amp;quot;&amp;nbsp;404&amp;nbsp;170&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;curl/7.29.0&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;&amp;quot;-&amp;quot;&amp;nbsp;0.001&amp;nbsp;192.168.1.230:80&amp;nbsp;404&amp;nbsp;0.001&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap; background-color: #B7DDE8;&quot;&gt;试验3.5&lt;/span&gt;&lt;/p&gt;&lt;p&gt;承接上面的例子,这时候我们把consul再次关闭,不重启nginx,然后将&lt;span style=&quot;text-wrap: wrap;&quot;&gt;grafana_upstream.conf里面的后端悄悄的删去一个只保留192.168.1.228,再次重新curl起来,你会发现只要请求的还是consul关闭之前的后端配置,也就是说算你consul关闭了,只要nginx不重新加载,缓存中一直是最后一次的配置依旧是可以正常使用的(当然是前提你consul集群没有更新新配置的情况下,因为现在nginx已经跟consul断开连接了,consul的更新nginx也不会及时更新了)。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;当我们nginx重新reload的时候呢,就会发现它重新回到了试验3.4的效果,请求除了大部分走到192.168.1.228上面外偶尔还会请求下127.0.0.1。&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;总结：通过上面的几个试验,我们可以知道nginx+upsync在各种情况下的请求转发,主要就是当consul出问题的时候,只要你nginx不重启,nginx就走最后一份路由缓存中的后端配置,如果你nginx重启了因为consul连接不上嘛,它就会加载你备份的后端配置文件中的配置,当你consul恢复后,会重新更新缓存。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.3 结合使用nginx_upstream_check_module&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;又要一个新的问题,如果你不是一个单一的后端,你有多个后端,用这种key/value得方式并不会健康检测,现在如果你consul获取多个后端,如果有一个节点有问题,是不是通过nginx的主动探测,不将流量转发给异常节点是不是就更好一点了,这就是要介绍的模块,具体介绍可搜网上面的信息。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;直接说问题,现阶段是这样啊,如果nginx-upsync-module和nginx_upstream_check_module不配合使用你是可以使用nginx的最新版本的,但是要配合使用的话,nginx就要降版本,不然会有下面的报错(当然如果nginx版本是1.20+的话也不能用https://github.com/yaoweibin/nginx_upstream_check_module.git得用https://github.com/xiaokai-wang/nginx_upstream_check_module.git)：&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;objs/addon/src/ngx_http_upsync_module.o：在函数‘ngx_http_upsync_add_peers’中：
/opt/soft/package/nginx-upsync-module/src/ngx_http_upsync_module.c:893：对‘ngx_http_upstream_check_add_dynamic_peer’未定义的引用
objs/addon/src/ngx_http_upsync_module.o：在函数‘ngx_http_upsync_del_peers’中：
/opt/soft/package/nginx-upsync-module/src/ngx_http_upsync_module.c:1126：对‘ngx_http_upstream_check_delete_dynamic_peer’未定义的引用
collect2:&amp;nbsp;错误：ld&amp;nbsp;返回&amp;nbsp;1
make[1]:&amp;nbsp;***&amp;nbsp;[objs/nginx]&amp;nbsp;错误&amp;nbsp;1
make[1]:&amp;nbsp;离开目录“/opt/soft/package/nginx-1.26.2”
make:&amp;nbsp;***&amp;nbsp;[build]&amp;nbsp;错误&amp;nbsp;2&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;nginx加载两个模块&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# cd /opt/soft/package/&lt;/p&gt;&lt;p&gt;#wget&amp;nbsp;https://nginx.org/download/nginx-1.20.2.tar.gz&lt;/p&gt;&lt;p&gt;#tar xf&amp;nbsp;&amp;nbsp;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;nginx-1.20.2.tar.gz&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;#git clone &lt;a href=&quot;https://github.com/weibocom/nginx-upsync-module.git&quot; _src=&quot;https://github.com/weibocom/nginx-upsync-module.git&quot;&gt;https://github.com/weibocom/nginx-upsync-module.git&lt;/a&gt; &lt;/span&gt;&lt;/p&gt;&lt;p&gt;#git clone &lt;a href=&quot;https://github.com/xiaokai-wang/nginx_upstream_check_module.git&quot; _src=&quot;https://github.com/xiaokai-wang/nginx_upstream_check_module.git&quot;&gt;https://github.com/xiaokai-wang/nginx_upstream_check_module.git&lt;/a&gt; &lt;/p&gt;&lt;p&gt;#cd&amp;nbsp;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;nginx-1.20.2&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;#patch -p1&amp;lt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;/opt/soft/package&lt;/span&gt;/nginx_upstream_check_module/check_1.20.1+.patch&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;#&lt;/span&gt;./configure --prefix=/opt/soft/nginx --sbin-path=/opt/soft/nginx/sbin/nginx --conf-path=/opt/soft/nginx/main-conf/nginx.conf --error-log-path=/opt/log/nginx/error.log --http-log-path=/opt/log/nginx/access.log --pid-path=/opt/soft/nginx/run/nginx.pid --lock-path=/opt/soft/nginx/run/nginx.lock --user=work --group=work --http-client-body-temp-path=/opt/soft/nginx/cache/client_temp --http-proxy-temp-path=/opt/soft/nginx/cache/proxy_temp --http-fastcgi-temp-path=/opt/soft/nginx/cache/fastcgi_temp --http-uwsgi-temp-path=/opt/soft/nginx/cache/uwsgi_tmp --http-scgi-temp-path=/opt/soft/nginx/cache/scgi_temp --with-http_v2_module --with-http_stub_status_module --with-http_ssl_module --with-http_realip_module --with-http_sub_module --with-http_gzip_static_module --with-pcre --with-http_addition_module --with-http_image_filter_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_gzip_static_module --with-file-aio --add-module=/opt/soft/package/nginx-upsync-module --add-module=&lt;span style=&quot;text-wrap: wrap;&quot;&gt;/opt/soft/package/&lt;/span&gt;nginx_upstream_check_module&lt;/p&gt;&lt;p&gt;#make -j 4&lt;/p&gt;&lt;p&gt;#make install&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;&lt;/span&gt;#/opt/soft/nginx/sbin/nginx -V&amp;nbsp; &amp;nbsp;#可以自行检测一下&lt;/p&gt;&lt;p&gt;#/opt/soft/package/nginx_upstream_check_module/README 可以查看示例&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;使用探测模块&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# cat grafana_pool_upstream.conf&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;grafana_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;server&amp;nbsp;127.0.0.1:11111;
&amp;nbsp;&amp;nbsp;&amp;nbsp;upsync&amp;nbsp;192.168.1.165:8500/v1/kv/offline/upstream/grafana/&amp;nbsp;upsync_timeout=5s&amp;nbsp;upsync_interval=1s&amp;nbsp;upsync_type=consul&amp;nbsp;strong_dependency=off;
&amp;nbsp;&amp;nbsp;&amp;nbsp;upsync_dump_path&amp;nbsp;/data/nginx/conf/grafana_upstream.conf;
&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;&amp;nbsp;/data/nginx/conf/grafana_*.conf;
&amp;nbsp;&amp;nbsp;&amp;nbsp;#interval:向后端发送的健康检查包的间隔。&amp;nbsp;rise:如果连续成功次数达到rise_count,服务器就被认为是up。fall:如果连续失败次数达到fall_count,服务器就被认为是down。
&amp;nbsp;&amp;nbsp;&amp;nbsp;#timeout:后端健康探测请求的超时时间。&amp;nbsp;type:健康检查包的类型，现在支持以下多种类型:tcp/ssl_hello/http/fastcgi/mysql/ajp
&amp;nbsp;&amp;nbsp;&amp;nbsp;#缺省配置：如果没有配置参数，默认值是：`interval=30000&amp;nbsp;fall=5&amp;nbsp;rise=2&amp;nbsp;timeout=1000&amp;nbsp;default_down=true&amp;nbsp;type=tcp`
&amp;nbsp;&amp;nbsp;&amp;nbsp;check&amp;nbsp;interval=3000&amp;nbsp;rise=2&amp;nbsp;fall=5&amp;nbsp;timeout=2000&amp;nbsp;type=http;
&amp;nbsp;&amp;nbsp;&amp;nbsp;#check_http_send：http健康检查包发送的请求内容,该指令可以配置http健康检查包发送的请求内容。为了减少传输数据量，推荐采用&amp;quot;HEAD&amp;quot;方法。
&amp;nbsp;&amp;nbsp;&amp;nbsp;#当采用长连接进行健康检查时，需在该指令中添加keep-alive请求头，如：&amp;quot;HEAD&amp;nbsp;/&amp;nbsp;HTTP/1.1\r\nConnection:&amp;nbsp;keep-alive\r\n\r\n&amp;quot;。&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;#同时，在采用&amp;quot;GET&amp;quot;方法的情况下，请求uri的size不宜过大，确保可以在1个interval内传输完成，否则会被健康检查模块视为后端服务器或网络异常。
&amp;nbsp;&amp;nbsp;&amp;nbsp;check_http_send&amp;nbsp;&amp;quot;GET&amp;nbsp;/health/defaultcheck&amp;nbsp;HTTP/1.0\r\n\r\n&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;#该指令指定HTTP回复的成功状态，默认认为2XX和3XX的状态是健康的。
&amp;nbsp;&amp;nbsp;&amp;nbsp;check_http_expect_alive&amp;nbsp;http_2xx&amp;nbsp;http_3xx&amp;nbsp;http_4xx;
}&lt;/pre&gt;&lt;p&gt;# /opt/soft/nginx/sbin/nginx&amp;nbsp; &amp;nbsp;#可以看看后端有没有探测URL,然后你再curl一下会发现5xx的后端不会再被访问到了,这个就自己for循环curl验证一下就可以了&lt;/p&gt;&lt;p&gt;#然后你再把http_4xx去掉,就是4xx也认为异常请求,你再curl探测会发现5xx和4xx的后端都不会接收到请求了：&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2025/02/202502131739445167274046.png&quot; alt=&quot;image.png&quot; width=&quot;420&quot; height=&quot;473&quot; style=&quot;width: 420px; height: 473px;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.4 记录一下curl往consul里面添加key信息&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;添加操作：&lt;br/&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#curl -X PUT http://$consul_ip:$port/v1/kv/upstreams/$upstream_name/$backend_ip:$backend_port&lt;/p&gt;&lt;p&gt;default: weight=1 max_fails=2 fail_timeout=10 down=0 backup=0;&lt;/p&gt;&lt;p&gt;#下面是一个完整的添加例子：&lt;/p&gt;&lt;p&gt;# curl -X PUT -d &amp;quot;{\&amp;quot;weight\&amp;quot;:1, \&amp;quot;max_fails\&amp;quot;:2, \&amp;quot;fail_timeout\&amp;quot;:10}&amp;quot;&amp;nbsp; http://192.168.1.165:8500/v1/kv/offline/upstream/grafana/192.168.1.229:80&amp;nbsp;#成功得话返回true&lt;/p&gt;&lt;p&gt;# curl -X PUT -d &amp;#39;{&amp;quot;weight&amp;quot;:1, &amp;quot;max_fails&amp;quot;:2, &amp;quot;fail_timeout&amp;quot;:10}&amp;#39;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;http://192.168.1.165:8500/v1/kv/offline/upstream&lt;/span&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;/grafana/192.168.1.229:80&lt;/span&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;&lt;span style=&quot;text-wrap: wrap; background-color: #E5B9B7;&quot;&gt;删除操作：&lt;/span&gt;&lt;/strong&gt;&lt;/span&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;#curl -X DELETE http://$consul_ip:$port/v1/kv/upstreams/$upstream_name/$backend_ip:$backend_port &lt;/span&gt;&lt;/p&gt;&lt;p&gt;#curl -X DELETE&amp;nbsp; http://192.168.1.165:8500/v1/kv/offline/upstream/grafana/192.168.1.229:80&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;调整权重操作：&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;#就是添加操作,不存在此key就是创建,存在就是覆盖&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #E5B9B7;&quot;&gt;&lt;strong&gt;关闭服务：&lt;/strong&gt;&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;#curl -X PUT -d &amp;quot;{\&amp;quot;weight\&amp;quot;:2, \&amp;quot;max_fails\&amp;quot;:2, \&amp;quot;fail_timeout\&amp;quot;:10, \&amp;quot;down\&amp;quot;:1}&amp;quot; http://192.168.1.165:8500/v1/kv/offline/upstream/grafana/192.168.1.229:80&lt;/p&gt;&lt;p&gt;#curl -X PUT -d &amp;#39;{&amp;quot;weight&amp;quot;:2, &amp;quot;max_fails&amp;quot;:2, &amp;quot;fail_timeout&amp;quot;:10, &amp;quot;down&amp;quot;:1}&amp;#39;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;http://192.168.1.165:8500/v1/kv/offline/upstream/grafana/192.168.1.2&lt;/span&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;29:80&lt;/span&gt;&lt;/p&gt;</description><pubDate>Sat, 08 Feb 2025 18:34:15 +0800</pubDate></item><item><title>Nginx结合Consul-Template(二)</title><link>https://blog.51niux.com/?id=321</link><description>&lt;h2&gt;&lt;span style=&quot;background-color: #FBD5B5;&quot;&gt;一、Consul KV&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;#要先来了解consul的重要功能KV&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;1.1 先简单了解下KV&lt;/span&gt;&lt;/h3&gt;&lt;h4&gt;&lt;span style=&quot;font-size: 14px; text-wrap: nowrap; background-color: #DBEEF3;&quot;&gt;&amp;nbsp;简单了解&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 14px; text-wrap: nowrap;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14px; text-wrap: nowrap;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Consul的KV存储是一个简单而强大键值存储系统,也是Consul的核心功能,但是请注意它是一个简单的KV存储。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14px; text-wrap: nowrap;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Consul KV允许用户存储索引对象它被广泛用于分布式系统中的配置管理和共享数据。&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Consul KV数据存储位于server上，但可以由任何agent（client或server）访问。本地集成的RPC功能允许客户端向服务器转发请求，包括读取和写入键/值。&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;KV存储可以通过consul KV CLI子命令、HTTP API和consul UI访问。如果需要限制访问，请启用并配置acl。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;数据存储本身位于Consul服务器的数据目录中。为了确保在完全中断的情况下不会丢失数据，请使用consul快照特性来备份数据。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Objects对Consul来说是不透明的，这意味着存储在key/value条目中的对象类型没有限制。object的主要限制是大小，最大限制是512 KB。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Consul的KV存储提供了类似文件系统的键值存储模型,其中每个键都是一个路径,类似于/path/to/key,而每个键对应的值可以是任意字节序列。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;consul-template的官网文档：&lt;a href=&quot;https://developer.hashicorp.com/consul/tutorials/network-automation/consul-template-load-balancing?utm_source=docs&quot; _src=&quot;https://developer.hashicorp.com/consul/tutorials/network-automation/consul-template-load-balancing?utm_source=docs&quot;&gt;https://developer.hashicorp.com/consul/tutorials/network-automation/consul-template-load-balancing?utm_source=docs&lt;/a&gt; &lt;/span&gt;&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;Consul KV 存储&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14px; text-wrap: nowrap;&quot;&gt;&amp;nbsp; &amp;nbsp; Consul的KV存储是基于Raft算法实现的。Consul将KV存储看作一个状态机，每个节点都维护一个本地的状态机和日志。当客户端向任何一个节点发送写操作请求时，该节点会将操作转发给leader，&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;leader将操作应用到状态机和日志中，并将结果通知所有follower。当大多数节点确认了某个操作时，该操作被认为是已提交的，leader将操作应用到状态机中，并将结果返回给客户端。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Consul的KV存储支持多种操作，包括读取、写入、更新和删除。每个键值对都由一个唯一的key标识，并且可以关联一个可选的value。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;Consul的KV存储支持版本控制，每个key都可以存储多个版本的value，客户端可以选择读取特定版本的value。&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;text-wrap: nowrap; background-color: #CCC1D9;&quot;&gt;1.2 跟着官网的例子学习一下key/value操作&lt;/span&gt;&lt;/h3&gt;&lt;h4&gt;&lt;span style=&quot;text-wrap: nowrap; background-color: #DBEEF3;&quot;&gt;向KV存储中添加数据&lt;/span&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;# consul kv put redis/config/minconns 1&amp;nbsp; &amp;nbsp;#插入key是redis/config/minconns,value是1,没有此key就是创建,有此key就是更新&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;Success!&amp;nbsp;Data&amp;nbsp;written&amp;nbsp;to:&amp;nbsp;redis/config/minconns&lt;/pre&gt;&lt;p&gt;# consul kv put redis/config/maxconns 25&amp;nbsp; #插入&lt;span style=&quot;text-wrap: wrap;&quot;&gt;key是redis/config/maxconns,value是25&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# consul kv put -flags=42 redis/config/users/admin zaphod #该命令的flags值为42。key支持设置64位整数标志值，该标志值不用于Consul内部，但可用于客户端向KV对添加元数据。&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;查询KV存储数据&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;&amp;nbsp;#consul kv get redis/config/minconns&amp;nbsp; #查询key是&lt;span style=&quot;text-wrap: wrap;&quot;&gt;redis/config/minconns的值&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;# consul kv get -detailed redis/config/users/admin&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-c&quot;&gt;CreateIndex&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;473907
Flags&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;42&amp;nbsp;&amp;nbsp;#如果put的时候不设置flags这里默认是0
Key&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;redis/config/users/admin
LockIndex&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;0
ModifyIndex&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;473907
Session&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-
Value&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;zaphod&lt;/pre&gt;&lt;p&gt;# consul kv get -recurse&amp;nbsp; #要列出存储中的所有键,结果按字典顺序返回。&lt;span style=&quot;text-wrap: wrap;&quot;&gt;&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;redis/config/maxconns:25
redis/config/minconns:1
redis/config/users/admin:zaphod
redis/config/users/admin1:zaphod&lt;/pre&gt;&lt;p&gt;# consul kv get -keys redis/config/&amp;nbsp; #这是查询redis/config下面都有什么key,跟上面命令不同,只显示此层信息,如果是目录比如users就显示redis/config/users/&lt;/p&gt;&lt;p&gt;#consul kv get -keys&amp;nbsp; #默认四列出根目录下的所有keys&lt;/p&gt;&lt;p&gt;下面让我们查看一下web ui上面的显示效果：&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2024/11/202411051730779273244680.png&quot; alt=&quot;image.png&quot; width=&quot;1014&quot; height=&quot;335&quot; style=&quot;width: 1014px; height: 335px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#注意上面web页面如果想创建目录而不是key的话,就这样test/,就是在后面加/就表示创建目录,么有/就是创建key就要输入value了。&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;删除数据&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;要删除KV存储中某个键的值，使用consul KV delete命令。&lt;/p&gt;&lt;p&gt;# consul kv delete redis/config/minconns&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;# consul kv delete -recurse redis/config/users&amp;nbsp; &amp;nbsp;#-recurse选项删除所有带有users前缀的键,如果你的config目录下有users和users1两个目录,这个命令就会把两个&lt;span style=&quot;text-wrap: wrap;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;目录都删除掉,如果你只想删除掉users这个目录就可以执行# consul kv delete -recurse redis/config/users/命令&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/p&gt;&lt;h3 style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;text-wrap: nowrap; background-color: #CCC1D9;&quot;&gt;1.3 http操作key/value&lt;/span&gt;&lt;/h3&gt;&lt;h4&gt;&lt;span style=&quot;text-wrap: nowrap; background-color: #DBEEF3;&quot;&gt;创建kv&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;&lt;span style=&quot;text-wrap: nowrap;&quot;&gt;# curl --request PUT --data &amp;quot;admin 123456&amp;quot; http://127.0.0.1:8500/v1/kv/redis01/config/user &lt;/span&gt;&lt;/p&gt;&lt;p&gt;# curl --request PUT --data &amp;quot;123456&amp;quot; http://127.0.0.1:8500/v1/kv/redis01/config/user/admin&lt;/p&gt;&lt;p&gt;#从上面的例子可以看到如果一个目录下面是可以同时存在同名的key的但是要是两种类型&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# curl --request PUT --data &amp;quot;123456&amp;quot; http://127.0.0.1:8500/v1/kv/redis01/config/user/admin?flags=6666&amp;nbsp; #如果要添加其他参数这样添加&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;true&lt;/pre&gt;&lt;p&gt;#让我们看看还能带上哪些其他参数？&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;dc&amp;nbsp;(string:&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;-&amp;nbsp;指定要查询的数据中心。这将默认为正在查询的agent的数据中心。
flags&amp;nbsp;(int:&amp;nbsp;0)&amp;nbsp;-&amp;nbsp;指定一个介于0和(2^64)-1之间的无符号值来存储键。API消费者可以为他们的应用程序选择任何方式来使用这个字段。
cas&amp;nbsp;(int:&amp;nbsp;0)&amp;nbsp;-&amp;nbsp;指定使用检查与设置操作。这对于更复杂的同步原语来说是非常有用的构建块。如果索引为0，那么Consul只会在键不存在的情况下才会放入该键。如果索引不为零，则仅在索引匹配该键的ModifyIndex时才设置该键。
acquire&amp;nbsp;(string:&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;-&amp;nbsp;提供用于锁获取操作的会话ID。
release&amp;nbsp;(string:&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;-&amp;nbsp;提供一个会话ID用于释放操作。这在与?acquire=配对时很有用，因为它允许客户端产生锁。这将使LockIndex保持不变，但将清除该键的关联会话。该密钥必须由此会话持有才能解锁。&lt;/pre&gt;&lt;p&gt;#那会不会像service一样添加key的consul节点关闭了,key就跟着消失了呢,答案是不会的,除非执行delete删除操作。&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;Read Key&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;如果给定路径中不存在键，则返回404而不是200响应。&lt;/p&gt;&lt;p&gt;如果递归或键查询参数为真，将返回一个键数组。如果ACL策略配置的原因响应数组不包含结果，则响应header中包含X-Consul-Results-Filtered-By-ACLs: true&amp;nbsp;&lt;/p&gt;&lt;p&gt;# curl&amp;nbsp; http://127.0.0.1:8500/v1/kv/redis01/config/user/admin&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[{&amp;quot;LockIndex&amp;quot;:0,&amp;quot;Key&amp;quot;:&amp;quot;redis01/config/user/admin&amp;quot;,&amp;quot;Flags&amp;quot;:6666,&amp;quot;Value&amp;quot;:&amp;quot;MTIzNDU2&amp;quot;,&amp;quot;CreateIndex&amp;quot;:476187,&amp;quot;ModifyIndex&amp;quot;:477574}]
#CreateIndex表示条目创建时间的内部索引值
#ModifyIndex是最后一个修改此键的索引
#LockIndex是在锁中成功获取该键的次数。如果锁被持有，会话键提供拥有该锁的会话
#Key就是条目的完整路径
#Flags是一个不透明的无符号整数，可以附加到每个条目。
#Value是一个base64编码的数据块。&lt;/pre&gt;&lt;p&gt;#可以看到value值并未明文而是base64加密的需要解密一下&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# echo &amp;quot;MTIzNDU2&amp;quot;|base64 -d&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;123456&lt;/pre&gt;&lt;p&gt;下面是一些查询条件：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;dc&amp;nbsp;(string:&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;
recurse&amp;nbsp;(bool:&amp;nbsp;false)&amp;nbsp;-&amp;nbsp;指定查找是否应该递归并将key视为前缀而不是文字匹配。
raw&amp;nbsp;(bool:&amp;nbsp;false)&amp;nbsp;-&amp;nbsp;指定响应只是键的原始值，没有任何编码或元数据。
keys&amp;nbsp;(bool:&amp;nbsp;false)&amp;nbsp;-指&amp;nbsp;定只返回键（不返回值或元数据）。指定此参数意味着递归。
separator&amp;nbsp;(string:&amp;nbsp;&amp;quot;&amp;quot;)&amp;nbsp;-&amp;nbsp;指定用于递归键查找的分隔符的字符串。此选项仅在与keys参数配对时使用，以限制返回的键的前缀，仅限给定的分隔符。&lt;/pre&gt;&lt;p&gt;# curl&amp;nbsp; http://127.0.0.1:8500/v1/kv/redis01/config/user/admin?raw&amp;nbsp; #可以看到只是单纯的显示value&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;123456&lt;/pre&gt;&lt;p&gt;# curl&amp;nbsp; http://127.0.0.1:8500/v1/kv/redis01/config/?keys&amp;nbsp; #可以看到是显示指定目录下所有的keys,以数组的形式展示&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;[&amp;quot;redis01/config/user&amp;quot;,&amp;quot;redis01/config/user/admin&amp;quot;,&amp;quot;redis01/config/user/admin1&amp;quot;,&amp;quot;redis01/config/user1/admin1&amp;quot;]&lt;/pre&gt;&lt;p&gt;# curl&amp;nbsp; http://127.0.0.1:8500/v1/kv/redis01/config?recurse=true&amp;nbsp; #显示所有key的详细信息,跟config?recurse一个效果&lt;/p&gt;&lt;p&gt;# curl&amp;nbsp; http://127.0.0.1:8500/v1/kv/redis01/config/user/admin?keys&amp;nbsp; #注意这是获取admin为前缀的key列表并非admin的key&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #DBEEF3; white-space-collapse: preserve;&quot;&gt;Delete Key&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;# curl --request DELETE http://127.0.0.1:8500/v1/kv/redis01/config/user/admin &amp;nbsp;#这是删除为redis01/config/user/admin的key不会删目录&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;true&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;/span&gt;# curl &lt;span style=&quot;color: #445566; font-family: 微软雅黑, Arial; white-space: pre-wrap; background-color: #FEFEFE;&quot;&gt;&lt;span style=&quot;white-space: pre-wrap;&quot;&gt;--request&lt;/span&gt; &amp;nbsp;&lt;/span&gt;DELETE http://127.0.0.1:8500/v1/kv/redis01/config/user/admin?recurse&amp;nbsp; #这是删除admin为前缀开头的key和目录&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FDEADA;&quot;&gt;二、先跟着官网结构简单学习下&lt;span style=&quot;color: #445566; font-family: 微软雅黑, Arial; text-wrap: wrap; background-color: #FDEADA;&quot;&gt;consul-template&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #E5E0EC;&quot;&gt;2.1 安装&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;官网地址：https://github.com/hashicorp/consul-template&lt;/p&gt;&lt;p&gt;# wget https://releases.hashicorp.com/consul-template/0.39.1/consul-template_0.39.1_linux_amd64.zip&lt;/p&gt;&lt;p&gt;#&amp;nbsp;unzip consul-template_0.39.1_linux_amd64.zip&lt;/p&gt;&lt;p&gt;#mv consul-template &amp;nbsp;/usr/local/bin/&lt;/p&gt;&lt;p&gt;# consul-template -v&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;consul-template&amp;nbsp;v0.39.1&amp;nbsp;(cc8f954)&lt;/pre&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjIwajVyb1BLVnd2JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJEZWxldGUlMjBLZXklMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTUlODglQTAlRTklOTklQTQlRTUlQUYlODYlRTklOTIlQTUlMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMkRlbGV0ZSUyMEtleSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;举个简单的例子：&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# cat /tmp/in.tpl&amp;nbsp; &amp;nbsp;#定义一个foo的key&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{&amp;nbsp;key&amp;nbsp;&amp;quot;foo&amp;quot;&amp;nbsp;}}&lt;/pre&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjIwajVyb1BLVnd2JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJEZWxldGUlMjBLZXklMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTUlODglQTAlRTklOTklQTQlRTUlQUYlODYlRTklOTIlQTUlMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMkRlbGV0ZSUyMEtleSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;# consul-template -template &amp;quot;in.tpl:out.txt&amp;quot; -once &amp;nbsp;#-once是执行一次,你会发现这个进程一直在监听等待,如果foo有值的话再次执行得话进程就不会再监测卡主了&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjIwajVyb1BLVnd2JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJEZWxldGUlMjBLZXklMjIlMkMlMjJkc3QlMjIlM0ElMjIlRTUlODglQTAlRTklOTklQTQlRTUlQUYlODYlRTklOTIlQTUlMjIlMkMlMjJtZXRhZGF0YSUyMiUzQSUyMiUyMiUyQyUyMm1ldGFEYXRhJTIyJTNBJTVCJTVEJTJDJTIydGV4dCUyMiUzQSUyMkRlbGV0ZSUyMEtleSUyMiU3RCU1RCU3RCU1RA==&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;# /opt/soft/consul/consul kv put foo bar &amp;nbsp;#给foo赋值bar,然后会发现的进程会执行完毕&lt;/span&gt;&lt;/p&gt;&lt;p&gt;# cat /tmp/out.txt&amp;nbsp; #会看到key名称是foo会把获取的值写入到out.txt文件中&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;bar&lt;/pre&gt;&lt;p&gt;# consul-template -template &amp;quot;in.tpl:out.txt&amp;quot;&amp;nbsp; #如果不加-once会怎么样？那么这个进程会一直监测着foo这个key，一旦值发生变化就会更新out.txt文件可以试下&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #E5E0EC;&quot;&gt;2.2&amp;nbsp;配置Consul模板&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Consul Template可以使用命令行标志和配置文件进行配置。&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;命令行标志&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;# consul-template -h&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;-config=&amp;lt;path&amp;gt;&amp;nbsp;#设置磁盘上配置文件或文件夹的路径。这可能是指定多次以加载多个文件或文件夹。如果有多个给定值后，它们从左向右合并，CLI参数取最高优先级。
-consul-addr=&amp;lt;address&amp;gt;&amp;nbsp;#设置Consul实例的地址
-consul-auth=&amp;lt;username[:password]&amp;gt;&amp;nbsp;#设置consul通信的基本身份验证用户名和密码
-consul-retry&amp;nbsp;#与Consul通信失败时使用重试逻辑
-consul-retry-attempts=&amp;lt;int&amp;gt;&amp;nbsp;#重试失败通信时使用的尝试次数
-consul-retry-backoff=&amp;lt;duration&amp;gt;&amp;nbsp;#用于回退持续时间的基数。这个数字将是每次重试都会成倍增加
-consul-retry-max-backoff=&amp;lt;duration&amp;gt;&amp;nbsp;#重试回退持续时间的最大限制。默认为一分钟。0表示无穷。回退将呈指数增长，直到给定值
-consul-ssl&amp;nbsp;#在连接到Consul时使用SSL
-consul-ssl-ca-cert=&amp;lt;string&amp;gt;&amp;nbsp;#根据此CA证书文件列表验证服务器证书
-consul-ssl-ca-path=&amp;lt;string&amp;gt;&amp;nbsp;#设置要用于TLS验证的CA的路径
-consul-ssl-cert=&amp;lt;string&amp;gt;&amp;nbsp;#要发送到服务器的SSL客户端证书
-consul-ssl-key=&amp;lt;string&amp;gt;&amp;nbsp;#用于客户端认证密钥交换的SSL/TLS私钥
-consul-ssl-server-name=&amp;lt;string&amp;gt;&amp;nbsp;#设置验证TLS时要使用的服务器的名称
-consul-ssl-verify&amp;nbsp;#通过SSL连接时验证证书
-consul-token=&amp;lt;token&amp;gt;&amp;nbsp;#设置Consul&amp;nbsp;API令牌
-consul-token-file=&amp;lt;path&amp;gt;&amp;nbsp;#将路径设置为包含Consul&amp;nbsp;API令牌的文件
-consul-transport-dial-keep-alive=&amp;lt;duration&amp;gt;&amp;nbsp;#设置用于keep-alive的时间
-consul-transport-dial-timeout=&amp;lt;duration&amp;gt;&amp;nbsp;#设置为建立连接而等待的时间
-consul-transport-disable-keep-alives&amp;nbsp;#禁用keep-alive（这会影响性能）
-consul-transport-max-idle-conns-per-host=&amp;lt;int&amp;gt;&amp;nbsp;#设置每台主机允许的最大空闲连接数
-consul-transport-tls-handshake-timeout=&amp;lt;duration&amp;gt;&amp;nbsp;#设置握手超时时间
-dedup&amp;nbsp;#启用重复数据删除模式—当有多个实例时，可以减少Consul上的负载
-default-left-delimiter&amp;nbsp;#模板的默认左分隔符
-default-right-delimiter&amp;nbsp;#模板的默认右分隔符
-dry&amp;nbsp;#将生成的模板打印到标准输出，而不是呈现
-exec=&amp;lt;command&amp;gt;&amp;nbsp;#启用exec模式，使其作为类似管理程序的进程运行
-exec-kill-signal=&amp;lt;signal&amp;gt;&amp;nbsp;#在优雅地终止进程时发送的信号
-exec-kill-timeout=&amp;lt;duration&amp;gt;&amp;nbsp;#强制杀死进程之前的等待时间
-exec-reload-signal=&amp;lt;signal&amp;gt;&amp;nbsp;#重新加载时发送的信号
-exec-splay=&amp;lt;duration&amp;gt;&amp;nbsp;#发送信号前的等待时间
-exec-env-pristine&amp;nbsp;#子进程不继承父进程的环境变量
-exec-env-custom&amp;nbsp;#子进程附加的自定义环境变量
-exec-env-allowlist&amp;nbsp;#暴漏给子进程的环境变量列表
-exec-env-denylist&amp;nbsp;#禁止暴漏给子进程的环境变量列表
-kill-signal=&amp;lt;signal&amp;gt;&amp;nbsp;#优雅关闭进程的信号
-log-level=&amp;lt;level&amp;gt;&amp;nbsp;#设置日志级别，取值为debug,info,warn,err
-max-stale=&amp;lt;duration&amp;gt;&amp;nbsp;#设置最大过期时间
-once&amp;nbsp;#不要将该进程作为守护进程运行
-parse-only&amp;nbsp;#不要处理模板仅解析它们的结构
-pid-file=&amp;lt;path&amp;gt;&amp;nbsp;#pid文件的路径
-reload-signal=&amp;lt;signal&amp;gt;&amp;nbsp;#监听到信号重新加载配置
-retry=&amp;lt;duration&amp;gt;&amp;nbsp;&amp;nbsp;#如果Consul在与API通信时返回错误，则需要等待的时间
-syslog&amp;nbsp;#将输出发送到syslog，而不是标准错误和标准输出。
-syslog-facility=&amp;lt;facility&amp;gt;&amp;nbsp;#设置syslog日志应该记录的功能
-syslog-name=&amp;lt;name&amp;gt;&amp;nbsp;#设置将出现在syslog中的应用程序的名称
-template=&amp;lt;template&amp;gt;&amp;nbsp;#在磁盘上以‘in:out(:command)’的格式添加一个要监视的新模板
-template-error-fatal=&amp;lt;bool&amp;gt;&amp;nbsp;#控制模板错误是否会导致consul-template立即退出。这将覆盖每个模板的设置
......
关于vault的就不列了
......
-wait=&amp;lt;duration&amp;gt;&amp;nbsp;#设置在编写模板（并触发命令）之前等待的‘min(:max)’时间
-v,&amp;nbsp;-version&amp;nbsp;&amp;nbsp;#打印版本&lt;/pre&gt;&lt;p&gt;# consul-template -template &amp;quot;in.tpl:out.txt&amp;quot; -exec &amp;quot;echo &amp;#39;haha&amp;#39;&amp;quot;&amp;nbsp; #先来一个例子,这里我们in.tpl里面定义了一个footest得key,注意这有点一次执行的意思,如果这是个新key没有value值的话,这个进程会一直等待直到有value出现,然后输出haha,如果这个key有value值的话就立马执行一次打印haha进程也就结束了。&lt;/p&gt;&lt;p&gt;# consul-template -template &amp;quot;in.tpl:out.txt:/bin/bash -c &amp;#39;echo haha&amp;#39;&amp;quot; #这样进程会是常驻状态&lt;/p&gt;&lt;p&gt;# consul-template -template &amp;quot;in.tpl:out.txt:/bin/bash -c &amp;#39;service nginx1 restart||true&amp;#39;&amp;quot;&amp;nbsp; #如果你希望就算命令执行错误,进程还依旧运行,而不是非0退出的话&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;2.3 配置文件&lt;/span&gt;&lt;br/&gt;&lt;/h3&gt;&lt;p&gt;配置文件是用HashiCorp配置语言编写,使用带有-config标志来使用配置文件。可以多次指定此参数以加载多个配置文件。最右侧的配置具有最高优先级。如果提供了目录的路径，则给定目录中的所有文件将按词法顺序递归合并。CLI上指定的命令优先于配置文件。&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;&lt;strong&gt;先来一个最简单的例子&lt;/strong&gt;&lt;/span&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;#cat /tmp/config.hcl&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;consul&amp;nbsp;{
&amp;nbsp;&amp;nbsp;address&amp;nbsp;=&amp;nbsp;&amp;quot;127.0.0.1:8500&amp;quot;
}

template&amp;nbsp;{
&amp;nbsp;&amp;nbsp;contents&amp;nbsp;=&amp;nbsp;&amp;quot;{{key&amp;nbsp;\&amp;quot;hello\&amp;quot;}}&amp;quot;
&amp;nbsp;&amp;nbsp;destination&amp;nbsp;=&amp;nbsp;&amp;quot;/tmp/out1.txt&amp;quot;
&amp;nbsp;&amp;nbsp;exec&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;command&amp;nbsp;=&amp;nbsp;&amp;quot;cat&amp;nbsp;/tmp/out1.txt&amp;quot;
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;# consul-template -config &amp;quot;/tmp/config.hcl&amp;quot;&amp;nbsp; &amp;nbsp;#使用配置文件,进程也是一直监听着这个key,如果又变化就会更新文件打印内容到屏幕,只有value变化的时候才会触发&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;下面是官网的一个配置文件详解：&lt;/span&gt;&lt;br/&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Consul Template部分：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;下面这些选项是配置Consul Template的顶级值。它们不是必需的，并且在省略时将回退到默认值。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;#这表示Consul&amp;nbsp;Template的配置部分的开始。本节中包含的所有值都属于Vault。
reload_signal&amp;nbsp;=&amp;nbsp;&amp;quot;SIGHUP&amp;quot;
kill_signal&amp;nbsp;=&amp;nbsp;&amp;quot;SIGINT&amp;quot;
max_stale&amp;nbsp;=&amp;nbsp;&amp;quot;10m&amp;quot;
block_query_wait&amp;nbsp;=&amp;nbsp;&amp;quot;60s&amp;quot;
log_level&amp;nbsp;=&amp;nbsp;&amp;quot;warn&amp;quot;
template_error_fatal&amp;nbsp;=&amp;nbsp;true
err_on_failed_lookup&amp;nbsp;=&amp;nbsp;true
wait&amp;nbsp;{&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;min&amp;nbsp;=&amp;nbsp;&amp;quot;5s&amp;quot;
&amp;nbsp;&amp;nbsp;max&amp;nbsp;=&amp;nbsp;&amp;quot;10s&amp;quot;
}&lt;/pre&gt;&lt;p&gt;要启用下面这些功能，请在配置文件中声明值&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;pid_file&amp;nbsp;=&amp;nbsp;&amp;quot;/path/to/pid&amp;quot;
syslog&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这将启用syslog日志记录。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这是要进行日志记录的syslog设施的名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;facility&amp;nbsp;=&amp;nbsp;&amp;quot;LOCAL5&amp;quot;
}
#这个块定义了记录到文件的配置
log_file&amp;nbsp;{&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#如果指定了路径，则启用该特性。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path&amp;nbsp;=&amp;nbsp;&amp;quot;/var/log/something.log&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#可以写入日志文件的字节数
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_rotate_bytes&amp;nbsp;=&amp;nbsp;1024000
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#默认情况下是24h小时轮转一次
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_rotate_duration&amp;nbsp;=&amp;nbsp;&amp;quot;3h&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#日志的数量.默认为0(不删除任何文件)。设置为-1表示在创建新日志文件时丢弃旧的日志文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;log_rotate_max_files&amp;nbsp;=&amp;nbsp;10
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Consul部分：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;通过声明Consul块，使Consul Template能够与Consul连接。&lt;/p&gt;&lt;p&gt;下面表示Consul配置部分的开始。本节中包含的所有值都与Consul有关。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;consul&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#此块指定与请求一起传递的基本身份验证信息。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auth&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled&amp;nbsp;&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username&amp;nbsp;=&amp;nbsp;&amp;quot;test&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password&amp;nbsp;=&amp;nbsp;&amp;quot;test&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;address&amp;nbsp;=&amp;nbsp;&amp;quot;127.0.0.1:8500&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这是一个用于读/写的Consul&amp;nbsp;Enterprise名称空间,这是一个测试功能
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;namespace&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这是连接到Consul时要使用的ACL令牌.如果没有在Consul集群上启用acl，则不需要设置此选项。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;token&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#可以使用此选项指定包含令牌的文件的路径
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;token_file&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这将控制从Consul返回错误时的重试行为。Consul&amp;nbsp;Template具有高度容错性，这意味着它在遇到故障时不会退出
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;retry&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这启用了重试。默认情况下，重试是启用的，所以这是多余的。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这指定在放弃之前尝试的次数。将其设置为零将实现无限次重试。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;attempts&amp;nbsp;=&amp;nbsp;12
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这是重试尝试之间的基本睡眠时间。每次重试休眠的时间比这个基数长2个指数。对于5次重试，睡眠时间将是：250ms、500ms、15秒、25秒，然后是4s。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;backoff&amp;nbsp;=&amp;nbsp;&amp;quot;250ms&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这是重试尝试之间的最大睡眠时间。当max_backoff设置为零时，重试尝试之间的指数睡眠没有上限。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#如果max_backoff设置为10s，&amp;nbsp;backoff设置为1s，则睡眠时间将为：1s，&amp;nbsp;2s,&amp;nbsp;4s,&amp;nbsp;8s,&amp;nbsp;10s,&amp;nbsp;10s，…
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_backoff&amp;nbsp;=&amp;nbsp;&amp;quot;1m&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#该块配置tcp连接选项
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;transport&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这控制在空闲状态下两个keepalive传输之间的持续时间
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dial_keep_alive&amp;nbsp;=&amp;nbsp;&amp;quot;10s&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这控制了重试之间的tcp超时。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dial_timeout&amp;nbsp;=&amp;nbsp;&amp;quot;10s&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这允许禁用保持活动连接&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;disable_keep_alives&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#最大空闲连接的数量
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_idle_conns_per_host&amp;nbsp;=&amp;nbsp;100
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#tls与consul握手的超时
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tls_handshake_timeout&amp;nbsp;=&amp;nbsp;&amp;quot;30s&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#此块配置用于连接到Consul服务器的SSL选项。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这将启用SSL。指定SSL的任何选项也将启用它。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这将启用SSL对等验证。默认值为&amp;quot;true&amp;quot;,将检查全局CA链,以确保给定的证书有效。如果使用的是尚未添加的自签名证书对于CA链,可能需要禁用SSL验证
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;verify&amp;nbsp;=&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这是用于身份验证的证书的路径。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cert&amp;nbsp;=&amp;nbsp;&amp;quot;/path/to/client/cert&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;quot;/path/to/client/key&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这是用作CA的证书颁发机构的路径。这对于自签名证书或使用自己内部证书颁发机构（CA）的组织非常有用。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ca_cert&amp;nbsp;=&amp;nbsp;&amp;quot;/path/to/ca&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这是指向PEM编码的CA证书文件目录的路径。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ca_path&amp;nbsp;=&amp;nbsp;&amp;quot;path/to/certs/&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这将设置用于验证的SNI服务器名称。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name&amp;nbsp;=&amp;nbsp;&amp;quot;my-server.com&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Templates部分：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;模板块定义了模板的配置。与其他块不同，这个块可以被指定多次来配置多个模板。也可以直接通过CLI配置模板。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;template&amp;nbsp;{
&amp;nbsp;&amp;nbsp;#这是磁盘上用作输入模板的源文件。这通常被称为“Consul&amp;nbsp;Template模板”。如果不使用&amp;#39;contents&amp;#39;选项，则需要此选项。
&amp;nbsp;&amp;nbsp;source&amp;nbsp;=&amp;nbsp;&amp;quot;/path/on/disk/to/template.ctmpl&amp;quot;
&amp;nbsp;&amp;nbsp;#这是源模板将在其中呈现的磁盘上的目标路径。如果父目录不存在，Consul&amp;nbsp;Template将尝试创建它们，除非create_dest_dirs为false。
&amp;nbsp;&amp;nbsp;destination&amp;nbsp;=&amp;nbsp;&amp;quot;/path/on/disk/where/template/will/render.txt&amp;quot;
&amp;nbsp;&amp;nbsp;#此选项告诉Consul&amp;nbsp;Template，如果目标路径的父目录不存在，则创建它们。默认值为true。
&amp;nbsp;&amp;nbsp;create_dest_dirs&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;#这个选项允许在配置文件中嵌入模板的内容，而不是为模板文件提供“源”路径。这对于短模板很有用。此选项与‘source’选项互斥。
&amp;nbsp;&amp;nbsp;contents&amp;nbsp;=&amp;nbsp;&amp;quot;{{&amp;nbsp;keyOrDefault&amp;nbsp;\&amp;quot;service/redis/maxconns@east-aws\&amp;quot;&amp;nbsp;\&amp;quot;5\&amp;quot;&amp;nbsp;}}&amp;quot;
&amp;nbsp;&amp;nbsp;#在访问不存在的结构体或映射field/key时出现错误退出。当访问不存在的字段时，默认行为将打印“&amp;lt;no&amp;nbsp;value&amp;gt;”
&amp;nbsp;&amp;nbsp;error_on_missing_key&amp;nbsp;=&amp;nbsp;false
&amp;nbsp;&amp;nbsp;#控制模板中的错误是否会导致consul-template立即退出。
&amp;nbsp;&amp;nbsp;error_fatal&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;#目标文件的文件权限
&amp;nbsp;&amp;nbsp;perms&amp;nbsp;=&amp;nbsp;0600
&amp;nbsp;&amp;nbsp;#创建文件的用户和用户组,如果不指定，Consul&amp;nbsp;Template将保留现有文件的所有权。
&amp;nbsp;&amp;nbsp;user&amp;nbsp;=&amp;nbsp;1000
&amp;nbsp;&amp;nbsp;group&amp;nbsp;=&amp;nbsp;1000&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#写入新模版之前会创建一份备份,它只保留一个备份
&amp;nbsp;&amp;nbsp;backup&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;#这些是要在模板中使用的分隔符。默认值是“{{”和“}}”
&amp;nbsp;&amp;nbsp;left_delimiter&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;quot;{{&amp;quot;
&amp;nbsp;&amp;nbsp;right_delimiter&amp;nbsp;=&amp;nbsp;&amp;quot;}}&amp;quot;
&amp;nbsp;&amp;nbsp;#这些是模板中不允许使用的函数。如果模板包含这些函数之一，它将退出并显示错误。
&amp;nbsp;&amp;nbsp;function_denylist&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;#沙盒路径
&amp;nbsp;&amp;nbsp;sandbox_path&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;#新模版到执行命令之间等待的最小和最大时间
&amp;nbsp;&amp;nbsp;wait&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;min&amp;nbsp;=&amp;nbsp;&amp;quot;2s&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max&amp;nbsp;=&amp;nbsp;&amp;quot;10s&amp;quot;
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;#这是可选的exec块，用于在呈现模板时提供要运行的命令。该命令只会在生成的模板发生更改时运行。
&amp;nbsp;&amp;nbsp;exec&amp;nbsp;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;command&amp;nbsp;=&amp;nbsp;[&amp;quot;restart&amp;quot;,&amp;nbsp;&amp;quot;service&amp;quot;,&amp;nbsp;&amp;quot;foo&amp;quot;]&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout&amp;nbsp;=&amp;nbsp;&amp;quot;30s&amp;quot;
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;#为了向后兼容，模板块还支持裸command和command_timeout设置。
&amp;nbsp;&amp;nbsp;command&amp;nbsp;=&amp;nbsp;[&amp;quot;restart&amp;quot;,&amp;nbsp;&amp;quot;service&amp;quot;,&amp;nbsp;&amp;quot;foo&amp;quot;]&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;command_timeout&amp;nbsp;=&amp;nbsp;&amp;quot;60s&amp;quot;
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Modes部分:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;使用以下选项配置Consul Template以在各种模式下运行。&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJ5THd2eXpNMDU0JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJPbmNlJTIwTW9kZSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFNCVCOCU4MCVFNiVBQyVBMSVFNiVBOCVBMSVFNSVCQyU4RiUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyT25jZSUyME1vZGUlMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;Once Mode(一次模式)：&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJ5THd2eXpNMDU0JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJPbmNlJTIwTW9kZSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFNCVCOCU4MCVFNiVBQyVBMSVFNiVBOCVBMSVFNSVCQyU4RiUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyT25jZSUyME1vZGUlMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;将Consul Template配置为只执行每个模板一次，并使用标志退出-once或在配置文件中退出。&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;once&amp;nbsp;=&amp;nbsp;true&lt;/pre&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJ5THd2eXpNMDU0JTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJPbmNlJTIwTW9kZSUyMiUyQyUyMmRzdCUyMiUzQSUyMiVFNCVCOCU4MCVFNiVBQyVBMSVFNiVBOCVBMSVFNSVCQyU4RiUyMiUyQyUyMm1ldGFkYXRhJTIyJTNBJTIyJTIyJTJDJTIybWV0YURhdGElMjIlM0ElNUIlNUQlMkMlMjJ0ZXh0JTIyJTNBJTIyT25jZSUyME1vZGUlMjIlN0QlNUQlN0QlNUQ=&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;/span&gt;De-Duplication Mode(重复数据删除模式):&lt;/p&gt;&lt;p&gt;此块定义了在重复数据删除模式下运行Consul Template的配置。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;deduplicate&amp;nbsp;{&amp;nbsp;&amp;nbsp;#&amp;nbsp;这将启用重复数据删除模式。指定任何其他选项也会启用
&amp;nbsp;&amp;nbsp;#&amp;nbsp;这将启用重复数据删除模式。指定任何其他选项也会启用
&amp;nbsp;&amp;nbsp;enabled&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;#这是Consul的KV存储路径的前缀，重复数据删除模板将在这里被预渲染和存储。
&amp;nbsp;&amp;nbsp;prefix&amp;nbsp;=&amp;nbsp;&amp;quot;consul-template/dedup/&amp;quot;}&lt;/pre&gt;&lt;p&gt;Exec Mode(执行模式):&lt;/p&gt;&lt;p&gt;此块定义了在执行模式下运行Consul Template的配置。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;exec&amp;nbsp;{
&amp;nbsp;&amp;nbsp;#这是作为子进程执行的命令。每个Consul&amp;nbsp;Template进程只能有一个命令。
&amp;nbsp;&amp;nbsp;command&amp;nbsp;=&amp;nbsp;[&amp;quot;/usr/bin/app&amp;quot;]
&amp;nbsp;&amp;nbsp;#等待命令完成的最大时间。默认情况下，该值为0，表示永久等待
&amp;nbsp;&amp;nbsp;timeout&amp;nbsp;=&amp;nbsp;&amp;quot;0&amp;quot;
&amp;nbsp;&amp;nbsp;#这是在终止命令之前等待的随机显示。默认值为0（无等待），但大型集群应考虑设置一个splay值，以防止所有子进程在数据更改时同时重新加载。
&amp;nbsp;&amp;nbsp;splay&amp;nbsp;=&amp;nbsp;&amp;quot;5s&amp;quot;
&amp;nbsp;&amp;nbsp;env&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这指定了子进程是否不应继承父进程的环境。默认情况下，子节点将具有对父节点环境变量的完全访问权限。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pristine&amp;nbsp;=&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这将以如下所示的形式指定额外的自定义环境变量，以注入子进程的运行时环境。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;custom&amp;nbsp;=&amp;nbsp;[&amp;quot;PATH=$PATH:/etc/myapp/bin&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这将指定一个环境变量列表，以独占地包含在向子进程公开的环境变量列表中。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;allowlist&amp;nbsp;=&amp;nbsp;[&amp;quot;CONSUL_*&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#这将指定一个环境变量列表，以便在向子进程公开的环境变量列表中独占禁止。如果指定，则任何与给定模式匹配的环境变量都不会公开给子进程，即使它们在allowlist中。
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;denylist&amp;nbsp;=&amp;nbsp;[&amp;quot;VAULT_*&amp;quot;]
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;#这定义了当监视的模板发生更改时将发送给子进程的信号。只有在进程启动后才会发送信号，并且只有在所有依赖模板至少渲染一次后才会启动进程。
&amp;nbsp;&amp;nbsp;reload_signal&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;#这定义了当Consul&amp;nbsp;Template正常关闭时发送给子进程的信号。应用程序应该开始优雅的清理。
&amp;nbsp;&amp;nbsp;kill_signal&amp;nbsp;=&amp;nbsp;&amp;quot;SIGINT&amp;quot;
&amp;nbsp;&amp;nbsp;#这定义了当Consul&amp;nbsp;Template退出时等待子进程优雅终止所需的时间。默认是30s
&amp;nbsp;&amp;nbsp;kill_timeout&amp;nbsp;=&amp;nbsp;&amp;quot;2s&amp;quot;
}&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #E5E0EC;&quot;&gt;2.4&amp;nbsp;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJLWTVMUlZqSnFyJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU5JTg3JThEJUU2JTk2JUIwJUU1JThBJUEwJUU4JUJEJUJEJUU5JTg1JThEJUU3JUJEJUFFJUU1JTkyJThDJUU2JUE4JUExJUU2JTlEJUJGJTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve; background-color: #E5E0EC;&quot;&gt;Reload Configuration and Templates&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJLWTVMUlZqSnFyJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU5JTg3JThEJUU2JTk2JUIwJUU1JThBJUEwJUU4JUJEJUJEJUU5JTg1JThEJUU3JUJEJUFFJUU1JTkyJThDJUU2JUE4JUExJUU2JTlEJUJGJTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; 虽然运行Consul Template有多种方式，但最常见的模式是将Consul Template作为系统服务运行。当Consul Template首次启动时，它从磁盘读取所有配置文件和模板，并将它们加载到内存中。从那时起，如果不重新加载，对磁盘上文件的更改不会传播到正在运行的进程。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJLWTVMUlZqSnFyJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU5JTg3JThEJUU2JTk2JUIwJUU1JThBJUEwJUU4JUJEJUJEJUU5JTg1JThEJUU3JUJEJUFFJUU1JTkyJThDJUU2JUE4JUExJUU2JTlEJUJGJTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; 如果要更新配置或模板，只需将HUP发送到正在运行的Consul Template进程，Consul Template将从磁盘重新加载所有配置和模板。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJLWTVMUlZqSnFyJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU5JTg3JThEJUU2JTk2JUIwJUU1JThBJUEwJUU4JUJEJUJEJUU5JTg1JThEJUU3JUJEJUFFJUU1JTkyJThDJUU2JUE4JUExJUU2JTlEJUJGJTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; 上面的解释很好理解,就是当你用consul-template加载一个配置文件启动后,类似于Nginx,配置就都加载到内存里面了,这时候如果你改变了比如config.hcl里面的配置,比如监听的key从hello变成了hellotest,或者输出的文件从out1.txt变成了out2.txt,改完之后是不生效的,需要重启consul-template进程或者重新加载。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJLWTVMUlZqSnFyJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU5JTg3JThEJUU2JTk2JUIwJUU1JThBJUEwJUU4JUJEJUJEJUU5JTg1JThEJUU3JUJEJUFFJUU1JTkyJThDJUU2JUE4JUExJUU2JTlEJUJGJTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJSZWxvYWQlMjBDb25maWd1cmF0aW9uJTIwYW5kJTIwVGVtcGxhdGVzJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;# kill -HUP &amp;nbsp; &amp;quot;consul-template的对应pid进程号&amp;quot; &amp;nbsp; #就能重新加载配置文件了&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #E5E0EC;&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve; background-color: #E5E0EC;&quot;&gt;2.5 模版语言&lt;/span&gt;(翻译官网内容,可忽略)&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;#我们将都有哪些参数简单了解一下：&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;Consul Template解析以Go模板格式编写的文件。如果您不熟悉语法，请阅读Go的文档和示例。除了go提供的模板函数外，Consul template还提供以下函数：&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;white-space-collapse: preserve; background-color: #DBEEF3;&quot;&gt;API Functions：&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;API函数与远程API调用交互，与Consul和Vault等外部服务通信&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;caLeaf&amp;nbsp;#查询Consul以获取代表单个服务的leaf&amp;nbsp;certificate
caRoots&amp;nbsp;#查询Consul以获取所有连接的受信任证书颁发机构(CA)根证书
connect&amp;nbsp;#根据可连接服务的运行状况查询Consul
datacenters&amp;nbsp;#查询Consul目录中的所有数据中心
exportedServices&amp;nbsp;#查询Consul以获取给定分区中的所有导出服务
file&amp;nbsp;#读取并输出磁盘上本地文件的内容。如果无法读取文件,则会发生错误。当文件更改时,Consul&amp;nbsp;Template将获取更改并重新渲染模板
key&amp;nbsp;#查询Consul以获取给定键路径处的值。如果密钥不存在，Consul&amp;nbsp;Template将阻止渲染，直到密钥存在。为了避免阻塞，请使用keyOrDefault或keyExists
keyExists&amp;nbsp;#查询Consul以获取给定键路径处的值。如果密钥存在,返回true否则返回false。与key不同如果key不存在，此函数将不会阻塞。这对于控制流量非常有用
keyOrDefault&amp;nbsp;#查询Consul以获取给定键路径处的值。如果密钥不存在，则将使用默认值。与key不同，如果key不存在，此函数将不会阻塞
ls&amp;nbsp;#查询Consul以获取给定关键路径上的所有顶级kv对
safeLs&amp;nbsp;#与ls相同，但如果KV前缀查询返回空白/空数据，则拒绝呈现模板。这对于渲染由consul模板填充的关键任务文件特别有用。
node&amp;nbsp;#在Consul中查询目录中的节点
nodes&amp;nbsp;#查询目录中所有节点的Consul
partitions&amp;nbsp;#查询所有分区的Consul
secret&amp;nbsp;#在给定路径上查询Vault中的密钥
secrets&amp;nbsp;#查询Vault以获取给定路径上的机密列表。并非所有端点都支持列表
pkiCert&amp;nbsp;#使用实例查询PKI证书的存储库。它返回用字符串编码的证书PEM。它返回PKI证书、CA和密钥作为字段：Cert、CA和Key。
service&amp;nbsp;#根据Consul的健康状况查询服务
services&amp;nbsp;#向Consul查询目录中的所有服务
tree&amp;nbsp;#查询Consul给定关键路径上所有kv对
safeTree&amp;nbsp;#与tree相同，但如果KV前缀查询返回空白/空数据，则拒绝渲染模板。这对于渲染由consul模板填充的关键任务文件特别有用。&lt;/pre&gt;&lt;h4&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;white-space-collapse: preserve; background-color: #DBEEF3;&quot;&gt;Scratch：&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;&lt;span data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIyaWQlMjIlM0ElMjJWRXExTjF5WkEwJTIyJTJDJTIycGFyYUlkeCUyMiUzQTAlMkMlMjJzcmMlMjIlM0ElMjJTY3JhdGNoJTIyJTJDJTIyZHN0JTIyJTNBJTIyJUU1JTg4JTkyJUU3JTk3JTk1JTIyJTJDJTIybWV0YWRhdGElMjIlM0ElMjIlMjIlMkMlMjJtZXRhRGF0YSUyMiUzQSU1QiU1RCUyQyUyMnRleHQlMjIlM0ElMjJTY3JhdGNoJTIyJTdEJTVEJTdEJTVE&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;scratchpad在模板上下文中可用，用于存储临时数据或计算。暂存数据不会在模板之间共享，也不会在调用之间缓存。&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;scratch.Key&amp;nbsp;#如果在指定key处的scratchpad中存在数据，则返回一个布尔值。即使该key处的数据为nil，这仍然返回true
scratch.Get&amp;nbsp;#返回scratchpad中指定键处的值。如果数据不存在，则返回nil
scratch.Set&amp;nbsp;#在给定key处保存给定值。如果该键上已经存在数据，则会被覆盖
scratch.SetX&amp;nbsp;#这与Set的行为完全相同，但如果值已存在，则不会覆盖
scratch.MapSet&amp;nbsp;#将值保存在映射中的指定key中。如果该键处已经存在数据，则覆盖该键
scratch.MapSetX&amp;nbsp;#这与MapSet的行为完全相同，但如果值已经存在，则不会覆盖
scratch.MapValues&amp;nbsp;#返回指定映射中所有值的排序列表(按key)&lt;/pre&gt;&lt;h4&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;Helper Functions(辅助函数)：&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;与API函数不同，helper函数不查询远程服务。这些函数对于解析数据、格式化数据、执行数学运算等都很有用&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;base64Decode&amp;nbsp;#接受base64编码的字符串并返回解码结果，如果给定的字符串不是有效的base64字符串，则返回错误
base64Encode&amp;nbsp;#接受一个字符串并返回一个base64编码的字符串
base64URLDecode&amp;nbsp;#接受一个base64编码的url安全字符串并返回解码的结果，如果给定的字符串不是一个有效的base64&amp;nbsp;url安全字符串，则返回一个错误
base64URLEncode&amp;nbsp;#接受字符串并返回base-64编码的url安全字符串
byKey&amp;nbsp;#接受从tree调用返回的对列表，并创建一个映射，根据它们的顶级目录对它们进行分组
byTag&amp;nbsp;#获取服务或服务函数返回的服务列表，并创建按标记分组服务的映射
byMeta&amp;nbsp;#接受service返回的服务列表，并返回按ServiceMeta值对服务进行分组的映射。多个服务元键可以作为逗号分隔的字符串传递
contains&amp;nbsp;#确定指针是否在可迭代元素内
containsAll&amp;nbsp;#如果所有针头都在一个可迭代元素中，则返回true，否则返回false。如果指针列表为空，则返回true
containsAny&amp;nbsp;#如果指针位于可迭代元素中，则返回true，否则返回false。如果指针列表为空，则返回false
containsNone&amp;nbsp;#如果可迭代元素中没有针头则返回true，否则返回false。如果指针列表为空，则返回true
containsNotAll&amp;nbsp;#如果某个指针不在可迭代元素中，则返回true，否则返回false。如果指针列表为空，则返回false
env&amp;nbsp;#读取当前进程可访问的给定环境变量
mustEnv&amp;nbsp;#读取当前进程可访问的给定环境变量。如果变量未定义或为空，则替换将失败并出现错误
envOrDefault&amp;nbsp;#读取当前进程可访问的给定环境变量。如果找到了环境变量，将使用该变量的值。这包括空值。否则，将使用默认值
executeTemplate&amp;nbsp;#执行并返回已定义的模板
explode&amp;nbsp;#从tree或ls调用中获取结果，并将其转换为用于解析/遍历的深嵌套映射
explodeMap&amp;nbsp;#使用与explosion相同的逻辑，获取map的值并将其转换为用于解析/遍历的深嵌套映射
indent&amp;nbsp;#通过在每行前添加N个空格来缩进一段文本
in&amp;nbsp;#确定指针是否在可迭代元素内
loop&amp;nbsp;#接受不同的参数，并根据这些参数改变其行为。如果给loop一个整数，它将返回一个从0开始的程序，循环到但不包括给定的整数
join&amp;nbsp;#将给定的字符串列表视为管道，并将它们连接到提供的字符串上
mergeMap&amp;nbsp;#获取explode和一个explode参数的结果，然后合并这两个映射。参数的源不会被管道映射覆盖
mergeMapWithOverride&amp;nbsp;#获取explode和一个explode参数的结果，然后合并这两个映射。参数的源将被管道映射覆盖
trimSpace&amp;nbsp;#接受提供的输入并修剪所有空格、制表符和换行符
trim&amp;nbsp;#接受提供的输入并裁剪所有前导和尾部的unicode
trimPrefix&amp;nbsp;#接受提供的输入并修剪前导前缀字符串
parseBool&amp;nbsp;#接收给定的字符串并将其解析为布尔值
parseFloat&amp;nbsp;#获取给定的字符串并将其解析为以10为底的float64
parseInt&amp;nbsp;#获取给定的字符串并将其解析为以10为基数的int64
parseJSON&amp;nbsp;#接受给定的输入(通常是key的值)，并将结果解析为JSON
parseUint&amp;nbsp;#获取给定的字符串并将其解析为以10为基数的int64
parseYAML&amp;nbsp;#接受给定的输入(通常是key的值)并将结果解析为YAML
plugin&amp;nbsp;#获取插件名称和可选有效负载，并执行Consul&amp;nbsp;Template插件
regexMatch&amp;nbsp;#将参数视为正则表达式，如果与给定字符串匹配，则返回true，否则返回false
regexReplaceAll&amp;nbsp;#将参数视为正则表达式，并将正则表达式的所有出现替换为给定的字符串
replaceAll&amp;nbsp;#将参数视为字符串，并将给定字符串的所有出现替换为给定字符串
sha256Hex&amp;nbsp;#将参数视为字符串并计算sha256_hex值
md5sum&amp;nbsp;#将字符串输入作为参数，并返回输入的十六进制编码的md5哈希
hmacSHA256Hex&amp;nbsp;#将key和消息作为字符串输入。返回具有给定参数的十六进制编码的HMAC-SHA256哈希
split&amp;nbsp;#在提供的分隔符上拆分给定字符串
splitToMap&amp;nbsp;#在提供的分隔符上拆分给定字符串，并将每个结果项拆分为key和value
timestamp&amp;nbsp;#以字符串(UTC)形式返回当前时间戳。如果没有给出参数，结果是当前RFC3339时间戳
toJSON&amp;nbsp;#从tree或ls调用获取结果并将其转换为JSON对象
toJSONPretty&amp;nbsp;#从tree或ls调用中获取结果，并将其转换为打印精美的JSON对象，缩进两个空格
toUnescapedJSON&amp;nbsp;#从tree或ls调用中获取结果,并将其转换为JSON对象,而不进行HTML转义。当处理数据库连接字符串或包含查询参数的uri时，这个函数会派上用场
toUnescapedJSONPretty&amp;nbsp;#从tree或ls调用中获取结果，并将其转换为打印精美的JSON对象，不使用HTML转义，缩进两个空格
toLower&amp;nbsp;#将参数作为字符串接受，并将其转换为小写
toTitle&amp;nbsp;#将参数作为字符串接受，并将其转换为标题大小写
toTOML&amp;nbsp;#从tree或ls调用中获取结果并将其转换为TOML对象
toUpper&amp;nbsp;#将参数作为字符串接受，并将其转换为大写
toYAML&amp;nbsp;#从tree或ls调用中获取结果，并将其转换为打印精美的YAML对象，缩进两个空格
sockaddr&amp;nbsp;#接受引号转义的模板字符串作为参数，并将其传递给hashicorp/go-sockaddr模板引擎
writeToFile&amp;nbsp;#将内容写入具有权限、用户名（或UID）、组名（或GID）和可选标志的文件，以选择追加模式或添加换行符&lt;/pre&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;Sprig Functions&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; Consul-template提供了在模板中访问Sprig库的功能。要在模板中使用sprrig函数，请在函数名前加上sprig_。Sprig函数的完整列表可以在Sprig函数文档中找到&lt;/p&gt;&lt;h4&gt;&lt;span style=&quot;background-color: #DBEEF3;&quot;&gt;Math Functions(数学函数)&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;以下函数可用于浮点数和整数值&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;add&amp;nbsp;#返回两个值的和
subtract&amp;nbsp;#返回第二个值与第一个值的差值
multiply&amp;nbsp;#返回两个值的乘积
divide&amp;nbsp;#返回第二个值与第一个值的除法
modulo&amp;nbsp;#返回第二个值与第一个值的模
minimum&amp;nbsp;#返回两个值的最小值
maximum&amp;nbsp;#返回两个值的最大值&lt;/pre&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;内容太多了,直接参照官网吧：&lt;/span&gt;&lt;a href=&quot;https://github.com/hashicorp/consul-template/blob/main/docs/templating-language.md&quot; _src=&quot;https://github.com/hashicorp/consul-template/blob/main/docs/templating-language.md&quot; style=&quot;white-space-collapse: preserve;&quot;&gt;https://github.com/hashicorp/consul-template/blob/main/docs/templating-language.md&lt;/a&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;white-space-collapse: preserve; background-color: #E5E0EC;&quot;&gt;2.6 Multi-phase Execution（多阶段执行）&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Consul Template对模板进行n次遍历评估，在每次遍历中累积依赖项。由于嵌套依赖，这是必需的，例如：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{&amp;nbsp;range&amp;nbsp;services&amp;nbsp;}}
{{&amp;nbsp;range&amp;nbsp;service&amp;nbsp;.Name&amp;nbsp;}}
&amp;nbsp;&amp;nbsp;{{&amp;nbsp;.Address&amp;nbsp;}}
{{&amp;nbsp;end&amp;nbsp;}}{{&amp;nbsp;end&amp;nbsp;}}&lt;/pre&gt;&lt;p&gt;在第一次传递期间，Consul Template不知道Consul中的任何服务，因此它必须执行查询。当这些结果返回时，将使用该结果对内循环进行求值，从而可能创建更多查询和监视。下面这种方式仍然会将依赖项添加到监视列表中，但不会计算内部if，从而避免了索引外错误。&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{&amp;nbsp;with&amp;nbsp;service&amp;nbsp;&amp;quot;foo&amp;quot;&amp;nbsp;}}
{{&amp;nbsp;with&amp;nbsp;index&amp;nbsp;.&amp;nbsp;0&amp;nbsp;}}
{{&amp;nbsp;.Node&amp;nbsp;}}{{&amp;nbsp;end&amp;nbsp;}}{{&amp;nbsp;end&amp;nbsp;}}&lt;/pre&gt;&lt;h2&gt;&lt;span style=&quot;background-color: #FDEADA;&quot;&gt;三、nginx结合consul-template实现配置文件动态更新&lt;/span&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #E5E0EC;&quot;&gt;3.1 使用示例例子了解一下&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;# cat test.ctmpl&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{range&amp;nbsp;services}}&amp;nbsp;{{$name&amp;nbsp;:=&amp;nbsp;.Name}}&amp;nbsp;{{$service&amp;nbsp;:=&amp;nbsp;service&amp;nbsp;.Name}}
upstream&amp;nbsp;{{$name}}&amp;nbsp;{
&amp;nbsp;&amp;nbsp;zone&amp;nbsp;upstream-{{$name}}&amp;nbsp;64k;
&amp;nbsp;&amp;nbsp;{{range&amp;nbsp;$service}}server&amp;nbsp;{{.Address}}:{{.Port}}&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;{{else}}server&amp;nbsp;127.0.0.1:65535;&amp;nbsp;#&amp;nbsp;force&amp;nbsp;a&amp;nbsp;502{{end}}
}&amp;nbsp;{{end}}&lt;/pre&gt;&lt;p&gt;# consul-template -template &amp;quot;/tmp/test.ctmpl:/tmp/out.conf&amp;quot; -exec &amp;quot;echo &amp;#39;I GET&amp;#39;&amp;quot;&amp;nbsp; &amp;nbsp;#可见执行成功了&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;I&amp;nbsp;GET&lt;/pre&gt;&lt;p&gt;# cat out.conf&amp;nbsp; #我们看下产生的内容,就能理解上面模版中的意思了&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;consul&amp;nbsp;{
&amp;nbsp;&amp;nbsp;zone&amp;nbsp;upstream-consul&amp;nbsp;64k;
&amp;nbsp;&amp;nbsp;server&amp;nbsp;192.168.1.164:8300&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;server&amp;nbsp;192.168.1.165:8300&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;server&amp;nbsp;192.168.1.166:8300&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;
}&amp;nbsp;&amp;nbsp;&amp;nbsp;
upstream&amp;nbsp;redis01&amp;nbsp;{
&amp;nbsp;&amp;nbsp;zone&amp;nbsp;upstream-redis01&amp;nbsp;64k;
&amp;nbsp;&amp;nbsp;server&amp;nbsp;192.168.1.165:6379&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;
}&amp;nbsp;&amp;nbsp;&amp;nbsp;
upstream&amp;nbsp;redis02&amp;nbsp;{
&amp;nbsp;&amp;nbsp;zone&amp;nbsp;upstream-redis02&amp;nbsp;64k;
&amp;nbsp;&amp;nbsp;server&amp;nbsp;192.168.1.164:6379&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;
}&lt;/pre&gt;&lt;p&gt;#如果你想取消息中其他的内容可以通过获取service的信息或者通过下面的方式看一下,比如:&lt;br/&gt;# cat /tmp/test.ctmpl&amp;nbsp; &amp;nbsp;#注意spew_sdump是记录消息到指定的文件,spew_dump则是将消息打印到屏幕而不会写入到指定的文件&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{range&amp;nbsp;services}}&amp;nbsp;{{$name&amp;nbsp;:=&amp;nbsp;.Name}}&amp;nbsp;{{$service&amp;nbsp;:=&amp;nbsp;service&amp;nbsp;.Name}}
&amp;nbsp;&amp;nbsp;{{range&amp;nbsp;$service}}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{{-&amp;nbsp;spew_sdump&amp;nbsp;$service&amp;nbsp;-}}
&amp;nbsp;&amp;nbsp;{{end}}&amp;nbsp;
&amp;nbsp;&amp;nbsp;{{range&amp;nbsp;$service}}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{{-&amp;nbsp;spew_dump&amp;nbsp;$name&amp;nbsp;.Address&amp;nbsp;.Port&amp;nbsp;-}}
&amp;nbsp;&amp;nbsp;{{end}}
{{end}}&lt;/pre&gt;&lt;p&gt;#然后再稍微复杂一点,你想让nginx的location直接就是service得name,就可以像下面那样：&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# cat /tmp/test2.ctmpl&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;{{$name}}&amp;nbsp;{
&amp;nbsp;&amp;nbsp;zone&amp;nbsp;upstream-{{$name}}&amp;nbsp;64k;
&amp;nbsp;&amp;nbsp;{{range&amp;nbsp;$service}}server&amp;nbsp;{{.Address}}:{{.Port}}&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;{{else}}server&amp;nbsp;127.0.0.1:65535;&amp;nbsp;#&amp;nbsp;force&amp;nbsp;a&amp;nbsp;502{{end}}
}&amp;nbsp;{{end}}

server&amp;nbsp;{
&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80&amp;nbsp;default_server;

&amp;nbsp;&amp;nbsp;location&amp;nbsp;/&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root&amp;nbsp;/usr/local/nginx/html/;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;index&amp;nbsp;index.html;
&amp;nbsp;&amp;nbsp;}

{{range&amp;nbsp;services}}&amp;nbsp;{{$name&amp;nbsp;:=&amp;nbsp;.Name}}
&amp;nbsp;&amp;nbsp;location&amp;nbsp;/{{$name}}&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;http://{{$name}};
&amp;nbsp;&amp;nbsp;}
{{end}}
}&lt;/pre&gt;&lt;p&gt;# cat /tmp/test2.ctmpl&amp;nbsp; &amp;nbsp;#如果你只想针对某个service进行监听,那就是把name定义成固定值了,如下面的示例&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{$name&amp;nbsp;:=&amp;nbsp;&amp;quot;redis01&amp;quot;&amp;nbsp;}}&amp;nbsp;{{$service&amp;nbsp;:=&amp;nbsp;service&amp;nbsp;$name}}
upstream&amp;nbsp;{{$name}}&amp;nbsp;{
&amp;nbsp;&amp;nbsp;zone&amp;nbsp;upstream-{{$name}}&amp;nbsp;64k;
&amp;nbsp;&amp;nbsp;{{range&amp;nbsp;$service}}server&amp;nbsp;{{.Address}}:{{.Port}}&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;{{else}}server&amp;nbsp;127.0.0.1:65535;&amp;nbsp;#&amp;nbsp;force&amp;nbsp;a&amp;nbsp;502{{end}}
}&lt;/pre&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;3.2 k/v获取演示一下&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;先来一个最简单了获取指定key的value：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;# cat /tmp/kv_test1.tpl&amp;nbsp; &amp;nbsp;#获取key是redis/config/users/admin1的值&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{&amp;nbsp;key&amp;nbsp;&amp;quot;redis/config/users/admin1&amp;quot;&amp;nbsp;}}&lt;/pre&gt;&lt;p&gt;# consul-template -template &amp;quot;/tmp/kv_test1.tpl:/tmp/kv_out.txt&amp;quot; -once&lt;/p&gt;&lt;p&gt;# cat /tmp/kv_out.txt&amp;nbsp; &amp;nbsp;#看下输出结果&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;zaphod&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;再来一个稍微复杂的,遍历一个目录下面有哪些key：&lt;/strong&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;# cat /tmp/kv_test1.tpl&amp;nbsp; &amp;nbsp;#注意只会遍历key,哪些目录不会显示&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{range&amp;nbsp;tree&amp;nbsp;&amp;quot;offline/&amp;quot;}}{{.}}
{{end}}&lt;/pre&gt;&lt;p&gt;# consul-template -template &amp;quot;/tmp/kv_test1.tpl:/tmp/kv_out.txt&amp;quot; -once&lt;/p&gt;&lt;p&gt;# cat /tmp/kv_out.txt&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{offline/upstream/grafana/192.168.1.164&amp;nbsp;upstream/grafana/192.168.1.164&amp;nbsp;3000&amp;nbsp;798755&amp;nbsp;798755&amp;nbsp;0&amp;nbsp;0&amp;nbsp;}
{offline/upstream/grafana/192.168.1.165&amp;nbsp;upstream/grafana/192.168.1.165&amp;nbsp;80&amp;nbsp;798759&amp;nbsp;798759&amp;nbsp;0&amp;nbsp;0&amp;nbsp;}
{offline/upstream/grafana1/192.168.1.197&amp;nbsp;upstream/grafana1/192.168.1.197&amp;nbsp;30001&amp;nbsp;811350&amp;nbsp;811350&amp;nbsp;0&amp;nbsp;0&amp;nbsp;}
{offline/upstream/grafana1/192.168.1.198&amp;nbsp;upstream/grafana1/192.168.1.198&amp;nbsp;30002&amp;nbsp;811354&amp;nbsp;811354&amp;nbsp;0&amp;nbsp;0&amp;nbsp;}
{offline/upstream_name/grafana&amp;nbsp;upstream_name/grafana&amp;nbsp;&amp;nbsp;811424&amp;nbsp;811424&amp;nbsp;0&amp;nbsp;0&amp;nbsp;}
{offline/upstream_name/grafana1&amp;nbsp;upstream_name/grafana1&amp;nbsp;&amp;nbsp;811427&amp;nbsp;811427&amp;nbsp;0&amp;nbsp;0&amp;nbsp;}&lt;/pre&gt;&lt;p&gt;# cat /tmp/kv_test1.tpl&amp;nbsp; #假设我们已经到最深的目录了,只需要把里面的key和value渲染出来就可以了&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{range&amp;nbsp;ls&amp;nbsp;&amp;quot;offline/upstream/grafana&amp;quot;}}{{.Key}}:{{.Value}}
{{end}}&lt;/pre&gt;&lt;p&gt;# consul-template -template &amp;quot;/tmp/kv_test1.tpl:/tmp/kv_out.txt&amp;quot; -once&lt;/p&gt;&lt;p&gt;# cat /tmp/kv_out.txt&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;192.168.1.164:3000
192.168.1.165:80&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;现在我们封装一下：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这就是一个比较贴合实际的场景了,比如你有多个集群,当然每个集群都有多个IP,你希望nginx的后端upstream以集群命名,然后集群名自动和后端关联,这样是不是就实现了动态变更的效果了,那么怎么实现一个nginx-template进程监听众多的集群key的变化呢,就是下面的例子了:&lt;/p&gt;&lt;p&gt;#cat&amp;nbsp;/tmp/grafana_upstream.tpl&amp;nbsp; #注意这里使用了printf将变量拼接,不然不太好实现字符串和变量的拼接&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{range&amp;nbsp;tree&amp;nbsp;&amp;quot;offline/upstream_name&amp;quot;}}{{&amp;nbsp;$server_name&amp;nbsp;:=&amp;nbsp;.Key}}&amp;nbsp;{{&amp;nbsp;$dir&amp;nbsp;:=&amp;nbsp;&amp;quot;offline/upstream&amp;quot;&amp;nbsp;}}&amp;nbsp;{{$key&amp;nbsp;:=&amp;nbsp;printf&amp;nbsp;&amp;quot;%s/%s&amp;quot;&amp;nbsp;$dir&amp;nbsp;$server_name&amp;nbsp;}}
upstream&amp;nbsp;{{$server_name}}_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;{{range&amp;nbsp;ls&amp;nbsp;$key}}
&amp;nbsp;&amp;nbsp;&amp;nbsp;server&amp;nbsp;{{.Key}}:{{.Value}}&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;{{else}}server&amp;nbsp;127.0.0.1:65535&amp;nbsp;down;
&amp;nbsp;&amp;nbsp;{{end}}
}{{end}}&lt;/pre&gt;&lt;p&gt;# consul-template -template &amp;quot;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;grafana_upstream&lt;/span&gt;&lt;span style=&quot;text-wrap: wrap;&quot;&gt;.tpl:&lt;/span&gt;/tmp/grafana_upstream.conf&amp;quot; -once&lt;/p&gt;&lt;p&gt;# cat /tmp/grafana_upstream.conf&amp;nbsp; &amp;nbsp;#看下产生的效果&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;upstream&amp;nbsp;grafana_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;server&amp;nbsp;192.168.1.164:3000&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;server&amp;nbsp;192.168.1.165:80&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
}&amp;nbsp;&amp;nbsp;
upstream&amp;nbsp;grafana1_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;server&amp;nbsp;192.168.1.197:30001&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
&amp;nbsp;&amp;nbsp;server&amp;nbsp;192.168.1.198:30002&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;
}&lt;/pre&gt;&lt;p&gt;#这时候你可以把-noce参数去掉,然后去修改grafana和grafana1下面的value,然后你再看看/tmp/grafana_upstream.conf里面的内容也跟着变化了。&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space: pre-wrap; color: #555555; font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px; background-color: #FFFFFF;&quot;&gt;博文来自：&lt;/span&gt;&lt;a href=&quot;https://blog.51niux.com/&quot; _src=&quot;http://www.51niux.com&quot; style=&quot;white-space: pre-wrap; text-decoration-line: none; color: rgb(58, 110, 165); background-color: rgb(254, 254, 254); font-family: &amp;quot;Microsoft YaHei&amp;quot;, &amp;quot;WenQuanYi Micro Hei&amp;quot;, Arial, Verdana, Tahoma, sans-serif; font-size: 15px;&quot;&gt;www.51niux.com&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;3.3 做个关于nginx的试验&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#上面可以看到文件是可以随时变化的,我们知道nginx是每次启动的时候将配置文件的信息加载到了内存里面,所以单纯的配置文件发生变更,其实nginx是不生效的,我们来验证一下。&lt;/p&gt;&lt;p&gt;首先创建两个grafan域名的配置文件.比如grafana.test.com引用grafana_pool做后端,同理grafana1.test.com使用grafana1_pool做后端。&lt;/p&gt;&lt;p&gt;然后启动一下template进程监控一下指定的k/v变化：&lt;/p&gt;&lt;p&gt;# consul-template --consul-addr 192.168.1.164:8500 --template &amp;quot;/tmp/kv_test1.tpl:/opt/soft/nginx/conf.d/upstream.conf&amp;quot;&amp;nbsp; --log-level=info&amp;nbsp; #加上&lt;span style=&quot;text-wrap: wrap;&quot;&gt;log-level=info&lt;/span&gt;有变化就会输出到屏幕,如下图:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2024/12/202412021733134802914467.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;#你刷新web页面会发现虽然配置文件变化了,但是nginx域名指向的后端是没变化的,所以启动命令还需要变化一下：&lt;/p&gt;&lt;p&gt;# consul-template --consul-addr 192.168.1.164:8500 --template &amp;quot;/tmp/kv_test1.tpl:/opt/soft/nginx/conf.d/upstream.conf:/opt/soft/nginx/sbin/nginx -s reload&amp;quot;&amp;nbsp; --log-level=info&amp;nbsp; &amp;nbsp;#就是让文件发生变化直接nginx重新加载配置文件,注意啊,这里又有一个新的问题,如果你&lt;span style=&quot;text-wrap: wrap;&quot;&gt;upstream.conf&lt;/span&gt;文件修改的有问题,也就是nginx -t会报错,这时候你直接reload的话nginx报错就会直接退出整个template进程了。你说那么我修改成reload || true这种形式不就好了,不管命令怎么报错我template进程都不会退出,但是虽然进程不退出了,但是你nginx没办法reload就导致了你的修改一直不生效。所以还不如有问题template直接退出,然后对template进程做监控,一旦template进程退出了就报警出来然后迅速排查问题。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;如果你想多个upstream后端配置文件启动得话：&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;# cat /tmp/grafana.tpl&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{&amp;nbsp;$server_name&amp;nbsp;:=&amp;nbsp;&amp;quot;grafana&amp;quot;}}&amp;nbsp;{{&amp;nbsp;$key&amp;nbsp;:=&amp;nbsp;&amp;quot;offline/upstream/grafana&amp;quot;&amp;nbsp;}}
upstream&amp;nbsp;{{$server_name}}_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;{{range&amp;nbsp;ls&amp;nbsp;$key}}
&amp;nbsp;&amp;nbsp;server&amp;nbsp;{{.Key}}:{{.Value}}&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;{{else}}server&amp;nbsp;127.0.0.1:65535&amp;nbsp;down;
&amp;nbsp;&amp;nbsp;{{end}}
}&lt;/pre&gt;&lt;p&gt;# cat /tmp/grafana1.tpl&amp;nbsp;&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;{{&amp;nbsp;$server_name&amp;nbsp;:=&amp;nbsp;&amp;quot;grafana1&amp;quot;}}&amp;nbsp;{{&amp;nbsp;$key&amp;nbsp;:=&amp;nbsp;&amp;quot;offline/upstream/grafana1&amp;quot;&amp;nbsp;}}
upstream&amp;nbsp;{{$server_name}}_pool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;{{range&amp;nbsp;ls&amp;nbsp;$key}}
&amp;nbsp;&amp;nbsp;server&amp;nbsp;{{.Key}}:{{.Value}}&amp;nbsp;max_fails=3&amp;nbsp;fail_timeout=60&amp;nbsp;weight=1;{{else}}server&amp;nbsp;127.0.0.1:65535&amp;nbsp;down;
&amp;nbsp;&amp;nbsp;{{end}}
}&lt;/pre&gt;&lt;p&gt;# consul-template --consul-addr 192.168.1.164:8500 --template &amp;quot;/tmp/grafana.tpl:/opt/soft/nginx/conf.d/grafana_upstream.conf:/opt/soft/nginx/sbin/nginx -s reload&amp;quot; --template &amp;quot;/tmp/grafana1.tpl:/opt/soft/nginx/conf.d/grafana1_upstream.conf:/opt/soft/nginx/sbin/nginx -s reload&amp;quot; --log-level=info&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2024/12/202412021733137335839433.png&quot; alt=&quot;image.png&quot; width=&quot;1025&quot; height=&quot;133&quot; style=&quot;width: 1025px; height: 133px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;#上图为修改了value值之后的一个进程输出&lt;/p&gt;&lt;h3 style=&quot;text-wrap: wrap;&quot;&gt;&lt;span style=&quot;background-color: #CCC1D9;&quot;&gt;3.4 使用配置文件启动template进程&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;#紧接上文,上面我们如果想生成多个文件都用的cli命令行传参的方式,我们换种方式把要监听并创建的配置文件都生成在配置文件中,这样我们的进程命令就不会太长&lt;/p&gt;&lt;p&gt;# cat grafana_upstream.hcl&amp;nbsp; #为了方便查看输出,我命令就是直接输出,可以看到配置文件中可以指定多个template的,syslog表示日志打到message日志没必要加&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-basic&quot;&gt;wait&amp;nbsp;{
&amp;nbsp;&amp;nbsp;min&amp;nbsp;=&amp;nbsp;&amp;quot;3s&amp;quot;
&amp;nbsp;&amp;nbsp;max&amp;nbsp;=&amp;nbsp;&amp;quot;9s&amp;quot;
}

syslog&amp;nbsp;{
&amp;nbsp;&amp;nbsp;enabled&amp;nbsp;=&amp;nbsp;true
&amp;nbsp;&amp;nbsp;facility&amp;nbsp;=&amp;nbsp;&amp;quot;LOCAL5&amp;quot;
}

log_level&amp;nbsp;=&amp;nbsp;&amp;quot;info&amp;quot;

consul&amp;nbsp;{
&amp;nbsp;&amp;nbsp;address&amp;nbsp;=&amp;nbsp;&amp;quot;192.168.1.164:8500&amp;quot;
}

template&amp;nbsp;{
&amp;nbsp;&amp;nbsp;source&amp;nbsp;=&amp;nbsp;&amp;quot;/tmp/grafana.tpl&amp;quot;
&amp;nbsp;&amp;nbsp;destination&amp;nbsp;=&amp;nbsp;&amp;quot;/tmp/grafana_upstream.conf&amp;quot;
&amp;nbsp;&amp;nbsp;command&amp;nbsp;=&amp;nbsp;&amp;quot;echo&amp;nbsp;aaa&amp;quot;
}
template&amp;nbsp;{
&amp;nbsp;&amp;nbsp;source&amp;nbsp;=&amp;nbsp;&amp;quot;/tmp/grafana1.tpl&amp;quot;
&amp;nbsp;&amp;nbsp;destination&amp;nbsp;=&amp;nbsp;&amp;quot;/tmp/grafana_upstream1.conf&amp;quot;
&amp;nbsp;&amp;nbsp;command&amp;nbsp;=&amp;nbsp;&amp;quot;echo&amp;nbsp;bbb&amp;quot;
}&lt;/pre&gt;&lt;p&gt;# consul-template -config=/tmp/grafana_upstream.hcl&amp;nbsp; #可以尝试修改下这两个监听的key查看一下,下面是测试输出&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://blog.51niux.com/zb_users/upload/2024/12/202412301735530662178923.png&quot; alt=&quot;image.png&quot; width=&quot;962&quot; height=&quot;224&quot; style=&quot;width: 962px; height: 224px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Fri, 01 Nov 2024 18:46:28 +0800</pubDate></item></channel></rss>