文章列表

【复刻】小智AI聊天机器人

作者:微信小助手

<section> <span leaf="">使用到的资料:</span> </section> <section> <span leaf=""><span textstyle="" style="font-weight: bold;">制作说明</span>:https://ccnphfhqs21z.feishu.cn/wiki/EH6wwrgvNiU7aykr7HgclP09nCh</span> </section> <section> <span leaf=""><span textstyle="" style="font-weight: bold;">管理平台</span>:https://xiaozhi.me/</span> </section> <section> <span leaf=""><span textstyle="" style="font-weight: bold;">开源代码</span>:https://github.com/78/xiaozhi-esp32</span> </section> <section> <span leaf=""><span textstyle="" style="font-weight: bold;">固件程序</span>:https://objects.githubusercontent.com/github-production-release-asset-2e65be/850267588/ac9e09b4-50a7-4aa7-88b8-7486f688f811?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=releaseassetproduction%2F20241223%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20241223T154246Z&amp;X-Amz-Expires=300&amp;X-Amz-Signature=53fa938abef5f0d4e672b8e68b84ab31bffcb3925546d1d18161339f25df1848&amp;X-Amz-SignedHeaders=host&amp;response-content-disposition=attachment%3B%20filename%3Dv0.9.7_bread-compact-wifi.zip&amp;response-content-type=application%2Foctet-stream</span> </section> <section style="text-align: center;" nodeleaf=""> <img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100000023" data-s="300,640" src="/upload/f9aaf5af670681289c62c4e8565ec39c.png" data-type="png" type="block"> </section> <section> <span leaf="">点绕圈中链接下载固件</span> </section> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>

Win11 锁屏和桌面Windows聚焦内容无法正常显示的修

作者:じ☆ve不哭

> Win11 锁屏和桌面Windows聚焦内容无法正常显示的修复方法 1、设置-个性化-锁屏界面-个性化锁屏界面切换到图片。 2、打开文件资源管理器,点击查看导航栏中的“显示”查看隐藏项。 3、删除c:用户\你的用户名\AppData\Local\Packages\Microsoft.Windows.ContentDeliveryManager_xxxxxxx\LocalState\Assets 文件夹里的所有文件(历史聚焦图片)。 4、删除c:用户\你的用户名\AppData\Local\Packages\Microsoft.Windows.ContentDeliveryManager_xxxxxxx\Settings文件夹里的所有文件(若提示文件占用,个性化里切回windows聚焦再切图片)。 5、个性化锁屏界面切换回windows聚焦。 6、等一小会儿,然后按Win + L查看效果。如果刚开始是黑色的,可以重启看看效果. 7、打开步骤3的assets文件夹。如果有新文件,则更新成功。

