<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>程序员的奋斗</title><link>http://www.busfly.net/</link><description>软件开发学习与分享</description><item><title>Social-Analyzer 一个强大的开源社交媒体分析工具</title><link>http://www.busfly.net/?id=28</link><description>&lt;h1 id=&quot;h1--social-analyzer-&quot;&gt;&lt;a name=&quot;开源社交媒体分析工具推荐：Social-Analyzer 全面指南&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;开源社交媒体分析工具推荐：Social-Analyzer 全面指南&lt;/h1&gt;&lt;p&gt;在网络安全、隐私保护或信息调研场景中，一款高效的社交媒体分析工具能帮我们快速定位关键信息。今天要分享的 &lt;strong&gt;Social-Analyzer&lt;/strong&gt;，就是这样一款功能强大的开源工具，无论是个人自查在线足迹，还是专业领域的合规分析，它都能提供有力支持。&lt;/p&gt;
&lt;h2 id=&quot;h2--social-analyzer-&quot;&gt;&lt;a name=&quot;一、Social-Analyzer 是什么？它有哪些核心能力？&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;一、Social-Analyzer 是什么？它有哪些核心能力？&lt;/h2&gt;&lt;p&gt;Social-Analyzer 是基于 Python 开发的开源社交媒体分析项目，整合了 API、命令行（CLI）和网页应用三种使用形式，核心目标是帮助用户在多平台上精准查找、分析个人资料，降低信息检索的复杂度。其功能亮点主要集中在以下几个方面：&lt;/p&gt;
&lt;h3 id=&quot;h3-1-&quot;&gt;&lt;a name=&quot;1. 多维度分析，覆盖场景广泛&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;1. 多维度分析，覆盖场景广泛&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;灵活的搜索方式&lt;/strong&gt;：支持字符串分析、名称排列组合查询，还能深挖名称起源、相似性及常见词汇，避免遗漏关联信息；&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;多技术协同查找&lt;/strong&gt;：结合 HTTPS 库、Webdriver 等技术，可同时搜索多个人资料并进行关联分析，提升信息整合效率；&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;多层级检测机制&lt;/strong&gt;：包含 OCR 识别、普通检测、高级验证、特殊场景适配等层级，有效降低误报率，确保结果准确性。&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;
&lt;h3 id=&quot;h3-2-&quot;&gt;&lt;a name=&quot;2. 人性化设计，适配不同需求&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;2. 人性化设计，适配不同需求&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据可视化呈现&lt;/strong&gt;：通过 Ixora 工具将元数据、分析模式转化为直观图表，非技术用户也能轻松理解；&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;高度自定义配置&lt;/strong&gt;：支持自定义搜索引擎（如 Google API、DuckDuckGo API）、用户代理、代理服务器、超时时间等，适配复杂网络环境；&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;多样化筛选功能&lt;/strong&gt;：可按 Alexa 排名、国家 / 地区筛选目标平台，也能按类型（如成人、音乐类网站）分类统计，精准定位分析范围。&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;
&lt;h3 id=&quot;h3-3-&quot;&gt;&lt;a name=&quot;3. 实用评分机制与合规场景&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;3. 实用评分机制与合规场景&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;0-100 分精准评分&lt;/strong&gt;：基于不同检测技术输出 “无可能 - 可能 - 确定” 的评分结果，辅助用户判断信息可信度；&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;合法合规应用&lt;/strong&gt;：可用于网络安全领域（预防网络欺凌、跟踪）、法律执行调查、个人隐私自查，需严格遵守目标平台协议及当地法规。&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;
&lt;h2 id=&quot;h2--social-analyzer-&quot;&gt;&lt;a name=&quot;二、如何获取 Social-Analyzer？官方地址与安装指南&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;二、如何获取 Social-Analyzer？官方地址与安装指南&lt;/h2&gt;&lt;p&gt;作为开源项目，Social-Analyzer 的核心资源托管在 GitHub 上，获取和安装过程简单易懂，以下是详细步骤：&lt;/p&gt;
&lt;h3 id=&quot;h3-1-&quot;&gt;&lt;a name=&quot;1. 官方地址（必存）&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;1. 官方地址（必存）&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GitHub 主仓库&lt;/strong&gt;：&lt;a href=&quot;https://github.com/qeeqbox/social-analyzer&quot;&gt;https&lt;/a&gt;&lt;a href=&quot;https://github.com/qeeqbox/social-analyzer&quot;&gt;://gi&lt;/a&gt;&lt;a href=&quot;https://github.com/qeeqbox/social-analyzer&quot;&gt;thub.&lt;/a&gt;&lt;a href=&quot;https://github.com/qeeqbox/social-analyzer&quot;&gt;com/q&lt;/a&gt;&lt;a href=&quot;https://github.com/qeeqbox/social-analyzer&quot;&gt;eeqbo&lt;/a&gt;&lt;a href=&quot;https://github.com/qeeqbox/social-analyzer&quot;&gt;x/soc&lt;/a&gt;&lt;a href=&quot;https://github.com/qeeqbox/social-analyzer&quot;&gt;ial-a&lt;/a&gt;&lt;a href=&quot;https://github.com/qeeqbox/social-analyzer&quot;&gt;nalyz&lt;/a&gt;&lt;a href=&quot;https://github.com/qeeqbox/social-analyzer&quot;&gt;er&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这里是项目的 “官方大本营”，包含最新源代码、详细文档、更新日志及问题反馈渠道，建议优先从该地址获取资源，避免第三方链接的安全风险。&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;
&lt;h3 id=&quot;h3-2-&quot;&gt;&lt;a name=&quot;2. 安装前的前提准备&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;2. 安装前的前提准备&lt;/h3&gt;&lt;p&gt;在安装工具前，需确保本地环境满足以下条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 版本&lt;/strong&gt;：需安装 Python 3.6 及以上（推荐 3.8+，兼容性更优），并验证 &lt;code&gt;python&lt;/code&gt; 和 &lt;code&gt;pip&lt;/code&gt; 命令可正常运行（终端输入 &lt;code&gt;python --version&lt;/code&gt; 和 &lt;code&gt;pip --version&lt;/code&gt; 查看）；&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;可选依赖&lt;/strong&gt;：若需使用模拟网页操作功能（如 Webdriver 相关检测），需提前安装对应浏览器驱动（如 ChromeDriver），并配置系统环境变量。&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;
&lt;h3 id=&quot;h3-3-&quot;&gt;&lt;a name=&quot;3. 两种安装方式，按需选择&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;3. 两种安装方式，按需选择&lt;/h3&gt;&lt;h4 id=&quot;h4--pip-&quot;&gt;&lt;a name=&quot;方式一：pip 直接安装（推荐新手，快速便捷）&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;方式一：pip 直接安装（推荐新手，快速便捷）&lt;/h4&gt;&lt;p&gt;打开终端（Windows 用 “命令提示符”，Mac/Linux 用 “终端”），输入以下命令，工具会自动从 PyPI 仓库下载最新版本并处理依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install social-analyzer&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&quot;h4--github-&quot;&gt;&lt;a name=&quot;方式二：GitHub 源码安装（适合需要最新开发版的用户）&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;方式二：GitHub 源码安装（适合需要最新开发版的用户）&lt;/h4&gt;&lt;p&gt;若需体验未正式发布的功能，可通过源码安装：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;克隆仓库到本地（需先安装 Git）：&lt;/li&gt;&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/qeeqbox/social-analyzer.git

cd social-analyzer  # 进入项目目录&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;安装依赖并完成配置：&lt;/li&gt;&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;pip install -r requirements.txt  # 安装所有依赖包&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;h3-4-&quot;&gt;&lt;a name=&quot;4. 验证安装与基础使用&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;4. 验证安装与基础使用&lt;/h3&gt;&lt;h4 id=&quot;h4--1-&quot;&gt;&lt;a name=&quot;（1）验证是否安装成功&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;（1）验证是否安装成功&lt;/h4&gt;&lt;p&gt;终端输入以下命令，若显示参数说明列表，代表安装完成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;social-analyzer --help&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&quot;h4--2-&quot;&gt;&lt;a name=&quot;（2）快速上手示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;（2）快速上手示例&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;命令行查询&lt;/strong&gt;：搜索用户名为 “test” 的资料，并显示元数据（格式化输出）：&lt;/li&gt;&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;social-analyzer --username test --metadata --pretty&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;网页界面使用&lt;/strong&gt;：部分版本支持启动本地网页服务（具体配置可参考 GitHub 仓库的 &lt;code&gt;README.md&lt;/code&gt;），适合偏好图形化操作的用户。&lt;/li&gt;&lt;/ul&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a name=&quot;三、使用注意事项&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;三、使用注意事项&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;合规优先&lt;/strong&gt;：工具仅可用于合法场景（如个人隐私自查、企业合规调查），禁止用于恶意跟踪、信息骚扰等违规行为，需遵守目标平台的用户协议及《网络安全法》等法律法规；&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;依赖更新&lt;/strong&gt;：项目依赖可能随版本迭代变化，若安装或使用时出现报错，建议查看 GitHub 仓库的最新 &lt;code&gt;README.md&lt;/code&gt;，确认是否有新增依赖或配置调整；&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;备份数据&lt;/strong&gt;：若需重置浏览器或调整系统配置（如代理、防火墙），建议提前备份重要书签或数据，避免意外丢失。&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;Social-Analyzer 作为一款开源工具，不仅功能全面，还持续更新优化，适合有社交媒体分析需求的个人或团队尝试。如果在使用中发现问题，也可以通过 GitHub 仓库的 Issues 板块向开发者反馈，共同完善工具生态。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;（注：文档部分内容可能由 AI 生成）&lt;/p&gt;
&lt;/blockquote&gt;
</description><pubDate>Sun, 02 Nov 2025 20:41:28 +0800</pubDate></item><item><title>docker配置国内镜像2024.11.18更新 亲测有效</title><link>http://www.busfly.net/?id=27</link><description>&lt;p&gt;根据最新的搜索结果，以下是一些国内可用的Docker镜像源配置，你可以按照以下步骤进行配置：&lt;/p&gt;
&lt;p&gt;转自： &lt;a href=&quot;https://zhuanlan.zhihu.com/p/7527374509&quot;&gt;https://zhuanlan.zhihu.com/p/7527374509&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;创建或修改Docker配置文件：&lt;br&gt;在Docker desktop 设置里可以直接配置 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;registry-mirrors&amp;quot;: [
     &amp;quot;https://docker.1ms.run&amp;quot;,
     &amp;quot;https://doublezonline.cloud&amp;quot;,
     &amp;quot;https://dislabaiot.xyz&amp;quot;,
     &amp;quot;https://hub.rat.dev&amp;quot;,
     &amp;quot;https://docker.1panel.live&amp;quot;,
     &amp;quot;https://docker.zhai.cm&amp;quot;,
     &amp;quot;https://a.ussh.net&amp;quot;,

     &amp;quot;https://docker.fxxk.dedyn.io&amp;quot;,
     &amp;quot;https://dockerpull.org&amp;quot;,
     &amp;quot;https://docker.unsee.tech&amp;quot;,
     &amp;quot;https://docker.nastool.de&amp;quot;,
     &amp;quot;https://docker.5z5f.com&amp;quot;,
     &amp;quot;https://docker.udayun.com&amp;quot;,
     &amp;quot;https://hub.geekery.cn&amp;quot;
 ]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在Linux系统中，你需要修改或创建/etc/docker/daemon.json文件。如果文件不存在，你可以使用以下命令创建它：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudomkdir-p/etc/docker sudotee/etc/docker/daemon.json&amp;lt;&amp;lt;-&amp;#39;EOF&amp;#39; { &amp;quot;registry-mirrors&amp;quot;:[ &amp;quot;https://docker.1ms.run&amp;quot;, &amp;quot;https://doublezonline.cloud&amp;quot;, &amp;quot;https://dislabaiot.xyz&amp;quot;, &amp;quot;https://docker.fxxk.dedyn.io&amp;quot;, &amp;quot;https://dockerpull.org&amp;quot;, &amp;quot;https://docker.unsee.tech&amp;quot;, &amp;quot;https://hub.rat.dev&amp;quot;, &amp;quot;https://docker.1panel.live&amp;quot;, &amp;quot;https://docker.nastool.de&amp;quot;, &amp;quot;https://docker.zhai.cm&amp;quot;, &amp;quot;https://docker.5z5f.com&amp;quot;, &amp;quot;https://a.ussh.net&amp;quot;, &amp;quot;https://docker.udayun.com&amp;quot;, &amp;quot;https://hub.geekery.cn&amp;quot; ] } EOF&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;重启Docker服务： 修改配置文件后，需要重启Docker服务以使配置生效：&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;sudosystemctldaemon-reload sudosystemctlrestartdocker&lt;/code&gt;&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;验证配置是否成功： 使用以下命令检查Docker信息，确认镜像源是否已经更改：&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;docker info&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;以上步骤可以帮助你配置国内镜像源，以加速Docker镜像的拉取速度。请根据实际情况选择可用的镜像源进行配置。这些镜像源均来自最新的搜索结果，确保了时效性。&lt;/p&gt;
&lt;p&gt;以下是网友补充的镜像地址，不确定是否可用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;docker.rainbond.cc&amp;quot;,
&amp;quot;mirror.ccs.tencentyun.com&amp;quot;,
&amp;quot;registry.docker-cn.com&amp;quot;,
&amp;quot;docker.mirrors.ustc.edu.cn&amp;quot;,
&amp;quot;hub-mirror.c.163.com&amp;quot;&lt;/code&gt;&lt;/pre&gt;</description><pubDate>Fri, 07 Feb 2025 17:19:31 +0800</pubDate></item><item><title>国内有特色的低代码快速开发平台产品有哪些？</title><link>http://www.busfly.net/?id=26</link><description>&lt;p&gt;随着信息技术的飞速发展，传统的软件开发模式已经逐渐明显存在不足之处，例如开发周期长、成本高昂、难以执行业务变化等。在此背景下，低代码开发逐渐出现了崭露头角，成为解决传统软件开发问题的一种新模式。&lt;strong&gt;本文将深入探讨什么是低代码、低代码平台的特点、低代码的局限性及挑战，以及一些成功的低代码平台案例，旨在帮助读者全面了解低代码开发。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;低代码开发平台推荐&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;低代码开发平台推荐&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1、宜搭&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.zhimg.com/50/v2-2950198e7aa01840b2f8c3d15dd97bfa_720w.jpg?source=2c26e567&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;宜搭大家都比较熟悉了，是钉钉开发的低代码开发平台，于2020年12月28日宣布推出。用户可以在可视化界面上以拖拉拽的方式编辑和配置页面，表单和流程，并一键发布到PC和手机端。通过该平台上所开发的应用，具备”云钉原生”特性，即能打通阿里云和钉钉的底层能力和技术，并可将企业原有IT系统与钉钉连接，降低企业数字化的成本与门槛。&lt;/p&gt;
&lt;p&gt;平台提供了超过100项图形化的开发组件，集合应用页面编排、业务模型编排、业务流程编排、服务编排及数据展现与分析等五大核心能力，用户”拖拉拽”开发组件，即可完成应用的搭建，不懂代码的业务人员，如HR、财务也可成为开发者。&lt;/p&gt;
&lt;p&gt;体验地址：&lt;a href=&quot;https://www.aliwork.com/&quot;&gt;https://www.aliwork.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2、微搭&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;微搭是腾讯云开发的低代码开发平台，团队将其视作为云开发的底层支撑，提供高度开放的开发环境，将繁琐的底层架构和基础设施抽象化为图形界面，通过行业化模板、拖放式组件和可视化配置快速构建多端应用（小程序、H5 应用、Web 应用等），免去了代码编写工作，让用户能够完全专注于业务场景。&lt;/p&gt;
&lt;p&gt;提供了应用开发的一站式低代码开发服务，从底层能力迭代至行业级方案，云原生全链路支撑为您的应用保驾护航，让用户能够完全专注于业务场景，小白也可以极速搭建出成熟、专业的应用。特点是：行业级解决能力、企业级应用管理、可视化拖拽开发、实现一页多端、云原生底层支持、应用持续生命力等。&lt;/p&gt;
&lt;p&gt;体验地址：&lt;a href=&quot;https://cloud.tencent.com/product/weda&quot;&gt;https://cloud.tencent.com/product/weda&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3、&lt;/strong&gt;Finclip Studio &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://picx.zhimg.com/50/v2-ae3aace5d999664907f078dfa8e31fc3_720w.jpg?source=2c26e567&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;与市面上常见的低代码平台不同，FinClip 提供的低代码工具更面向于研发人员使用。在Finclip Studio 上主要可以通过拖拉拽方式绘制小程序，平台拥有非常直观和灵活的可视化建模工具，如列表、动作栏、表单、轮播等，可以满足大多数业务需求，从而减少开发人员的工作量，避免大量的编码工作和测试工作。开发人员可以自行根据需要对各种组件进行配置，创建自定义表单、报告和小程序应用都更加轻便。&lt;/p&gt;
&lt;p&gt;传统的开发流程：数据库搭建 → 后端代码 → 前端代码 → 部署构建&lt;br&gt;FinClip Studio 上：数据库搭建 → 界面化构建（个性化需求可以在组件内添加代码）&lt;/p&gt;
&lt;p&gt;在此推荐它的很大原因在于，低代码功能只是FinClip Studio其中一个附属功能，真正强大的点在于，通过平台搭建出来的小程序，不仅仅可以在微信中运行，也能在你自己的app中运行。（只需要在自己的app中内嵌一个SDK即可）。&lt;/p&gt;
&lt;p&gt;体验地址：&lt;a href=&quot;https://www.finclip.com/downloads&quot;&gt;https://www.finclip.com/downloads&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;开源低代码开发框架推荐&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;开源低代码开发框架推荐&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;除了代码开发平台也给各位程序员推荐几款开源的低代码开发框架，选取在Github上口碑不错的推荐。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1、 Appsmith&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.zhimg.com/50/v2-01d090672ad86a5fe3eee280b9b755c7_720w.jpg?source=2c26e567&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;AppSmith 是印度一家创业公司开发的低代码开发工具，它的原型是另一家名叫 Retool 的美国的创业公司。AppSmith 本身是开源软件，但是它提供一个云端版本。主要用于构建管理面板、内部工具和仪表板等，允许拖放 UI 组件来构建页面，通过连接到任何 API、数据库或 GraphQL 源，并使用 JavaScript 语言编写逻辑，可以在短时间内创建内部应用程序。&lt;/p&gt;
&lt;p&gt;Github（⭐️ 28.9k）：&lt;a href=&quot;https://github.com/appsmithorg/appsmith&quot;&gt;https://github.com/appsmithorg/appsmith&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2、Amis&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pica.zhimg.com/50/v2-ba4c870651d5bc4aff5bc0c162901a23_720w.jpg?source=2c26e567&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Amis 是百度开源的一个前端低代码框架，通过 JSON 配置就能生成各种后台页面，内置 100+ 种 UI 组件，能够满足各种页面组件展现的需求，极大减少开发成本，甚至可以不需要了解前端。&lt;/p&gt;
&lt;p&gt;Github（⭐️ 14.7k）：Github地址： &lt;a href=&quot;https://github.com/baidu/amis&quot;&gt;https://github.com/baidu/amis&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3、mometa&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://picx.zhimg.com/50/v2-a278c681533b5be239d486a39fe0fb37_720w.jpg?source=2c26e567&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;mometa 不是传统主流的低代码平台（如 amis/云凤蝶），mometa 是面向研发的、代码可视设计编辑平台；它更像是dreamweaver、gui 可视编辑 之于 程序员。&lt;/p&gt;
&lt;p&gt;它用于解决的问题有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对低代码平台不形成依赖，二次开发可以无缝进入代码开发模式&lt;/li&gt;&lt;li&gt;同时支持所见即所得的可视编辑，用于提效，提升开发体验&lt;/li&gt;&lt;li&gt;提供物料生态，可自定义物料，提升物料使用体验，提升复用率&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;mometa 定位更多是 基于程序员本地开发的模式，新增了可视化编码的能力（修改的也是本地的代码文件本身）;它更像是辅助编码工具，而不是 No-Code 的平台方案；目前的实现依赖 webpack dev 开发模式，后续可能会考虑兼容。&lt;/p&gt;
&lt;p&gt;Github（⭐️ 3.7k）：&lt;a href=&quot;https://github.com/imcuttle/mometa&quot;&gt;https://github.com/imcuttle/mometa&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;原文： 国内有特色的低代码快速开发平台产品有哪些？ - Xxx1m的回答 - 知乎&lt;br&gt;&lt;a href=&quot;https://www.zhihu.com/question/402663485/answer/3207073689&quot;&gt;https://www.zhihu.com/question/402663485/answer/3207073689&lt;/a&gt;&lt;/p&gt;
</description><pubDate>Sun, 12 May 2024 11:58:02 +0800</pubDate></item><item><title>SpringBoot 3.0 新特性，内置声明式 HTTP 客户端</title><link>http://www.busfly.net/?id=25</link><description>&lt;h2 id=&quot;h2--strong-http-interface-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;http interface&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;http interface&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;从 Spring 6 和 Spring Boot 3 开始，Spring 框架支持将远程 HTTP 服务代理成带有特定注解的 Java http interface。类似的库，如 OpenFeign 和 Retrofit 仍然可以使用，但 http interface 为 Spring 框架添加内置支持。&lt;/p&gt;
&lt;h2 id=&quot;h2--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;什么是声明式客户端&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;什么是声明式客户端&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;声明式 http 客户端主旨是使得编写 java http 客户端更容易。为了贯彻这个理念，采用了通过处理注解来&lt;strong&gt;自动生成请求的方式&lt;/strong&gt;（官方称呼为声明式、模板化）。通过声明式 http 客户端实现我们就可以在 java 中像调用一个本地方法一样完成一次 http 请求，大大减少了编码成本，同时提高了代码可读性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;举个例子，如果想调用 /tenants 的接口，只需要定义如下的接口类即可&lt;/li&gt;&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface TenantClient {

  @GetExchange(&amp;quot;/tenants&amp;quot;)
  Flux&amp;lt;User&amp;gt; getAll();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spring 会在运行时提供接口的调用的具体实现，如上请求我们可以如 Java 方法一样调用&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Autowired 
TenantClient tenantClient;
tenantClient.getAll().subscribe(

);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;h2--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;测试使用&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;测试使用&lt;/strong&gt;&lt;/h2&gt;&lt;h3 id=&quot;h3--strong-1-maven-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;1. maven 依赖&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;1. maven 依赖&lt;/strong&gt;&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;!-- For webclient support --&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-boot-starter-webflux&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;如下图： 目前官方只提供了非阻塞 webclient 的 http interface 实现，所以依赖中我们需要添加 webflux&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://pic3.zhimg.com/v2-eb32d30ed852f614022a3d3feebb5d16_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3--strong-2-http-interface-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;2. 创建 Http interface 类型&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;2. 创建 Http interface 类型&lt;/strong&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;需要再接口类上添加 &lt;code&gt;&lt;a href=&quot;https://github.com/HttpExchange&quot; title=&quot;&amp;#64;HttpExchange&quot; class=&quot;at-link&quot;&gt;@HttpExchange&lt;/a&gt;&lt;/code&gt; 声明此类事 http interface 端点&lt;/li&gt;&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@HttpExchange
public interface DemoApi {

    @GetExchange(&amp;quot;/admin/tenant/list&amp;quot;)
    String list();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;方法上支持如下注解&lt;/li&gt;&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@GetExchange:  for HTTP GET requests.
@PostExchange:  for HTTP POST requests.
@PutExchange: for HTTP PUT requests.
@DeleteExchange: for HTTP DELETE requests.
@PatchExchange:  for HTTP PATCH requests.&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;方法参数支持的注解&lt;/li&gt;&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@PathVariable: 占位符参数.
@RequestBody: 请求body.
@RequestParam: 请求参数.
@RequestHeader: 请求头.
@RequestPart: 表单请求.
@CookieValue: 请求cookie.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;h3--strong-2-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;2. 注入声明式客户端&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;2. 注入声明式客户端&lt;/strong&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;通过给 HttpServiceProxyFactory 注入携带目标接口 baseUrl 的的 webclient，实现 webclient 和 http interface 的关联&lt;/li&gt;&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;   @Bean
    DemoApi demoApi() {
        WebClient client = WebClient.builder().baseUrl(&amp;quot;http://pigx.pigx.vip/&amp;quot;).build();
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
        return factory.createClient(DemoApi.class);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;h3--strong-3-http-interface-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;3. 单元测试调用 http interface&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;3. 单元测试调用 http interface&lt;/strong&gt;&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@SpringBootTest
class DemoApplicationTests {
 @Autowired
 private DemoApi demoApi;

 @Test
 void testDemoApi() {
  demoApi.list();
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;“ &lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//github.com/pig-mesh/pig&quot;&gt;基于Spring Boot 2.7、 Spring Cloud 2021 &amp;amp; Alibaba、 SAS OAuth2 一个可支持企业各业务系统或产品快速开发实现的开源微服务应用开发平台&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原文： &lt;a href=&quot;https://zhuanlan.zhihu.com/p/588278414&quot;&gt;https://zhuanlan.zhihu.com/p/588278414&lt;/a&gt;&lt;/p&gt;
</description><pubDate>Sun, 12 May 2024 11:34:07 +0800</pubDate></item><item><title>基于 magic-api、Amis、h5-Dooring、mica 搭建自己的低代码平台</title><link>http://www.busfly.net/?id=24</link><description>&lt;h2 id=&quot;h2--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;一、前言&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;一、前言&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;2021 开年“低代码”成了热门话题，各大云厂商都在加码。作为普通企业的我们是否有也可以深度定制一套自己的“低代码”平台呢？&lt;/p&gt;
&lt;h2 id=&quot;h2--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;二、云厂商的低代码平台&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;二、云厂商的低代码平台&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;阿里推出了易搭，通过简单的拖拽、配置，即可完成业务应用的搭建。旨在为广大中小企业提供一套低成本的企业应用搭建解决方案。应用无缝植入钉钉企业工作台，随时随地、高效协同。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic3.zhimg.com/v2-588e071a43a462c752ac165f3eaefb2e_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;腾讯则是推出了微搭，通过行业化模板、拖放式组件和可视化配置快速构建多端应用（小程序、H5 应用、Web 应用等），打通了小程序、云函数。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic3.zhimg.com/v2-88ef9cf11f6d64f918bfc3c8eb024276_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;h2--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;三、搭建我们自己的低代码平台？！&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;三、搭建我们自己的低代码平台？！&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;回到前言中的问题，我们是否可以基于开源项目来快速搭建我们自己的低代码平台呢？答案是肯定的，目前已经有很多非常不错的开源项目，apijson、dataway 还有后面我要重点介绍的 magic-api 都是非常不错的低代码开源项目。下面大家请跟着我一起来看看今天我要推荐的三个低代码开源项目：百度 amis、h5-Dooring 和 magic-api。&lt;/p&gt;
&lt;h2 id=&quot;h2--strong-3-1-amis-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;3.1百度 amis（前端）&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;3.1百度 amis（前端）&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;百度 amis 是一套前端低代码框架，通过 JSON 配置就能生成各种后台页面，极大减少开发成本，甚至可以不需要了解前端。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.zhimg.com/v2-9948b089e0bcc3ae84e9633c1fb236cc_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;h2--strong-3-2-h5-dooring-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;3.2 h5-Dooring（前端）&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;3.2 h5-Dooring（前端）&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;h5-Dooring，让 H5 制作像搭积木一样简单, 轻松搭建 H5 页面, H5 网站, PC 端网站, 可视化设计。&lt;/p&gt;
&lt;p&gt;H5 页面拖拽生成：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic3.zhimg.com/v2-6760585bd891a1c516a77e84581d90de_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;新建数字大屏：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic2.zhimg.com/v2-afb9969046c74dcc8b5bc0bb9fee6815_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;数字大屏效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic2.zhimg.com/v2-af2c77df71891007ad04a8248842cc9d_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;更多请查看官网&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=http%3A//h5.dooring.cn/%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;http://h5.dooring.cn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;magic-api 是一个基于 Java 的接口快速开发框架，编写接口将通过 magic-api 提供的 UI 界面完成，自动映射为 HTTP 接口，无需定义 Controller、Service、Dao、Mapper、XML、VO 等 Java 对象即可完成常见的 HTTP API 接口开发。&lt;/p&gt;
&lt;p&gt;在线开发调试 UI：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic4.zhimg.com/v2-c83365756da5968316d80724564a578f_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;h2--strong-magic-api-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;四、magic-api 搭建&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;四、magic-api 搭建&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;自 magic-api 在开源中国开源，笔者一直在关注此项目。magic-api 搭建比较简单，跟着官方仓库快速开始即可。&lt;/p&gt;
&lt;h3 id=&quot;h3--strong-4-1-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;4.1 加入依赖&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;4.1 加入依赖&lt;/strong&gt;&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;!-- 以 spring-boot-starter 的方式引用 --&amp;gt; 
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.ssssssss&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;magic-api-spring-boot-starter&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.0.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;h3--strong-4-2-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;4.2 添加配置&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;4.2 添加配置&lt;/strong&gt;&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;server.port=9999 
#配置 web 页面入口 
magic-api.web=/magic/web 
#配置文件存储位置。当以 classpath 开头时，为只读模式
magic-api.resource.location=/data/magic-api&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;h3--strong-4-3-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;4.3 效果&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;4.3 效果&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;https://pic4.zhimg.com/v2-91c60469c7cbec42cbc7edabf3df6eaf_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3--strong-4-4-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;4.4 添加接口&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;4.4 添加接口&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;https://pic1.zhimg.com/v2-7c2524498ac45eca76b0b518950aaa6c_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;点击执行就可以看到效果，这里不做展示，magic-api 官方做了大量的各种各样的功能演示，大家可以去体验体验。地址：&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=http%3A//140.143.210.90%3A9999/magic/web/index.html%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;http://140.143.210.90:9999/magic/web/index.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.zhimg.com/v2-da48d717a59f9cc24bdf9e23d0149fa8_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3--strong-4-5-magic-api-magic-script-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;4.5 magic-api 核心之 magic-script&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;4.5 magic-api 核心之 magic-script&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;magic-api 底层使用的是作者（小东）自研的 magic-script 来执行脚本，他是一个语法类似 js 的 JVM 脚本语言。作者开发很多好用的功能， 其中的&lt;strong&gt;linq&lt;/strong&gt;功能就是非常 的一个。&lt;/p&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var list = [{ sex : 0, name : &amp;#39;小明&amp;#39; },{ sex : 1, name : &amp;#39;小花&amp;#39; }]
return select * from list t where t.sex = 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;{ &amp;quot;sex&amp;quot;: 0, &amp;quot;name&amp;quot;: &amp;quot;小明&amp;quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;h2--strong-mica-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;五、结合 mica 使用&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;五、结合 mica 使用&lt;/strong&gt;&lt;/h2&gt;&lt;h3 id=&quot;h3--strong-5-1-mica-bom-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;5.1 加入 mica-bom 依赖&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;5.1 加入 mica-bom 依赖&lt;/strong&gt;&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependencyManagement&amp;gt;
    &amp;lt;dependencies&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;net.dreamlu&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;mica-bom&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;${mica.version}&amp;lt;/version&amp;gt;
            &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
            &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;
        &amp;lt;/dependency&amp;gt;
    &amp;lt;/dependencies&amp;gt;
&amp;lt;/dependencyManagement&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;h3--strong-5-2-mica-logging-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;5.2 mica-logging 无缝接入&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;5.2 mica-logging 无缝接入&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;mica-logging 的接入比较简单加入依赖即可：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic2.zhimg.com/v2-0848df6d2f8df6e1d6f50453042a8435_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;注意：更多配置，如 json 日志和 logstash 日志收集请查看 mica 文档。&lt;/p&gt;
&lt;h3 id=&quot;h3--strong-5-3-mica-captcha-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;5.3 mica-captcha 验证码&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;5.3 mica-captcha 验证码&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;base64 验证码：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic2.zhimg.com/v2-c75cdc44cb83cae2b3b92188a98658bd_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3--strong-5-4-mica-ip2region-ip-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;5.4 mica-ip2region ip 转位置信息&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;5.4 mica-ip2region ip 转位置信息&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;https://pic4.zhimg.com/v2-44daf4c32f56766ba56dbc676744b98b_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;示例项目源码：&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//gitee.com/596392912/magic-api-example%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;https://gitee.com/596392912/magic-api-example&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从 mica 的接入可以看出，magic-api 的扩展性非常好，更多的自定义配置和扩展我们可以查看&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//ssssssss.org/guide/intro.html%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;官方文档&lt;/a&gt;&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic2.zhimg.com/v2-b61f03229c1e7b0f11d843413aaa08a9_b.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;h2--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;六、低代码开源&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;六、低代码开源&lt;/strong&gt;&lt;/h2&gt;&lt;h3 id=&quot;h3--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;前端：&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;前端：&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;amis（百度前端低代码框架）：&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//github.com/baidu/amis%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;https://github.com/baidu/amis&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Sortable：&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//github.com/SortableJS/Sortable%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;https://github.com/SortableJS/Sortable&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;码良：&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//gitee.com/ymm-tech/gods-pen%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;https://gitee.com/ymm-tech/gods-pen&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;h5-Dooring：&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//github.com/MrXujiang/h5-Dooring%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;https://github.com/MrXujiang/h5-Dooring&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;后端：&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;后端：&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;apijson：&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//github.com/Tencent/APIJSON%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;https://github.com/Tencent/APIJSON&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;dataway：&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//gitee.com/zycgit/hasor%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;https://gitee.com/zycgit/hasor&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;magic-api：&lt;strong&gt;&lt;a href=&quot;https://link.zhihu.com/?target=https%3A//gitee.com/ssssssss-team/magic-api%3FfileGuid%3DdPkpKQo7Yvi4X8qO&quot;&gt;https://gitee.com/ssssssss-team/magic-api&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;h2--strong-strong-&quot;&gt;&lt;a name=&quot;&lt;strong&gt;七、总结&lt;/strong&gt;&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;&lt;strong&gt;七、总结&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;基于 magic-api 等开源项目我们也可以搭建自己的低代码平台。当然开发这个低代码平台我们还是需要些工作量，主要是打通 web ui 和后端接口的 api。笔者曾经也经历过一个电商项目，很多活动都是周五下午接到需求，周一就必须要上线。借助 magic-api 这类低代码工具我们可以快速完成这类需求。最后，希望这篇文章对大家有所启发。&lt;/p&gt;
&lt;p&gt;源文： &lt;a href=&quot;https://zhuanlan.zhihu.com/p/363171382&quot;&gt;https://zhuanlan.zhihu.com/p/363171382&lt;/a&gt;&lt;/p&gt;
</description><pubDate>Sun, 12 May 2024 11:12:17 +0800</pubDate></item><item><title>作为一个后端开发，需要一个怎样的低代码平台？</title><link>http://www.busfly.net/?id=18</link><description>&lt;h2 id=&quot;h2--&quot;&gt;&lt;a name=&quot;作为一个后端开发，需要一个怎样的低代码平台？&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;作为一个后端开发，需要一个怎样的低代码平台？&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;作为一个后端开发，后端代码并不是什么麻烦，不需要优先考虑后端低代码。&lt;/li&gt;&lt;li&gt;并且，后端更喜欢拥有一定的掌控力，通过自己的开发来实现想要的功能。&lt;/li&gt;&lt;li&gt;最重要的是数据及文件，所以不太愿意直接使用某些低代码平台SAAS服务，所有数据和功能都由其提供，那我们就被弱化成了用户而不是开发者。&lt;/li&gt;&lt;li&gt;希望低代码平台提供前端解决方案。&lt;/li&gt;&lt;li&gt;并且不需要专业的前端知识（比如VUE等）&lt;/li&gt;&lt;li&gt;但是又能提供灵活的扩展能力，来解决一些复杂场景&lt;/li&gt;&lt;/ol&gt;
&lt;h2 id=&quot;h2--&quot;&gt;&lt;a name=&quot;有哪些更适合后端的低代码平台？&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;有哪些更适合后端的低代码平台？&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;amis：百度出品，通过一个JSON描述性文件，就可以渲染对非常完善的页面。&lt;/li&gt;&lt;li&gt;PagePlug：PagePlug 是 Appsmith 的中国化项目，是一个开源的、声明式的、可视化的前端低代码框架，可以用来制作 web 应用、微信小程序。 线上PagePlug-Saas版： &lt;a href=&quot;https://methodot.com/&quot;&gt;https://methodot.com/&lt;/a&gt; 官方文档： &lt;a href=&quot;https://docs.pageplug.cn/&quot;&gt;https://docs.pageplug.cn/&lt;/a&gt; gitee项目： &lt;a href=&quot;https://gitee.com/cloudtogo/pageplug&quot;&gt;https://gitee.com/cloudtogo/pageplug&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;这2个是我现在重点推荐，适合后端同学的低代码开发框架。&lt;/p&gt;
&lt;p&gt;amis我主要用在公司内部办公系统，后端提供几个http的api，前端通过可视化配置页面就可以配置出一个完善的页面。最关键的是，页面最终只是一个Json配置内容，配合三套环境（开发、测试、线上）流程，完美适配公司开发流程。通过版本方式，还可以实现快速回滚，适用性极佳。&lt;/p&gt;
&lt;p&gt;PagePlug是最近在研究的，按照前面的条件，其实并不太符合我的要求，但是，amis主要适用办公系统，不太适合普通用户系统，比如小程序、商城等，要做这些系统，也有很多选择，比如uni-app，但是，uni-app还是要求比较多的学习前端开发框架和知识，而PagePlug在这方面明显要求低很多，只要基础的HTML、JS差不多就可以上手了。最重要的是，PagePlug对数据库是持开放态度，可以直接引用我们自己的数据库，而不被过渡集成和强制。经过一小段时间的研究发现，其SAAS服务也是非常友善的，使用他提供的SAAS服务与源码自建服务基本上完全一样，数据库都是可以由我们后端自行设计及创建管理，有这就满足我的底限要求了。&lt;/p&gt;
</description><pubDate>Sat, 04 May 2024 17:47:06 +0800</pubDate></item><item><title>用python3教你任意Html主内容提取功能</title><link>http://www.busfly.net/?id=15</link><description>&lt;p&gt;本文将和大家分享一些从互联网上爬取语料的经验。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;0x1 工具准备&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;工欲善其事必先利其器，爬取语料的根基便是基于python。&lt;/p&gt;
&lt;p&gt;我们基于python3进行开发，主要使用以下几个模块：requests、lxml、json。&lt;/p&gt;
&lt;p&gt;简单介绍一个各模块的功能&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;01｜requests&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;requests是一个Python第三方库，处理URL资源特别方便。它的官方文档上写着大大口号：HTTP for Humans(为人类使用HTTP而生)。相比python自带的urllib使用体验，笔者认为requests的使用体验比urllib高了一个数量级。&lt;/p&gt;
&lt;p&gt;我们简单的比较一下：&lt;/p&gt;
&lt;p&gt;urllib:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt; import urllib2
 import urllib
 URL_GET = &amp;quot;https://api.douban.com/v2/event/list&amp;quot;
 #构建请求参数
 params = urllib.urlencode({&amp;#39;loc&amp;#39;:&amp;#39;108288&amp;#39;,&amp;#39;day_type&amp;#39;:&amp;#39;weekend&amp;#39;,&amp;#39;type&amp;#39;:&amp;#39;exhibition&amp;#39;})
 #发送请求
 response = urllib2.urlopen(&amp;#39;?&amp;#39;.join([URL_GET,&amp;#39;%s&amp;#39;])%params)
#Response Headers
print(response.info())
#Response Code
print(response.getcode())
#Response Body
print(response.read())&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;requests：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import requests
 URL_GET = &amp;quot;https://api.douban.com/v2/event/list&amp;quot;
 #构建请求参数
 params = {&amp;#39;loc&amp;#39;:&amp;#39;108288&amp;#39;,&amp;#39;day_type&amp;#39;:&amp;#39;weekend&amp;#39;,&amp;#39;type&amp;#39;:&amp;#39;exhibition&amp;#39;}

 #发送请求
 response = requests.get(URL_GET,params=params)
 #Response Headers
print(response.headers)
#Response Code
print(response.status_code)
#Response Body
print(response.text)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以发现，这两种库还是有一些区别的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;参数的构建：urllib需要对参数进行urlencode编码处理，比较麻烦；requests无需额外编码处理，十分简洁。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;请求发送：urllib需要额外对url参数进行构造，变为符合要求的形式；requests则简明很多，直接get对应链接与参数。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;连接方式：看一下返回数据的头信息的“connection”，使用urllib库时，”connection”:”close”，说明每次请求结束关掉socket通道，而使用requests库使用了urllib3，多次请求重复使用一个socket，”connection”:”keep-alive”，说明多次请求使用一个连接，消耗更少的资源&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;编码方式：requests库的编码方式Accept-Encoding更全，在此不做举例&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;综上所诉，使用requests更为简明、易懂，极大的方便我们开发。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;02｜lxml&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;BeautifulSoup是一个库，而XPath是一种技术，python中最常用的XPath库是lxml。&lt;/p&gt;
&lt;p&gt;当我们拿到requests返回的页面后，我们怎么拿到想要的数据呢？这个时候祭出lxml这强大的HTML/XML解析工具。python从不缺解析库，那么我们为什么要在众多库里选择lxml呢？我们选择另一款出名的HTML解析库BeautifulSoup来进行对比。&lt;/p&gt;
&lt;p&gt;我们简单的比较一下：&lt;/p&gt;
&lt;p&gt;BeautifulSoup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from bs4 import BeautifulSoup #导入库
# 假设html是需要被解析的html
#将html传入BeautifulSoup 的构造方法,得到一个文档的对象
soup = BeautifulSoup(html,&amp;#39;html.parser&amp;#39;,from_encoding=&amp;#39;utf-8&amp;#39;)
#查找所有的h4标签
links = soup.find_all(&amp;quot;h4&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;lxml:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from lxml import etree
# 假设html是需要被解析的html
#将html传入etree 的构造方法,得到一个文档的对象
root = etree.HTML(html)
#查找所有的h4标签
links = root.xpath(&amp;quot;//h4&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以发现，这两种库还是有一些区别的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;解析html: BeautifulSoup的解析方式和JQ的写法类似，API非常人性化，支持css选择器；lxml的语法有一定的学习成本&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;性能：BeautifulSoup是基于DOM的，会载入整个文档，解析整个DOM树，因此时间和内存开销都会大很多；而lxml只会局部遍历，另外lxml是用c写的，而BeautifulSoup是用python写的，明显的性能上lxml&amp;gt;&amp;gt;BeautifulSoup。&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;综上所诉，使用BeautifulSoup更为简明、易用，lxml虽然有一定学习成本，但总体也很简明易懂，最重要的是它基于C编写，速度快很多，对于笔者这种强迫症，自然而然就选lxml啦。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;03｜json&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;python自带json库，对于基础的json的处理，自带库完全足够。但是如果你想更偷懒，可以使用第三方json库，常见的有demjson、simplejson。&lt;/p&gt;
&lt;p&gt;这两种库，无论是import模块速度，还是编码、解码速度，都是simplejson更胜一筹，再加上兼容性 simplejson 更好。所以大家如果想使用方库，可以使用simplejson。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;0x2 确定语料源&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;将武器准备好之后，接下来就需要确定爬取方向。&lt;/p&gt;
&lt;p&gt;以电竞类语料为例，现在我们要爬电竞类相关语料。大家熟悉的电竞平台有企鹅电竞、企鹅电竞和企鹅电竞(斜眼)，所以我们以企鹅电竞上直播的游戏作为数据源进行爬取。&lt;/p&gt;
&lt;p&gt;我们登陆企鹅电竞官网，进入游戏列表页，可以发现页面上有很多游戏，通过人工去写这些游戏名收益明显不高，于是我们就开始我们爬虫的第一步：游戏列表爬取。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://www.busfly.net/zb_users/upload/2023/06/202306211450575334339.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
 from lxml import etree
 # 更新游戏列表
 def _updateGameList():
   # 发送HTTP请求时的HEAD信息，用于伪装为浏览器
   heads = {
    &amp;#39;Connection&amp;#39;: &amp;#39;Keep-Alive&amp;#39;,
    &amp;#39;Accept&amp;#39;: &amp;#39;text/html, application/xhtml+xml, */*&amp;#39;,
    &amp;#39;Accept-Language&amp;#39;: &amp;#39;en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3&amp;#39;,
    &amp;#39;Accept-Encoding&amp;#39;: &amp;#39;gzip, deflate&amp;#39;,
    &amp;#39;User-Agent&amp;#39;: &amp;#39;Mozilla/6.1 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko&amp;#39;
  }
  # 需要爬取的游戏列表页
  url = &amp;#39;https://egame.qq.com/gamelist&amp;#39;
  # 不压缩html，最大链接时间为10妙
  res = requests.get(url, headers=heads, verify=False, timeout=10)
  # 为防止出错，编码utf-8
  res.encoding = &amp;#39;utf-8&amp;#39;
  # 将html构建为Xpath模式
  root = etree.HTML(res.content)
  # 使用Xpath语法，获取游戏名
  gameList = root.xpath(&amp;quot;//ul[@class=&amp;#39;livelist-mod&amp;#39;]//li//p//text()&amp;quot;)
  # 输出爬到的游戏名
  print(gameList)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当我们拿到这几十个游戏名后，下一步就是对这几十款游戏进行语料爬取，这时候问题就来了，我们要从哪个网站来爬这几十个游戏的攻略呢，taptap?多玩？17173？在对这几个网站进行分析后，发现这些网站仅有一些热门游戏的文章语料，一些冷门或者低热度的游戏，例如“灵魂筹码”、“奇迹：觉醒”、“死神来了”等，很难在这些网站上找到大量文章语料，如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://www.busfly.net/zb_users/upload/2023/06/202306211452129675439.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们可以发现，“ 奇迹：觉醒”、“灵魂筹码”的文章语料特别少，数量上不符合我们的要求。 那么有没有一个比较通用的资源站，它拥有着无比丰富的文章语料，可以满足我们的需求。&lt;/p&gt;
&lt;p&gt;其实静下心来想想，这个资源站我们天天都有用到，那就是百度。我们在百度新闻搜索相关游戏，拿到搜索结果列表，这些列表的链接的网页内容几乎都与搜索结果强相关，这样我们数据源不够丰富的问题便轻松解决了。但是此时出现了一个新的问题，并且是一个比较难解决的问题——如何抓取到任意网页的文章内容？&lt;/p&gt;
&lt;p&gt;因为不同的网站都有不同的页面结构，我们无法与预知将会爬到哪个网站的数据，并且我们也不可能针对每一个网站都去写一套爬虫，那样的工作量简直难以想象！但是我们也不能简单粗暴的将页面中的所有文字都爬下来，用那样的语料来进行训练无疑是噩梦！&lt;/p&gt;
&lt;p&gt;经过与各个网站斗智斗勇、查询资料与思索之后，终于找到一条比较通用的方案，下面为大家讲一讲笔者的思路。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;0x3 任意网站的文章语料爬取&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;01｜提取方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1）基于Dom树正文提取&lt;/p&gt;
&lt;p&gt;2）基于网页分割找正文块&lt;/p&gt;
&lt;p&gt;3）基于标记窗的正文提取&lt;/p&gt;
&lt;p&gt;4）基于数据挖掘或机器学习&lt;/p&gt;
&lt;p&gt;5）基于行块分布函数正文提取&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;02｜提取原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;大家看到这几种是不是都有点疑惑了，它们到底是怎么提取的呢？让笔者慢慢道来。&lt;/p&gt;
&lt;p&gt;1）基于Dom树的正文提取：&lt;/p&gt;
&lt;p&gt;这一种方法主要是通过比较规范的HTML建立Dom树，然后地柜遍历Dom，比较并识别各种非正文信息，包括广告、链接和非重要节点信息，将非正文信息抽离之后，余下来的自然就是正文信息。&lt;/p&gt;
&lt;p&gt;但是这种方法有两个问题&lt;/p&gt;
&lt;p&gt;① 特别依赖于HTML的良好结构，如果我们爬取到一个不按W3c规范的编写的网页时，这种方法便不是很适用。&lt;/p&gt;
&lt;p&gt;② 树的建立和遍历时间复杂度、空间复杂度都较高，树的遍历方法也因HTML标签会有不同的差异。&lt;/p&gt;
&lt;p&gt;2) 基于网页分割找正文块 ：&lt;/p&gt;
&lt;p&gt;这一种方法是利用HTML标签中的分割线以及一些视觉信息（如文字颜色、字体大小、文字信息等）。&lt;/p&gt;
&lt;p&gt;这种方法存在一个问题：&lt;/p&gt;
&lt;p&gt;① 不同的网站HTML风格迥异，分割没有办法统一，无法保证通用性。&lt;/p&gt;
&lt;p&gt;3) 基于标记窗的正文提取：&lt;/p&gt;
&lt;p&gt;先科普一个概念——标记窗，我们将两个标签以及其内部包含的文本合在一起成为一个标记窗（比如 &amp;lt;h1&amp;gt;我是h1&amp;lt;/h1&amp;gt; 中的“我是h1”就是标记窗内容）,取出标记窗的文字。&lt;/p&gt;
&lt;p&gt;这种方法先取文章标题、HTML中所有的标记窗，在对其进行分词。然后计算标题的序列与标记窗文本序列的词语距离L，如果L小于一个阈值，则认为此标记窗内的文本是正文。&lt;/p&gt;
&lt;p&gt;这种方法虽然看上去挺好，但其实也是存在问题的：&lt;/p&gt;
&lt;p&gt;① 需要对页面中的所有文本进行分词，效率不高。&lt;/p&gt;
&lt;p&gt;② 词语距离的阈值难以确定，不同的文章拥有不同的阈值。&lt;/p&gt;
&lt;p&gt;4）基于数据挖掘或机器学习&lt;/p&gt;
&lt;p&gt;使用大数据进行训练，让机器提取主文本。&lt;/p&gt;
&lt;p&gt;这种方法肯定是极好的，但是它需要先有html与正文数据，然后进行训练。我们在此不进行探讨。&lt;/p&gt;
&lt;p&gt;5）基于行块分布函数正文提取&lt;/p&gt;
&lt;p&gt;对于任意一个网页，它的正文和标签总是杂糅在一起。此方法的核心有亮点：① 正文区的密度；② 行块的长度；一个网页的正文区域肯定是文字信息分布最密集的区域之一，这个区域可能最大（评论信息长、正文较短），所以同时引进行块长度进行判断。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现思路：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;① 我们先将HTML去标签，只留所有正文，同时留下标签取出后的所有空白位置信息，我们称其为Ctext；&lt;/p&gt;
&lt;p&gt;② 对每一个Ctext取周围k行(k&amp;lt;5)，合起来称为Cblock；&lt;/p&gt;
&lt;p&gt;③ 对Cblock去掉所有空白符，其文字总长度称为Clen；&lt;/p&gt;
&lt;p&gt;④ 以Ctext为横坐标轴，以各行的Clen为纵轴，建立坐标系。&lt;/p&gt;
&lt;p&gt;以这个网页为例： &lt;a href=&quot;http://www.gov.cn/ldhd/2009-11/08/content_1459564.htm&quot;&gt;http://www.gov.cn/ldhd/2009-11/08/content_1459564.htm&lt;/a&gt;   该网页的正文区域为145行至182行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://www.busfly.net/zb_users/upload/2023/06/202306211453088897267.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;由上图可知，正确的文本区域全都是分布函数图上含有最值且连续的一个区域，这个区域往往含有一个骤升点和一个骤降点。因此，网页正文抽取问题转化为了求行块分布函数上的骤升点和骤降点两个边界点，这两个边界点所含的区域包含了当前网页的行块长度最大值并且是连续的。&lt;/p&gt;
&lt;p&gt;经过大量实验，证明此方法对于中文网页的正文提取有较高的准确度,此算法的优点在于，行块函数不依赖与HTML代码，与HTML标签无关，实现简单，准确率较高。&lt;/p&gt;
&lt;p&gt;主要逻辑代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 假设content为已经拿到的html
 # Ctext取周围k行(k&amp;lt;5),定为3
 blocksWidth = 3
 # 每一个Cblock的长度
 Ctext_len = []
 # Ctext
 lines = content.split(&amp;#39;n&amp;#39;)
 # 去空格
for i in range(len(lines)):
  if lines[i] == &amp;#39; &amp;#39; or lines[i] == &amp;#39;n&amp;#39;:
    lines[i] = &amp;#39;&amp;#39;
# 计算纵坐标，每一个Ctext的长度
for i in range(0, len(lines) - blocksWidth):
  wordsNum = 0
  for j in range(i, i + blocksWidth):
    lines[j] = lines[j].replace(&amp;quot;\s&amp;quot;, &amp;quot;&amp;quot;)
    wordsNum += len(lines[j])
  Ctext_len.append(wordsNum)
# 开始标识
start = -1
# 结束标识
end = -1
# 是否开始标识
boolstart = False
# 是否结束标识
boolend = False
# 行块的长度阈值
max_text_len = 88
# 文章主内容
main_text = []
# 没有分割出Ctext
if len(Ctext_len) &amp;lt; 3:
  return &amp;#39;没有正文&amp;#39;
for i in range(len(Ctext_len) - 3):
  # 如果高于这个阈值
  if(Ctext_len[i] &amp;gt; max_text_len and (not boolstart)):
    # Cblock下面3个都不为0，认为是正文
    if (Ctext_len[i + 1] != 0 or Ctext_len[i + 2] != 0 or Ctext_len[i + 3] != 0):
      boolstart = True
      start = i
      continue
  if (boolstart):
    # Cblock下面3个中有0，则结束
    if (Ctext_len[i] == 0 or Ctext_len[i + 1] == 0):
      end = i
      boolend = True
  tmp = []
  # 判断下面还有没有正文
  if(boolend):
    for ii in range(start, end + 1):
      if(len(lines[ii]) &amp;lt; 5):
        continue
      tmp.append(lines[ii] + &amp;quot;n&amp;quot;)
    str = &amp;quot;&amp;quot;.join(list(tmp))
    # 去掉版权信息
    if (&amp;quot;Copyright&amp;quot; in str or &amp;quot;版权所有&amp;quot; in str):
      continue
    main_text.append(str)
    boolstart = boolend = False
# 返回主内容
result = &amp;quot;&amp;quot;.join(list(main_text))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;0x4 结语&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;至此我们就可以获取任意内容的文章语料了，但这仅仅是开始，获取到了这些语料后我们还需要在一次进行清洗、分词、词性标注等，才能获得真正可以使用的语料。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以上所述是小编给大家介绍的用python3教你任意Html主内容提取功能，希望对大家有所帮助，如果大家有任何疑问请给我留言，小编会及时回复大家的。在此也非常感谢大家对我们网站的支持！&lt;/p&gt;
</description><pubDate>Wed, 21 Jun 2023 14:47:28 +0800</pubDate></item><item><title>LangChain 完整指南：使用大语言模型构建强大的应用程序</title><link>http://www.busfly.net/?id=14</link><description>&lt;blockquote&gt;
&lt;p&gt;LangChain 是一个强大的框架，可以简化构建高级语言模型应用程序的过程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;http://www.busfly.net/zb_users/upload/2023/06/202306141601188673841.png&quot; alt=&quot;LangChain&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;h2-what-is-langchain-&quot;&gt;&lt;a name=&quot;What is LangChain?&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;What is LangChain?&lt;/h2&gt;&lt;p&gt;LangChain是一个强大的框架，旨在帮助开发人员使用语言模型构建端到端的应用程序。它提供了一套工具、组件和接口，可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain 可以轻松管理与语言模型的交互，将多个组件链接在一起，并集成额外的资源，例如 API 和数据库。&lt;/p&gt;
&lt;p&gt;LangChain有很多核心概念：&lt;/p&gt;
&lt;h3 id=&quot;h3-1-components-and-chains&quot;&gt;&lt;a name=&quot;1. Components and Chains&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;1. Components and Chains&lt;/h3&gt;&lt;p&gt;在 LangChain 中，Component 是模块化的构建块，可以组合起来创建强大的应用程序。Chain 是组合在一起以完成特定任务的一系列 Components（或其他 Chain）。例如，一个 Chain 可能包括一个 Prompt 模板、一个语言模型和一个输出解析器，它们一起工作以处理用户输入、生成响应并处理输出。&lt;/p&gt;
&lt;h3 id=&quot;h3-2-prompt-templates-and-values&quot;&gt;&lt;a name=&quot;2. Prompt Templates and Values&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;2. Prompt Templates and Values&lt;/h3&gt;&lt;p&gt;Prompt Template 负责创建 PromptValue，这是最终传递给语言模型的内容。Prompt Template 有助于将用户输入和其他动态信息转换为适合语言模型的格式。PromptValues 是具有方法的类，这些方法可以转换为每个模型类型期望的确切输入类型（如文本或聊天消息）。&lt;/p&gt;
&lt;h3 id=&quot;h3-3-example-selectors&quot;&gt;&lt;a name=&quot;3. Example Selectors&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;3. Example Selectors&lt;/h3&gt;&lt;p&gt;当您想要在 Prompts 中动态包含示例时，Example Selectors 很有用。他们接受用户输入并返回一个示例列表以在提示中使用，使其更强大和特定于上下文。&lt;/p&gt;
&lt;h3 id=&quot;h3-4-output-parsers&quot;&gt;&lt;a name=&quot;4. Output Parsers&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;4. Output Parsers&lt;/h3&gt;&lt;p&gt;Output Parsers 负责将语言模型响应构建为更有用的格式。它们实现了两种主要方法：一种用于提供格式化指令，另一种用于将语言模型的响应解析为结构化格式。这使得在您的应用程序中处理输出数据变得更加容易。&lt;/p&gt;
&lt;h3 id=&quot;h3-5-indexes-and-retrievers&quot;&gt;&lt;a name=&quot;5. Indexes and Retrievers&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;5. Indexes and Retrievers&lt;/h3&gt;&lt;p&gt;Index 是一种组织文档的方式，使语言模型更容易与它们交互。检索器是用于获取相关文档并将它们与语言模型组合的接口。LangChain 提供了用于处理不同类型的索引和检索器的工具和功能，例如矢量数据库和文本拆分器。&lt;/p&gt;
&lt;h3 id=&quot;h3-6-chat-message-history&quot;&gt;&lt;a name=&quot;6. Chat Message History&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;6. Chat Message History&lt;/h3&gt;&lt;p&gt;LangChain 主要通过聊天界面与语言模型进行交互。ChatMessageHistory 类负责记住所有以前的聊天交互数据，然后可以将这些交互数据传递回模型、汇总或以其他方式组合。这有助于维护上下文并提高模型对对话的理解。&lt;/p&gt;
&lt;h3 id=&quot;h3-7-agents-and-toolkits&quot;&gt;&lt;a name=&quot;7. Agents and Toolkits&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;7. Agents and Toolkits&lt;/h3&gt;&lt;p&gt;Agent 是在 LangChain 中推动决策制定的实体。他们可以访问一套工具，并可以根据用户输入决定调用哪个工具。Tookits 是一组工具，当它们一起使用时，可以完成特定的任务。代理执行器负责使用适当的工具运行代理。&lt;/p&gt;
&lt;p&gt;通过理解和利用这些核心概念，您可以利用 LangChain 的强大功能来构建适应性强、高效且能够处理复杂用例的高级语言模型应用程序。&lt;/p&gt;
&lt;h2 id=&quot;h2-what-is-a-langchain-agent-&quot;&gt;&lt;a name=&quot;What is a LangChain Agent?&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;What is a LangChain Agent?&lt;/h2&gt;&lt;p&gt;LangChain Agent 是框架中驱动决策制定的实体。它可以访问一组工具，并可以根据用户的输入决定调用哪个工具。代理帮助构建复杂的应用程序，这些应用程序需要自适应和特定于上下文的响应。当存在取决于用户输入和其他因素的未知交互链时，它们特别有用。&lt;/p&gt;
&lt;p&gt;要使用 LangChain，开发人员首先要导入必要的组件和工具，例如 LLMs, chat models, agents, chains, 内存功能。这些组件组合起来创建一个可以理解、处理和响应用户输入的应用程序。&lt;/p&gt;
&lt;p&gt;LangChain 为特定用例提供了多种组件，例如个人助理、文档问答、聊天机器人、查询表格数据、与 API 交互、提取、评估和汇总。&lt;/p&gt;
&lt;h2 id=&quot;h2-what-s-a-langchain-model-&quot;&gt;&lt;a name=&quot;What’s a LangChain model?&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;What’s a LangChain model?&lt;/h2&gt;&lt;p&gt;LangChain model 是一种抽象，表示框架中使用的不同类型的模型。LangChain 中的模型主要分为三类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;LLM（大型语言模型）&lt;/strong&gt;：这些模型将文本字符串作为输入并返回文本字符串作为输出。它们是许多语言模型应用程序的支柱。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;聊天模型( Chat Model)&lt;/strong&gt;：聊天模型由语言模型支持，但具有更结构化的 API。他们将聊天消息列表作为输入并返回聊天消息。这使得管理对话历史记录和维护上下文变得容易。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;文本嵌入模型(Text Embedding Models)&lt;/strong&gt;：这些模型将文本作为输入并返回表示文本嵌入的浮点列表。这些嵌入可用于文档检索、聚类和相似性比较等任务。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;开发人员可以为他们的用例选择合适的 LangChain 模型，并利用提供的组件来构建他们的应用程序。&lt;/p&gt;
&lt;h2 id=&quot;h2-langchain-&quot;&gt;&lt;a name=&quot;LangChain 的主要特点&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;LangChain 的主要特点&lt;/h2&gt;&lt;p&gt;LangChain 旨在为六个主要领域的开发人员提供支持：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;LLM 和提示&lt;/strong&gt;：LangChain 使管理提示、优化它们以及为所有 LLM 创建通用界面变得容易。此外，它还包括一些用于处理 LLM 的便捷实用程序。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;链(Chain)&lt;/strong&gt;：这些是对 LLM 或其他实用程序的调用序列。LangChain 为链提供标准接口，与各种工具集成，为流行应用提供端到端的链。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;数据增强生成&lt;/strong&gt;：LangChain 使链能够与外部数据源交互以收集生成步骤的数据。例如，它可以帮助总结长文本或使用特定数据源回答问题。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Agents&lt;/strong&gt;：Agents 让 LLM 做出有关行动的决定，采取这些行动，检查结果，并继续前进直到工作完成。LangChain 提供了代理的标准接口，多种代理可供选择，以及端到端的代理示例。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;内存&lt;/strong&gt;：LangChain 有一个标准的内存接口，有助于维护链或代理调用之间的状态。它还提供了一系列内存实现和使用内存的链或代理的示例。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;评估&lt;/strong&gt;：很难用传统指标评估生成模型。这就是为什么 LangChain 提供提示和链来帮助开发者自己使用 LLM 评估他们的模型。&lt;/li&gt;&lt;/ol&gt;
&lt;h2 id=&quot;h2-u4F7Fu7528u793Au4F8B&quot;&gt;&lt;a name=&quot;使用示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;使用示例&lt;/h2&gt;&lt;p&gt;LangChain 支持大量用例，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;针对特定文档的问答&lt;/strong&gt;：根据给定的文档回答问题，使用这些文档中的信息来创建答案。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;聊天机器人&lt;/strong&gt;：构建可以利用 LLM 的功能生成文本的聊天机器人。&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Agents&lt;/strong&gt;：开发可以决定行动、采取这些行动、观察结果并继续执行直到完成的代理。&lt;/li&gt;&lt;/ul&gt;
&lt;h3 id=&quot;h3--langchain-&quot;&gt;&lt;a name=&quot;快速入门指南：使用 LangChain 构建端到端语言模型应用程序&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;快速入门指南：使用 LangChain 构建端到端语言模型应用程序&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;安装&lt;/strong&gt;&lt;br&gt;首先，安装 LangChain。只需运行以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install langchain&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;环境设置&lt;/strong&gt;&lt;br&gt;现在，由于 LangChain 经常需要与模型提供者、数据存储、API 等集成，我们将设置我们的环境。在这个例子中，我们将使用 OpenAI 的 API，因此我们需要安装他们的 SDK：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install openai&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;接下来，让我们在终端中设置环境变量：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export OPENAI_API_KEY = &amp;quot;...&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;或者，如果您更喜欢在 Jupyter notebook 或 Python 脚本中工作，您可以像这样设置环境变量:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os 
os .environ[ &amp;quot;OPENAI_API_KEY&amp;quot; ] = &amp;quot;...&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;构建语言模型应用程序：LLM&lt;/strong&gt;&lt;br&gt;安装好 LangChain 并设置好环境后，我们就可以开始构建我们的语言模型应用程序了。LangChain 提供了一堆模块，您可以使用它们来创建语言模型应用程序。您可以将这些模块组合起来用于更复杂的应用程序，或者将它们单独用于更简单的应用程序。  &lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;构建语言模型应用程序：Chat Model&lt;/strong&gt;&lt;br&gt;除了 LLM，您还可以使用聊天模型。这些是语言模型的变体，它们在底层使用语言模型但具有不同的界面。聊天模型使用聊天消息作为输入和输出，而不是“文本输入、文本输出”API。聊天模型 API 的使用还比较新，所以大家都还在寻找最佳抽象使用方式。&lt;/p&gt;
&lt;p&gt;要完成聊天，您需要将一条或多条消息传递给聊天模型。LangChain 目前支持 AIMessage、HumanMessage、SystemMessage 和 ChatMessage 类型。您将主要使用 HumanMessage、AIMessage 和 SystemMessage。&lt;br&gt;下面是使用聊天模型的示例：&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;from langchain.chat_models import ChatOpenAI
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;chat = ChatOpenAI(temperature=0)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;您可以通过传递一条消息来完成:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chat([HumanMessage(content=&amp;quot;Translate this sentence from English to French. I love programming.&amp;quot;)])
# -&amp;gt; AIMessage(content=&amp;quot;J&amp;#39;aime programmer.&amp;quot;, additional_kwargs={})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;或者传递多条消息给 OpenAI 的 gpt-3.5-turbo 和 gpt-4 models：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;messages = [
    SystemMessage(content=&amp;quot;You are a helpful assistant that translates English to Chinese.&amp;quot;),
    HumanMessage(content=&amp;quot;Translate this sentence from English to Chinese. I love programming.&amp;quot;)
]
chat(messages)
# -&amp;gt; AIMessage(content=&amp;quot;我喜欢编程。(Wǒ xǐhuān biānchéng.)&amp;quot;, additional_kwargs={})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;您还可以使用 generate 为多组消息生成完成。这将返回一个带有附加消息参数的 LLMResult：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;batch_messages = [
    [
        SystemMessage(content=&amp;quot;You are a helpful assistant that translates English to Chinese.&amp;quot;),
        HumanMessage(content=&amp;quot;Translate this sentence from English to Chinese. I love programming.&amp;quot;)
    ],
    [
        SystemMessage(content=&amp;quot;You are a helpful assistant that translates English to Chinese.&amp;quot;),
        HumanMessage(content=&amp;quot;Translate this sentence from English to Chinese. I love artificial intelligence.&amp;quot;)
    ],
]
result = chat.generate(batch_messages)
result
# -&amp;gt; LLMResult(generations=[[ChatGeneration(text=&amp;quot;我喜欢编程。(Wǒ xǐhuān biānchéng.)&amp;quot;, generation_info=None, message=AIMessage(content=&amp;quot;我喜欢编程。(Wǒ xǐhuān biānchéng.)&amp;quot;, additional_kwargs={}))], [ChatGeneration(text=&amp;quot;我喜爱人工智能。(Wǒ xǐ&amp;#39;ài rén gōng zhì néng.)&amp;quot;, generation_info=None, message=AIMessage(content=&amp;quot;我喜爱人工智能。(Wǒ xǐ&amp;#39;ài rén gōng zhì néng.)&amp;quot;, additional_kwargs={}))]], llm_output={&amp;#39;token_usage&amp;#39;: {&amp;#39;prompt_tokens&amp;#39;: 71, &amp;#39;completion_tokens&amp;#39;: 18, &amp;#39;total_tokens&amp;#39;: 89}})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;您还可以从 LLMResult 中提取 tokens 使用等信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result.llm_output[&amp;#39;token_usage&amp;#39;]
# -&amp;gt; {&amp;#39;prompt_tokens&amp;#39;: 71, &amp;#39;completion_tokens&amp;#39;: 18, &amp;#39;total_tokens&amp;#39;: 89}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于聊天模型，您还可以通过使用 MessagePromptTemplate 来使用模板。您可以从一个或多个 MessagePromptTemplates 创建 ChatPromptTemplate。ChatPromptTemplate 的方法&lt;code&gt;format_prompt&lt;/code&gt;返回一个 PromptValue，您可以将其转换为字符串或 Message 对象，具体取决于您是否要使用格式化值作为 LLM 或聊天模型的输入。&lt;/p&gt;
&lt;p&gt;以下是一个例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;chat = ChatOpenAI(temperature=0)
template=&amp;quot;You are a helpful assistant that translates {input_language} to {output_language}.&amp;quot;
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template=&amp;quot;{text}&amp;quot;
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])
# get a chat completion from the formatted messages
chat(chat_prompt.format_prompt(input_language=&amp;quot;English&amp;quot;, output_language=&amp;quot;Chinese&amp;quot;, text=&amp;quot;I love programming.&amp;quot;).to_messages())
# -&amp;gt; AIMessage(content=&amp;quot;我喜欢编程。(Wǒ xǐhuān biānchéng.)&amp;quot;, additional_kwargs={})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;您也可以将 LLMChain 与 Chat Model 一起使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.chat_models import ChatOpenAI
from langchain import LLMChain
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;chat = ChatOpenAI(temperature=0)
template=&amp;quot;You are a helpful assistant that translates {input_language} to {output_language}.&amp;quot;
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template=&amp;quot;{text}&amp;quot;
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])
chain = LLMChain(llm=chat, prompt=chat_prompt)
chain.run(input_language=&amp;quot;English&amp;quot;, output_language=&amp;quot;Chinese&amp;quot;, text=&amp;quot;I love programming.&amp;quot;)
# -&amp;gt; &amp;quot;我喜欢编程。(Wǒ xǐhuān biānchéng.)&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;您还可以将代理与聊天模型一起使用。使用 &lt;code&gt;AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION&lt;/code&gt; 作为代理类型初始化 &lt;code&gt;Agent&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.chat_models import ChatOpenAI
from langchain.llms import OpenAI&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;# First, let&amp;#39;s load the language model we&amp;#39;re going to use to control the agent.
chat = ChatOpenAI(temperature=0)
# Next, let&amp;#39;s load some tools to use. Note that the `llm-math` tool uses an LLM, so we need to pass that in.
llm = OpenAI(temperature=0)
tools = load_tools([&amp;quot;serpapi&amp;quot;, &amp;quot;llm-math&amp;quot;], llm=llm)
# Finally, let&amp;#39;s initialize an agent with the tools, the language model, and the type of agent we want to use.
agent = initialize_agent(tools, chat, agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
# Now let&amp;#39;s test it out!
agent.run(&amp;quot;Who is Olivia Wilde&amp;#39;s boyfriend? What is his current age raised to the 0.23 power?&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在此示例中，代理将以交互的方式执行搜索和计算以提供最终答案。&lt;/p&gt;
&lt;p&gt;最后，让我们探索将内存与使用聊天模型初始化的链和代理一起使用。这与 Memory for LLMs 的主要区别在于我们可以将以前的消息保留为它们自己唯一的内存对象，而不是将它们压缩成一个字符串。&lt;/p&gt;
&lt;p&gt;下面是使用 a 的示例&lt;code&gt;ConversationChain&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain.prompts import (
    ChatPromptTemplate, 
    MessagesPlaceholder, 
    SystemMessagePromptTemplate, 
    HumanMessagePromptTemplate
)
from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(&amp;quot;The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.&amp;quot;),
    MessagesPlaceholder(variable_name=&amp;quot;history&amp;quot;),
    HumanMessagePromptTemplate.from_template(&amp;quot;{input}&amp;quot;)
])
llm = ChatOpenAI(temperature=0)
memory = ConversationBufferMemory(return_messages=True)
conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm)
conversation.predict(input=&amp;quot;Hi there!&amp;quot;)
# -&amp;gt; &amp;#39;Hello! How can I assist you today?&amp;#39;
conversation.predict(input=&amp;quot;I&amp;#39;m doing well! Just having a conversation with an AI.&amp;quot;)
# -&amp;gt; &amp;quot;That sounds like fun! I&amp;#39;m happy to chat with you. Is there anything specific you&amp;#39;d like to talk about?&amp;quot;
conversation.predict(input=&amp;quot;Tell me about yourself.&amp;quot;)
# -&amp;gt; &amp;quot;Sure! I am an AI language model created by OpenAI. I was trained on a large dataset of text from the internet, which allows me to understand and generate human-like language. I can answer questions, provide information, and even have conversations like this one. Is there anything else you&amp;#39;d like to know about me?&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在此示例中，我们使用 a&lt;code&gt;ConversationChain&lt;/code&gt;来维护跨与 AI 的多次交互的对话上下文。&lt;/p&gt;
&lt;p&gt;就是这样！现在您已经对如何使用 LangChain 构建端到端的语言模型应用有了深入的了解。通过遵循这些示例，您可以使用 LLM、聊天模型、代理、链和内存功能开发强大的语言模型应用程序。&lt;/p&gt;
&lt;h2 id=&quot;h2-u7ED3u8BBA&quot;&gt;&lt;a name=&quot;结论&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;结论&lt;/h2&gt;&lt;p&gt;总之，LangChain 是一个强大的框架，它通过提供模块化和灵活的方法简化了构建高级语言模型应用程序的过程。通过了解组件、链、提示模板、输出解析器、索引、检索器、聊天消息历史记录和代理等核心概念，您可以创建适合您特定需求的自定义解决方案。LangChain 的适应性和易用性使其成为开发人员的宝贵工具，使他们能够释放语言模型的全部潜力，并在广泛的用例中创建智能的、上下文感知的应用程序。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://levelup.gitconnected.com/a-complete-guide-to-langchain-building-powerful-applications-with-large-language-models-53ad54a1e7eb&quot;&gt;原文链接&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description><pubDate>Wed, 14 Jun 2023 09:47:41 +0800</pubDate></item><item><title>Java 中 CompletableFuture的方法thenCombineAsync()与thenCombine() 的区别</title><link>http://www.busfly.net/?id=13</link><description>&lt;p&gt;Java 中 CompletableFuture的方法thenCombineAsync()与thenCombine()有什么区别呢，首先我们来看一下他们的主要功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;thenCombine 会把 两个 CompletionStage 的任务都执行完成后，把两个任务的结果一块交给 thenCombine 来处理。&lt;/li&gt;&lt;li&gt;当两个CompletionStage都执行完成后，把结果一块交给thenAcceptBoth来进行消耗&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;thenAcceptBoth和thenCombine都是CompletableFuture中的方法，它们的区别在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;thenAcceptBoth方法：接收两个CompletableFuture对象，等待这两个对象都完成后执行一个消费者函数（Consumer），将这两个对象的结果传递给该函数进行处理，但是不会返回任何结果。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;thenCombine方法：接收两个CompletableFuture对象，等待这两个对象都完成后执行一个合并函数（BiFunction），将这两个对象的结果传递给该函数进行处理，并返回合并后的结果。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;因此，thenAcceptBoth方法适用于需要在两个任务完成后执行一个操作的场景，而thenCombine方法适用于需要在两个任务完成后将它们的结果进行合并的场景。&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;private static void thenCombineTest() throws Exception {
    CompletableFuture&amp;lt;Integer&amp;gt; f1 = CompletableFuture.supplyAsync(()-&amp;gt;{return 1;});

    CompletableFuture&amp;lt;Integer&amp;gt; f2 = CompletableFuture.supplyAsync(()-&amp;gt;{return 2;});

    String result = f1.thenCombine(f2, (f1Result,F2Result) -&amp;gt;{
            return &amp;quot;f1=&amp;quot;+f1Result+&amp;quot;;f2=&amp;quot;+F2Result+&amp;quot;;&amp;quot;;
    }).join();

    System.out.println(result);
}&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;private static void thenAcceptBothTest() throws Exception {
    CompletableFuture&amp;lt;Integer&amp;gt; f1 = CompletableFuture.supplyAsync(()-&amp;gt;{return 1;});

    CompletableFuture&amp;lt;Integer&amp;gt; f2 = CompletableFuture.supplyAsync(()-&amp;gt;{return 2;});

    f1.thenAcceptBoth(f2, (f1Result,F2Result) -&amp;gt;{
            System.out.println(&amp;quot;f1=&amp;quot;+f1Result+&amp;quot;;f2=&amp;quot;+F2Result+&amp;quot;;&amp;quot;);
    });
}&lt;/code&gt;&lt;/pre&gt;</description><pubDate>Wed, 31 May 2023 13:53:07 +0800</pubDate></item><item><title>异步编程：Java 中 CompletableFuture 使用详解</title><link>http://www.busfly.net/?id=12</link><description>&lt;p&gt;1、 runAsync 和 supplyAsync方法&lt;/p&gt;
&lt;p&gt;CompletableFuture 提供了四个静态方法来创建一个异步操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static CompletableFuture&amp;lt;Void&amp;gt; runAsync(Runnable runnable)
public static CompletableFuture&amp;lt;Void&amp;gt; runAsync(Runnable runnable, Executor executor)
public static &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; supplyAsync(Supplier&amp;lt;U&amp;gt; supplier)
public static &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; supplyAsync(Supplier&amp;lt;U&amp;gt; supplier, Executor executor)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池，则使用指定的线程池运行。以下所有的方法都类同。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;runAsync方法不支持返回值。&lt;/li&gt;&lt;li&gt;supplyAsync可以支持返回值。&lt;/li&gt;&lt;/ul&gt;
&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;//无返回值
public static void runAsync() throws Exception {
    CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.runAsync(() -&amp;gt; {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println(&amp;quot;run end ...&amp;quot;);
    });

    future.get();
}

//有返回值
public static void supplyAsync() throws Exception {         
    CompletableFuture&amp;lt;Long&amp;gt; future = CompletableFuture.supplyAsync(() -&amp;gt; {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println(&amp;quot;run end ...&amp;quot;);
        return System.currentTimeMillis();
    });

    long time = future.get();
    System.out.println(&amp;quot;time = &amp;quot;+time);
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-2-&quot;&gt;&lt;a name=&quot;2、计算结果完成时的回调方法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;2、计算结果完成时的回调方法&lt;/h2&gt;&lt;p&gt;当CompletableFuture的计算结果完成，或者抛出异常的时候，可以执行特定的Action。主要是下面的方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public CompletableFuture&amp;lt;T&amp;gt; whenComplete(BiConsumer&amp;lt;? super T,? super Throwable&amp;gt; action)
public CompletableFuture&amp;lt;T&amp;gt; whenCompleteAsync(BiConsumer&amp;lt;? super T,? super Throwable&amp;gt; action)
public CompletableFuture&amp;lt;T&amp;gt; whenCompleteAsync(BiConsumer&amp;lt;? super T,? super Throwable&amp;gt; action, Executor executor)
public CompletableFuture&amp;lt;T&amp;gt; exceptionally(Function&amp;lt;Throwable,? extends T&amp;gt; fn)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到Action的类型是BiConsumer&amp;lt;? super T,? super Throwable&amp;gt;它可以处理正常的计算结果，或者异常情况。&lt;/p&gt;
&lt;p&gt;whenComplete 和 whenCompleteAsync 的区别：&lt;br&gt;whenComplete：是执行当前任务的线程执行继续执行 whenComplete 的任务。&lt;br&gt;whenCompleteAsync：是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。&lt;/p&gt;
&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;public static void whenComplete() throws Exception {
    CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.runAsync(() -&amp;gt; {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        if(new Random().nextInt()%2&amp;gt;=0) {
            int i = 12/0;
        }
        System.out.println(&amp;quot;run end ...&amp;quot;);
    });

    future.whenComplete(new BiConsumer&amp;lt;Void, Throwable&amp;gt;() {
        @Override
        public void accept(Void t, Throwable action) {
            System.out.println(&amp;quot;执行完成！&amp;quot;);
        }

    });
    future.exceptionally(new Function&amp;lt;Throwable, Void&amp;gt;() {
        @Override
        public Void apply(Throwable t) {
            System.out.println(&amp;quot;执行失败！&amp;quot;+t.getMessage());
            return null;
        }
    });

    TimeUnit.SECONDS.sleep(2);
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-3-thenapply-&quot;&gt;&lt;a name=&quot;3、 thenApply 方法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;3、 thenApply 方法&lt;/h2&gt;&lt;p&gt;当一个线程依赖另一个线程时，可以使用 thenApply 方法来把这两个线程串行化。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; thenApply(Function&amp;lt;? super T,? extends U&amp;gt; fn)
public &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; thenApplyAsync(Function&amp;lt;? super T,? extends U&amp;gt; fn)
public &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; thenApplyAsync(Function&amp;lt;? super T,? extends U&amp;gt; fn, Executor executor)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Function&amp;lt;? super T,? extends U&amp;gt;&lt;br&gt;T：上一个任务返回结果的类型&lt;br&gt;U：当前任务的返回值类型&lt;/p&gt;
&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;private static void thenApply() throws Exception {
    CompletableFuture&amp;lt;Long&amp;gt; future = CompletableFuture.supplyAsync(new Supplier&amp;lt;Long&amp;gt;() {
        @Override
        public Long get() {
            long result = new Random().nextInt(100);
            System.out.println(&amp;quot;result1=&amp;quot;+result);
            return result;
        }
    }).thenApply(new Function&amp;lt;Long, Long&amp;gt;() {
        @Override
        public Long apply(Long t) {
            long result = t*5;
            System.out.println(&amp;quot;result2=&amp;quot;+result);
            return result;
        }
    });

    long result = future.get();
    System.out.println(result);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;第二个任务依赖第一个任务的结果。&lt;/p&gt;
&lt;h2 id=&quot;h2-4-handle-&quot;&gt;&lt;a name=&quot;4、 handle 方法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;4、 handle 方法&lt;/h2&gt;&lt;p&gt;handle 是执行任务完成时对结果的处理。&lt;br&gt;handle 方法和 thenApply 方法处理方式基本一样。不同的是 handle 是在任务完成后再执行，还可以处理异常的任务。thenApply 只可以执行正常的任务，任务出现异常则不执行 thenApply 方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public &amp;lt;U&amp;gt; CompletionStage&amp;lt;U&amp;gt; handle(BiFunction&amp;lt;? super T, Throwable, ? extends U&amp;gt; fn);
public &amp;lt;U&amp;gt; CompletionStage&amp;lt;U&amp;gt; handleAsync(BiFunction&amp;lt;? super T, Throwable, ? extends U&amp;gt; fn);
public &amp;lt;U&amp;gt; CompletionStage&amp;lt;U&amp;gt; handleAsync(BiFunction&amp;lt;? super T, Throwable, ? extends U&amp;gt; fn,Executor executor);&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;public static void handle() throws Exception{
    CompletableFuture&amp;lt;Integer&amp;gt; future = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {

        @Override
        public Integer get() {
            int i= 10/0;
            return new Random().nextInt(10);
        }
    }).handle(new BiFunction&amp;lt;Integer, Throwable, Integer&amp;gt;() {
        @Override
        public Integer apply(Integer param, Throwable throwable) {
            int result = -1;
            if(throwable==null){
                result = param * 2;
            }else{
                System.out.println(throwable.getMessage());
            }
            return result;
        }
     });
    System.out.println(future.get());
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从示例中可以看出，在 handle 中可以根据任务是否有异常来进行做相应的后续处理操作。而 thenApply 方法，如果上个任务出现错误，则不会执行 thenApply 方法。&lt;/p&gt;
&lt;h2 id=&quot;h2-5-thenaccept-&quot;&gt;&lt;a name=&quot;5、 thenAccept 消费处理结果&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;5、 thenAccept 消费处理结果&lt;/h2&gt;&lt;p&gt;接收任务的处理结果，并消费处理，无返回结果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public CompletionStage&amp;lt;Void&amp;gt; thenAccept(Consumer&amp;lt;? super T&amp;gt; action);
public CompletionStage&amp;lt;Void&amp;gt; thenAcceptAsync(Consumer&amp;lt;? super T&amp;gt; action);
public CompletionStage&amp;lt;Void&amp;gt; thenAcceptAsync(Consumer&amp;lt;? super T&amp;gt; action,Executor executor);&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;public static void thenAccept() throws Exception{
    CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            return new Random().nextInt(10);
        }
    }).thenAccept(integer -&amp;gt; {
        System.out.println(integer);
    });
    future.get();
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从示例代码中可以看出，该方法只是消费执行完成的任务，并可以根据上面的任务返回的结果进行处理。并没有后续的输错操作。&lt;/p&gt;
&lt;h2 id=&quot;h2-6-thenrun-&quot;&gt;&lt;a name=&quot;6、thenRun 方法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;6、thenRun 方法&lt;/h2&gt;&lt;p&gt;跟 thenAccept 方法不一样的是，不关心任务的处理结果。只要上面的任务执行完成，就开始执行 thenAccept 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public CompletionStage&amp;lt;Void&amp;gt; thenRun(Runnable action);
public CompletionStage&amp;lt;Void&amp;gt; thenRunAsync(Runnable action);
public CompletionStage&amp;lt;Void&amp;gt; thenRunAsync(Runnable action,Executor executor);&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;public static void thenRun() throws Exception{
    CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            return new Random().nextInt(10);
        }
    }).thenRun(() -&amp;gt; {
        System.out.println(&amp;quot;thenRun ...&amp;quot;);
    });
    future.get();
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;该方法同 thenAccept 方法类似。不同的是上个任务处理完成后，并不会把计算的结果传给 thenRun 方法。只是处理玩任务后，执行 thenAccept 的后续操作。&lt;/p&gt;
&lt;h2 id=&quot;h2-7-thencombine-&quot;&gt;&lt;a name=&quot;7、thenCombine 合并任务&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;7、thenCombine 合并任务&lt;/h2&gt;&lt;p&gt;thenCombine 会把 两个 CompletionStage 的任务都执行完成后，把两个任务的结果一块交给 thenCombine 来处理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public &amp;lt;U,V&amp;gt; CompletionStage&amp;lt;V&amp;gt; thenCombine(CompletionStage&amp;lt;? extends U&amp;gt; other,BiFunction&amp;lt;? super T,? super U,? extends V&amp;gt; fn);
public &amp;lt;U,V&amp;gt; CompletionStage&amp;lt;V&amp;gt; thenCombineAsync(CompletionStage&amp;lt;? extends U&amp;gt; other,BiFunction&amp;lt;? super T,? super U,? extends V&amp;gt; fn);
public &amp;lt;U,V&amp;gt; CompletionStage&amp;lt;V&amp;gt; thenCombineAsync(CompletionStage&amp;lt;? extends U&amp;gt; other,BiFunction&amp;lt;? super T,? super U,? extends V&amp;gt; fn,Executor executor);&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;private static void thenCombine() throws Exception {
    CompletableFuture&amp;lt;String&amp;gt; future1 = CompletableFuture.supplyAsync(new Supplier&amp;lt;String&amp;gt;() {
        @Override
        public String get() {
            return &amp;quot;hello&amp;quot;;
        }
    });
    CompletableFuture&amp;lt;String&amp;gt; future2 = CompletableFuture.supplyAsync(new Supplier&amp;lt;String&amp;gt;() {
        @Override
        public String get() {
            return &amp;quot;hello&amp;quot;;
        }
    });
    CompletableFuture&amp;lt;String&amp;gt; result = future1.thenCombine(future2, new BiFunction&amp;lt;String, String, String&amp;gt;() {
        @Override
        public String apply(String t, String u) {
            return t+&amp;quot; &amp;quot;+u;
        }
    });
    System.out.println(result.get());
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-8-thenacceptboth&quot;&gt;&lt;a name=&quot;8、thenAcceptBoth&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;8、thenAcceptBoth&lt;/h2&gt;&lt;p&gt;当两个CompletionStage都执行完成后，把结果一块交给thenAcceptBoth来进行消耗&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public &amp;lt;U&amp;gt; CompletionStage&amp;lt;Void&amp;gt; thenAcceptBoth(CompletionStage&amp;lt;? extends U&amp;gt; other,BiConsumer&amp;lt;? super T, ? super U&amp;gt; action);
public &amp;lt;U&amp;gt; CompletionStage&amp;lt;Void&amp;gt; thenAcceptBothAsync(CompletionStage&amp;lt;? extends U&amp;gt; other,BiConsumer&amp;lt;? super T, ? super U&amp;gt; action);
public &amp;lt;U&amp;gt; CompletionStage&amp;lt;Void&amp;gt; thenAcceptBothAsync(CompletionStage&amp;lt;? extends U&amp;gt; other,BiConsumer&amp;lt;? super T, ? super U&amp;gt; action,     Executor executor);
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;private static void thenAcceptBoth() throws Exception {
    CompletableFuture&amp;lt;Integer&amp;gt; f1 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f1=&amp;quot;+t);
            return t;
        }
    });

    CompletableFuture&amp;lt;Integer&amp;gt; f2 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f2=&amp;quot;+t);
            return t;
        }
    });
    f1.thenAcceptBoth(f2, new BiConsumer&amp;lt;Integer, Integer&amp;gt;() {
        @Override
        public void accept(Integer t, Integer u) {
            System.out.println(&amp;quot;f1=&amp;quot;+t+&amp;quot;;f2=&amp;quot;+u+&amp;quot;;&amp;quot;);
        }
    });
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-9-applytoeither-&quot;&gt;&lt;a name=&quot;9、applyToEither 方法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;9、applyToEither 方法&lt;/h2&gt;&lt;p&gt;两个CompletionStage，谁执行返回的结果快，我就用那个CompletionStage的结果进行下一步的转化操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public &amp;lt;U&amp;gt; CompletionStage&amp;lt;U&amp;gt; applyToEither(CompletionStage&amp;lt;? extends T&amp;gt; other,Function&amp;lt;? super T, U&amp;gt; fn);
public &amp;lt;U&amp;gt; CompletionStage&amp;lt;U&amp;gt; applyToEitherAsync(CompletionStage&amp;lt;? extends T&amp;gt; other,Function&amp;lt;? super T, U&amp;gt; fn);
public &amp;lt;U&amp;gt; CompletionStage&amp;lt;U&amp;gt; applyToEitherAsync(CompletionStage&amp;lt;? extends T&amp;gt; other,Function&amp;lt;? super T, U&amp;gt; fn,Executor executor);&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;private static void applyToEither() throws Exception {
    CompletableFuture&amp;lt;Integer&amp;gt; f1 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f1=&amp;quot;+t);
            return t;
        }
    });
    CompletableFuture&amp;lt;Integer&amp;gt; f2 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f2=&amp;quot;+t);
            return t;
        }
    });

    CompletableFuture&amp;lt;Integer&amp;gt; result = f1.applyToEither(f2, new Function&amp;lt;Integer, Integer&amp;gt;() {
        @Override
        public Integer apply(Integer t) {
            System.out.println(t);
            return t * 2;
        }
    });

    System.out.println(result.get());
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-10-accepteither-&quot;&gt;&lt;a name=&quot;10、acceptEither 方法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;10、acceptEither 方法&lt;/h2&gt;&lt;p&gt;两个CompletionStage，谁执行返回的结果快，我就用那个CompletionStage的结果进行下一步的消耗操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public CompletionStage&amp;lt;Void&amp;gt; acceptEither(CompletionStage&amp;lt;? extends T&amp;gt; other,Consumer&amp;lt;? super T&amp;gt; action);
public CompletionStage&amp;lt;Void&amp;gt; acceptEitherAsync(CompletionStage&amp;lt;? extends T&amp;gt; other,Consumer&amp;lt;? super T&amp;gt; action);
public CompletionStage&amp;lt;Void&amp;gt; acceptEitherAsync(CompletionStage&amp;lt;? extends T&amp;gt; other,Consumer&amp;lt;? super T&amp;gt; action,Executor executor);&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;private static void acceptEither() throws Exception {
    CompletableFuture&amp;lt;Integer&amp;gt; f1 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f1=&amp;quot;+t);
            return t;
        }
    });

    CompletableFuture&amp;lt;Integer&amp;gt; f2 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f2=&amp;quot;+t);
            return t;
        }
    });
    f1.acceptEither(f2, new Consumer&amp;lt;Integer&amp;gt;() {
        @Override
        public void accept(Integer t) {
            System.out.println(t);
        }
    });
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-11-runaftereither-&quot;&gt;&lt;a name=&quot;11、runAfterEither 方法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;11、runAfterEither 方法&lt;/h2&gt;&lt;p&gt;两个CompletionStage，任何一个完成了都会执行下一步的操作（Runnable）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public CompletionStage&amp;lt;Void&amp;gt; runAfterEither(CompletionStage&amp;lt;?&amp;gt; other,Runnable action);
public CompletionStage&amp;lt;Void&amp;gt; runAfterEitherAsync(CompletionStage&amp;lt;?&amp;gt; other,Runnable action);
public CompletionStage&amp;lt;Void&amp;gt; runAfterEitherAsync(CompletionStage&amp;lt;?&amp;gt; other,Runnable action,Executor executor);&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;private static void runAfterEither() throws Exception {
    CompletableFuture&amp;lt;Integer&amp;gt; f1 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f1=&amp;quot;+t);
            return t;
        }
    });

    CompletableFuture&amp;lt;Integer&amp;gt; f2 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f2=&amp;quot;+t);
            return t;
        }
    });
    f1.runAfterEither(f2, new Runnable() {

        @Override
        public void run() {
            System.out.println(&amp;quot;上面有一个已经完成了。&amp;quot;);
        }
    });
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-12-runafterboth&quot;&gt;&lt;a name=&quot;12、runAfterBoth&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;12、runAfterBoth&lt;/h2&gt;&lt;p&gt;两个CompletionStage，都完成了计算才会执行下一步的操作（Runnable）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public CompletionStage&amp;lt;Void&amp;gt; runAfterBoth(CompletionStage&amp;lt;?&amp;gt; other,Runnable action);
public CompletionStage&amp;lt;Void&amp;gt; runAfterBothAsync(CompletionStage&amp;lt;?&amp;gt; other,Runnable action);
public CompletionStage&amp;lt;Void&amp;gt; runAfterBothAsync(CompletionStage&amp;lt;?&amp;gt; other,Runnable action,Executor executor);&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;private static void runAfterBoth() throws Exception {
    CompletableFuture&amp;lt;Integer&amp;gt; f1 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f1=&amp;quot;+t);
            return t;
        }
    });

    CompletableFuture&amp;lt;Integer&amp;gt; f2 = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
        @Override
        public Integer get() {
            int t = new Random().nextInt(3);
            try {
                TimeUnit.SECONDS.sleep(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(&amp;quot;f2=&amp;quot;+t);
            return t;
        }
    });
    f1.runAfterBoth(f2, new Runnable() {

        @Override
        public void run() {
            System.out.println(&amp;quot;上面两个任务都执行完成了。&amp;quot;);
        }
    });
}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-13-thencompose-&quot;&gt;&lt;a name=&quot;13、thenCompose 方法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;13、thenCompose 方法&lt;/h2&gt;&lt;p&gt;thenCompose 方法允许你对两个 CompletionStage 进行流水线操作，第一个操作完成时，将其结果作为参数传递给第二个操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; thenCompose(Function&amp;lt;? super T, ? extends CompletionStage&amp;lt;U&amp;gt;&amp;gt; fn);
public &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; thenComposeAsync(Function&amp;lt;? super T, ? extends CompletionStage&amp;lt;U&amp;gt;&amp;gt; fn) ;
public &amp;lt;U&amp;gt; CompletableFuture&amp;lt;U&amp;gt; thenComposeAsync(Function&amp;lt;? super T, ? extends CompletionStage&amp;lt;U&amp;gt;&amp;gt; fn, Executor executor) ;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;h2-u793Au4F8B&quot;&gt;&lt;a name=&quot;示例&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;示例&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;private static void thenCompose() throws Exception {
        CompletableFuture&amp;lt;Integer&amp;gt; f = CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println(&amp;quot;t1=&amp;quot;+t);
                return t;
            }
        }).thenCompose(new Function&amp;lt;Integer, CompletionStage&amp;lt;Integer&amp;gt;&amp;gt;() {
            @Override
            public CompletionStage&amp;lt;Integer&amp;gt; apply(Integer param) {
                return CompletableFuture.supplyAsync(new Supplier&amp;lt;Integer&amp;gt;() {
                    @Override
                    public Integer get() {
                        int t = param *2;
                        System.out.println(&amp;quot;t2=&amp;quot;+t);
                        return t;
                    }
                });
            }

        });
        System.out.println(&amp;quot;thenCompose result : &amp;quot;+f.get());
    }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;原文：&lt;a href=&quot;https://zhuanlan.zhihu.com/p/348642967&quot;&gt;https://zhuanlan.zhihu.com/p/348642967&lt;/a&gt;&lt;/p&gt;
</description><pubDate>Wed, 31 May 2023 13:45:51 +0800</pubDate></item></channel></rss>