一个注解完美实现分布式锁

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-bottom: 0px;padding-left: 10px;padding-right: 10px;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;font-family: Optima, " microsoft yahei, pingfangsc-regular, serif;font-size: 16px;color: rgb(0, 0, 0);line-height: 1.5em;word-spacing: 0em;letter-spacing: 0em;word-break: break-word;text-align: left;> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-left: 20px;border-style: none none none solid;border-width: 1px 1px 1px 3px;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0) rgb(255, 177, 27);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">1. 业务背景</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数据库的数据进行操作,需要避免对之前的业务造成影响。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-left: 20px;border-style: none none none solid;border-width: 1px 1px 1px 3px;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0) rgb(255, 177, 27);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">2. 分析流程</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">使用 Redis 作为分布式锁,将锁的状态放到 Redis 统一维护,解决集群中单机 JVM 信息不互通的问题,规定操作顺序,保护用户的数据正确。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;"><strong style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">梳理设计流程</strong></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 新建注解 <code style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>@interface</code>,在注解里设定入参标志 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 增加 AOP 切点,扫描特定注解 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 建立 <code style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>@Aspect</code> 切面任务,注册 bean 和拦截特定方法 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 特定方法参数 <code style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>ProceedingJoinPoint</code>,对方法 <code style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>pjp.proceed()</code> 前后进行拦截 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 切点前进行加锁,任务执行后进行删除 key </section></li> </ul> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(255, 177, 27);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(58, 58, 58);font-size: 16px;line-height: 1.8em;letter-spacing: 0em;">核心步骤:加锁、解锁和续时</p> </blockquote> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 18px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">加锁</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">使用了 <code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>RedisTemplate</code> 的 <code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>opsForValue.setIfAbsent</code> 方法,判断是否有 key,设定一个随机数 <code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>UUID.random().toString</code>,生成一个随机数作为 value。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">从 redis 中获取锁之后,对 key 设定 <code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>expire</code> 失效时间,到期后自动释放锁。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">按照这种设计,只有第一个成功设定 Key 的请求,才能进行后续的数据操作,后续其它请求由于无法获得锁资源,将会失败结束。</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 18px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">超时问题</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">担心<code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;> pjp.proceed()</code> 切点执行的方法太耗时,导致 Redis 中的 key 由于超时提前释放了。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">例如,线程 A 先获取锁,<code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>proceed</code> 方法耗时,超过了锁超时时间,到期释放了锁,这时另一个线程 B 成功获取 Redis 锁,两个线程同时对同一批数据进行操作,导致数据不准确。</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 18px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">解决方案:增加一个「续时」</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">任务不完成,锁不释放:</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">维护了一个定时线程池 <code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>ScheduledExecutorService</code>,每隔 2s 去扫描加入队列中的 Task,判断是否失效时间是否快到了,公式为:<code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>【失效时间】&lt;= 【当前时间】+【失效间隔(三分之一超时)】</code></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="php"><code><span class="code-snippet_outer"><span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 线程池,每个 JVM 使用一个线程去维护 keyAliveTime,定时执行 runnable</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">static</span> <span class="code-snippet__keyword">final</span> ScheduledExecutorService SCHEDULER = </span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">new</span> ScheduledThreadPoolExecutor(<span class="code-snippet__number">1</span>, </span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">new</span> BasicThreadFactory.Builder().namingPattern(<span class="code-snippet__string">"redisLock-schedule-pool"</span>).daemon(<span class="code-snippet__keyword">true</span>).build());</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">static</span> {</span></code><code><span class="code-snippet_outer"> SCHEDULER.scheduleAtFixedRate(() -&gt; {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// do something to extend time</span></span></code><code><span class="code-snippet_outer"> }, <span class="code-snippet__number">0</span>, <span class="code-snippet__number">2</span>, TimeUnit.SECONDS);</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-left: 20px;border-style: none none none solid;border-width: 1px 1px 1px 3px;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0) rgb(255, 177, 27);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">3. 设计方案</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">经过上面的分析,设计出了这个方案:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100077200" data-ratio="0.6499508357915438" src="/upload/7e7fc9590a42376d79003986ba9a9ceb.png" data-type="png" data-w="1017" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> </figure> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">前面已经说了整体流程,这里强调一下几个核心步骤:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 拦截注解 @RedisLock,获取必要的参数 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 加锁操作 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 续时操作 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 结束业务,释放锁 </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-left: 20px;border-style: none none none solid;border-width: 1px 1px 1px 3px;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0) rgb(255, 177, 27);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">4. 实操</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">之前也有整理过 AOP 使用方法,可以参考一下</p> <blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgb(255, 177, 27);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(58, 58, 58);font-size: 16px;line-height: 1.8em;letter-spacing: 0em;">相关属性类配置</p> </blockquote> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">业务属性枚举设定</span> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="typescript"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">enum</span> RedisLockTypeEnum {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 自定义 key 前缀</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> ONE(<span class="code-snippet__string">"Business1"</span>, <span class="code-snippet__string">"Test1"</span>),</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> TWO(<span class="code-snippet__string">"Business2"</span>, <span class="code-snippet__string">"Test2"</span>);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> <span class="code-snippet__built_in">String</span> code;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> <span class="code-snippet__built_in">String</span> desc;</span></code><code><span class="code-snippet_outer"> RedisLockTypeEnum(<span class="code-snippet__built_in">String</span> code, <span class="code-snippet__built_in">String</span> desc) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.code = code;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.desc = desc;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">public</span> <span class="code-snippet__built_in">String</span> getCode() {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> code;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">public</span> <span class="code-snippet__built_in">String</span> getDesc() {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> desc;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">public</span> <span class="code-snippet__built_in">String</span> getUniqueKey(<span class="code-snippet__built_in">String</span> key) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> <span class="code-snippet__built_in">String</span>.format(<span class="code-snippet__string">"%s:%s"</span>, <span class="code-snippet__keyword">this</span>.getCode(), key);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section></h5> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">任务队列保存参数</span> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">class</span> <span class="code-snippet__title">RedisLockDefinitionHolder</span> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 业务唯一 key</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> String businessKey;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 加锁时间 (秒 s)</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> Long lockTime;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 上次更新时间(ms)</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> Long lastModifyTime;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 保存当前线程</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> Thread currentTread;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 总共尝试次数</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">int</span> tryCount;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 当前尝试次数</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">int</span> currentCount;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 更新的时间周期(毫秒),公式 = 加锁时间(转成毫秒) / 3</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">private</span> Long modifyPeriod;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">public</span> <span class="code-snippet__title">RedisLockDefinitionHolder</span>(<span class="code-snippet__params">String businessKey, Long lockTime, Long lastModifyTime, Thread currentTread, <span class="code-snippet__keyword">int</span> tryCount</span>)</span> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.businessKey = businessKey;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.lockTime = lockTime;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.lastModifyTime = lastModifyTime;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.currentTread = currentTread;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.tryCount = tryCount;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.modifyPeriod = lockTime * <span class="code-snippet__number">1000</span> / <span class="code-snippet__number">3</span>;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section></h5> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">设定被拦截的注解名字</span> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer">@Retention(RetentionPolicy.RUNTIME)</span></code><code><span class="code-snippet_outer">@Target({ElementType.METHOD, ElementType.TYPE})</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> @<span class="code-snippet__keyword">interface</span> <span class="code-snippet__title">RedisLockAnnotation</span> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 特定参数识别,默认取第 0 个下标</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">int</span> <span class="code-snippet__title">lockFiled</span>(<span class="code-snippet__params"></span>) <span class="code-snippet__keyword">default</span> 0</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 超时重试次数</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">int</span> <span class="code-snippet__title">tryCount</span>(<span class="code-snippet__params"></span>) <span class="code-snippet__keyword">default</span> 3</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 自定义加锁类型</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function">RedisLockTypeEnum <span class="code-snippet__title">typeEnum</span>(<span class="code-snippet__params"></span>)</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 释放时间,秒 s 单位</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__function"><span class="code-snippet__keyword">long</span> <span class="code-snippet__title">lockTime</span>(<span class="code-snippet__params"></span>) <span class="code-snippet__keyword">default</span> 30</span>;</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section></h5> <h5 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">核心切面拦截的操作</span><span style="display: none;"></span></h5> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;"><code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>RedisLockAspect.java</code> 该类分成三部分来描述具体作用</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;"><strong style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">Pointcut 设定</strong></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="css"><code><span class="code-snippet_outer"><span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * @annotation 中的路径表示拦截特定注解</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer">@<span class="code-snippet__keyword">Pointcut</span>("@<span class="code-snippet__keyword">annotation</span>(<span class="code-snippet__keyword">cn</span>.<span class="code-snippet__keyword">sevenyuan</span>.<span class="code-snippet__keyword">demo</span>.<span class="code-snippet__keyword">aop</span>.<span class="code-snippet__keyword">lock</span>.<span class="code-snippet__keyword">RedisLockAnnotation</span>)")</span></code><code><span class="code-snippet_outer">public void redisLockPC() {</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;"><strong style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">Around 前后进行加锁和释放锁</strong></p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">前面步骤定义了我们想要拦截的切点,下一步就是在切点前后做一些自定义操作:</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="typescript"><code><span class="code-snippet_outer"><span class="code-snippet__meta">@Around</span>(value = <span class="code-snippet__string">"redisLockPC()"</span>)</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> <span class="code-snippet__built_in">Object</span> around(ProceedingJoinPoint pjp) throws Throwable {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 解析参数</span></span></code><code><span class="code-snippet_outer"> Method method = resolveMethod(pjp);</span></code><code><span class="code-snippet_outer"> RedisLockAnnotation annotation = method.getAnnotation(RedisLockAnnotation.class);</span></code><code><span class="code-snippet_outer"> RedisLockTypeEnum typeEnum = annotation.typeEnum();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__built_in">Object</span>[] params = pjp.getArgs();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__built_in">String</span> ukString = params[annotation.lockFiled()].toString();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 省略很多参数校验和判空</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__built_in">String</span> businessKey = typeEnum.getUniqueKey(ukString);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__built_in">String</span> uniqueValue = UUID.randomUUID().toString();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 加锁</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__built_in">Object</span> result = <span class="code-snippet__literal">null</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">try</span> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__built_in">boolean</span> isSuccess = redisTemplate.opsForValue().setIfAbsent(businessKey, uniqueValue);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (!isSuccess) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">throw</span> <span class="code-snippet__keyword">new</span> Exception(<span class="code-snippet__string">"You can't do it,because another has get the lock =-="</span>);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> redisTemplate.expire(businessKey, annotation.lockTime(), TimeUnit.SECONDS);</span></code><code><span class="code-snippet_outer"> Thread currentThread = Thread.currentThread();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 将本次 Task 信息加入「延时」队列中</span></span></code><code><span class="code-snippet_outer"> holderList.add(<span class="code-snippet__keyword">new</span> RedisLockDefinitionHolder(businessKey, annotation.lockTime(), System.currentTimeMillis(),</span></code><code><span class="code-snippet_outer"> currentThread, annotation.tryCount()));</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 执行业务操作</span></span></code><code><span class="code-snippet_outer"> result = pjp.proceed();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 线程被中断,抛出异常,中断此次请求</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (currentThread.isInterrupted()) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">throw</span> <span class="code-snippet__keyword">new</span> InterruptedException(<span class="code-snippet__string">"You had been interrupted =-="</span>);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> } <span class="code-snippet__keyword">catch</span> (InterruptedException e ) {</span></code><code><span class="code-snippet_outer"> log.error(<span class="code-snippet__string">"Interrupt exception, rollback transaction"</span>, e);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">throw</span> <span class="code-snippet__keyword">new</span> Exception(<span class="code-snippet__string">"Interrupt exception, please send request again"</span>);</span></code><code><span class="code-snippet_outer"> } <span class="code-snippet__keyword">catch</span> (Exception e) {</span></code><code><span class="code-snippet_outer"> log.error(<span class="code-snippet__string">"has some error, please check again"</span>, e);</span></code><code><span class="code-snippet_outer"> } <span class="code-snippet__keyword">finally</span> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 请求结束后,强制删掉 key,释放锁</span></span></code><code><span class="code-snippet_outer"> redisTemplate.delete(businessKey);</span></code><code><span class="code-snippet_outer"> log.info(<span class="code-snippet__string">"release the lock, businessKey is ["</span> + businessKey + <span class="code-snippet__string">"]"</span>);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> result;</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">上述流程简单总结一下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 解析注解参数,获取注解值和方法上的参数值 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> redis 加锁并且设置超时时间 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 将本次 Task 信息加入「延时」队列中,进行续时,方式提前释放锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 加了一个线程中断标志 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 结束请求,finally 中释放锁 </section></li> </ul> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;"><strong style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">续时操作</strong></p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">这里用了 <code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>ScheduledExecutorService</code>,维护了一个线程,不断对任务队列中的任务进行判断和延长超时时间:</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span class="code-snippet_outer"><span class="code-snippet__comment">// 扫描的任务队列</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">static</span> ConcurrentLinkedQueue <redislockdefinitionholder> holderList = <span class="code-snippet__keyword">new</span> ConcurrentLinkedQueue(); </redislockdefinitionholder></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">/**</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 线程池,维护keyAliveTime</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">private</span> <span class="code-snippet__keyword">static</span> final ScheduledExecutorService SCHEDULER = <span class="code-snippet__keyword">new</span> ScheduledThreadPoolExecutor(<span class="code-snippet__number">1</span>,</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">new</span> BasicThreadFactory.Builder().namingPattern(<span class="code-snippet__string">"redisLock-schedule-pool"</span>).daemon(<span class="code-snippet__literal">true</span>).build());</span></code><code><span class="code-snippet_outer">{</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 两秒执行一次「续时」操作</span></span></code><code><span class="code-snippet_outer"> SCHEDULER.scheduleAtFixedRate(() -&gt; {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 这里记得加 try-catch,否者报错后定时任务将不会再执行=-=</span></span></code><code><span class="code-snippet_outer"> Iterator <redislockdefinitionholder> iterator = holderList.iterator(); </redislockdefinitionholder></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">while</span> (iterator.hasNext()) {</span></code><code><span class="code-snippet_outer"> RedisLockDefinitionHolder holder = iterator.next();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 判空</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (holder == <span class="code-snippet__literal">null</span>) {</span></code><code><span class="code-snippet_outer"> iterator.<span class="code-snippet__keyword">remove</span>();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">continue</span>;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 判断 key 是否还有效,无效的话进行移除</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (redisTemplate.opsForValue().<span class="code-snippet__keyword">get</span>(holder.getBusinessKey()) == <span class="code-snippet__literal">null</span>) {</span></code><code><span class="code-snippet_outer"> iterator.<span class="code-snippet__keyword">remove</span>();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">continue</span>;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 超时重试次数,超过时给线程设定中断</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (holder.getCurrentCount() &gt; holder.getTryCount()) {</span></code><code><span class="code-snippet_outer"> holder.getCurrentTread().interrupt();</span></code><code><span class="code-snippet_outer"> iterator.<span class="code-snippet__keyword">remove</span>();</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">continue</span>;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 判断是否进入最后三分之一时间</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">long</span> curTime = System.currentTimeMillis();</span></code><code><span class="code-snippet_outer"> boolean shouldExtend = (holder.getLastModifyTime() + holder.getModifyPeriod()) &lt;= curTime;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (shouldExtend) {</span></code><code><span class="code-snippet_outer"> holder.setLastModifyTime(curTime);</span></code><code><span class="code-snippet_outer"> redisTemplate.expire(holder.getBusinessKey(), holder.getLockTime(), TimeUnit.SECONDS);</span></code><code><span class="code-snippet_outer"> log.info(<span class="code-snippet__string">"businessKey : ["</span> + holder.getBusinessKey() + <span class="code-snippet__string">"], try count : "</span> + holder.getCurrentCount());</span></code><code><span class="code-snippet_outer"> holder.setCurrentCount(holder.getCurrentCount() + <span class="code-snippet__number">1</span>);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> }, <span class="code-snippet__number">0</span>, <span class="code-snippet__number">2</span>, TimeUnit.SECONDS);</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">这段代码,用来实现设计图中虚线框的思想,避免一个请求十分耗时,导致提前释放了锁。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">这里加了「线程中断」<code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>Thread#interrupt</code>,希望超过重试次数后,能让线程中断(未经严谨测试,仅供参考哈哈哈哈)</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">不过建议如果遇到这么耗时的请求,还是能够从根源上查找,分析耗时路径,进行业务优化或其它处理,避免这些耗时操作。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">所以记得多打点 Log,分析问题时可以更快一点。记录项目日志,一个注解搞定</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-left: 20px;border-style: none none none solid;border-width: 1px 1px 1px 3px;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0) rgb(255, 177, 27);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">5. 开始测试</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">在一个入口方法中,使用该注解,然后在业务中模拟耗时请求,使用了 <code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>Thread#sleep</code></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="kotlin"><code><span class="code-snippet_outer"><span class="code-snippet__meta">@GetMapping(<span class="code-snippet__meta-string">"/testRedisLock"</span>)</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__meta">@RedisLockAnnotation(typeEnum = RedisLockTypeEnum.ONE, lockTime = 3)</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> Book testRedisLock(<span class="code-snippet__meta">@RequestParam(<span class="code-snippet__meta-string">"userId"</span>)</span> <span class="code-snippet__built_in">Long</span> userId) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">try</span> {</span></code><code><span class="code-snippet_outer"> log.info(<span class="code-snippet__string">"睡眠执行前"</span>);</span></code><code><span class="code-snippet_outer"> Thread.sleep(<span class="code-snippet__number">10000</span>);</span></code><code><span class="code-snippet_outer"> log.info(<span class="code-snippet__string">"睡眠执行后"</span>);</span></code><code><span class="code-snippet_outer"> } <span class="code-snippet__keyword">catch</span> (Exception e) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// log error</span></span></code><code><span class="code-snippet_outer"> log.info(<span class="code-snippet__string">"has some error"</span>, e);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> <span class="code-snippet__literal">null</span>;</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">使用时,在方法上添加该注解,然后设定相应参数即可,根据 <code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>typeEnum</code> 可以区分多种业务,限制该业务被同时操作。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">测试结果:</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="css"><code><span class="code-snippet_outer">2020<span class="code-snippet__selector-tag">-04-04</span> 14<span class="code-snippet__selector-pseudo">:55</span><span class="code-snippet__selector-pseudo">:50.864</span> <span class="code-snippet__selector-tag">INFO</span> 9326 <span class="code-snippet__selector-tag">---</span> <span class="code-snippet__selector-attr">[nio-8081-exec-1]</span> <span class="code-snippet__selector-tag">c</span><span class="code-snippet__selector-class">.s</span><span class="code-snippet__selector-class">.demo</span><span class="code-snippet__selector-class">.controller</span><span class="code-snippet__selector-class">.BookController</span> : 睡眠执行前</span></code><code><span class="code-snippet_outer">2020<span class="code-snippet__selector-tag">-04-04</span> 14<span class="code-snippet__selector-pseudo">:55</span><span class="code-snippet__selector-pseudo">:52.855</span> <span class="code-snippet__selector-tag">INFO</span> 9326 <span class="code-snippet__selector-tag">---</span> <span class="code-snippet__selector-attr">[k-schedule-pool]</span> <span class="code-snippet__selector-tag">c</span><span class="code-snippet__selector-class">.s</span><span class="code-snippet__selector-class">.demo</span><span class="code-snippet__selector-class">.aop</span><span class="code-snippet__selector-class">.lock</span><span class="code-snippet__selector-class">.RedisLockAspect</span> : <span class="code-snippet__selector-tag">businessKey</span> : <span class="code-snippet__selector-attr">[Business1:1024]</span>, <span class="code-snippet__selector-tag">try</span> <span class="code-snippet__selector-tag">count</span> : 0</span></code><code><span class="code-snippet_outer">2020<span class="code-snippet__selector-tag">-04-04</span> 14<span class="code-snippet__selector-pseudo">:55</span><span class="code-snippet__selector-pseudo">:54.851</span> <span class="code-snippet__selector-tag">INFO</span> 9326 <span class="code-snippet__selector-tag">---</span> <span class="code-snippet__selector-attr">[k-schedule-pool]</span> <span class="code-snippet__selector-tag">c</span><span class="code-snippet__selector-class">.s</span><span class="code-snippet__selector-class">.demo</span><span class="code-snippet__selector-class">.aop</span><span class="code-snippet__selector-class">.lock</span><span class="code-snippet__selector-class">.RedisLockAspect</span> : <span class="code-snippet__selector-tag">businessKey</span> : <span class="code-snippet__selector-attr">[Business1:1024]</span>, <span class="code-snippet__selector-tag">try</span> <span class="code-snippet__selector-tag">count</span> : 1</span></code><code><span class="code-snippet_outer">2020<span class="code-snippet__selector-tag">-04-04</span> 14<span class="code-snippet__selector-pseudo">:55</span><span class="code-snippet__selector-pseudo">:56.851</span> <span class="code-snippet__selector-tag">INFO</span> 9326 <span class="code-snippet__selector-tag">---</span> <span class="code-snippet__selector-attr">[k-schedule-pool]</span> <span class="code-snippet__selector-tag">c</span><span class="code-snippet__selector-class">.s</span><span class="code-snippet__selector-class">.demo</span><span class="code-snippet__selector-class">.aop</span><span class="code-snippet__selector-class">.lock</span><span class="code-snippet__selector-class">.RedisLockAspect</span> : <span class="code-snippet__selector-tag">businessKey</span> : <span class="code-snippet__selector-attr">[Business1:1024]</span>, <span class="code-snippet__selector-tag">try</span> <span class="code-snippet__selector-tag">count</span> : 2</span></code><code><span class="code-snippet_outer">2020<span class="code-snippet__selector-tag">-04-04</span> 14<span class="code-snippet__selector-pseudo">:55</span><span class="code-snippet__selector-pseudo">:58.852</span> <span class="code-snippet__selector-tag">INFO</span> 9326 <span class="code-snippet__selector-tag">---</span> <span class="code-snippet__selector-attr">[k-schedule-pool]</span> <span class="code-snippet__selector-tag">c</span><span class="code-snippet__selector-class">.s</span><span class="code-snippet__selector-class">.demo</span><span class="code-snippet__selector-class">.aop</span><span class="code-snippet__selector-class">.lock</span><span class="code-snippet__selector-class">.RedisLockAspect</span> : <span class="code-snippet__selector-tag">businessKey</span> : <span class="code-snippet__selector-attr">[Business1:1024]</span>, <span class="code-snippet__selector-tag">try</span> <span class="code-snippet__selector-tag">count</span> : 3</span></code><code><span class="code-snippet_outer">2020<span class="code-snippet__selector-tag">-04-04</span> 14<span class="code-snippet__selector-pseudo">:56</span><span class="code-snippet__selector-pseudo">:00.857</span> <span class="code-snippet__selector-tag">INFO</span> 9326 <span class="code-snippet__selector-tag">---</span> <span class="code-snippet__selector-attr">[nio-8081-exec-1]</span> <span class="code-snippet__selector-tag">c</span><span class="code-snippet__selector-class">.s</span><span class="code-snippet__selector-class">.demo</span><span class="code-snippet__selector-class">.controller</span><span class="code-snippet__selector-class">.BookController</span> : <span class="code-snippet__selector-tag">has</span> <span class="code-snippet__selector-tag">some</span> <span class="code-snippet__selector-tag">error</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__selector-tag">java</span><span class="code-snippet__selector-class">.lang</span><span class="code-snippet__selector-class">.InterruptedException</span>: <span class="code-snippet__selector-tag">sleep</span> <span class="code-snippet__selector-tag">interrupted</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__selector-tag">at</span> <span class="code-snippet__selector-tag">java</span><span class="code-snippet__selector-class">.lang</span><span class="code-snippet__selector-class">.Thread</span><span class="code-snippet__selector-class">.sleep</span>(<span class="code-snippet__selector-tag">Native</span> <span class="code-snippet__selector-tag">Method</span>) <span class="code-snippet__selector-attr">[na:1.8.0_221]</span></span></code></pre> </section> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">我这里测试的是重试次数过多,失败的场景,如果减少睡眠时间,就能让业务正常执行。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">如果同时请求,你将会发现以下错误信息:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100077199" data-ratio="0.06111111111111111" src="/upload/299b8c326135208734e3668eea816e53.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> </figure> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">表示我们的锁🔐的确生效了,避免了重复请求。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-left: 20px;border-style: none none none solid;border-width: 1px 1px 1px 3px;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0) rgb(255, 177, 27);border-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">6. 总结</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">对于耗时业务和核心数据,不能让重复的请求同时操作数据,避免数据的不正确,所以要使用分布式锁来对它们进行保护。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">再来梳理一下设计流程:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 新建注解 <code style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>@interface</code>,在注解里设定入参标志 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 增加 AOP 切点,扫描特定注解 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 建立 <code style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>@Aspect</code> 切面任务,注册 bean 和拦截特定方法 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 特定方法参数 <code style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>ProceedingJoinPoint</code>,对方法 <code style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>pjp.proceed()</code> 前后进行拦截 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(58, 58, 58);font-size: 15px;line-height: 1.8em;letter-spacing: 0em;"> 切点前进行加锁,任务执行后进行删除 key </section></li> </ul> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">本次学习是通过 Review 小伙伴的代码设计,从中了解分布式锁的具体实现,仿照他的设计,重新写了一份简化版的业务处理。对于之前没考虑到的「续时」操作,这里使用了守护线程来定时判断和延长超时时间,避免了锁提前释放。</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">于是乎,同时回顾了三个知识点:</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">1、AOP 的实现和常用方法</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">2、定时线程池 <code style="color: rgb(155, 110, 35);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgb(255, 245, 227);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: " operator mono, consolas, monaco, menlo, monospace;word-break: break-all;>ScheduledExecutorService</code> 的使用和参数含义</p> <p data-tool="mdnice编辑器" style="color: rgb(58, 58, 58);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">3、线程 的含义以及用法(这个挺有意思的,可以深入再学习一下)</p> </section>

根据官方案例使用milvus向量数据库打造问答RAG系统

作者:微信小助手

<section style="max-width: 100%;box-sizing: border-box;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);overflow-wrap: break-word !important;margin-bottom: 0px;"> <section powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="padding-right: 10px;padding-left: 10px;max-width: 100%;box-sizing: border-box;line-height: 1.6;word-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="padding-right: 30px;padding-left: 30px;max-width: 100%;box-sizing: border-box;line-height: 1.6;word-wrap: break-word !important;"> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="Mzg2NzAzMDMwOA==" data-headimg="http://mmbiz.qpic.cn/sz_mmbiz_png/0arf3teEpjCjeKrgtiajadiadeQyY7ia9LBlhQNuGwdHvj5kk4Jm4Cos7ImoCE8lNh4pf2WGuUknxohQicBy7WlmNw/0?wx_fmt=png" data-nickname="烟火的成长" data-alias="" data-signature="记录个人成长,理财,感悟和思考" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(136, 136, 136);font-size: 20px;box-sizing: border-box !important;word-wrap: break-word !important;"></span></strong></p> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(136, 136, 136);font-size: 20px;box-sizing: border-box !important;word-wrap: break-word !important;">“</span></strong><span style="max-width: 100%;color: rgb(136, 136, 136);box-sizing: border-box !important;word-wrap: break-word !important;">&nbsp;向量数据库是RAG技术的重要底座之一&nbsp;<strong style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;font-size: 20px;box-sizing: border-box !important;word-wrap: break-word !important;">”</span></strong></span><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> </section> </section> </section> <section style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section style="padding-right: 22px;padding-left: 22px;max-width: 100%;box-sizing: border-box;line-height: 1.6;word-wrap: break-word !important;"> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> </section> </section> </section> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;">关于RAG和向量数据库的基础知识这里就不再做介绍了,不懂的可以翻看之前的文章。</p> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;"><br></p> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;">今天的主要目的是使用milvus向量数据库来实现RAG检索增强,后面会附上代码。</p> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;word-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> </section> </section> </section> </section> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;margin-bottom: 0px;"><br></p> <p style="margin-bottom: 0px;"><br></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;margin-bottom: 0px;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);white-space: normal;background-color: rgb(255, 255, 255);text-align: center;margin-bottom: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="font-size: 20px;">使用milvus实现RAG<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;margin-bottom: 0px;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <p style="max-width: 100%;min-height: 1em;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;margin-bottom: 0px;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <p style="margin-bottom: 0px;"><br></p> <section style="max-width: 100%;box-sizing: border-box;text-wrap-mode: wrap;background-color: rgb(255, 255, 255);margin-bottom: 0px;overflow-wrap: break-word !important;"> <section powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section style="padding-right: 10px;padding-left: 10px;max-width: 100%;box-sizing: border-box;line-height: 1.6;overflow-wrap: break-word !important;"> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">RAG的核心在于检索,而与传统字符匹配和分词检索方式不同的是RAG主要是基于语义检索的方式,也就是向量检索。而一个好的向量数据库就成为RAG技术环节中必不可少的一环。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <blockquote class="js_blockquote_wrap" data-type="1" data-url="https://mp.weixin.qq.com/s/07N-0FXwFFxoZI-WbJHs8g" data-author-name="DFires" data-content-utf8-length="91" data-source-title="RAG与本地知识库,向量数据库,以及知识图谱的联系与区别" style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;"> <section class="js_blockquote_digest"> <section> 备注:RAG并不是只支持向量检索,也可以使用传统的检索方式,主要根据不同的应用场景选择最合适的方式;RAG的核心是准确,高效的检索到有效数据,也就是说RAG重视的是结果,而不是过程。 </section> </section> <section class="blockquote_info js_blockquote_source" data-json="%7B%22type%22%3A%22inner%22%2C%22source%22%3A%22url%22%2C%22digest%22%3A%22%22%2C%22digestLen%22%3A91%2C%22text%22%3A%22%E5%A4%87%E6%B3%A8%EF%BC%9ARAG%E5%B9%B6%E4%B8%8D%E6%98%AF%E5%8F%AA%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E6%A3%80%E7%B4%A2%EF%BC%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E4%BC%A0%E7%BB%9F%E7%9A%84%E6%A3%80%E7%B4%A2%E6%96%B9%E5%BC%8F%EF%BC%8C%E4%B8%BB%E8%A6%81%E6%A0%B9%E6%8D%AE%E4%B8%8D%E5%90%8C%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF%E9%80%89%E6%8B%A9%E6%9C%80%E5%90%88%E9%80%82%E7%9A%84%E6%96%B9%E5%BC%8F%EF%BC%9BRAG%E7%9A%84%E6%A0%B8%E5%BF%83%E6%98%AF%E5%87%86%E7%A1%AE%EF%BC%8C%E9%AB%98%E6%95%88%E7%9A%84%E6%A3%80%E7%B4%A2%E5%88%B0%E6%9C%89%E6%95%88%E6%95%B0%E6%8D%AE%EF%BC%8C%E4%B9%9F%E5%B0%B1%E6%98%AF%E8%AF%B4RAG%E9%87%8D%E8%A7%86%E7%9A%84%E6%98%AF%E7%BB%93%E6%9E%9C%EF%BC%8C%E8%80%8C%E4%B8%8D%E6%98%AF%E8%BF%87%E7%A8%8B%E3%80%82%22%2C%22article%22%3A%7B%22title%22%3A%22RAG%E4%B8%8E%E6%9C%AC%E5%9C%B0%E7%9F%A5%E8%AF%86%E5%BA%93%EF%BC%8C%E5%90%91%E9%87%8F%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%8C%E4%BB%A5%E5%8F%8A%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1%E7%9A%84%E8%81%94%E7%B3%BB%E4%B8%8E%E5%8C%BA%E5%88%AB%22%2C%22url%22%3A%22https%3A%2F%2Fmp.weixin.qq.com%2Fs%2F07N-0FXwFFxoZI-WbJHs8g%22%2C%22nickname%22%3A%22AI%E6%8E%A2%E7%B4%A2%E6%97%B6%E4%BB%A3%22%2C%22authorName%22%3A%22DFires%22%7D%2C%22hasReportOverSize%22%3Afalse%2C%22editorReportData%22%3A%5B%7B%22id%22%3A%22122333%22%2C%22key%22%3A%2275%22%2C%22len%22%3A1%7D%2C%7B%22id%22%3A%22122333%22%2C%22key%22%3A%2277%22%2C%22len%22%3A1%7D%2C%7B%22id%22%3A%22122333%22%2C%22key%22%3A%2278%22%2C%22len%22%3A1%7D%5D%7D"> <span class="blockquote_biz">DFires,公众号:AI探索时代<a href="https://mp.weixin.qq.com/s/07N-0FXwFFxoZI-WbJHs8g" class="blockquote_article">RAG与本地知识库,向量数据库,以及知识图谱的联系与区别</a></span> </section> </blockquote> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">milvus是我国企业自主开发的一款向量数据库,根据其官方介绍,milvus既可以方便本地开发测试,也可以大规模集群部署支持上百亿的向量检索需求。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">而且,milvus同时还支持多种检索方式和算法:<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100002386" data-ratio="0.6675925925925926" data-s="300,640" src="/upload/f07c57712269fb879fc219109b426ea9.png" data-type="png" data-w="1080" style=""></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">并且集成多种语言的SDK:<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100002387" data-ratio="0.7230046948356808" data-s="300,640" src="/upload/b496c03161490be04ba5f8a04ee02255.png" data-type="png" data-w="852" style=""></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">当然,今天的主要目的不是为了给milvus产品打广告,而是使用milvus实现RAG——检索增强问答系统。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="25" data-source-title="milvus官方文档" style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;"> <section class="js_blockquote_digest"> <p>https://milvus.io/docs/zh</p> </section> <section class="blockquote_info js_blockquote_source" data-json="%7B%22type%22%3A%22out%22%2C%22source%22%3A%22url%22%2C%22digest%22%3A%22%3Cp%3Ehttps%3A%2F%2Fmilvus.io%2Fdocs%2Fzh%3C%2Fp%3E%22%2C%22digestLen%22%3A25%2C%22text%22%3A%22%22%2C%22article%22%3A%7B%7D%2C%22hasReportOverSize%22%3Afalse%2C%22editorReportData%22%3A%5B%7B%22id%22%3A%22122333%22%2C%22key%22%3A%2276%22%2C%22len%22%3A1%7D%5D%2C%22from%22%3A%22milvus%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%22%7D"> <span class="blockquote_other">milvus官方文档</span> </section> </blockquote> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;"> <mpcpc js_editor_cpcad="" class="js_cpc_area res_iframe cpc_iframe" src="/cgi-bin/readtemplate?t=tmpl/cpc_tmpl#1734253143487" data-category_id_list="1|2|5|6|7|8|16|17|21|24|28|29|31|35|36|37|39|41|42|43|46|47|48|50|51|55|56|57|58|59|60|61|62|63|64|65|66|67|68" data-id="1734253143487"></mpcpc></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><span style="font-size: 20px;"><em><strong>安装milvus</strong></em></span><span style="display: none;line-height: 0px;">‍‍‍‍‍‍‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">milvus有多种版本,每种版本支持的场景不太一样,但安装方式都非常简单。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">轻量级可以直接通过pip进行安装,并直接嵌入到python代码中:<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <section class="code-snippet__fix code-snippet__js"> <pre class="code-snippet__js" data-lang="nginx"><code><span class="code-snippet_outer"><span class="code-snippet__comment">#&nbsp;本地python&nbsp;sdk 使用</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attribute">pip</span> install mivlus</span></code></pre> </section> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">而单机版和集群版都是通过docker 进行安装,官方提供了详细的安装命令:<span style="display: none;line-height: 0px;">‍</span></p> <section class="code-snippet__fix code-snippet__js"> <pre class="code-snippet__js" data-lang="nginx"><code><span class="code-snippet_outer"><span class="code-snippet__comment">#&nbsp;单机安装&nbsp;linux系统 需要梯子</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attribute">curl</span> -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">bash standalone_embed.sh start</span></code><code><span class="code-snippet_outer"><br></span></code></pre> </section> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <table style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;"> <tbody> <tr> <td width="156.66666666666666" valign="top" style="word-break: break-all;">版本<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></td> <td width="118.66666666666666" valign="top" style="word-break: break-all;">说明</td> <td width="138" valign="top" style="word-break: break-all;">使用场景<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></td> <td width="138" valign="top" style="word-break: break-all;">数据量<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></td> </tr> <tr> <td width="157" valign="top" style="word-break: break-all;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;letter-spacing: 0.578px;background-color: rgb(255, 255, 255);">milvus Lite&nbsp;</span></td> <td width="118.66666666666666" valign="top" style="word-break: break-all;">轻量级版本</td> <td width="138" valign="top" style="word-break: break-all;">常用于本地开发使用<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></td> <td width="138" valign="top" style="word-break: break-all;">百万到千万级向量数据<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></td> </tr> <tr> <td width="157" valign="top" style="word-break: break-all;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;letter-spacing: 0.578px;background-color: rgb(255, 255, 255);">milvus Standalone</span></td> <td width="118.66666666666666" valign="top" style="word-break: break-all;">单机版<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></td> <td width="138" valign="top" style="word-break: break-all;">常用于小规模用户使用<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></td> <td width="138" valign="top" style="word-break: break-all;">上亿级向量数据<span style="display: none;line-height: 0px;">‍</span></td> </tr> <tr> <td width="157" valign="top" style="word-break: break-all;"><span style="caret-color: rgb(62, 62, 62);color: rgb(62, 62, 62);font-size: 16px;letter-spacing: 0.578px;background-color: rgb(255, 255, 255);">milvus Distributed</span></td> <td width="118.66666666666666" valign="top" style="word-break: break-all;">集群版<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></td> <td width="138" valign="top" style="word-break: break-all;">常用于大规模业务场景<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></td> <td width="138" valign="top" style="word-break: break-all;">百亿级向量数据</td> </tr> </tbody> </table> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">为了方便管理,milvus提供了一个可视化的客户端工具——attu,只需要一个命令就可以安装:<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <section class="code-snippet__fix code-snippet__js"> <pre class="code-snippet__js" data-lang="nginx"><code><span class="code-snippet_outer"><span class="code-snippet__comment">#&nbsp;attu&nbsp;安装命令&nbsp;这个{milvus&nbsp;server&nbsp;IP}&nbsp;要换成你自己的服务器地址或本地地址 </span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attribute">docker</span>&nbsp;run&nbsp;-p&nbsp;<span class="code-snippet__number">8000</span>:<span class="code-snippet__number">3000</span>&nbsp;-e&nbsp;MILVUS_URL={<span class="code-snippet__attribute">milvus</span>&nbsp;server&nbsp;IP}:<span class="code-snippet__number">19530</span>&nbsp;zilliz/attu:v2.<span class="code-snippet__number">4</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">项目地址: https://github.com/zilliztech/attu</span></code></pre> </section> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">这里有一个坑,&nbsp;<span style="white-space:pre-wrap;caret-color: rgb(51, 51, 51);color: rgb(51, 51, 51);font-family: Consolas, " liberation mono, menlo, courier, monospace;font-size: 14px;letter-spacing: 0.578px;text-align: left;background-color: rgb(250, 250, 250);>zilliz/attu:v2.</span><span class="code-snippet__number" style="white-space:pre-wrap;max-width: 1000%;font-family: Consolas, " liberation mono, menlo, courier, monospace;font-size: 14px;letter-spacing: 0.578px;text-align: left;>4 attu的镜像有些平台会拉不下来,大家可以换个思路使用自己的电脑或者能够拉下attu镜像的服务器,拉取之后把使用 save命令把镜像保存下来,然后再部署到你的电脑或服务器上。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><span class="code-snippet__number" style="white-space:pre-wrap;max-width: 1000%;font-family: Consolas, " liberation mono, menlo, courier, monospace;font-size: 14px;letter-spacing: 0.578px;text-align: left;><br></span></p> <p style="text-align: left;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><span style="color:#0e9ce5;font-family:Consolas, Liberation Mono, Menlo, Courier, monospace;"><span style="caret-color: rgb(14, 156, 229);font-size: 14px;white-space-collapse: preserve;">当然,attu客户端工具并不是必须的,只是方便管理milvus ,也可以不使用。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></span></span></p> <p style="text-align: left;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><span style="color:#0e9ce5;font-family:Consolas, Liberation Mono, Menlo, Courier, monospace;"><span style="caret-color: rgb(14, 156, 229);font-size: 14px;white-space-collapse: preserve;"><br></span></span></p> <p style="text-align: left;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><span style="color:#0e9ce5;font-family:Consolas, Liberation Mono, Menlo, Courier, monospace;"><span style="caret-color: rgb(14, 156, 229);font-size: 14px;white-space-collapse: preserve;">如下是attu的管理页面:<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></span></span></p> <p style="text-align: left;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><span style="color:#0e9ce5;font-family:Consolas, Liberation Mono, Menlo, Courier, monospace;"><span style="caret-color: rgb(14, 156, 229);font-size: 14px;white-space-collapse: preserve;"><br></span></span></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100002388" data-ratio="0.6361111111111111" data-s="300,640" src="/upload/c1bc05f20dcbd9e0cbfa1c588a03bfd4.png" data-type="png" data-w="1080" style=""></p> <p style="text-align: left;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">用户本地或在服务上安装好milvus向量数据库之后,就可以直接使用了。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">在python 中连接milvus数据库的方式有两种,然后用户所有与milvus数据库的操作都可以基于milvus_client客户端对象实现。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <section class="code-snippet__fix code-snippet__js"> <pre class="code-snippet__js" data-lang="ini"><code><span class="code-snippet_outer"><span class="code-snippet__comment"># milvus 客户端有两种连接方式 一种是本地开发测试使用 一种是独立部署</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># 独立部署方式</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># milvus_client = MilvusClient(</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># uri="你的milvus ip:19530",</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># token="root:milvus" # 默认用户名和密码 </span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># )</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># 本地方式 会在本地创建一个milvus 数据库</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__attr">milvus_client</span> = MilvusClient(<span class="code-snippet__string">"milvus_demo.db"</span>)</span></code></pre> </section> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">其次,milvus也像传统的关系型数据库一样,拥有数据库的概念;不同的数据可以放到不同的数据库中,默认数据库就是default,如果你没有声明或创建数据库,那么你的数据默认都在default数据库中。</p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">但milvus的主要操作是通过Collections来实现的;Collections就类似于传统数据库中的表结构。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">如下图所示:</p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100002389" data-ratio="0.65" data-s="300,640" src="/upload/d8754a33bba7c95255eca7b2da82ab67.png" data-type="png" data-w="1080" style=""></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">在学会milvus数据库的基本使用之后,就可以通过嵌入模型把数据导入到milvus数据库的collection中;然后通过调用大模型实现RAG问答。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="65" data-source-title=""> <section class="js_blockquote_digest"> <section> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">详细实现可以查看官方文档:<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;">https://milvus.io/docs/zh/build-rag-with-milvus.md</p> </section> </section> </blockquote> <p>完整代码如下所示,这里使用的是阿里云的通义千问模型,嵌入模型使用的也是阿里云的嵌入模型;当然milvus官方也提供了一些嵌入模型,用户也可以根据自己的喜好选择一些第三方的模型。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p><br></p> <p>需要安装的python 包,当然作者这里只是记录了一部分包,如果代码执行出错提示缺包,用户自行下载即可。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p><br></p> <section class="code-snippet__fix code-snippet__js"> <pre class="code-snippet__js" data-lang="sql"><code><span class="code-snippet_outer">!pip <span class="code-snippet__keyword">install</span> jq</span></code><code><span class="code-snippet_outer">!pip <span class="code-snippet__keyword">install</span> pymilvus[<span class="code-snippet__keyword">model</span>]</span></code><code><span class="code-snippet_outer">!pip <span class="code-snippet__keyword">install</span> pymilvus</span></code><code><span class="code-snippet_outer">!pip <span class="code-snippet__keyword">install</span> tqdm</span></code><code><span class="code-snippet_outer">!pip <span class="code-snippet__keyword">install</span> openai</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment">#&nbsp;python&nbsp;环境是3.9</span></span></code></pre> </section> <p><br><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p><span style="letter-spacing: 0.578px;background-color: rgb(255, 255, 255);">这里的代码是完整的可执行代码,只需要把文件路径换成你本地下载的文件即可;还有就是大模型客户端可以选择你自己的模型和key。</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p><br></p> <p>这里的测试文件使用的是milvus官方提供的问题文档,下载地址:<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="javascript"><code><span class="code-snippet_outer">https:<span class="code-snippet__comment">//github.com/milvus-io/milvus-docs/releases/download/v2.4.6-preview/milvus_docs_2.4.x_en.zip</span></span></code></pre> </section> <section class="code-snippet__fix code-snippet__js"> <pre class="code-snippet__js" data-lang="python"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">from</span> glob <span class="code-snippet__keyword">import</span> glob</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">from</span> openai <span class="code-snippet__keyword">import</span> OpenAI</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">from</span> pymilvus <span class="code-snippet__keyword">import</span> MilvusClient</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">from</span> tqdm <span class="code-snippet__keyword">import</span> tqdm</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">import</span> json</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">text_lines = []</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># 路径 milvus_docs/en/faq/*.md</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">for</span> file_path <span class="code-snippet__keyword">in</span> glob(<span class="code-snippet__string">"换成你的文件路径"</span>, recursive=<span class="code-snippet__keyword">True</span>):</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">with</span> open(file_path, <span class="code-snippet__string">"r"</span>) <span class="code-snippet__keyword">as</span> file:</span></code><code><span class="code-snippet_outer"> file_text = file.read()</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> text_lines += file_text.split(<span class="code-snippet__string">"# "</span>)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">print(<span class="code-snippet__string">"text_lines: "</span>, text_lines)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># 这里使用的是openai的工具包 可以连接openai, 通义千问, 等多种兼容openai格式的大模型服务商 这里使用的是阿里的通义千问模型</span></span></code><code><span class="code-snippet_outer">openai_client = OpenAI(</span></code><code><span class="code-snippet_outer"> api_key=<span class="code-snippet__string">"换成你的key"</span>,</span></code><code><span class="code-snippet_outer"> base_url=<span class="code-snippet__string">"https://dashscope.aliyuncs.com/compatible-mode/v1"</span></span></code><code><span class="code-snippet_outer">)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># 嵌入模型</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">def</span> <span class="code-snippet__title">emb_text</span><span class="code-snippet__params">(text)</span>:</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> (</span></code><code><span class="code-snippet_outer"> openai_client.embeddings.create(input=text, model=<span class="code-snippet__string">"text-embedding-v3"</span>).data[<span class="code-snippet__number">0</span>].embedding</span></code><code><span class="code-snippet_outer"> )</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">test_embedding = emb_text(<span class="code-snippet__string">"This is a test"</span>)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">embedding_dim = len(test_embedding)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">print(embedding_dim)</span></code><code><span class="code-snippet_outer">print(test_embedding[:<span class="code-snippet__number">10</span>])</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># milvus 客户端有两种连接方式 一种是本地开发测试使用 一种是独立部署</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># 独立部署方式</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># milvus_client = MilvusClient(</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># uri="你的milvus ip:19530",</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># token="root:milvus"</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># )</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"># 本地方式 会在本地创建一个milvus 数据库</span></span></code><code><span class="code-snippet_outer">milvus_client = MilvusClient(<span class="code-snippet__string">"milvus_demo.db"</span>)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">collection_name = <span class="code-snippet__string">"my_rag_collection"</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">if</span> milvus_client.has_collection(collection_name):</span></code><code><span class="code-snippet_outer"> milvus_client.drop_collection(collection_name)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">milvus_client.create_collection(</span></code><code><span class="code-snippet_outer"> collection_name=collection_name,</span></code><code><span class="code-snippet_outer"> dimension=embedding_dim,</span></code><code><span class="code-snippet_outer"> metric_type=<span class="code-snippet__string">"IP"</span>,</span></code><code><span class="code-snippet_outer"> consistency_level=<span class="code-snippet__string">"Strong"</span></span></code><code><span class="code-snippet_outer">)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">data = []</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">for</span> i, line <span class="code-snippet__keyword">in</span> enumerate(tqdm(text_lines, desc=<span class="code-snippet__string">"Creating embeddings"</span>)):</span></code><code><span class="code-snippet_outer"> data.append({<span class="code-snippet__string">"id"</span>: i, <span class="code-snippet__string">"vector"</span>: emb_text(line), <span class="code-snippet__string">"text"</span>: line})</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">print(<span class="code-snippet__string">"data: "</span>, len(data))</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">milvus_client.insert(collection_name=collection_name, data=data)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">question = <span class="code-snippet__string">"How is data stored in milvus?"</span></span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">search_res = milvus_client.search(</span></code><code><span class="code-snippet_outer"> collection_name=collection_name,</span></code><code><span class="code-snippet_outer"> data=[</span></code><code><span class="code-snippet_outer"> emb_text(question)</span></code><code><span class="code-snippet_outer"> ],</span></code><code><span class="code-snippet_outer"> limit=<span class="code-snippet__number">3</span>,</span></code><code><span class="code-snippet_outer"> search_params={<span class="code-snippet__string">"metric_type"</span>: <span class="code-snippet__string">"IP"</span>, <span class="code-snippet__string">"params"</span>: {}},</span></code><code><span class="code-snippet_outer"> output_fields=[<span class="code-snippet__string">"text"</span>]</span></code><code><span class="code-snippet_outer">)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">retrieved_lines_with_distances = [</span></code><code><span class="code-snippet_outer"> (res[<span class="code-snippet__string">"entity"</span>][<span class="code-snippet__string">"text"</span>], res[<span class="code-snippet__string">"distance"</span>]) <span class="code-snippet__keyword">for</span> res <span class="code-snippet__keyword">in</span> search_res[<span class="code-snippet__number">0</span>]</span></code><code><span class="code-snippet_outer">]</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">print(<span class="code-snippet__string">"retrieved: "</span>, retrieved_lines_with_distances)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">print(json.dumps(retrieved_lines_with_distances, indent=<span class="code-snippet__number">4</span>))</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">context = <span class="code-snippet__string">"\n"</span>.join(</span></code><code><span class="code-snippet_outer"> [line_with_distance[<span class="code-snippet__number">0</span>] <span class="code-snippet__keyword">for</span> line_with_distance <span class="code-snippet__keyword">in</span> retrieved_lines_with_distances]</span></code><code><span class="code-snippet_outer">)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">SYSTEM_PROMPT = <span class="code-snippet__string">"""</span></span></code><code><span class="code-snippet_outer">Human: You are an AI assistant. You are able to find answers to the questions from the contextual passage snippets provided.</span></code><code><span class="code-snippet_outer">"""</span></code><code><span class="code-snippet_outer">USER_PROMPT = <span class="code-snippet__string">f"""</span></span></code><code><span class="code-snippet_outer">Use the following pieces of information enclosed in <context> tags to provide an answer to the question enclosed in <question> tags. </question> </context></span></code><code><span class="code-snippet_outer"> <context></context></span></code><code><span class="code-snippet_outer"><span class="code-snippet__subst">{context}</span></span></code><code><span class="code-snippet_outer"></span></code><code><span class="code-snippet_outer"> <question></question></span></code><code><span class="code-snippet_outer"><span class="code-snippet__subst">{question}</span></span></code><code><span class="code-snippet_outer"></span></code><code><span class="code-snippet_outer">"""</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer">response = openai_client.chat.completions.create(</span></code><code><span class="code-snippet_outer">&nbsp;&nbsp;&nbsp;&nbsp;model=<span class="code-snippet__string">"qwen-plus"</span>,&nbsp;<span class="code-snippet__comment">#&nbsp;可以换成你自己喜欢的模型</span></span></code><code><span class="code-snippet_outer"> messages=[</span></code><code><span class="code-snippet_outer"> {<span class="code-snippet__string">"role"</span>: <span class="code-snippet__string">"system"</span>, <span class="code-snippet__string">"content"</span>: SYSTEM_PROMPT},</span></code><code><span class="code-snippet_outer"> {<span class="code-snippet__string">"role"</span>: <span class="code-snippet__string">"user"</span>, <span class="code-snippet__string">"content"</span>: USER_PROMPT},</span></code><code><span class="code-snippet_outer"> ],</span></code><code><span class="code-snippet_outer">)</span></code><code><span class="code-snippet_outer">print(response.choices[<span class="code-snippet__number">0</span>].message.content)</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"><br></span></code></pre> </section> <p style="color: rgb(62, 62, 62);font-size: 16px;white-space-collapse: collapse;max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><br></p> </section> </section> </section> </section> <p style="margin-bottom: 0px;">当然,上面文章写的不明白的地方,用户可以自己看milvus的官方文档,操作起来真的特别简单,这个案例就是作者自己根据milvus的官方文档实现的。<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="margin-bottom: 0px;"><br></p> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="58" data-source-title=""> <section class="js_blockquote_digest"> <section> <p style="margin-bottom: 0px;">文档地址:https://milvus.io/docs/zh/build-rag-with-milvus.md<span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span><span style="display: none;line-height: 0px;">‍</span></p> <p style="margin-bottom: 0px;"><br></p> </section> </section> </blockquote> <p><br></p> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="Mzg2NzAzMDMwOA==" data-headimg="http://mmbiz.qpic.cn/sz_mmbiz_png/0arf3teEpjCjeKrgtiajadiadeQyY7ia9LBlhQNuGwdHvj5kk4Jm4Cos7ImoCE8lNh4pf2WGuUknxohQicBy7WlmNw/0?wx_fmt=png" data-nickname="烟火的成长" data-alias="" data-signature="记录个人成长,理财,感悟和思考" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p style="margin-bottom: 0px;"><br></p> <p><br></p> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>

Billd-Live:竟然bilibili也开源了吗?完美的在线视频解决方案,需要的同学还不抓紧入手,知识点特别多~~~

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 10px;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;font-family: Optima, 'Microsoft YaHei', PingFangSC-regular, serif;font-size: 16px;color: rgb(0, 0, 0);line-height: 1.5em;word-spacing: 0em;letter-spacing: 0em;word-break: break-word;overflow-wrap: break-word;text-align: left;"> <h1 data-tool="mdnice编辑器" style="max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;margin: 0px;padding: 8px 10px;font-weight: 400;font-size: 14px;color: rgb(0, 0, 0);font-family: Optima, " microsoft yahei, pingfangsc-regular, serif;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;orphans: 2;text-align: left;text-indent: 0em;text-transform: none;widows: 2;word-spacing: 0em;-webkit-text-stroke-width: 0px;white-space: normal;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;-webkit-tap-highlight-color: transparent;outline: 0px;background: none 0% auto no-repeat scroll padding-box border-box rgb(255, 255, 255);line-height: 1.8em;letter-spacing: 0.02em;width: auto;word-break: break-word;visibility: visible;><span leaf="">嗨,大家好,我是小华同学,关注我们获得“</span><strong style="max-width: 100%;overflow-wrap: break-word !important;box-sizing: border-box !important;margin: 0px;padding: 0px;-webkit-tap-highlight-color: transparent;outline: 0px;visibility: visible;"><span leaf="">最新、最全、最优质</span></strong><span leaf="">”开源项目和高效工作学习方法</span></h1> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> </figure> <blockquote style="margin-top: 20px;margin-bottom: 20px;margin-left: 0px;margin-right: 0px;padding-top: 10px;padding-bottom: 10px;padding-left: 20px;padding-right: 10px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgb(53, 179, 120);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;background-attachment: scroll;background-clip: border-box;background-color: rgb(251, 249, 253);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;display: block;overflow-x: auto;overflow-y: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;text-align: left;font-weight: normal;"></span> <p style="text-indent: 0em;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;margin-top: 0px;margin-right: 0px;margin-bottom: 0px;margin-left: 0px;"><span leaf="">Billd-Live 是一个基于 Vue3 + WebRTC + Node + SRS + FFmpeg 搭建的直播间项目,实现了类似 bilibili 的 Web 在线直播功能。你可以作为房主发布直播,也可以作为观众进入别人的直播间观看直播内容。</span></p> </blockquote> <h2 data-cacheurl="" data-remoteid="" data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-left: 0px;padding-right: 0px;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 50%;background-position-y: 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;text-align: left;text-shadow: none;transform: none;-webkit-box-reflect: unset;background-image: url(https://mmbiz.qpic.cn/mmbiz_png/mRgvnruIwyCiaTibDzxHwJfACLlBIf5Bv1wQkflOG6U88ickrzUEPSKrBt5YpibgfsyvLjzh3NkktVCp2nScib9osKA/640?wx_fmt=png&amp;from=appmsg);"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">特色功能</span></span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">Billd-Live 拥有丰富的功能,包括但不限于:</span></p> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">原生 webrtc 推拉流</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">srs webrtc 推流,支持&nbsp;</span><code style="background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">http-flv</span></code><span leaf="">、</span><code style="background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">hls</span></code><span leaf="">、</span><code style="background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">webrtc</span></code><span leaf="">、</span><code style="background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">rtmp</span></code><span leaf="">拉流</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">msr 推流,ffmpeg 转码,支持&nbsp;</span><code style="background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">http-flv</span></code><span leaf="">、</span><code style="background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">hls</span></code><span leaf="">、</span><code style="background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">webrtc</span></code><span leaf="">、</span><code style="background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">rtmp</span></code><span leaf="">拉流</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">一对一打 PK</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">一对多打 PK</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">多对多打 PK</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">多平台转推(b 站、虎牙直播)</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">前端混流</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">推流鉴权</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">拉流鉴权</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">OBS、FFmpeg推流</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">用户模块(qq 登录)</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">支付模块(支付宝当面付)</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">订单模块</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">商品模块</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">礼物模块</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">直播后台</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">响应式页面</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">适配多语言(i18n)</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">移动端App(Flutter)</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">客户端App(Electron)</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">接入bilibili直播</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">接入腾讯云(云直播)</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">接入腾讯云(实时音视频 TRTC)</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">私有化部署</span> </section></li> </ul> <h2 data-cacheurl="" data-remoteid="" data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-left: 0px;padding-right: 0px;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 50%;background-position-y: 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;text-align: left;text-shadow: none;transform: none;-webkit-box-reflect: unset;background-image: url(https://mmbiz.qpic.cn/mmbiz_png/mRgvnruIwyCiaTibDzxHwJfACLlBIf5Bv1wQkflOG6U88ickrzUEPSKrBt5YpibgfsyvLjzh3NkktVCp2nScib9osKA/640?wx_fmt=png&amp;from=appmsg);"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">技术栈</span></span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">Billd-Live 使用了以下技术栈:</span></p> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">前端相关</span></strong><span leaf="">:Vue3 以及相关技术栈、Typescript、WebRTC、WebCodecs、Web Workder、Web Audio、Canvas</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">后端相关</span></strong><span leaf="">:Nodejs 以及相关技术栈、Koa2、Sequelize、Mysql、Redis、Socket.io</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">流媒体服务器相关</span></strong><span leaf="">:SRS、 FFmpeg、Coturn</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">Docker 相关</span></strong><span leaf="">:Docker</span> </section></li> </ul> <h2 data-cacheurl="" data-remoteid="" data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-left: 0px;padding-right: 0px;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 50%;background-position-y: 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;text-align: left;text-shadow: none;transform: none;-webkit-box-reflect: unset;background-image: url(https://mmbiz.qpic.cn/mmbiz_png/mRgvnruIwyCiaTibDzxHwJfACLlBIf5Bv1wQkflOG6U88ickrzUEPSKrBt5YpibgfsyvLjzh3NkktVCp2nScib9osKA/640?wx_fmt=png&amp;from=appmsg);"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">生态</span></span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">以下是 Billd-Live 项目的相关组件:</span></p> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <p style="color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">直播间前台</span></strong><span leaf="">:</span><span leaf="">billd-live</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">线上地址</span></p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <p style="color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">直播间后端</span></strong><span leaf="">:</span><span leaf="">billd-live-server</span><span leaf=""><br></span><span leaf=""></span><span leaf=""><br></span><span leaf="">线上地址</span></p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <p style="color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">直播间后台</span></strong><span leaf="">:</span><span leaf="">billd-live-admin</span><span leaf=""><br></span><span leaf=""></span><span leaf=""><br></span><span leaf="">线上地址</span></p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <p style="color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">直播间移动端</span></strong><span leaf="">:</span><span leaf="">billd-live-flutter</span><span leaf=""><br></span><span leaf=""></span><span leaf=""><br></span><span leaf="">下载地址</span></p> </section></li> </ul> <h2 data-cacheurl="" data-remoteid="" data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-left: 0px;padding-right: 0px;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 50%;background-position-y: 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;text-align: left;text-shadow: none;transform: none;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">本地启动</span></span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">Billd-Live 提供了详细的本地启动指南,包括安装依赖、更新依赖、运行和打包步骤。</span></p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span data-cacheurl="" data-remoteid="" style="background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;color: rgb(0, 0, 0);display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-top: 0px;margin-bottom: -2px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;background-image: url(https://mmbiz.qpic.cn/mmbiz_png/mRgvnruIwyCiaTibDzxHwJfACLlBIf5Bv1mVzTcY4kAzsHIlpzIeiber4oKo3UYjMW01foMYiaaMwia7ofhexgibTR6w/640?wx_fmt=png&amp;from=appmsg);"></span><span style="display: none;"></span><span style="font-size: 16px;color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 8px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">billd-live</span></span><span style="display: none;"></span></h3> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">安装依赖(建议使用 node 版本:v18.19.0)</span> </section></li> </ul> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd3eF7SQNH3UcAsXXvIAnM1HCROrPVIqZA9VsQIU0HIaO8PEwbG0C17ZJ69KTWgL7HnibMethI4aaNTdb39bRaBCX/640?wx_fmt=svg&amp;from=appmsg);"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span leaf="">pnpm i</span><span leaf=""><br></span></code></pre> <h2 data-cacheurl="" data-remoteid="" data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-left: 0px;padding-right: 0px;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 50%;background-position-y: 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;text-align: left;text-shadow: none;transform: none;-webkit-box-reflect: unset;background-image: url(https://mmbiz.qpic.cn/mmbiz_png/mRgvnruIwyCiaTibDzxHwJfACLlBIf5Bv1wQkflOG6U88ickrzUEPSKrBt5YpibgfsyvLjzh3NkktVCp2nScib9osKA/640?wx_fmt=png&amp;from=appmsg);"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">应用场景</span></span><span style="display: none;"></span></h2> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">教育直播</span></strong><span leaf="">: 可以用于在线教育、远程授课等场景,实现师生互动和实时教学。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">游戏直播</span></strong><span leaf="">: 可以用于游戏直播、电竞比赛等场景,实现游戏画面和声音的实时传输。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">电商直播</span></strong><span leaf="">: 可以用于电商直播、直播带货等场景,实现商品展示和交易。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">企业直播</span></strong><span leaf="">: 可以用于企业培训、会议直播等场景,实现远程协作和沟通。</span> </section></li> </ul> <h2 data-cacheurl="" data-remoteid="" data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-left: 0px;padding-right: 0px;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 50%;background-position-y: 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;text-align: left;text-shadow: none;transform: none;-webkit-box-reflect: unset;background-image: url(https://mmbiz.qpic.cn/mmbiz_png/mRgvnruIwyCiaTibDzxHwJfACLlBIf5Bv1wQkflOG6U88ickrzUEPSKrBt5YpibgfsyvLjzh3NkktVCp2nScib9osKA/640?wx_fmt=png&amp;from=appmsg);"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">同类项目</span></span><span style="display: none;"></span></h2> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">OBS</span></strong><span leaf="">: 开源的视频直播和屏幕录制软件,功能强大,但需要一定的技术门槛。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">XSplit</span></strong><span leaf="">: 商业化的视频直播和屏幕录制软件,功能丰富,但需要付费使用。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">Bilibili 直播</span></strong><span leaf="">: Bilibili 官方提供的直播平台,功能完善,但需要满足一定的条件才能开通直播权限。</span> </section></li> </ul> <h2 data-cacheurl="" data-remoteid="" data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-left: 0px;padding-right: 0px;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 50%;background-position-y: 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;text-align: left;text-shadow: none;transform: none;-webkit-box-reflect: unset;background-image: url(https://mmbiz.qpic.cn/mmbiz_png/mRgvnruIwyCiaTibDzxHwJfACLlBIf5Bv1wQkflOG6U88ickrzUEPSKrBt5YpibgfsyvLjzh3NkktVCp2nScib9osKA/640?wx_fmt=png&amp;from=appmsg);"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">预览</span></span><span style="display: none;"></span></h2> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">电脑端 (Web</span></strong><span leaf="">)</span> </section></li> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">首页:展示热门直播和推荐直播。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">直播间:观看直播,发送弹幕,赠送礼物。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">发起直播:选择直播主题,设置直播参数,开始直播。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">排行榜:查看主播排行榜和观众排行榜。</span> </section></li> </ul> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <span leaf=""><img src="/upload/4505504b5cdb6a1d0d83d511276f1742.jpg" class="rich_pages wxw-img" data-ratio="0.562962962962963" data-type="jpeg" data-w="1080" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;" data-imgfileid="100003895"></span> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <span leaf=""><img src="/upload/9b4239f05f2ff7b85b72466cc9398256.png" class="rich_pages wxw-img" data-ratio="0.562962962962963" data-type="png" data-w="1080" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;" data-imgfileid="100003900"></span> </figure> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">移动端 (Web</span></strong><span leaf="">)</span> </section></li> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">首页:展示热门直播和推荐直播。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">直播间:观看直播,发送弹幕,赠送礼物。</span> </section></li> </ul> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <span leaf=""><img src="/upload/098a9365a96a6f79c4c97732d6803e8f.png" class="rich_pages wxw-img" data-ratio="1.7804878048780488" data-type="png" data-w="410" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;" data-imgfileid="100003896"></span> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <span leaf=""><img src="/upload/87d4c6db36ef7f81b6a7f40e3a68add5.png" class="rich_pages wxw-img" data-ratio="1.7761557177615572" data-type="png" data-w="411" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;" data-imgfileid="100003897"></span> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <span leaf=""><img src="/upload/d5b1b69b6e444c40f10c0b164da49e82.png" class="rich_pages wxw-img" data-ratio="1.8389830508474576" data-type="png" data-w="944" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;" data-imgfileid="100003898"></span> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <span leaf=""><img src="/upload/d85fa816c5d3c6bd452e41fbaa3bbb9a.png" class="rich_pages wxw-img" data-ratio="1.8389830508474576" data-type="png" data-w="944" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;" data-imgfileid="100003899"></span> </figure> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <strong style="color: rgb(74, 74, 74);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">安卓端 (Flutter</span></strong><span leaf="">)</span> </section></li> <ul style="list-style-type: disc;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">直播间详情、分区详情</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;font-weight: normal;"> <span leaf="">直播中心:查看个人信息,管理直播房间。</span> </section></li> </ul> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <span leaf=""><img src="/upload/d85fa816c5d3c6bd452e41fbaa3bbb9a.png" class="rich_pages wxw-img" data-ratio="1.8389830508474576" data-type="png" data-w="944" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;" data-imgfileid="100003905"></span> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <span leaf=""><img src="/upload/b9ef54819635f4acd768e99a4c7409bd.png" class="rich_pages wxw-img" data-ratio="1.8484848484848484" data-type="png" data-w="792" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;" data-imgfileid="100003904"></span> </figure> <h2 data-cacheurl="" data-remoteid="" data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-left: 0px;padding-right: 0px;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 50%;background-position-y: 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;text-align: left;text-shadow: none;transform: none;-webkit-box-reflect: unset;background-image: url(https://mmbiz.qpic.cn/mmbiz_png/mRgvnruIwyCiaTibDzxHwJfACLlBIf5Bv1wQkflOG6U88ickrzUEPSKrBt5YpibgfsyvLjzh3NkktVCp2nScib9osKA/640?wx_fmt=png&amp;from=appmsg);"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">总结</span></span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);font-size: 16px;line-height: 1.8em;letter-spacing: 0.02em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 16px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">Billd-Live 是一个功能强大、易于使用的直播解决方案,适用于各种直播场景。它具有原生 WebRTC 推拉流、多种推拉流方式、互动功能、多平台转推、前端混流、鉴权功能等特点,并支持移动端 App、客户端 App 和私有化部署。</span></p> <h2 data-cacheurl="" data-remoteid="" data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-left: 0px;padding-right: 0px;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-origin: padding-box;background-position-x: 50%;background-position-y: 50%;background-repeat: no-repeat;background-size: 63px;width: auto;height: auto;align-items: unset;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;text-align: left;text-shadow: none;transform: none;-webkit-box-reflect: unset;background-image: url(https://mmbiz.qpic.cn/mmbiz_png/mRgvnruIwyCiaTibDzxHwJfACLlBIf5Bv1wQkflOG6U88ickrzUEPSKrBt5YpibgfsyvLjzh3NkktVCp2nScib9osKA/640?wx_fmt=png&amp;from=appmsg);"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(72, 179, 120);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: transparent;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">项目地址</span></span><span style="display: none;"></span></h2> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(https://mmbiz.qpic.cn/mmbiz_svg/wzJhLVPsrd3eF7SQNH3UcAsXXvIAnM1HCROrPVIqZA9VsQIU0HIaO8PEwbG0C17ZJ69KTWgL7HnibMethI4aaNTdb39bRaBCX/640?wx_fmt=svg&amp;from=appmsg);"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span leaf="">https://github.com/galaxy-s10/billd-live</span><span leaf=""><br></span></code></pre> </section> <section> <span leaf=""><br></span> </section> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>

Springboot集成Easy Rules引擎,实现一个商品优惠券系统

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 10px;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;font-family: Optima, PingFangSC-light, serif;font-size: 16px;color: rgb(0, 0, 0);line-height: 1.5em;word-spacing: 0em;letter-spacing: 0em;word-break: break-word;overflow-wrap: break-word;text-align: left;"> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">Easy Rules是一个轻量级的Java规则引擎,它允许开发者将业务规则从代码中解耦出来,使规则的管理和执行更加灵活。</span></p> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">现在让我们一起利用Spring Boot结合Easy Rules和MyBatis技术栈,可以实现一个高效且易于维护的优惠券系统例子。</span></p> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">1. 添加依赖项</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">在&nbsp;</span><code style="color: rgb(248, 57, 41);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">pom.xml</span></code><span leaf="">&nbsp;文件中确保包含以下依赖项:</span></p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependencies</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""><!-- Spring Boot Starter Web --></span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.springframework.boot</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">spring-boot-starter-web</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""><!-- MyBatis Framework --></span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.mybatis.spring.boot</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">mybatis-spring-boot-starter</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf="">3.0.1</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""><!-- MySQL Connector --></span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">mysql</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">mysql-connector-java</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">scope</span></span><span leaf="">&gt;</span></span><span leaf="">runtime</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">scope</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""><!-- Easy Rules Core --></span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.jeasy</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">easy-rules-core</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf="">4.4.0</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""><!-- Easy Rules Support for Spring --></span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.jeasy</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">easy-rules-support-spring</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf="">4.4.0</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">version</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""><!-- Lombok (Optional, for reducing boilerplate code) --></span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.projectlombok</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">lombok</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">optional</span></span><span leaf="">&gt;</span></span><span leaf="">true</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">optional</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""><!-- Test Dependencies --></span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf="">org.springframework.boot</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">groupId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf="">spring-boot-starter-test</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">artifactId</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">&lt;</span><span style="color: #e06c75;line-height: 26px;"><span leaf="">scope</span></span><span leaf="">&gt;</span></span><span leaf="">test</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">scope</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependency</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span><span style="line-height: 26px;"><span leaf=""><!--/</span--><span style="color: #e06c75;line-height: 26px;"><span leaf="">dependencies</span></span><span leaf="">&gt;</span></span><span leaf=""><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">2. 配置 MyBatis 和数据库连接</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">在&nbsp;</span><code style="color: rgb(248, 57, 41);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">application.properties</span></code><span leaf="">&nbsp;文件中配置数据库连接信息和 MyBatis 设置:</span></p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span leaf=""># 数据库配置</span><span leaf=""><br></span><span leaf="">spring.datasource.url=jdbc:mysql://localhost:3306/coupon_system?useSSL=false&amp;serverTimezone=UTC</span><span leaf=""><br></span><span leaf="">spring.datasource.username=root</span><span leaf=""><br></span><span leaf="">spring.datasource.password=root</span><span leaf=""><br></span><span leaf="">spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""># MyBatis 配置</span><span leaf=""><br></span><span leaf="">mybatis.type-aliases-package=com.example.couponsystemdb.model</span><span leaf=""><br></span><span leaf="">mybatis.mapper-locations=classpath*:mapper/*.xml</span><span leaf=""><br></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">3. 定义业务实体类</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">我们需要定义一些业务实体类来表示购物车、商品和优惠券信息。</span></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 5px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(248, 57, 41);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">ShoppingCart.java</span></span><span style="display: none;"></span></h2> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">package</span></span><span leaf="">&nbsp;com.example.couponsystemdb.model;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;lombok.Data;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;java.util.ArrayList;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;java.util.List;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Data</span></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">class</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">ShoppingCart</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">double</span></span><span leaf="">&nbsp;totalPrice =&nbsp;</span><span style="color: #d19a66;line-height: 26px;"><span leaf="">0</span></span><span leaf="">;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 购物车总价格</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">final</span></span><span leaf="">&nbsp;List <item> items =&nbsp; </item></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">new</span></span><span leaf="">&nbsp;ArrayList&lt;&gt;();&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 商品列表</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 向购物车添加商品</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;item 商品对象</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">void</span></span><span leaf="">&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">addItem</span></span><span style="line-height: 26px;"><span leaf="">(Item item)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; items.add(item);</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; totalPrice += item.getPrice();</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 应用折扣</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;discount 折扣比例(例如 0.10 表示 10% 折扣)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">void</span></span><span leaf="">&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">applyDiscount</span></span><span style="line-height: 26px;"><span leaf="">(</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">double</span></span><span leaf="">&nbsp;discount)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; totalPrice -= totalPrice * discount;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span></code></pre> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 5px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(248, 57, 41);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">Item.java</span></span><span style="display: none;"></span></h2> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">package</span></span><span leaf="">&nbsp;com.example.couponsystemdb.model;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;lombok.Data;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Data</span></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">class</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">Item</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;String name;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 商品名称</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;String category;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 商品类别</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">double</span></span><span leaf="">&nbsp;price;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 商品价格</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 构造函数</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;name 商品名称</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;category 商品类别</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;price 商品价格</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">Item</span></span><span style="line-height: 26px;"><span leaf="">(String name, String category,&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">double</span></span><span leaf="">&nbsp;price)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">this</span></span><span leaf="">.name = name;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">this</span></span><span leaf="">.category = category;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">this</span></span><span leaf="">.price = price;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span></code></pre> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 5px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(248, 57, 41);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">CouponRule.java</span></span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">这个类用于从数据库加载规则信息。</span></p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">package</span></span><span leaf="">&nbsp;com.example.couponsystemdb.model;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;lombok.Data;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Data</span></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">class</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">CouponRule</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;Long id;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 规则ID</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;String ruleName;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 规则名称</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;String description;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 规则描述</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;String conditionExpression;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 条件表达式</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;String actionExpression;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 动作表达式</span></span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">4. 创建数据库表和初始化数据</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">创建一个名为&nbsp;</span><code style="color: rgb(248, 57, 41);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">coupon_system</span></code><span leaf="">&nbsp;的数据库,并在其中创建&nbsp;</span><code style="color: rgb(248, 57, 41);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">coupon_rule</span></code><span leaf="">&nbsp;表。然后插入一些初始规则数据。</span></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 5px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(248, 57, 41);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">SQL 脚本</span></span><span style="display: none;"></span></h2> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">CREATE</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">DATABASE</span></span><span leaf="">&nbsp;coupon_system;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">USE</span></span><span leaf="">&nbsp;coupon_system;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">CREATE</span></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">TABLE</span></span><span leaf="">&nbsp;coupon_rule (</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">id</span></span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">BIGINT</span></span><span leaf="">&nbsp;AUTO_INCREMENT PRIMARY&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">KEY</span></span><span leaf="">,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; rule_name&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">VARCHAR</span></span><span leaf="">(</span><span style="color: #d19a66;line-height: 26px;"><span leaf="">255</span></span><span leaf="">)&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">NOT</span></span><span style="color: #56b6c2;line-height: 26px;"><span leaf="">NULL</span></span><span leaf="">,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; description&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">TEXT</span></span><span leaf="">,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; condition_expression&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">TEXT</span></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">NOT</span></span><span style="color: #56b6c2;line-height: 26px;"><span leaf="">NULL</span></span><span leaf="">,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; action_expression&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">TEXT</span></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">NOT</span></span><span style="color: #56b6c2;line-height: 26px;"><span leaf="">NULL</span></span><span leaf=""><br></span><span leaf="">);</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">INSERT</span></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">INTO</span></span><span leaf="">&nbsp;coupon_rule (rule_name, description, condition_expression, action_expression)&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">VALUES</span></span><span leaf=""><br></span><span leaf="">(</span><span style="color: #98c379;line-height: 26px;"><span leaf="">'Total Price Discount Rule'</span></span><span leaf="">,&nbsp;</span><span style="color: #98c379;line-height: 26px;"><span leaf="">'Offers a 10% discount if the total price exceeds 1000'</span></span><span leaf="">,</span><span leaf=""><br></span><span style="color: #98c379;line-height: 26px;"><span leaf="">'cart.getTotalPrice() &gt; 1000'</span></span><span leaf="">,</span><span leaf=""><br></span><span style="color: #98c379;line-height: 26px;"><span leaf="">'cart.applyDiscount(0.10); System.out.println("Applied 10% discount for total price exceeding 1000");'</span></span><span leaf="">),</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">(</span><span style="color: #98c379;line-height: 26px;"><span leaf="">'Category Discount Rule'</span></span><span leaf="">,&nbsp;</span><span style="color: #98c379;line-height: 26px;"><span leaf="">'Offers an additional 5% discount on Electronics category'</span></span><span leaf="">,</span><span leaf=""><br></span><span style="color: #98c379;line-height: 26px;"><span leaf="">'cart.getItems().stream().anyMatch(item -&gt; item.getCategory().equals("Electronics"))'</span></span><span leaf="">,</span><span leaf=""><br></span><span style="color: #98c379;line-height: 26px;"><span leaf="">'cart.applyDiscount(0.05); System.out.println("Applied 5% discount for purchasing electronics");'</span></span><span leaf="">);</span><span leaf=""><br></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">5. 定义动态规则</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">我们将使用 Easy Rules 的&nbsp;</span><code style="color: rgb(248, 57, 41);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">DynamicRule</span></code><span leaf="">&nbsp;类来从数据库加载规则并执行。</span></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 5px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(248, 57, 41);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">DynamicCouponRule.java</span></span><span style="display: none;"></span></h2> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">package</span></span><span leaf="">&nbsp;com.example.couponsystemdb.rule;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.annotation.Action;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.annotation.Condition;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.annotation.Fact;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.api.Facts;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.core.DynamicRule;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.beans.factory.annotation.Autowired;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.stereotype.Component;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Component</span></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">class</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">DynamicCouponRule</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">extends</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">DynamicRule</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Autowired</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;CouponService couponService;</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 评估条件</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;facts 事实对象</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@return</span></span><span leaf="">&nbsp;是否满足条件</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@throws</span></span><span leaf="">&nbsp;Exception 异常</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Override</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Condition</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">boolean</span></span><span leaf="">&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">evaluate</span></span><span style="line-height: 26px;"><span leaf="">(Facts facts)</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">throws</span></span><span leaf="">&nbsp;Exception&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">return</span></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">super</span></span><span leaf="">.evaluate(facts);</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 执行动作</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;facts 事实对象</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@throws</span></span><span leaf="">&nbsp;Exception 异常</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Override</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Action</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">void</span></span><span leaf="">&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">execute</span></span><span style="line-height: 26px;"><span leaf="">(Facts facts)</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">throws</span></span><span leaf="">&nbsp;Exception&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">super</span></span><span leaf="">.execute(facts);</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 从数据库设置规则</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;ruleId 规则ID</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">void</span></span><span leaf="">&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">setRuleFromDatabase</span></span><span style="line-height: 26px;"><span leaf="">(Long ruleId)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; CouponRule couponRule = couponService.getCouponRuleById(ruleId);</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; setName(couponRule.getRuleName());</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; setDescription(couponRule.getDescription());</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; setConditionExpression(couponRule.getConditionExpression());</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; setActionExpression(couponRule.getActionExpression());</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">6. 配置规则引擎</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">我们需要配置规则引擎并将规则注册到 Spring 上下文中。</span></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 5px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(248, 57, 41);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">EasyRulesConfig.java</span></span><span style="display: none;"></span></h2> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">package</span></span><span leaf="">&nbsp;com.example.couponsystemdb.config;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.api.RulesEngine;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.core.DefaultRulesEngine;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.context.annotation.Bean;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.context.annotation.Configuration;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Configuration</span></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">class</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">EasyRulesConfig</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 配置规则引擎</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@return</span></span><span leaf="">&nbsp;规则引擎实例</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Bean</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;RulesEngine&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">rulesEngine</span></span><span style="line-height: 26px;"><span leaf="">()</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">return</span></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">new</span></span><span leaf="">&nbsp;DefaultRulesEngine();</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">7. 创建 Service 层</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">创建一个服务层来处理与数据库的交互。</span></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 5px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(248, 57, 41);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">CouponService.java</span></span><span style="display: none;"></span></h2> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">package</span></span><span leaf="">&nbsp;com.example.couponsystemdb.service;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;com.example.couponsystemdb.mapper.CouponRuleMapper;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;com.example.couponsystemdb.model.CouponRule;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.beans.factory.annotation.Autowired;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.stereotype.Service;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Service</span></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">class</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">CouponService</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Autowired</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;CouponRuleMapper couponRuleMapper;</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 根据规则ID获取规则</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;id 规则ID</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@return</span></span><span leaf="">&nbsp;规则对象</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;CouponRule&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">getCouponRuleById</span></span><span style="line-height: 26px;"><span leaf="">(Long id)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">return</span></span><span leaf="">&nbsp;couponRuleMapper.selectById(id);</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">8. 创建 Mapper 接口</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">使用 MyBatis 映射器接口来操作数据库。</span></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 5px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(248, 57, 41);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">CouponRuleMapper.java</span></span><span style="display: none;"></span></h2> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">package</span></span><span leaf="">&nbsp;com.example.couponsystemdb.mapper;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;com.example.couponsystemdb.model.CouponRule;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.apache.ibatis.annotations.Mapper;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.apache.ibatis.annotations.Select;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Mapper</span></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">interface</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">CouponRuleMapper</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 根据规则ID查询规则</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;id 规则ID</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@return</span></span><span leaf="">&nbsp;规则对象</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Select</span></span><span leaf="">(</span><span style="color: #98c379;line-height: 26px;"><span leaf="">"SELECT * FROM coupon_rule WHERE id = #{id}"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span leaf="">CouponRule&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">selectById</span></span><span style="line-height: 26px;"><span leaf="">(Long id)</span></span></span><span leaf="">;</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">9. 编写主应用程序类</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">最后,在主应用程序类中使用规则引擎来处理购物车中的商品。</span></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow-x: unset;overflow-y: unset;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);line-height: 1.8em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 10px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: solid;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 5px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(248, 57, 41);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">CouponSystemApplication.java</span></span><span style="display: none;"></span></h2> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">package</span></span><span leaf="">&nbsp;com.example.couponsystemdb;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;com.example.couponsystemdb.model.ShoppingCart;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;com.example.couponsystemdb.model.Item;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;com.example.couponsystemdb.rule.DynamicCouponRule;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.api.Facts;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.api.Rules;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.jeasy.rules.api.RulesEngine;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.beans.factory.annotation.Autowired;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.boot.CommandLineRunner;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.boot.SpringApplication;</span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">import</span></span><span leaf="">&nbsp;org.springframework.boot.autoconfigure.SpringBootApplication;</span><span leaf=""><br></span><span leaf=""><br></span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@SpringBootApplication</span></span><span leaf=""><br></span><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">class</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">CouponSystemApplication</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">implements</span></span><span leaf="">&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">CommandLineRunner</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Autowired</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;RulesEngine rulesEngine;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 规则引擎</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Autowired</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;DynamicCouponRule dynamicCouponRule;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 动态规则</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Autowired</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">private</span></span><span leaf="">&nbsp;CouponService couponService;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 优惠券服务</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">static</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">void</span></span><span leaf="">&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">main</span></span><span style="line-height: 26px;"><span leaf="">(String[] args)</span></span><span leaf="">&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; SpringApplication.run(CouponSystemApplication</span><span style="line-height: 26px;"><span leaf="">.</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">class</span></span><span leaf="">,&nbsp;</span><span style="color: #e6c07b;line-height: 26px;"><span leaf="">args</span></span><span leaf="">)</span></span><span leaf="">;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">/**</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;* 应用启动时运行的方法</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@param</span></span><span leaf="">&nbsp;args 命令行参数</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">@throws</span></span><span leaf="">&nbsp;Exception 异常</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp;*/</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">@Override</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">public</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">void</span></span><span leaf="">&nbsp;</span><span style="color: #61aeee;line-height: 26px;"><span leaf="">run</span></span><span style="line-height: 26px;"><span leaf="">(String... args)</span></span><span leaf="">&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">throws</span></span><span leaf="">&nbsp;Exception&nbsp;</span></span><span leaf="">{</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; ShoppingCart cart =&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">new</span></span><span leaf="">&nbsp;ShoppingCart();&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 创建购物车</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; cart.addItem(</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">new</span></span><span leaf="">&nbsp;Item(</span><span style="color: #98c379;line-height: 26px;"><span leaf="">"Laptop"</span></span><span leaf="">,&nbsp;</span><span style="color: #98c379;line-height: 26px;"><span leaf="">"Electronics"</span></span><span leaf="">,&nbsp;</span><span style="color: #d19a66;line-height: 26px;"><span leaf="">1200</span></span><span leaf="">));&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 添加商品</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; cart.addItem(</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">new</span></span><span leaf="">&nbsp;Item(</span><span style="color: #98c379;line-height: 26px;"><span leaf="">"Book"</span></span><span leaf="">,&nbsp;</span><span style="color: #98c379;line-height: 26px;"><span leaf="">"Books"</span></span><span leaf="">,&nbsp;</span><span style="color: #d19a66;line-height: 26px;"><span leaf="">30</span></span><span leaf="">));&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 添加商品</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; Facts facts =&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">new</span></span><span leaf="">&nbsp;Facts();&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 创建事实对象</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; facts.put(</span><span style="color: #98c379;line-height: 26px;"><span leaf="">"cart"</span></span><span leaf="">, cart);&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 将购物车放入事实对象</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; Rules rules =&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">new</span></span><span leaf="">&nbsp;Rules();&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 创建规则集合</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 从数据库加载规则并注册到规则引擎</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; Long[] ruleIds = {</span><span style="color: #d19a66;line-height: 26px;"><span leaf="">1L</span></span><span leaf="">,&nbsp;</span><span style="color: #d19a66;line-height: 26px;"><span leaf="">2L</span></span><span leaf="">};&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 数据库中的规则ID</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">for</span></span><span leaf="">&nbsp;(Long ruleId : ruleIds) {</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dynamicCouponRule.setRuleFromDatabase(ruleId);&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 从数据库设置规则</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rules.register(dynamicCouponRule);&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 注册规则</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; rulesEngine.fire(rules, facts);&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 执行规则引擎</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(</span><span style="color: #98c379;line-height: 26px;"><span leaf="">"Final price after discounts: "</span></span><span leaf="">&nbsp;+ cart.getTotalPrice());&nbsp;</span><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">// 输出最终价格</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">运行应用程序</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">保存所有文件并在终端中运行以下命令启动应用程序:</span></p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span leaf="">mvn spring-boot:run</span><span leaf=""><br></span></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">你应该会看到以下输出:</span></p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;margin-top: 10px;margin-bottom: 10px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"><span data-cacheurl="" data-remoteid="" style="display: block;background: none;height: 30px;width: 100%;background-size: 40px;background-repeat: no-repeat;background-color: #282c34;margin-bottom: -7px;border-radius: 5px;background-position: 10px 10px;background-image: url(" https: mmbiz.qpic.cn mmbiz_svg n4hwkmwbsvrnjeax6mil2g3mwbiaiuouhzqkof8tlvvq7zjslspfnxcrru39ibzq80y1yqrpaq2xh9vbuwi5vxydd5378xmvq 640?wx_fmt="svg&amp;from=appmsg&quot;);&quot;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span leaf="">Applied 10% discount&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">for</span></span><span leaf="">&nbsp;total price exceeding 1000</span><span leaf=""><br></span><span leaf="">Applied 5% discount&nbsp;</span><span style="color: #c678dd;line-height: 26px;"><span leaf="">for</span></span><span leaf="">&nbsp;purchasing electronics</span><span leaf=""><br></span><span leaf="">Final price after discounts: 1071.0</span><span leaf=""><br></span></code></pre> <h1 data-tool="mdnice编辑器" style="border-top-color: rgb(248, 57, 41);margin-top: 30px;margin-bottom: 15px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: solid;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 2px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: center;line-height: 1.5em;overflow-x: unset;overflow-y: unset;text-align: center;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="display: none;"></span><span style="font-size: 20px;color: rgb(255, 255, 255);background-color: rgb(248, 57, 41);line-height: 1.8em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 1px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 13px;border-bottom-right-radius: 13px;box-shadow: none;display: block;font-weight: normal;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset;padding-top: 3px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span leaf="">总结</span></span><span style="display: none;"></span></h1> <p data-tool="mdnice编辑器" style="color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;text-indent: 0em;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 8px;padding-bottom: 8px;padding-left: 0px;padding-right: 0px;"><span leaf="">这个示例展示了如何在 Spring Boot 应用程序中集成 Easy Rules 和 MyBatis,以便通过数据库灵活配置优惠券规则。通过这种方式,你可以在不修改代码的情况下添加新的规则。以下是关键点总结:</span></p> <ol style="list-style-type: decimal;margin-top: 8px;margin-bottom: 8px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 25px;padding-right: 0px;color: rgb(0, 0, 0);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;font-weight: normal;"> <strong style="color: rgb(248, 57, 41);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">数据库配置</span></strong><span leaf="">:使用 MyBatis 和 MySQL 来存储和管理规则。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;font-weight: normal;"> <strong style="color: rgb(248, 57, 41);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">动态规则</span></strong><span leaf="">:使用 Easy Rules 的&nbsp;</span><code style="background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span leaf="">DynamicRule</span></code><span leaf="">&nbsp;类来加载和执行从数据库获取的规则。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;font-weight: normal;"> <strong style="color: rgb(248, 57, 41);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">服务层</span></strong><span leaf="">:通过服务层来处理与数据库的交互。</span> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(53, 53, 53);font-size: 16px;line-height: 1.8em;letter-spacing: 0.04em;text-align: left;font-weight: normal;"> <strong style="color: rgb(248, 57, 41);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;"><span leaf="">规则引擎配置</span></strong><span leaf="">:配置规则引擎并注册动态规则。</span> </section></li> </ol> </section>

Alibaba分布式事务组件Seata TCC模式实战

作者:微信小助手

<article> <p><span style="font-weight: bold;font-size: 20px;"><span leaf="">1. 什么是TCC</span></span></p> <p><span leaf="">TCC 基于分布式事务中的</span><span style="color: rgb(223, 64, 42);"><span leaf="">二阶段提交协议</span></span><span leaf="">实现,它的全称为 Try-Confirm-Cancel,即</span><span style="color: rgb(223, 64, 42);"><span leaf="">资源预留(Try)、确认操作(Confirm)、取消操作(Cancel)</span></span><span leaf="">,他们的具体含义如下:</span></p> <ol style="margin:0px;" class="list-paddingleft-1"> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#df402a;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:decimal;><span style="color: rgb(223, 64, 42);"><span leaf="">Try:对业务资源的检查并预留;</span></span></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#df402a;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:decimal;><span style="color: rgb(223, 64, 42);"><span leaf="">Confirm:对业务处理进行提交,即 commit 操作,只要 Try 成功,那么该步骤一定成功;</span></span></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#df402a;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:decimal;><span style="color: rgb(223, 64, 42);"><span leaf="">Cancel:对业务处理进行取消,即回滚操作,该步骤回对 Try 预留的资源进行释放。</span></span></li> </ol> </article> <section style="text-align: center;" nodeleaf=""> <img src="/upload/923b1e71610be7dd5a2772fd5386d1b6.png" class="rich_pages wxw-img" data-ratio="0.6074074074074074" data-s="300,640" data-type="png" data-w="1080" type="block" data-imgfileid="100001388"> </section> <article data-content="[{" type:block,id:4898-1657170593323,name:list-item,data:{version:1,listid:ujpa-1695360830930,listlevel:1,listtype:unordered,style:{lineheight:1.5}},nodes:[{type:text,id:zmm4-1695360797152,leaves:[{text:xa是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。,marks:[{type:bold},{type:color,value:#df402a},{type:backgroundcolor,value:#ffffff}]}]}],state:{}},{type:block,id:6861-1661927008339,name:list-item,data:{version:1,listid:ujpa-1695360830930,listlevel:1,listtype:unordered},nodes:[{type:text,id:8zw5-1695360797152,leaves:[{text:tcc是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。,marks:[{type:bold},{type:color,value:#df402a},{type:backgroundcolor,value:#ffffff}]}]}],state:{}}]> <ul style="margin:0px;" class="list-paddingleft-1"> <li style="white-space:pre-wrap;line-height:1.5;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#df402a;font-weight:bold;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="font-weight: bold;color: rgb(223, 64, 42);background-color: rgb(255, 255, 255);"><span leaf="">XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。</span></span></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#df402a;font-weight:bold;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="font-weight: bold;color: rgb(223, 64, 42);background-color: rgb(255, 255, 255);"><span leaf="">TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。</span></span></li> </ul> </article> <section> <span leaf=""><br></span> </section> <article> <p><span style="color: rgb(223, 64, 42);"><span leaf="">TCC 是一种侵入式的分布式事务解决方案</span></span><span leaf="">,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但</span><span style="color: rgb(223, 64, 42);"><span leaf="">优点是 TCC 完全不依赖数据库,能够实现跨数据库、跨应用资源管理</span></span><span leaf="">,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。</span></p> <p><span leaf="">常见开源TCC框架:</span></p> <ul style="list-style-type: disc;" class="list-paddingleft-1"> <li> <section> <span leaf="">Seata TCC</span> </section></li> <li> <section> <span leaf="">Hmily</span> </section></li> <li> <section> <span leaf="">Tcc-Transaction</span> </section></li> <li> <section> <span leaf="">ByteTCC</span> </section></li> <li> <section> <span leaf="">EasyTransaction</span> </section></li> </ul> <section> <span leaf=""><br></span> </section> </article> <article> <p><span style="font-weight: bold;font-size: 20px;"><span leaf="">2. 以用户下单为例</span></span></p> <p><span style="font-weight: bold;font-size: inherit;"><span leaf=""><span textstyle="" style="font-size: 18px;">try-commit</span></span></span></p> <p><span style="color: rgb(223, 64, 42);"><span leaf="">try 阶段首先进行预留资源,然后在 commit 阶段扣除资源。</span></span><span style="color: rgb(36, 41, 46);"><span leaf="">如下图:</span></span></p> <section nodeleaf=""> <img src="/upload/f184f8b4df30988c593484719814ffd8.png" class="rich_pages wxw-img" data-ratio="1.3592814371257484" data-type="png" data-w="501" style="width: 501px;height: 681px;" data-imgfileid="100001390"> </section> <p><span style="font-weight: bold;font-size: inherit;"></span></p> <p><span style="font-weight: bold;font-size: inherit;"><span leaf=""><span textstyle="" style="font-size: 18px;">try-cancel</span></span></span></p> <p><span style="color: rgb(223, 64, 42);"><span leaf="">try 阶段首先进行预留资源,预留资源时扣减库存失败导致全局事务回滚,在 cancel 阶段释放资源。</span></span><span style="color: rgb(36, 41, 46);"><span leaf="">如下图:</span></span></p> <section nodeleaf=""> <img src="/upload/8474331e5303fc8e6041450772593a4d.png" class="rich_pages wxw-img" data-ratio="1.3592814371257484" data-type="png" data-w="501" style="width: 501px;height: 681px;" data-imgfileid="100001389"> </section> </article> <section> <span leaf=""><br></span> </section> <article> <p><span style="color: rgb(36, 41, 46);font-weight: bold;font-size: 18px;"><span leaf="">3. Seata TCC 模式</span></span></p> <p><span leaf="">一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:</span></p> <ul style="list-style-type: disc;" class="list-paddingleft-1"> <li> <section> <span leaf="">一阶段 prepare 行为</span> </section></li> <li> <section> <span leaf="">二阶段 commit 或 rollback 行为</span> </section></li> </ul> <section nodeleaf=""> <img src="/upload/7053105a755e6e54300c1d5a5e25a11b.png" class="rich_pages wxw-img" data-ratio="0.5475247524752476" data-type="png" data-w="1010" style="width: 870px;height: 451px;" data-imgfileid="100001391"> </section> <p><span style="color: rgb(223, 64, 42);"><span leaf="">在Seata中,AT模式与TCC模式事实上都是两阶段提交的具体实现,</span></span><span style="color: rgb(36, 41, 46);"><span leaf="">他们的区别在于:</span></span></p> <p><span style="color: rgb(223, 64, 42);"><span leaf="">AT 模式基于 支持本地 ACID 事务的关系型数据库:</span></span></p> <ul style="margin:0px;" class="list-paddingleft-1"> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#24292e;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(36, 41, 46);"><span leaf="">一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。</span></span></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#24292e;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(36, 41, 46);"><span leaf="">二阶段 commit 行为:马上成功结束,自动异步批量清理回滚日志。</span></span></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#24292e;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(36, 41, 46);"><span leaf="">二阶段 rollback 行为:通过回滚日志,自动生成补偿操作,完成数据回滚。</span></span></li> </ul> <p><span style="color: rgb(36, 41, 46);"><span leaf="">相应的,</span></span><span style="color: rgb(223, 64, 42);"><span leaf="">TCC 模式不依赖于底层数据资源的事务支持:</span></span></p> <ul style="margin:0px;" class="list-paddingleft-1"> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#24292e;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(36, 41, 46);"><span leaf="">一阶段 prepare 行为:调用自定义的 prepare 逻辑。</span></span></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#24292e;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(36, 41, 46);"><span leaf="">二阶段 commit 行为:调用自定义的 commit 逻辑。</span></span></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#24292e;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(36, 41, 46);"><span leaf="">二阶段 rollback 行为:调用自定义的 rollback 逻辑。</span></span></li> </ul> <p><span leaf="">简单点概括,</span><span style="color: rgb(223, 64, 42);"><span leaf="">SEATA的TCC模式就是手工的AT模式,它允许你自定义两阶段的处理逻辑而不依赖AT模式的undo_log。</span></span></p> <section> <span leaf=""><br></span> </section> </article> <article data-content="[{" type:block,id:b2h0-1695360830953,name:heading,data:{version:1,level:h2},nodes:[{type:text,id:ao4l-1695360797155,leaves:[{text:2.4&nbsp;seata&nbsp;tcc模式接口如何改造,marks:[{type:bold},{type:fontsize,value:18}]}]}],state:{}},{type:block,id:8511-1661778066124,name:paragraph,data:{version:1},nodes:[{type:text,id:dhe6-1695360797155,leaves:[{text:假设现有一个业务需要同时使用服务&nbsp;a&nbsp;和服务&nbsp;b&nbsp;完成一个事务操作,我们在服务&nbsp;a&nbsp;定义该服务的一个&nbsp;tcc&nbsp;接口:,marks:[]}]}],state:{}}]> <p><span style="font-weight: bold;font-size: 18px;"><span leaf="">4. Seata TCC模式接口如何改造</span></span></p> <p><span leaf="">假设现有一个业务需要同时使用服务 A 和服务 B 完成一个事务操作,我们在服务 A 定义该服务的一个 TCC 接口:</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="typescript"><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">interface</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__title">TccActionOne</span><span class="code-snippet__plaintext">&nbsp;{</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__meta">@TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">(name =&nbsp;</span><span class="code-snippet__string">"prepare"</span><span class="code-snippet__plaintext">, commitMethod =&nbsp;</span><span class="code-snippet__string">"commit"</span><span class="code-snippet__plaintext">, rollbackMethod =&nbsp;</span><span class="code-snippet__string">"rollback"</span><span class="code-snippet__plaintext">)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__built_in">boolean</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__title">prepare</span><span class="code-snippet__plaintext">(</span><span class="code-snippet__title">BusinessActionContext</span><span class="code-snippet__plaintext">&nbsp;actionContext,&nbsp;</span><span class="code-snippet__meta">@BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName =&nbsp;</span><span class="code-snippet__string">"a"</span><span class="code-snippet__plaintext">)&nbsp;</span><span class="code-snippet__title">String</span><span class="code-snippet__plaintext">&nbsp;a);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__built_in">boolean</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__title">commit</span><span class="code-snippet__plaintext">(</span><span class="code-snippet__title">BusinessActionContext</span><span class="code-snippet__plaintext">&nbsp;actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__built_in">boolean</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__title">rollback</span><span class="code-snippet__plaintext">(</span><span class="code-snippet__title">BusinessActionContext</span><span class="code-snippet__plaintext">&nbsp;actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code></pre> </section> <article data-content="[{" type:block,id:5186-1657204029425,name:paragraph,data:{version:1},nodes:[{type:text,id:3m0i-1695360797156,leaves:[{text:同样,在服务&nbsp;b&nbsp;定义该服务的一个&nbsp;tcc&nbsp;接口:,marks:[]}]}],state:{}}]> <p><span leaf="">同样,在服务 B 定义该服务的一个 TCC 接口:</span></p> </article> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="cs"><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">interface</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__title">TccActionTwo</span><span class="code-snippet__plaintext">&nbsp;{</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; @TwoPhaseBusinessAction(name =&nbsp;</span><span class="code-snippet__string">"prepare"</span><span class="code-snippet__plaintext">, commitMethod =&nbsp;</span><span class="code-snippet__string">"commit"</span><span class="code-snippet__plaintext">, rollbackMethod =&nbsp;</span><span class="code-snippet__string">"rollback"</span><span class="code-snippet__plaintext">)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__function"><span class="code-snippet__keyword">public</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__function"><span class="code-snippet__keyword">void</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__function"><span class="code-snippet__title">prepare</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">(</span></span><span class="code-snippet__function"><span class="code-snippet__params"><span class="code-snippet__plaintext">BusinessActionContext actionContext, @BusinessActionContextParameter(paramName =&nbsp;</span></span></span><span class="code-snippet__function"><span class="code-snippet__params"><span class="code-snippet__string">"b"</span></span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">) String b)</span></span><span class="code-snippet__plaintext">;</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__function"><span class="code-snippet__keyword">public</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__function"><span class="code-snippet__keyword">void</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__function"><span class="code-snippet__title">commit</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">(</span></span><span class="code-snippet__function"><span class="code-snippet__params">BusinessActionContext actionContext</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">)</span></span><span class="code-snippet__plaintext">;</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__function"><span class="code-snippet__keyword">public</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__function"><span class="code-snippet__keyword">void</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__function"><span class="code-snippet__title">rollback</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">(</span></span><span class="code-snippet__function"><span class="code-snippet__params">BusinessActionContext actionContext</span></span><span class="code-snippet__function"><span class="code-snippet__plaintext">)</span></span><span class="code-snippet__plaintext">;</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code></pre> </section> <article data-content="[{" type:block,id:6556-1657203763728,name:paragraph,data:{version:1},nodes:[{type:text,id:l8ci-1695360797156,leaves:[{text:在业务所在系统中开启全局事务并执行服务&nbsp;a&nbsp;和服务&nbsp;b&nbsp;的&nbsp;tcc&nbsp;预留资源方法:,marks:[]}]}],state:{}}]> <p><span leaf="">在业务所在系统中开启全局事务并执行服务 A 和服务 B 的 TCC 预留资源方法:</span></p> </article> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="typescript"><code><span leaf=""><span class="code-snippet__meta">@GlobalTransactional</span></span></code><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__title">String</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__title">doTransactionCommit</span><span class="code-snippet__plaintext">(){</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">//服务A事务参与者</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; tccActionOne.prepare(null,"one");</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; //服务B事务参与者</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; tccActionTwo.prepare(null,"two");</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code></pre> </section> <article> <p><span leaf="">以上就是使用 Seata TCC 模式实现一个全局事务的例子,</span><span style="color: rgb(223, 64, 42);"><span leaf="">TCC 模式同样使用 @GlobalTransactional 注解开启全局事务</span></span><span leaf="">,而服务 A 和服务 B 的 TCC 接口为事务参与者,Seata 会把一个 TCC 接口当成一个 Resource,也叫 TCC Resource。</span></p> <p><span style="color: rgb(36, 41, 46);font-weight: bold;font-size: 18px;"><span leaf=""><br></span></span></p> <p><span style="color: rgb(36, 41, 46);font-weight: bold;font-size: 18px;"><span leaf="">5. TCC如何控制异常</span></span></p> <p><span style="color: rgb(223, 64, 42);"><span leaf="">在 TCC 模型执行的过程中,还可能会出现各种异常,其中最为常见的有空回滚、幂等、悬挂等。</span></span><span leaf="">TCC 模式是分布式事务中非常重要的事务模式,但是幂等、悬挂和空回滚一直是 TCC 模式需要考虑的问题,Seata 框架在 1.5.1 版本完美解决了这些问题。</span></p> <p><span leaf=""><br></span></p> <article> <p><span style="font-weight: bold;font-size: inherit;"><span leaf=""><span textstyle="" style="font-size: 18px;">如何处理空回滚</span></span></span></p> <p><span style="color: rgb(223, 64, 42);"><span leaf="">空回滚指的是在一个分布式事务中,在没有调用参与方的 Try 方法的情况下,TM 驱动二阶段回滚调用了参与方的 Cancel 方法。</span></span></p> <p><span style="font-weight: bold;"><span leaf="">那么空回滚是如何产生的呢?</span></span></p> <section nodeleaf=""> <img src="/upload/908b3fe021bd5260a30d19b20604d948.png" class="rich_pages wxw-img" data-ratio="0.7925925925925926" data-type="png" data-w="1080" style="width: 620px;height: 491px;" data-imgfileid="100001392"> </section> <p><span leaf="">如上图所示,全局事务开启后,参与者 A 分支注册完成之后会执行参与者一阶段 RPC 方法,如果此时参与者 A 所在的机器发生宕机,网络异常,都会造成 RPC 调用失败,即参与者 A 一阶段方法未成功执行,但是此时全局事务已经开启,Seata 必须要推进到终态,在全局事务回滚时会调用参与者 A 的 Cancel 方法,从而造成空回滚。</span></p> <p><span style="font-weight: bold;"><span leaf="">要想防止空回滚,那么必须在 Cancel 方法中识别这是一个空回滚,Seata 是如何做的呢?</span></span></p> <p><span style="color: rgb(223, 64, 42);"><span leaf="">Seata 的做法是新增一个 TCC 事务控制表</span></span><span leaf="">,包含事务的 XID 和 BranchID 信息,在 Try 方法执行时插入一条记录,表示一阶段执行了,执行 Cancel 方法时读取这条记录,如果记录不存在,说明 Try 方法没有执行。</span></p> <p><span style="font-weight: bold;font-size: inherit;"><span leaf=""><br></span></span></p> <p><span style="font-weight: bold;font-size: inherit;"><span leaf=""><span textstyle="" style="font-size: 18px;">如何处理幂等</span></span></span></p> <p><span style="color: rgb(223, 64, 42);"><span leaf="">幂等问题指的是 TC 重复进行二阶段提交,因此 Confirm/Cancel 接口需要支持幂等处理,即不会产生资源重复提交或者重复释放。</span></span></p> <p><span style="font-weight: bold;"><span leaf="">那么幂等问题是如何产生的呢?</span></span></p> </article> <section style="text-align: center;" nodeleaf=""> <img src="/upload/31bf33db9cd14d523f7ad2272c9906da.png" class="rich_pages wxw-img" data-ratio="0.7981481481481482" data-s="300,640" data-type="png" data-w="1080" type="block" data-imgfileid="100001393"> </section> <article> <p><span leaf="">如上图所示,参与者 A 执行完二阶段之后,由于网络抖动或者宕机问题,会造成 TC 收不到参与者 A 执行二阶段的返回结果,TC 会重复发起调用,直到二阶段执行结果成功。</span></p> <p><span style="font-weight: bold;"><span leaf=""><br></span></span></p> <p><span style="font-weight: bold;"><span leaf=""><span textstyle="" style="font-size: 18px;">Seata 是如何处理幂等问题的呢?</span></span></span></p> <p><span leaf="">同样的也是在 TCC 事务控制表中增加一个记录状态的字段 status,该字段有 3 个值,分别为:</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang=""><code><span leaf=""><span class="code-snippet__plaintext">tried:1</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">committed:2</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">rollbacked:3</span></span></code></pre> </section> <p><span leaf=""><br></span></p> <ol style="margin:0px;" class="list-paddingleft-1"></ol> <p><span leaf="">二阶段 Confirm/Cancel 方法执行后,将状态改为 committed 或 rollbacked 状态。当重复调用二阶段 Confirm/Cancel 方法时,判断事务状态即可解决幂等问题。</span></p> <p><span leaf=""><br></span></p> <article> <p><span style="font-weight: bold;font-size: inherit;"><span leaf=""><span textstyle="" style="font-size: 18px;">如何处理悬挂</span></span></span></p> <p><span style="color: rgb(223, 64, 42);"><span leaf="">悬挂指的是二阶段 Cancel 方法比 一阶段 Try 方法优先执行,由于允许空回滚的原因,在执行完二阶段 Cancel 方法之后直接空回滚返回成功,此时全局事务已结束,但是由于 Try 方法随后执行,这就会造成一阶段 Try 方法预留的资源永远无法提交和释放了。</span></span></p> <p><span style="font-weight: bold;"><span leaf="">那么悬挂是如何产生的呢?</span></span></p> <section nodeleaf=""> <img src="/upload/511ff13516d324ab37740b81da03dedd.png" class="rich_pages wxw-img" data-ratio="0.799074074074074" data-type="png" data-w="1080" style="width: 620px;height: 495px;" data-imgfileid="100001394"> </section> <p><span leaf="">如上图所示,在执行参与者 A 的一阶段 Try 方法时,出现网路拥堵,由于 Seata 全局事务有超时限制,执行 Try 方法超时后,TM 决议全局回滚,回滚完成后如果此时 RPC 请求才到达参与者 A,执行 Try 方法进行资源预留,从而造成悬挂。</span></p> <p><span leaf=""><br></span></p> <p><span style="font-weight: bold;"><span leaf="">Seata 是怎么处理悬挂的呢?</span></span></p> <p><span leaf="">在 TCC 事务控制表记录状态的字段 status 中增加一个状态:</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang=""><code><span leaf=""><span class="code-snippet__plaintext">suspended:4</span></span></code></pre> </section> <section> <span leaf=""><br></span> </section> <p><span leaf="">当执行二阶段 Cancel 方法时,如果发现 TCC 事务控制表没有相关记录,说明二阶段 Cancel 方法优先一阶段 Try 方法执行,因此插入一条 status=4 状态的记录,当一阶段 Try 方法后面执行时,判断 status=4 ,则说明有二阶段 Cancel 已执行,并返回 false 以阻止一阶段 Try 方法执行成功。</span></p> </article> <p><span leaf=""><br></span></p> <article> <p><span style="font-weight: bold;font-size: 18px;"><span leaf="">6. Spring Cloud Alibaba整合Seata TCC实战</span></span></p> <p><span style="font-weight: bold;font-size: inherit;"><span leaf="">业务场景</span></span></p> <p><span leaf="">用户下单,整个业务逻辑由三个微服务构成:</span></p> <ul style="margin:0px;" class="list-paddingleft-1"> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#df402a;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(223, 64, 42);background-color: rgb(255, 255, 255);"><span leaf="">库存服务:对给定的商品扣除库存数量。</span></span></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#df402a;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(223, 64, 42);background-color: rgb(255, 255, 255);"><span leaf="">订单服务:根据采购需求创建订单。</span></span></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:#df402a;font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(223, 64, 42);background-color: rgb(255, 255, 255);"><span leaf="">账户服务:从用户</span><span leaf="">账</span><span leaf="">户中扣除余额。</span></span></li> </ul> <section nodeleaf=""> <img src="/upload/4e33be4d3443cb7c8e1805a2385fbf85.png" class="rich_pages wxw-img" data-ratio="1.0079260237780714" data-type="png" data-w="757" style="width: 620px;height: 625px;" data-imgfileid="100001395"> </section> <p><span style="font-weight: bold;font-size: inherit;"></span></p> <p><span style="color: rgb(223, 64, 42);font-weight: bold;"><span leaf="">1) 环境准备</span></span></p> <ul style="list-style-type: disc;" class="list-paddingleft-1"> <li> <section> <span leaf="">父pom指定微服务版本</span> </section></li> </ul> <p><span leaf=""><br></span></p> <section style="text-align: center;" nodeleaf=""> <img src="/upload/bb635f7d8928461a739cb132003d90a5.png" class="rich_pages wxw-img" data-ratio="0.16944444444444445" data-s="300,640" data-type="png" data-w="1080" type="block" data-imgfileid="100001396"> </section> <article data-content="[{" type:block,id:4467-1657243647542,name:list-item,data:{version:1,listid:ewrj-1695360830938,listlevel:1,listtype:unordered},nodes:[{type:text,id:wbma-1695360797159,leaves:[{text:启动seata&nbsp;server(tc)端,seata&nbsp;server使用nacos作为配置中心和注册中心,marks:[]}]}],state:{}},{type:block,id:9076-1657243647542,name:list-item,data:{version:1,listid:ewrj-1695360830938,listlevel:1,listtype:unordered},nodes:[{type:text,id:idac-1695360797159,leaves:[{text:启动nacos服务,marks:[]}]}],state:{}}]> <section> <span leaf=""><br></span> </section> <ul style="list-style-type: disc;" class="list-paddingleft-1"> <li> <section> <span leaf="">启动nacos服务</span> </section></li> <li> <section> <span leaf="">启动Seata Server(TC)端,Seata Server使用nacos作为配置中心和注册中心</span> </section></li> </ul> </article> <section> <span leaf=""><br></span> </section> <p><span style="color: rgb(223, 64, 42);font-weight: bold;"><span leaf="">2) &nbsp;微服务导入seata依赖</span></span></p> <p><span leaf="">spring-cloud-starter-alibaba-seata内部集成了seata,并实现了xid传递</span></p> </article> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="xml"><code><span leaf=""><span class="code-snippet__comment"><!-- seata--></span></span></code><code><span leaf=""><span class="code-snippet__tag"><span class="code-snippet__plaintext">&lt;</span></span><span class="code-snippet__tag"><span class="code-snippet__name">dependency</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">&gt;</span></span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__tag"><span class="code-snippet__plaintext">&lt;</span></span><span class="code-snippet__tag"><span class="code-snippet__name">groupId</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">&gt;</span></span><span class="code-snippet__plaintext">com.alibaba.cloud</span><span class="code-snippet__tag"><span class="code-snippet__plaintext"><!--/</span--></span><span class="code-snippet__tag"><span class="code-snippet__name">groupId</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">&gt;</span></span></span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__tag"><span class="code-snippet__plaintext">&lt;</span></span><span class="code-snippet__tag"><span class="code-snippet__name">artifactId</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">&gt;</span></span><span class="code-snippet__plaintext">spring-cloud-starter-alibaba-seata</span><span class="code-snippet__tag"><span class="code-snippet__plaintext"><!--/</span--></span><span class="code-snippet__tag"><span class="code-snippet__name">artifactId</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">&gt;</span></span></span></span></code><code><span leaf=""><span class="code-snippet__tag"><span class="code-snippet__plaintext"><!--/</span--></span><span class="code-snippet__tag"><span class="code-snippet__name">dependency</span></span><span class="code-snippet__tag"><span class="code-snippet__plaintext">&gt;</span></span></span></span></code></pre> </section> <article data-content="[{" type:block,id:mhpc-1695360830965,name:paragraph,data:{},nodes:[{type:text,id:gdno-1695360797161,leaves:[{text:&nbsp;3)微服务application.yml中添加seata配置,marks:[{type:color,value:#df402a},{type:bold}]}]}],state:{}}]> <p><span style="color: rgb(223, 64, 42);font-weight: bold;"><span leaf="">&nbsp;3)微服务application.yml中添加seata配置</span></span></p> </article> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="bash"><code><span leaf=""><span class="code-snippet__plaintext">seata:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; application-id:&nbsp;</span><span class="code-snippet__variable">${spring.application.name}</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;&nbsp;</span><span class="code-snippet__comment"># seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; tx-service-group: default_tx_group</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; registry:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; # 指定nacos作为注册中心</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; type: nacos</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; nacos:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; application: seata-server</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; server-addr: 127.0.0.1:8848</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; namespace:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; group: SEATA_GROUP</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; config:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; # 指定nacos作为配置中心</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; type: nacos</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; nacos:</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; server-addr: 127.0.0.1:8848</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; namespace: 7e838c12-8554-4231-82d5-6d93573ddf32</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; group: SEATA_GROUP</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; data-id: seataServer.properties</span></span></code></pre> </section> <article> <p><span style="color: rgb(223, 64, 42);"><span leaf="">注意:请确保client与server的注册中心和配置中心namespace和group一致</span></span></p> <p><span style="color: rgb(223, 64, 42);font-weight: bold;"><span leaf=""><br></span></span></p> <p><span style="color: rgb(223, 64, 42);font-weight: bold;"><span leaf="">4)定义TCC接口</span></span></p> <p><span leaf="">TCC相关注解如下:</span></p> <ul style="margin:0px;" class="list-paddingleft-1"> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:rgb(0, 0, 0);font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(223, 64, 42);"><span leaf="">@LocalTCC 适用于SpringCloud+Feign模式下的TCC,@LocalTCC一定需要注解在接口上</span></span> <section> <span leaf="">,此接口可以是寻常的业务接口,只要实现了TCC的两阶段提交对应方法便可</span> </section></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:rgb(0, 0, 0);font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;><span style="color: rgb(223, 64, 42);"><span leaf="">@TwoPhaseBusinessAction 注解try方法,其中name为当前tcc方法的bean名称,写方法名便可(全局唯一),commitMethod指向提交方法,rollbackMethod指向事务回滚方法。</span></span> <section> <span leaf="">指定好三个方法之后,seata会根据全局事务的成功或失败,去帮我们自动调用提交方法或者回滚方法。</span> </section></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:rgb(0, 0, 0);font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;> <section> <span leaf="">@BusinessActionContextParameter 注解可以将参数传递到二阶段(commitMethod/rollbackMethod)的方法。</span> </section></li> </ul> </article> </article> </article> </article> <article data-content="[{" type:block,id:b2h0-1695360830953,name:heading,data:{version:1,level:h2},nodes:[{type:text,id:ao4l-1695360797155,leaves:[{text:2.4&nbsp;seata&nbsp;tcc模式接口如何改造,marks:[{type:bold},{type:fontsize,value:18}]}]}],state:{}},{type:block,id:8511-1661778066124,name:paragraph,data:{version:1},nodes:[{type:text,id:dhe6-1695360797155,leaves:[{text:假设现有一个业务需要同时使用服务&nbsp;a&nbsp;和服务&nbsp;b&nbsp;完成一个事务操作,我们在服务&nbsp;a&nbsp;定义该服务的一个&nbsp;tcc&nbsp;接口:,marks:[]}]}],state:{}}]> <article> <article> <article> <ul style="margin:0px;" class="list-paddingleft-1"> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:rgb(0, 0, 0);font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;> <section> <span leaf="">@BusinessActionContextParameter 注解可以将参数传递到二阶段(commitMethod/rollbackMethod)的方法。</span> </section></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:rgb(0, 0, 0);font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;> <section> <span leaf="">BusinessActionContext 便是指TCC事务上下文</span> </section></li> </ul> <section> <span leaf=""><br></span> </section> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="php"><code><span leaf=""><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;*&nbsp;</span><span class="code-snippet__doctag">@author</span><span class="code-snippet__plaintext">&nbsp;Fox</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;* 通过&nbsp;</span><span class="code-snippet__doctag">@LocalTCC</span><span class="code-snippet__plaintext">&nbsp;这个注解,RM 初始化的时候会向 TC 注册一个分支事务。</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">@LocalTCC</span></span></code><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__class"><span class="code-snippet__keyword">interface</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__class"><span class="code-snippet__title">OrderService</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__plaintext">{</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* TCC的try方法:保存订单信息,状态为支付中</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 定义两阶段提交,在try阶段通过</span><span class="code-snippet__doctag">@TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">注解定义了分支事务的 resourceId,commit和 cancel 方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;name = 该tcc的bean名称,全局唯一</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;commitMethod = commit 为二阶段确认方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;rollbackMethod = rollback 为二阶段取消方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;BusinessActionContextParameter注解 传递参数到二阶段中</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;useTCCFence seata1.5.1的新特性,用于解决TCC幂等,悬挂,空回滚问题,需增加日志表tcc_fence_log</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; @</span><span class="code-snippet__title">TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">(name =&nbsp;</span><span class="code-snippet__string">"prepareSaveOrder"</span><span class="code-snippet__plaintext">, commitMethod =&nbsp;</span><span class="code-snippet__string">"commit"</span><span class="code-snippet__plaintext">, rollbackMethod =&nbsp;</span><span class="code-snippet__string">"rollback"</span><span class="code-snippet__plaintext">, useTCCFence =&nbsp;</span><span class="code-snippet__literal">true</span><span class="code-snippet__plaintext">)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__title">Order prepareSaveOrder</span><span class="code-snippet__plaintext">(OrderVo orderVo, @</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName =&nbsp;</span><span class="code-snippet__string">"orderId"</span><span class="code-snippet__plaintext">) Long orderId);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* TCC的confirm方法:订单状态改为支付成功</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 二阶段确认方法可以另命名,但要保证与commitMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* context可以传递try方法的参数</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title">&nbsp;commit</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* TCC的cancel方法:订单状态改为支付失败</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 二阶段取消方法可以另命名,但要保证与rollbackMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title">&nbsp;rollback</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code><code><span leaf=""><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;*&nbsp;</span><span class="code-snippet__doctag">@author</span><span class="code-snippet__plaintext">&nbsp;Fox</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;* 通过&nbsp;</span><span class="code-snippet__doctag">@LocalTCC</span><span class="code-snippet__plaintext">&nbsp;这个注解,RM 初始化的时候会向 TC 注册一个分支事务。</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">@LocalTCC</span></span></code><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__class"><span class="code-snippet__keyword">interface</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__class"><span class="code-snippet__title">StorageService</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__plaintext">{</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* Try: 库存-扣减数量,冻结库存+扣减数量</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 定义两阶段提交,在try阶段通过</span><span class="code-snippet__doctag">@TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">注解定义了分支事务的 resourceId,commit和 cancel 方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;name = 该tcc的bean名称,全局唯一</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;commitMethod = commit 为二阶段确认方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;rollbackMethod = rollback 为二阶段取消方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;BusinessActionContextParameter注解 传递参数到二阶段中</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;commodityCode 商品编号</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;count 扣减数量</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; @</span><span class="code-snippet__title">TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">(name =&nbsp;</span><span class="code-snippet__string">"deduct"</span><span class="code-snippet__plaintext">, commitMethod =&nbsp;</span><span class="code-snippet__string">"commit"</span><span class="code-snippet__plaintext">, rollbackMethod =&nbsp;</span><span class="code-snippet__string">"rollback"</span><span class="code-snippet__plaintext">, useTCCFence =&nbsp;</span><span class="code-snippet__literal">true</span><span class="code-snippet__plaintext">)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title">&nbsp;deduct</span><span class="code-snippet__plaintext">(@</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName =&nbsp;</span><span class="code-snippet__string">"commodityCode"</span><span class="code-snippet__plaintext">) String commodityCode,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;@</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName =&nbsp;</span><span class="code-snippet__string">"count"</span><span class="code-snippet__plaintext">)&nbsp;</span><span class="code-snippet__keyword">int</span><span class="code-snippet__plaintext">&nbsp;count);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* Confirm: 冻结库存-扣减数量</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 二阶段确认方法可以另命名,但要保证与commitMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* context可以传递try方法的参数</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title">&nbsp;commit</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* Cancel: 库存+扣减数量,冻结库存-扣减数量</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 二阶段取消方法可以另命名,但要保证与rollbackMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title">&nbsp;rollback</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code><code><span leaf=""><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;*&nbsp;</span><span class="code-snippet__doctag">@author</span><span class="code-snippet__plaintext">&nbsp;Fox</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;* 通过&nbsp;</span><span class="code-snippet__doctag">@LocalTCC</span><span class="code-snippet__plaintext">&nbsp;这个注解,RM 初始化的时候会向 TC 注册一个分支事务。</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">@LocalTCC</span></span></code><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__class"><span class="code-snippet__keyword">interface</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__class"><span class="code-snippet__title">AccountService</span></span><span class="code-snippet__class"><span class="code-snippet__plaintext">&nbsp;</span></span><span class="code-snippet__plaintext">{</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 用户账户扣款</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 定义两阶段提交,在try阶段通过</span><span class="code-snippet__doctag">@TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">注解定义了分支事务的 resourceId,commit和 cancel 方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;name = 该tcc的bean名称,全局唯一</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;commitMethod = commit 为二阶段确认方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* &nbsp;rollbackMethod = rollback 为二阶段取消方法</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;userId</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;money 从用户账户中扣除的金额</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; @</span><span class="code-snippet__title">TwoPhaseBusinessAction</span><span class="code-snippet__plaintext">(name =&nbsp;</span><span class="code-snippet__string">"debit"</span><span class="code-snippet__plaintext">, commitMethod =&nbsp;</span><span class="code-snippet__string">"commit"</span><span class="code-snippet__plaintext">, rollbackMethod =&nbsp;</span><span class="code-snippet__string">"rollback"</span><span class="code-snippet__plaintext">, useTCCFence =&nbsp;</span><span class="code-snippet__literal">true</span><span class="code-snippet__plaintext">)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title">&nbsp;debit</span><span class="code-snippet__plaintext">(@</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName =&nbsp;</span><span class="code-snippet__string">"userId"</span><span class="code-snippet__plaintext">) String userId,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @</span><span class="code-snippet__title">BusinessActionContextParameter</span><span class="code-snippet__plaintext">(paramName =&nbsp;</span><span class="code-snippet__string">"money"</span><span class="code-snippet__plaintext">)&nbsp;</span><span class="code-snippet__keyword">int</span><span class="code-snippet__plaintext">&nbsp;money);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 提交事务,二阶段确认方法可以另命名,但要保证与commitMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* context可以传递try方法的参数</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title">&nbsp;commit</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__comment">/**</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;* 回滚事务,二阶段取消方法可以另命名,但要保证与rollbackMethod一致</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@param</span><span class="code-snippet__plaintext">&nbsp;actionContext</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*&nbsp;</span><span class="code-snippet__doctag">@return</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp;*/</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">boolean</span><span class="code-snippet__title">&nbsp;rollback</span><span class="code-snippet__plaintext">(BusinessActionContext actionContext);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code></pre> </section> <section> <span leaf=""><br></span> </section> </article> <article data-content="[{" type:block,id:2639-1657243892913,name:paragraph,data:{version:1},nodes:[{type:text,id:eb2n-1695360797164,leaves:[{text:tcc&nbsp;幂等、悬挂和空回滚问题如何解决?,marks:[{type:color,value:#df402a}]}]}],state:{}},{type:block,id:8795-1657244433615,name:paragraph,data:{version:1},nodes:[{type:text,id:mjv2-1695360797164,leaves:[{text:tcc&nbsp;模式中存在的三大问题是幂等、悬挂和空回滚。,marks:[{type:color,value:#df402a}]},{text:在&nbsp;seata1.5.1&nbsp;版本中,增加了一张事务控制表,表名是&nbsp;tcc_fence_log&nbsp;来解决这个问题。而在@twophasebusinessaction&nbsp;注解中提到的属性&nbsp;usetccfence&nbsp;就是来指定是否开启这个机制,这个属性值默认是&nbsp;false。,marks:[]}]}],state:{}}]> <p><span style="color: rgb(223, 64, 42);"><span leaf="">TCC 幂等、悬挂和空回滚问题如何解决?</span></span></p> <p><span style="color: rgb(223, 64, 42);"><span leaf="">TCC 模式中存在的三大问题是幂等、悬挂和空回滚。</span></span><span leaf="">在 Seata1.5.1 版本中,增加了一张事务控制表,表名是 tcc_fence_log 来解决这个问题。而在@TwoPhaseBusinessAction 注解中提到的属性 useTCCFence 就是来指定是否开启这个机制,这个属性值默认是 false。</span></p> </article> <p><span leaf=""><br></span></p> <article data-content="[{" type:block,id:8388-1657244433615,name:paragraph,data:{version:1},nodes:[{type:text,id:jyrq-1695360797164,leaves:[{text:5)微服务增加tcc_fence_log日志表,marks:[{type:bold},{type:color,value:#df402a}]}]}],state:{}}]> <p><span style="font-weight: bold;color: rgb(223, 64, 42);"><span leaf="">5)微服务增加tcc_fence_log日志表</span></span></p> </article> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="sql"><code><span leaf=""><span class="code-snippet__plaintext"># tcc_fence_log 建表语句如下(MySQL 语法)</span></span></code><code><span leaf=""><span class="code-snippet__keyword">CREATE</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">TABLE</span><span class="code-snippet__plaintext">&nbsp;IF&nbsp;</span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">EXISTS</span><span class="code-snippet__plaintext">&nbsp;`tcc_fence_log`</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">(</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; `xid` &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span class="code-snippet__type">VARCHAR</span><span class="code-snippet__plaintext">(</span><span class="code-snippet__number">128</span><span class="code-snippet__plaintext">) &nbsp;</span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext">&nbsp;COMMENT&nbsp;</span><span class="code-snippet__string">'global id'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; `branch_id` &nbsp; &nbsp;&nbsp;</span><span class="code-snippet__type">BIGINT</span><span class="code-snippet__plaintext">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext">&nbsp;COMMENT&nbsp;</span><span class="code-snippet__string">'branch id'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; `action_name` &nbsp;&nbsp;</span><span class="code-snippet__type">VARCHAR</span><span class="code-snippet__plaintext">(</span><span class="code-snippet__number">64</span><span class="code-snippet__plaintext">) &nbsp;&nbsp;</span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext">&nbsp;COMMENT&nbsp;</span><span class="code-snippet__string">'action name'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; `status` &nbsp; &nbsp; &nbsp; &nbsp;TINYINT &nbsp; &nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext">&nbsp;COMMENT&nbsp;</span><span class="code-snippet__string">'status(tried:1;committed:2;rollbacked:3;suspended:4)'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; `gmt_create` &nbsp; &nbsp;DATETIME(</span><span class="code-snippet__number">3</span><span class="code-snippet__plaintext">) &nbsp;&nbsp;</span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext">&nbsp;COMMENT&nbsp;</span><span class="code-snippet__string">'create time'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; `gmt_modified` &nbsp;DATETIME(</span><span class="code-snippet__number">3</span><span class="code-snippet__plaintext">) &nbsp;&nbsp;</span><span class="code-snippet__keyword">NOT</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__keyword">NULL</span><span class="code-snippet__plaintext">&nbsp;COMMENT&nbsp;</span><span class="code-snippet__string">'update time'</span><span class="code-snippet__plaintext">,</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp;&nbsp;</span><span class="code-snippet__keyword">PRIMARY</span><span class="code-snippet__plaintext">&nbsp;KEY (`xid`, `branch_id`),</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; KEY `idx_gmt_modified` (`gmt_modified`),</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; KEY `idx_status` (`status`)</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">) ENGINE&nbsp;</span><span class="code-snippet__operator">=</span><span class="code-snippet__plaintext">&nbsp;InnoDB</span></span></code><code><span leaf=""><span class="code-snippet__keyword">DEFAULT</span><span class="code-snippet__plaintext">&nbsp;CHARSET&nbsp;</span><span class="code-snippet__operator">=</span><span class="code-snippet__plaintext">&nbsp;utf8mb4;</span></span></code></pre> </section> <p><span leaf=""><br></span></p> <p><span style="font-weight: bold;font-size: inherit;"></span></p> </article> <article> <p><span style="font-weight: bold;color: rgb(223, 64, 42);"><span leaf="">6)TCC接口的业务实现</span></span></p> <p><span leaf="">参考课堂代码</span></p> <p><span leaf=""><br></span></p> <p><span style="font-weight: bold;color: rgb(223, 64, 42);"><span leaf="">7) 在全局事务发起者中添加@GlobalTransactional注解</span></span></p> <p><span leaf="">核心代码</span></p> </article> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="ruby"><code><span leaf=""><span class="code-snippet__variable">@GlobalTransactional</span><span class="code-snippet__plaintext">(name=</span><span class="code-snippet__string">"createOrder"</span><span class="code-snippet__plaintext">,rollbackFor=</span><span class="code-snippet__title">Exception</span><span class="code-snippet__plaintext">.</span><span class="code-snippet__keyword">class</span><span class="code-snippet__plaintext">)</span></span></code><code><span leaf=""><span class="code-snippet__keyword">public</span><span class="code-snippet__plaintext">&nbsp;</span><span class="code-snippet__title">Order</span><span class="code-snippet__plaintext">&nbsp;saveOrder(</span><span class="code-snippet__title">OrderVo</span><span class="code-snippet__plaintext">&nbsp;orderVo) {</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; log.info(</span><span class="code-snippet__string">"=============用户下单================="</span><span class="code-snippet__plaintext">);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; log.info(</span><span class="code-snippet__string">"当前 XID: {}"</span><span class="code-snippet__plaintext">,&nbsp;</span><span class="code-snippet__title">RootContext</span><span class="code-snippet__plaintext">.getXID());</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; /</span><span class="code-snippet__regexp">/获取全局唯一订单号 &nbsp;测试使用</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; Long orderId = UUIDGenerator.generateUUID();</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; /</span><span class="code-snippet__regexp">/阶段一: 创建订单</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; Order order = orderService.prepareSaveOrder(orderVo,orderId);</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; /</span><span class="code-snippet__regexp">/扣减库存</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; storageFeignService.deduct(orderVo.getCommodityCode(), orderVo.getCount());</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; /</span><span class="code-snippet__regexp">/扣减余额</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; accountFeignService.debit(orderVo.getUserId(), orderVo.getMoney());</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">&nbsp; &nbsp; return order;</span></span></code><code><span leaf=""><span class="code-snippet__plaintext">}</span></span></code></pre> </section> <p><span leaf=""><br></span></p> </article> <article data-content="[{" type:block,id:da7m-1695360830967,name:paragraph,data:{},nodes:[{type:text,id:vnbj-1695360797165,leaves:[{text:8)测试分布式事务是否生效,marks:[{type:color,value:#df402a},{type:bold}]}]}],state:{}},{type:block,id:4039-1661780592020,name:list-item,data:{version:1,listid:tixx-1695360830941,listlevel:1,listtype:unordered},nodes:[{type:text,id:j67r-1695360797165,leaves:[{text:分布式事务成功,模拟正常下单、扣库存,扣余额,marks:[]}]}],state:{index:1}},{type:block,id:4743-1661780592020,name:list-item,data:{version:1,listid:tixx-1695360830941,listlevel:1,listtype:unordered},nodes:[{type:text,id:nyxj-1695360797165,leaves:[{text:分布式事务失败,模拟下单扣库存成功、扣余额失败,事务是否回滚,marks:[]}]}],state:{}}]> <p><span style="color: rgb(223, 64, 42);font-weight: bold;"><span leaf="">8)测试分布式事务是否生效</span></span></p> <ul style="margin:0px;" class="list-paddingleft-1"> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:rgb(0, 0, 0);font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;> <section> <span leaf="">分布式事务成功,模拟正常下单、扣库存,扣余额</span> </section></li> <li style="white-space:pre-wrap;line-height:1.75;font-size:14px;text-align:left;list-style-position:inside;word-break:break-word;color:rgb(0, 0, 0);font-weight:normal;font-style:normal;text-decoration:none;background-color:rgba(0, 0, 0, 0);font-family:" microsoft yahei, stxihei;list-style-type:disc;> <section> <span leaf="">分布式事务失败,模拟下单扣库存成功、扣余额失败,事务是否回滚</span> </section></li> </ul> </article> </article>

最难的账务逻辑解析:交易中、交易成功,一笔交易多次记账,该怎么设计

作者:微信小助手

<section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-indent: 0em;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;><span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">本文揭开一个世纪谜团:在做账户设计时,90%的人都会陷入的困局。</span></span> </section> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-indent: 0em;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;>账户设计时,业务状态的流转及账务处理往往容易让人困惑。</span><span style="font-size: 16px;letter-spacing: 0.578px;text-indent: 0em;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;>同一笔交易,在“交易中”状态需要记一笔账,到“交易成功”时又要记一笔账,如何在账户中清晰体现这一过程,是设计中的一个难点,很多人容易在此犯错。</span><br> </section> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">一旦陷入这个误区,就可能难以自拔。</span> </section> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">设计梳理不清的一个常见场景是,将交易状态混入了账户流水中。当账户与业务状态紧密耦合时,就会变得难以理清。</span> </section> <p style="line-height: 1.6em;text-align: justify;margin: 0px 8px;text-indent: 0em;"><strong><span style="letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);font-size: 24px;">01</span></strong></p> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <strong><span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">一个令人困惑的设计逻辑</span></strong><span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);"><br></span> </section> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">如下图所示,是知识星球上一位同学的提问:在“交易中”状态时记一笔收入,计入冻结余额部分,此时看似没有问题。</span> </section> <section style="text-align: center;margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100019265" data-ratio="0.19907407407407407" data-s="300,640" src="/upload/04ee9118c6178d2aab9b808a73ce8a44.png" data-type="png" data-w="1080" style=""> </section> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;">当业务层的交易变成成功时,麻烦就来了,此时要进行“冻结余额”解冻10元到可用余额中,那么“02”流水交易状态变成“交易成功”<br></span> </section> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;">问题是,03号流水中的可用余额和冻结余额也需要跟着变化,幸好只有一个03,如果已经新产生了1000万笔流水呢,那数据更新量是巨大的<br></span> </section> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">当业务层的交易状态变为成功时,问题就来了。此时需要将“冻结余额”中的10元解冻至可用余额。随之,“02”号流水的交易状态也会更新为“交易成功”。</span> </section> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">问题在于,03号流水中的可用余额和冻结余额也需要相应调整。幸好现在只涉及一个03号流水,但如果已经新产生了1000万笔流水,那数据更新的工作量将是巨大的。</span> </section> <section style="text-align: center;margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100019266" data-ratio="0.20092592592592592" data-s="300,640" src="/upload/4a3b3933efb2953114d99759babe894d.png" data-type="png" data-w="1080" style=""> </section> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.6em;margin-bottom: 0px;"><span style="font-size: 24px;"><strong><span style="font-size: 24px;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.034em;>02</span></strong></span></p> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <strong><span style="font-size: 16px;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.034em;>掉坑里了!问题在哪?</span></strong><span style="font-size: 16px;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.034em;></span> </section> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.034em;>这意味着,对于历史中的非终态交易记账,一旦交易状态发生变化,就会牵连到后续大量的记账都需要跟着调整。</span><br> </section> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;">对于并发量较大的业务来说,账务系统可就要累垮了,因为它得不断地、一遍遍地更新大量的历史账户流水。</span> </section> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;">而且,这还只是从“交易中”到“交易成功”的情况。如果交易从“交易中”变为“交易失败”怎么办?如果业务线要新增一个状态怎么办?财务需要数据怎么办?给商户的账单又该怎么处理?</span> </section> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;">状态一直在变,没有稳定的终态,每一秒都可能不同,这样账务就变得不可查、不可信了。</span> </section> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;">这里最大的问题就是把业务与账户强耦合在一起了。</span><span style="font-size: 16px;"><span style="font-size: 16px;color: rgb(0, 128, 255);"></span></span> </section> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.6em;margin-bottom: 0px;"><span style="font-size: 24px;"><strong><span style="font-size: 24px;letter-spacing: 0.578px;text-indent: 0em;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;>03</span></strong></span></p> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <strong><span style="font-size: 16px;letter-spacing: 0.578px;text-indent: 0em;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;>我们需要建立几点基本认知:</span></strong><span style="font-size: 16px;letter-spacing: 0.578px;text-indent: 0em;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;></span><br> </section> <p style="text-align: justify;margin: 0px 8px 24px;text-indent: 0em;line-height: 1.6em;"><span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">事实不可变更:账务是对业务已经发生事实的记录,这些事实一旦产生就不能变更,就像流水一样,一旦登记就成为了终态。</span></p> <p style="text-align: justify;margin: 0px 8px 24px;text-indent: 0em;line-height: 1.6em;"><span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">非一对一强对应:业务单据和账户单据之间并非必然的一对一关系,而可能是一对多、多对一或多对多的关系。因此,我们不能简单地将交易等同于流水。</span></p> <p style="text-align: justify;margin: 0px 8px 24px;text-indent: 0em;line-height: 1.6em;"><span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">理解会计事件:交易的状态变化构成了一次会计事件,而账户关注的是这些会计事件。对于每一次事件,我们都应该记录一笔账,而不是随着交易状态的改变去修改账户的状态。</span></p> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;"></span> </section> <p style="line-height: 1.6em;text-align: justify;margin: 0px 8px;text-indent: 0em;"><span style="font-size: 24px;"><strong><span style="font-size: 24px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">04</span></strong></span></p> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <strong><span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">该问题的解决方法是:</span></strong><span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);"></span> </section> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">将交易状态从账户中剔除,让账户保持其“纯粹性”。账户不再关注业务状态的流转,而只专注于处理每一次的记账请求,每次记账都是终态。如下图所示。</span> </section> <section style="text-align: center;margin-left: 8px;margin-right: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100019278" data-ratio="0.1962962962962963" data-s="300,640" src="/upload/44ad434f108e85def3149ad46cb4a628.png" data-type="png" data-w="1080" style=""> </section> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">我们将记账类型抽象出“冻结”、“解冻”等更加精确的账务处理类型。因此,一笔交易的状态变化会对应多笔账户流水的记录。</span> </section> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">在交易进行时,记录的是02类型的“收入”账,金额计入冻结余额。</span> </section> <section style="line-height: 1.6em;text-align: justify;margin: 0px 8px 24px;text-indent: 0em;"> <span style="font-size: 16px;letter-spacing: 0.578px;text-decoration: none solid rgba(0, 0, 0, 0.9);">交易成功后,则记录04类型的“解冻”账,此时总余额保持不变,冻结余额减少,可用余额相应增加,如下图所示。</span> </section> <section style="text-align: center;margin-left: 8px;margin-right: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100019280" data-ratio="0.22962962962962963" data-s="300,640" src="/upload/b6ac9e8c385c6f7d24d2a10fd7c1ce5f.png" data-type="png" data-w="1080" style=""> </section> <section style="margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;letter-spacing: 0.578px;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;>如果交易失败了,那么04类型的记账就应该是一笔支出账,用于扣减冻结余额,如下图所示。</span><br> </section> <section style="text-align: center;margin-left: 8px;margin-right: 8px;"> <img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100019281" data-ratio="0.23148148148148148" data-s="300,640" src="/upload/7446292b13b3d1150e4ebf49c737472f.png" data-type="png" data-w="1080" style=""> </section> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.6em;margin-bottom: 0px;"><span style="font-size: 24px;"><strong><span style="font-size: 24px;letter-spacing: 0.578px;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;>05</span></strong></span></p> <section style="margin-bottom: 24px;margin-left: 8px;margin-right: 8px;line-height: 1.6em;"> <strong><span style="font-size: 16px;letter-spacing: 0.578px;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;>防耦合设计原理:</span></strong> </section> <section style="margin-bottom: 24px;margin-left: 8px;margin-right: 8px;line-height: 1.6em;"> <span style="font-size: 16px;letter-spacing: 0.578px;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;>将业务与账户系统进行隔离,账户系统不关注业务的流转情况,只专注于处理每一次的记账请求。账户系统为业务提供各类记账服务,业务在不同事件发生时,向账户系统发起记账请求。</span> </section> <section style="text-align: center;margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <img class="rich_pages wxw-img js_insertlocalimg" data-imgfileid="100019275" data-ratio="0.6361111111111111" data-s="300,640" src="/upload/03b861139fe9b994502fc7b5d3d14107.png" data-type="png" data-w="1080" style=""> </section> <section style="text-align: left;margin-left: 8px;margin-right: 8px;margin-bottom: 24px;line-height: 1.6em;"> <span style="font-size: 16px;letter-spacing: 0.578px;font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;>以上只是针对星球提问给出的一种解法。当然,账务实现有多种方案,如流水账、复式记账、客户账、内部账等,每种情况都可以选择不同的实现方案。</span><span style="font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.034em;font-size: 16px;>详见本文:</span><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247502319&amp;idx=1&amp;sn=c1ea867f6091fce1b5a7e623b0136960&amp;scene=21#wechat_redirect" textvalue="详解账务系统,从入门到精通" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" style="font-family: mp-quote, " pingfang sc, system-ui, -apple-system, blinkmacsystemfont, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.034em;text-decoration: underline;font-size: 16px;>详解账务系统,从入门到精通</a> </section> <p style="-webkit-tap-highlight-color: transparent;margin-right: 8px;margin-bottom: 0px;margin-left: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;text-indent: 0em;letter-spacing: 0.578px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;color: rgb(136, 136, 136);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, " helvetica neue, pingfang sc, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;font-size: 12px;letter-spacing: 0.034em;text-indent: 0em;>支付全链路爽文推荐</span></p> <p style="-webkit-tap-highlight-color: transparent;margin-bottom: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;">【入门】</span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;text-decoration: underline;"><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247502534&amp;idx=1&amp;sn=6c0d6148cca90367a5ece1d7e2070bfb&amp;scene=21#wechat_redirect" textvalue="一文搞定“支付入门”" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="outline: 0px;color: var(--weui-LINK);cursor: default;">一文搞定“支付入门”</a></span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;"><br style="-webkit-tap-highlight-color: transparent;outline: 0px;"></span></p> <p style="-webkit-tap-highlight-color: transparent;margin-bottom: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;">【入门】</span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;text-decoration: underline;"><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247499633&amp;idx=1&amp;sn=01b6b024c176884a1390af126536304d&amp;scene=21#wechat_redirect" textvalue="一文搞懂184个支付名词" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="outline: 0px;color: var(--weui-LINK);cursor: default;">一文搞懂184个支付名词</a></span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;"><br style="-webkit-tap-highlight-color: transparent;outline: 0px;"></span></p> <p style="-webkit-tap-highlight-color: transparent;margin-bottom: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;">【全局】</span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;text-decoration: underline;"><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247502329&amp;idx=1&amp;sn=38b85ae0de56df071aecbb38165e570f&amp;scene=21#wechat_redirect" textvalue="88张图,把支付清结算串起来" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="outline: 0px;color: var(--weui-LINK);cursor: default;">88张图,把支付清结算串起来</a></span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;"></span></p> <p style="-webkit-tap-highlight-color: transparent;margin-bottom: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;">【全局】</span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;text-decoration: underline;"><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247501204&amp;idx=1&amp;sn=a2e3335e18f591f16ebab0add53b17c5&amp;scene=21#wechat_redirect" textvalue="1.9万字:支付清算生态" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="outline: 0px;color: var(--weui-LINK);cursor: default;">1.9万字:支付清算生态</a></span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;"></span></p> <p style="-webkit-tap-highlight-color: transparent;margin-bottom: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;">【交易】</span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;text-decoration: underline;"><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247502865&amp;idx=1&amp;sn=69fb82d4041ddd27c3c2bf1ae285a284&amp;scene=21#wechat_redirect" textvalue="一文搞懂“交易核心”:交易、订单、账单、支付" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">一文搞懂“交易核心”:交易、订单、账单、支付</a></span></p> <p style="-webkit-tap-highlight-color: transparent;margin-bottom: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;">【支付】</span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;text-decoration: underline;"><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247496752&amp;idx=1&amp;sn=873a2c35041818c331ba1062cc3c99e2&amp;scene=21#wechat_redirect" textvalue="3.5万字:一文搞懂“支付系统”" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="outline: 0px;color: var(--weui-LINK);cursor: default;">3.5万字:一文搞懂“支付系统”</a></span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;"><br style="-webkit-tap-highlight-color: transparent;outline: 0px;"></span></p> <p style="-webkit-tap-highlight-color: transparent;margin-bottom: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;">【清结算】</span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;text-decoration: underline;"><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247502337&amp;idx=1&amp;sn=27a277ed1d41abf0ede0e331603597ca&amp;scene=21#wechat_redirect" textvalue="万字:清结算,全局实现原理" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="outline: 0px;color: var(--weui-LINK);cursor: default;">万字:清结算,全局实现原理</a></span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;"><br style="-webkit-tap-highlight-color: transparent;outline: 0px;"></span></p> <p style="-webkit-tap-highlight-color: transparent;margin-bottom: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;">【账务】</span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;text-decoration: underline;"><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247502319&amp;idx=1&amp;sn=c1ea867f6091fce1b5a7e623b0136960&amp;scene=21#wechat_redirect" textvalue="详解账务系统,从入门到精通" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="outline: 0px;color: var(--weui-LINK);cursor: default;">3.5万字详解账务系统,从入门到精通</a></span></p> <p style="-webkit-tap-highlight-color: transparent;margin-bottom: 8px;outline: 0px;font-family: " pingfang sc, system-ui, -apple-system, system-ui, helvetica neue, hiragino sans gb, microsoft yahei ui, yahei, arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: normal;><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;letter-spacing: 0.578px;">【线下】</span><span style="-webkit-tap-highlight-color: transparent;outline: 0px;font-size: 12px;letter-spacing: 0.578px;text-decoration: underline;"><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=Mzg2MTg1NTM4NA==&amp;mid=2247502789&amp;idx=1&amp;sn=70f466e7983aaebaaa95bdb98ed0fd21&amp;scene=21#wechat_redirect" textvalue="支付清结算全链路,2天线下集训营" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="outline: 0px;color: var(--weui-LINK);cursor: default;">支付清结算全链路,2天线下集训营</a></span></p> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>

11.7k star,省时省力免费的开源神器!接私活效率爆表,秒杀宝塔!实现项目部署秒上线

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-bottom: 0px;padding-left: 10px;padding-right: 10px;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;font-family: Optima, " microsoft yahei, pingfangsc-regular, serif;font-size: 16px;color: rgb(0, 0, 0);line-height: 1.5em;word-spacing: 0em;letter-spacing: 0em;word-break: break-word;text-align: left;> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><img class="rich_pages wxw-img" data-imgfileid="100013228" data-ratio="0.21203703703703702" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/21c94b88f20250da8c142be9db7ecad1.png">Dokploy 是一个开源的 PaaS(平台即服务)解决方案,旨在简化应用程序和数据库的部署和管理。作为 Vercel、Netlify 和 Heroku 的替代方案,Dokploy 提供了多种功能,使开发人员能够轻松地部署和管理各种类型的应用程序和数据库。<img class="rich_pages wxw-img" data-imgfileid="100013230" data-ratio="0.5351851851851852" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/b45e9f18f2b9ead524b861ea4839f3ba.png"></p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="background-attachment: scroll;background-clip: border-box;background-image: url(" https: mmbiz.qpic.cn sz_mmbiz_png ujhtnqg0diaia3syywaepjmf5ib8sbatuzmtpyezyqgv2llzzhzibpwmzqwkhverpizzttfyqbnrh4xxpzaaxzkp6a 640?wx_fmt="png&amp;from=appmsg&quot;);background-origin:" padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;></span><span style="display: none;"></span><span style="color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">项目简介</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">Dokploy 是一个免费且可自托管的 PaaS 平台,专为开发人员设计。它支持多种编程语言,包括 Node.js、PHP、Python、Go 和 Ruby 等。Dokploy 提供了强大的功能,如多节点扩展、实时监控、自动备份和模板部署等,使得应用程序的部署和管理变得更加简单和高效。<img data-imgfileid="100013229" data-ratio="0.5287037037037037" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/e4cc0cb9a480a43d86cfadd9fb14e343.png"></p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="background-attachment: scroll;background-clip: border-box;background-image: url(" https: mmbiz.qpic.cn sz_mmbiz_png ujhtnqg0diaia3syywaepjmf5ib8sbatuzmtpyezyqgv2llzzhzibpwmzqwkhverpizzttfyqbnrh4xxpzaaxzkp6a 640?wx_fmt="png&amp;from=appmsg&quot;);background-origin:" padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;></span><span style="display: none;"></span><span style="color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">项目特点</span><span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">多节点扩展</strong>:Dokploy 支持使用 Docker Swarm 进行多节点扩展,能够轻松管理集群中的多个节点,确保应用程序的高可用性和可扩展性。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">实时监控</strong>:Dokploy 提供了实时监控功能,可以监控 CPU、内存、存储和网络使用情况,帮助开发人员及时发现和解决问题。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">自动备份</strong>:Dokploy 支持 MySQL、PostgreSQL、MongoDB、MariaDB 和 Redis 等数据库的自动备份,确保数据的安全性和可靠性。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">模板部署</strong>:Dokploy 提供了多种开源模板,如 Plausible、Pocketbase 和 Calcom 等,开发人员可以一键部署这些模板,快速搭建应用程序。<img class="rich_pages wxw-img" data-imgfileid="100013231" data-ratio="0.7314814814814815" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/86419318d74ed2fabf1fc46415a50b95.png"></p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">Traefik 集成</strong>:Dokploy 自动与 Traefik 集成,实现路由和负载均衡,确保应用程序的高效运行。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">CLI 和 API 访问</strong>:Dokploy 提供了命令行界面(CLI)和 API 访问,开发人员可以通过命令行或 API 管理应用程序和数据库。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">多服务器部署</strong>:Dokploy 支持远程服务器的部署和管理,开发人员可以轻松地在多个服务器上部署和管理应用程序。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">自托管</strong>:Dokploy 支持自托管,开发人员可以在自己的 VPS 上运行 Dokploy,完全掌控部署环境。</p> </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="background-attachment: scroll;background-clip: border-box;background-image: url(" https: mmbiz.qpic.cn sz_mmbiz_png ujhtnqg0diaia3syywaepjmf5ib8sbatuzmtpyezyqgv2llzzhzibpwmzqwkhverpizzttfyqbnrh4xxpzaaxzkp6a 640?wx_fmt="png&amp;from=appmsg&quot;);background-origin:" padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;></span><span style="display: none;"></span><span style="color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">如何快速开始</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">要快速开始使用 Dokploy,可以按照以下步骤进行:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">安装 Dokploy</strong>:在 VPS 上运行以下命令,安装 Dokploy:</p> <pre style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><span style="display: block;background: url(" https: mmbiz.qpic.cn mmbiz_svg sqqm3ojyw2jdmvpsbcoolk7cn7ibcz47chb2fplafqxs9mfxlqoqahhnmp7icialoajxefgzmscptcojj1z9ms9ml5r8xl9gb2j 640?wx_fmt="svg&amp;from=appmsg&quot;)" 10px 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;></span><code style="overflow-x: auto;color: rgb(171, 178, 191);width: auto;margin-left: 2px;margin-right: 2px;padding: 15px 4px 2px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);word-break: break-all;background: rgb(40, 44, 52);border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;">curl&nbsp;-sSL&nbsp;https://dokploy.com/install.sh&nbsp;|&nbsp;sh<br></code></pre> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">这将下载并安装 Dokploy 的所有必要组件。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">配置 Dokploy</strong>:安装完成后,可以通过 Dokploy 的命令行界面(CLI)或 Web 界面进行配置。Dokploy 提供了详细的文档,帮助开发人员快速上手。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">部署应用程序</strong>:Dokploy 支持多种部署方式,包括使用 Docker Compose、Nixpacks 和 Heroku Buildpacks 等。开发人员可以根据自己的需求选择合适的部署方式。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">管理数据库</strong>:Dokploy 支持多种数据库的管理和备份,开发人员可以通过 Dokploy 的 Web 界面或 CLI 管理数据库。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;"> <p style="line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;"><strong style="color: rgb(74, 74, 74);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">监控和维护</strong>:Dokploy 提供了实时监控和通知功能,开发人员可以随时监控应用程序的运行状态,并在出现问题时及时收到通知。<img class="rich_pages wxw-img" data-imgfileid="100013227" data-ratio="0.20555555555555555" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;height: auto !important;" src="/upload/2e15482349a2909e67f0ecfc3eb9248f.png"></p> </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;"><span style="background-attachment: scroll;background-clip: border-box;background-image: url(" https: mmbiz.qpic.cn sz_mmbiz_png ujhtnqg0diaia3syywaepjmf5ib8sbatuzmtpyezyqgv2llzzhzibpwmzqwkhverpizzttfyqbnrh4xxpzaaxzkp6a 640?wx_fmt="png&amp;from=appmsg&quot;);background-origin:" padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: 15px 15px;width: 15px;height: 15px;align-items: unset;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-size: 22px;font-weight: bold;flex-direction: unset;float: unset;justify-content: unset;letter-spacing: 0px;line-height: 1.5em;margin-bottom: -2px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;-webkit-box-reflect: unset;></span><span style="display: none;"></span><span style="color: rgb(72, 179, 120);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 8px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">结论</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">Dokploy 是一个功能强大且易于使用的 PaaS 平台,适合各种规模的开发团队。通过提供多节点扩展、实时监控、自动备份和模板部署等功能,Dokploy 大大简化了应用程序和数据库的部署和管理过程。</p> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">无论是初学者还是经验丰富的开发人员,都可以通过 Dokploy 快速上手并高效地管理自己的应用程序和数据库。</p> <p data-tool="mdnice编辑器" style="color: rgb(63, 63, 63);line-height: 1.8em;letter-spacing: 0.02em;text-indent: 0em;padding-top: 16px;padding-bottom: 8px;">地址:github.com/Dokploy/dokploy</p> </section> <blockquote data-tool="mdnice编辑器" data-style="border-top: none; border-right: none; border-bottom: none; font-size: 0.9em; overflow: auto; background: rgb(251, 249, 253); color: rgb(106, 115, 125); margin-bottom: 20px; margin-top: 20px; padding: 15px 20px; line-height: 27px; border-left-color: rgb(53, 179, 120);" class="js_darkmode__35" style="margin-top: 20px;margin-bottom: 20px;padding: 15px 20px;border-left-color: rgb(53, 179, 120);color: rgb(106, 115, 125);font-size: 0.9em;line-height: 27px;outline: 0px;border-top: none;border-right: none;border-bottom: none;background: rgb(251, 249, 253);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, " pingfang sc, cambria, cochin, georgia, times, times new roman, serif;letter-spacing: 0.544px;text-align: left;overflow: auto;></blockquote>

领导发话:谁再用Redis处理过期订单,立马走人!

作者:微信小助手

<ne-clipboard source="https%3A%2F%2Fwww.yuque.com%2Fu12222632%2Fas5rgl%2Fuxcci9m2kwhf35ey"></ne-clipboard><ne-clipboard source="https%3A%2F%2Fwww.yuque.com%2Fu12222632%2Fas5rgl%2Fzriesaekzg0lx17t%2Fedit%3Ftoc_node_uuid%3DfXvsEpI9iNzsugH6"></ne-clipboard> <h2 style="font-size: 24px;line-height: 32px;margin: 21px 0 5px 0;"><span style="color: rgba(6, 8, 31, 0.88);"><span leaf=""><span textstyle="" style="font-weight: bold;">引言</span></span></span></h2> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">在电商和支付场景中,处理用户订单的延时关闭是至关重要的。然而,有些方案看似简单却潜藏危险,尤其是使用</span></span><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">&nbsp;</span></span><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">Redis</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">&nbsp;</span></span><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">做过期订单关闭。身为团队的一员,切记:</span></span><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">谁再用Redis处理过期订单,立马走人!</span></span></strong></p> <h2 style="font-size: 24px;line-height: 32px;margin: 21px 0 5px 0;"><span style="color: rgba(6, 8, 31, 0.88);"><span leaf=""><span textstyle="" style="font-weight: bold;">不推荐的方案</span></span></span></h2> <h3 style="font-size: 20;line-height: 28px;margin: 16px 0 5px 0;"><span style="color: rgba(6, 8, 31, 0.88);"><span leaf=""><span textstyle="" style="font-weight: bold;">1. Redis 过期监听</span></span></span></h3> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">Redis的过期监听机制并不可靠。官方说明指出,过期事件是在Redis删除键时生成的,而非在TTL到达时立即触发。因此,您可能遇到以下问题:</span></span></p> <ul style="margin: 0;padding-left: 23px;" class="list-paddingleft-1"> <li><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">过期延迟</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">:过期通知的触发时间会延迟,可能延迟数分钟。</span></span></li> <li><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">“发送即忘”策略</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">:无法保证消息送达,当客户端断线时会丢失所有分发的事件。</span></span></li> </ul> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">这种不可靠的机制,绝对不适合用于过期订单的管理。</span></span></p> <section style="text-align: center;" nodeleaf=""> <img src="/upload/a823b02651ba7448dafa77fda484721e.png" class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.3421875" data-s="300,640" data-type="png" data-w="640" style="height: auto !important;" type="block" data-imgfileid="100001055"> </section> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf=""><br></span></span></p> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf=""><br></span></span></p> <h3 style="font-size: 20;line-height: 28px;margin: 16px 0 5px 0;"><span style="color: rgba(6, 8, 31, 0.88);"><span leaf=""><span textstyle="" style="font-weight: bold;">2. RabbitMQ 死信队列</span></span></span></h3> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">RabbitMQ提供死信机制,要让消息成为死信,必须满足特定条件,例如消息过期或被否定确认。然而,这同样存在时间上的不确定性:</span></span></p> <ul style="margin: 0;padding-left: 23px;" class="list-paddingleft-1"> <li><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">不保证投递时间</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">:消息在成为死信之前,如果有更早的消息未被处理,则后面的消息无法及时处理。</span></span></li> </ul> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">虽然可以用来排查消息,但它并不理想。</span></span></p> <section style="text-align: center;" nodeleaf=""> <img src="/upload/8773f0451678a58569b7b5e492c57f79.png" class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.2265625" data-s="300,640" data-type="png" data-w="640" style="height: auto !important;" type="block" data-imgfileid="100001056"> </section> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf=""><br></span></span></p> <h3 style="font-size: 20;line-height: 28px;margin: 16px 0 5px 0;"><span style="color: rgba(6, 8, 31, 0.88);"><span leaf=""><span textstyle="" style="font-weight: bold;">3. 时间轮</span></span></span></h3> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">时间轮是一种良好的定时任务实现,但大多数实现是纯内存的,没有持久化:</span></span></p> <ul style="margin: 0;padding-left: 23px;" class="list-paddingleft-1"> <li><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">任务丢失风险</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">:一旦进程崩溃,所有的定时任务都会消失。</span></span></li> </ul> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">在使用时间轮之前,请务必考虑其风险性。</span></span></p> <section style="text-align: center;" nodeleaf=""> <img src="/upload/35ab542db340511948cf25b49d4f4df5.png" class="rich_pages wxw-img" data-ratio="0.5381984036488028" data-s="300,640" data-w="877" style="height: auto !important;" type="block" data-imgfileid="100001061"> </section> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf=""><br></span></span></p> <h2 style="font-size: 24px;line-height: 32px;margin: 21px 0 5px 0;"><span style="color: rgba(6, 8, 31, 0.88);"><span leaf=""><span textstyle="" style="font-weight: bold;">推荐方案</span></span></span></h2> <h3 style="font-size: 20;line-height: 28px;margin: 16px 0 5px 0;"><span style="color: rgba(6, 8, 31, 0.88);"><span leaf=""><span textstyle="" style="font-weight: bold;">使用专业消息队列</span></span></span></h3> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">对于延时任务,推荐使用</span></span><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">&nbsp;</span></span><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">RocketMQ</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">&nbsp;</span></span><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">或</span></span><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">&nbsp;</span></span><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">Pulsar</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">&nbsp;</span></span><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">等具有定时投递功能的专业消息队列。这些系统设计上为高并发和可靠性做了优化,确保消息的准确投递。</span></span></p> <section style="text-align: center;" nodeleaf=""> <img src="/upload/f973ef62313673947a073cf0c3e9462d.png" class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.3421875" data-s="300,640" data-type="png" data-w="640" style="height: auto !important;" type="block" data-imgfileid="100001057"> </section> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf=""><br></span></span></p> <h3 style="font-size: 20;line-height: 28px;margin: 16px 0 5px 0;"><span style="color: rgba(6, 8, 31, 0.88);"><span leaf=""><span textstyle="" style="font-weight: bold;">Redisson DelayedQueue</span></span></span></h3> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">如果在特定场景下无法使用专业队列,</span></span><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">Redisson DelayQueue</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">&nbsp;</span></span><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">是基于Redis ZSet的一种可选方案。虽然在Redis崩溃时依然可能丢失消息,但在Redis运行稳定的情况下,DelayQueue可以有效避免消息丢失。</span></span></p> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf="">同时,请设计补偿机制以应对潜在的Redis崩溃。</span></span></p> <section style="text-align: center;" nodeleaf=""> <img src="/upload/042db9f753bcb95e0234659a04e7a249.png" class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.5390625" data-s="300,640" data-type="png" data-w="640" style="height: auto !important;" type="block" data-imgfileid="100001063"> </section> <section> <span leaf=""><br></span> </section> <section nodeleaf=""> <mp-common-cpsad class="js_uneditable custom_select_card new_cps_iframe" data-pluginname="mpcps" data-adtype="short-play" data-templateid="video-play" data-cpsversion="v120" data-goodssouce="1" data-videocarddata="{" dramaname:我刚一胎六宝,你说你十代单传,categoryname:都市 爱情,videocoverurl:https: wxaintpcos.wxqcloud.qq.com.cn public wx690f86c2e4552666 wxadramacoverimage 99b02549816686e2b8d81045d96e0ec9.jpg,dramanum:96} data-showchangebtn="1" data-disablechangevideo="1" data-dramaid="190831" srcappid="wx690f86c2e4552666" data-playappid="wxbd2b3445f6af2282" data-planid="202408291755476919990" data-traceid="36859f10-3de6-464f-948d-188b1f75d7ad190831" data-defaultpath="plugin-private%3A%2F%2Fwx94a6522b1d640c3b%2Fpages%2Fplaylet%2Fplaylet%3FdramaId%3D190831%26srcAppid%3Dwx690f86c2e4552666%26wxTicket%3DdGlja2V0Mjk1NDMzMzE3ODI3NDEwMzM5ODg3OTQ1NQ" data-mediano="1"></mp-common-cpsad> </section> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf=""><br></span></span></p> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf=""><br></span></span></p> <h2 style="box-sizing: border-box;"><span leaf=""><span textstyle="" style="font-weight: bold;">方案比较</span></span></h2> <table style="box-sizing: border-box;"> <thead> <tr style="box-sizing: border-box;"> <th valign="top" style="box-sizing: border-box;"> <section> <span leaf="">方案</span> </section></th> <th valign="top" style="box-sizing: border-box;"> <section> <span leaf="">优点</span> </section></th> <th valign="top" style="box-sizing: border-box;"> <section> <span leaf="">缺点</span> </section></th> </tr> </thead> <tbody> <tr style="box-sizing: border-box;"> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">消息队列</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">可靠性高,支持高并发</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">需要额外的基础设施</span> </section></td> </tr> <tr style="box-sizing: border-box;"> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">Redisson DelayedQueue</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">简单易用,基于 Redis</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">依赖于 Redis 的稳定性</span> </section></td> </tr> <tr style="box-sizing: border-box;"> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">Redis 过期监听</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">实现简单,易于上手</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">不可靠,可能会延迟,缺乏一致性</span> </section></td> </tr> <tr style="box-sizing: border-box;"> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">RabbitMQ 死信队列</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">可用于排查和重新投递</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">不保证投递时间,依赖于消息的状态</span> </section></td> </tr> <tr style="box-sizing: border-box;"> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">时间轮</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">高效的定时任务管理</span> </section></td> <td valign="top" style="box-sizing: border-box;"> <section> <span leaf="">无持久化,进程崩溃后任务丢失</span> </section></td> </tr> </tbody> </table> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf=""><br></span></span></p> <h2 style="font-size: 24px;line-height: 32px;margin: 21px 0 5px 0;"><span style="color: rgba(6, 8, 31, 0.88);"><span leaf=""><span textstyle="" style="font-weight: bold;">结论</span></span></span></h2><ne-clipboard source="https%3A%2F%2Fwww.yuque.com%2Fu12222632%2Fas5rgl%2Fuxcci9m2kwhf35ey"></ne-clipboard> <p style="margin: 0;padding: 0;min-height: 24px;text-align: justify;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">对于延时任务的实现,推荐使用&nbsp;</span></span><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">RocketMQ</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">、</span></span><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">Pulsar</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">&nbsp;等专业消息队列,它们提供了可靠的延时投递功能。</span></span></p> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">在无法使用专业消息队列的情况下,可以考虑使用</span></span><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">&nbsp;</span></span><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">Redisson DelayedQueue</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">,但需设计补偿机制以应对 Redis 崩溃等情况。</span></span></p> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">如果没有其他选择,可以使用</span></span><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">&nbsp;</span></span><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">时间轮</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">,但需注意其重启频率和持久化问题。</span></span></p> <p style="margin: 0;padding: 0;min-height: 24px;"><strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">注意:</span></span></strong><strong><span style="color: #DF2A3F;font-size: 16px;"><span leaf="">永远不要使用 Redis 的过期监听来实现定时任务</span></span></strong><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">,因为它不具备可靠性和一致性保障,容易导致任务丢失。</span></span></p> <p style="margin: 0;padding: 0;min-height: 24px;text-align: justify;"><strong><span style="color: #DF2A3F;font-size: 16px;"><span leaf="">永远不要使用 redis 过期监听实现定时任务。</span></span></strong></p> <p style="margin: 0;padding: 0;min-height: 24px;text-align: justify;"><strong><span style="color: #DF2A3F;font-size: 16px;"></span></strong></p> <p style="margin: 0;padding: 0;min-height: 24px;text-align: justify;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 16px;"><span leaf="">希望这篇文章能帮助你更好地理解延时任务的实现方式及其最佳实践!如有疑问,欢迎交流讨论。</span></span></p> <section class="mp_profile_iframe_wrp" nodeleaf=""> <mp-common-profile class="custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-nickname="Fox爱分享" data-alias="dcl_yc" data-from="0" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/aSJ8tDK6zEtIJWnEiahMNNA6DNjiafUUzmichdKkWCFc9ibqGnymD5kYxiaH8ZpohgAXzTn2gLVHqDe4uqKwCYNakvw/0?wx_fmt=png" data-signature="分享微服务、中间件、消息队列、搜索引擎、分布式存储和高并发架构等方面的知识" data-id="MzU1ODk1NTQ0Mg==" data-service_type="1"></mp-common-profile> </section> <p style="margin: 0;padding: 0;min-height: 24px;"><span style="color: rgba(6, 8, 31, 0.88);font-size: 14px;"><span leaf=""><br></span></span></p> <h2 style="font-size: 24px;line-height: 32px;margin: 21px 0 5px 0;"><span leaf=""><br></span></h2> <h2 style="font-size: 24px;line-height: 32px;margin: 21px 0 5px 0;"><span leaf=""><br></span></h2> <section> <span leaf=""><br></span> </section> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>