文章列表

如何做到无感刷新Token?

作者:じ☆ve宝贝

<h2 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;font-weight: bold;color: black;font-size: 22px;border-bottom: 4px solid rgb(64, 184, 250);"><span style="display: flex;color: #40B8FA;font-size: 20px;margin-left: 25px;">为什么需要无感刷新Token?</span><span style="display: flex;width: 200px;height: 10px;border-top-left-radius: 20px;background: rgba(64, 184, 250, 0.5);color: rgb(255, 255, 255);font-size: 16px;letter-spacing: 0.544px;justify-content: flex-end;float: right;margin-top: -10px;"></span></h2> <ul data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;font-size: 15px;color: rgb(89, 89, 89);list-style-type: circle;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;"><strong style="color: rgb(53, 148, 247);">「最近浏览到一个文章里面的提问,是这样的:」</strong></p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">当我在系统页面上做业务操作的时候会出现突然闪退的情况,然后跳转到登录页面需要重新登录系统,系统使用了Redis做缓存来存储用户ID,和用户的token信息,这是什么问题呢?</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;"><strong style="color: rgb(53, 148, 247);">「解答:」</strong></p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">突然闪退,一般都是由于你的token过期的问题,导致身份失效。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;"><strong style="color: rgb(53, 148, 247);">「解决方案:」</strong></p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">自动刷新token</p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">token续约</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;"><strong style="color: rgb(53, 148, 247);">「思路」</strong></p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">如果Token即将过期,你在验证用户权限的同时,为用户生成一个新的Token并返回给客户端,客户端需要更新本地存储的Token,</p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">还可以做定时任务来刷新Token,可以不生成新的Token,在快过期的时候,直接给Token增加时间</p> </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;font-weight: bold;color: black;font-size: 22px;border-bottom: 4px solid rgb(64, 184, 250);"><span style="display: flex;width: 20px;height: 20px;background-size: 20px 20px;background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/TLH3CicPVibrdibBMILeF8CicWeSvZ23v8btwtIMbkTwRmos7t5aawrahU9CianKiaXgAyc63QY0JtwjtS8QpWFORSaA/640?wx_fmt=png&amp;from=appmsg&quot;);margin-bottom: -22px;"></span><span style="display: flex;color: #40B8FA;font-size: 20px;margin-left: 25px;">自动刷新token</span><span style="display: flex;width: 200px;height: 10px;border-top-left-radius: 20px;background: rgba(64, 184, 250, 0.5);color: rgb(255, 255, 255);font-size: 16px;letter-spacing: 0.544px;justify-content: flex-end;float: right;margin-top: -10px;"></span></h2> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 自动刷新token是属于后端的解决方案,由后端来检查一个Token的过期时间是否快要过期了,如果快要过期了,就往请求头中重新 </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 放一个token,然后前端那边做拦截,拿到请求头里面的新的token,如果这个新的token和老的token不一致,直接将本地的token更换 </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 接下来拿代码举例子 </section> <ul data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;font-size: 15px;color: rgb(89, 89, 89);list-style-type: circle;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 先引入依赖 </section></li> </ul> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/et9q9FvjBBqZBvomWB5E7RXxEwYD8leMQzEhibJibDVicnpeswsderxPcL15NAc4kNXpDRicm6YJ8LRHhe5Y1DKNXHW3DgNPr6icP/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;letter-spacing: 0px;background: rgb(40, 44, 52);border-radius: 5px;margin-left: 0px;margin-right: 0px;"> <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">dependency</span>&gt;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">groupId</span>&gt;</span>cn.hutool <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">groupId</span>&gt;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">artifactId</span>&gt;</span>hutool-all <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">artifactId</span>&gt;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">version</span>&gt;</span>5.5.1 <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">version</span>&gt;</span> <br> <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">dependency</span>&gt;</span> <br> <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">dependency</span>&gt;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">groupId</span>&gt;</span>com.alibaba <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">groupId</span>&gt;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">artifactId</span>&gt;</span>fastjson <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">artifactId</span>&gt;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">version</span>&gt;</span>1.2.33 <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">version</span>&gt;</span> <br> <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">dependency</span>&gt;</span> <br> <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">dependency</span>&gt;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">groupId</span>&gt;</span>io.jsonwebtoken <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">groupId</span>&gt;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">artifactId</span>&gt;</span>jjwt <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">artifactId</span>&gt;</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;">&lt;<span style="color: #e06c75;line-height: 26px;">version</span>&gt;</span>0.9.1 <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">version</span>&gt;</span> <br> <span style="line-height: 26px;">&lt;/<span style="color: #e06c75;line-height: 26px;">dependency</span>&gt;</span> <br> </section></pre> <ul data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;font-size: 15px;color: rgb(89, 89, 89);list-style-type: circle;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 这是一个生成token的例子 </section></li> </ul> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/et9q9FvjBBqZBvomWB5E7RXxEwYD8leMQzEhibJibDVicnpeswsderxPcL15NAc4kNXpDRicm6YJ8LRHhe5Y1DKNXHW3DgNPr6icP/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;letter-spacing: 0px;background: rgb(40, 44, 52);border-radius: 5px;margin-left: 0px;margin-right: 0px;"> <span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;io.jsonwebtoken.Claims; <br> <span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;io.jsonwebtoken.JwtBuilder; <br> <span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;io.jsonwebtoken.Jwts; <br> <span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;io.jsonwebtoken.SignatureAlgorithm; <br> <br> <span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;javax.crypto.SecretKey; <br> <span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;javax.crypto.spec.SecretKeySpec; <br> <span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;java.util.Base64; <br> <span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;java.util.Date; <br> <span style="color: #c678dd;line-height: 26px;">import</span>&nbsp;java.util.UUID; <br> <br> <span style="color: #c678dd;line-height: 26px;">public</span>&nbsp; <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">class</span>&nbsp;<span style="color: #e6c07b;line-height: 26px;">JwtUtil</span>&nbsp;</span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;有效期为</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">public</span>&nbsp; <span style="color: #c678dd;line-height: 26px;">static</span>&nbsp; <span style="color: #c678dd;line-height: 26px;">final</span>&nbsp;Long&nbsp;JWT_TTL&nbsp;=&nbsp; <span style="color: #d19a66;line-height: 26px;">60</span>&nbsp;*&nbsp; <span style="color: #d19a66;line-height: 26px;">60</span>&nbsp;*&nbsp; <span style="color: #d19a66;line-height: 26px;">1000</span>&nbsp;*&nbsp; <span style="color: #d19a66;line-height: 26px;">24</span>; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;60&nbsp;*&nbsp;60&nbsp;*&nbsp;1000&nbsp;*&nbsp;24&nbsp;&nbsp;一个小时</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;设置秘钥明文&nbsp;---&nbsp;自己改就行</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">public</span>&nbsp; <span style="color: #c678dd;line-height: 26px;">static</span>&nbsp; <span style="color: #c678dd;line-height: 26px;">final</span>&nbsp;String&nbsp;JWT_KEY&nbsp;=&nbsp; <span style="color: #98c379;line-height: 26px;">"qx"</span>; <br>&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;用于生成uuid,用来标识唯一</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;String&nbsp;<span style="color: #61aeee;line-height: 26px;">getUUID</span><span style="line-height: 26px;">()</span></span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;uuid&nbsp;=&nbsp;UUID.randomUUID().toString().replaceAll( <span style="color: #98c379;line-height: 26px;">"-"</span>,&nbsp; <span style="color: #98c379;line-height: 26px;">""</span>); <span style="color: #5c6370;font-style: italic;line-height: 26px;">//token用UUID来代替</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;uuid; <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;标识唯一<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;subject&nbsp;:&nbsp;我们想要加密存储的数据<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ttl&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;我们想要设置的过期时间<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span> <br> <br>&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;生成token&nbsp;&nbsp;jwt加密&nbsp;subject&nbsp;token中要存放的数据(json格式)</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;String&nbsp;<span style="color: #61aeee;line-height: 26px;">createJWT</span><span style="line-height: 26px;">(String&nbsp;subject)</span>&nbsp;</span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JwtBuilder&nbsp;builder&nbsp;=&nbsp;getJwtBuilder(subject,&nbsp; <span style="color: #c678dd;line-height: 26px;">null</span>,&nbsp;getUUID()); <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;设置过期时间</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;builder.compact(); <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;生成token&nbsp;&nbsp;jwt加密</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;String&nbsp;<span style="color: #61aeee;line-height: 26px;">createJWT</span><span style="line-height: 26px;">(String&nbsp;subject,&nbsp;Long&nbsp;ttlMillis)</span>&nbsp;</span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JwtBuilder&nbsp;builder&nbsp;=&nbsp;getJwtBuilder(subject,&nbsp;ttlMillis,&nbsp;getUUID()); <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;设置过期时间</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;builder.compact(); <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;创建token&nbsp;jwt加密</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;String&nbsp;<span style="color: #61aeee;line-height: 26px;">createJWT</span><span style="line-height: 26px;">(String&nbsp;id,&nbsp;String&nbsp;subject,&nbsp;Long&nbsp;ttlMillis)</span>&nbsp;</span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JwtBuilder&nbsp;builder&nbsp;=&nbsp;getJwtBuilder(subject,&nbsp;ttlMillis,&nbsp;id); <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;设置过期时间</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;builder.compact(); <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">private</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;JwtBuilder&nbsp;<span style="color: #61aeee;line-height: 26px;">getJwtBuilder</span><span style="line-height: 26px;">(String&nbsp;subject,&nbsp;Long&nbsp;ttlMillis,&nbsp;String&nbsp;uuid)</span>&nbsp;</span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SignatureAlgorithm&nbsp;signatureAlgorithm&nbsp;=&nbsp;SignatureAlgorithm.HS256; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SecretKey&nbsp;secretKey&nbsp;=&nbsp;generalKey(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">long</span>&nbsp;nowMillis&nbsp;=&nbsp;System.currentTimeMillis(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Date&nbsp;now&nbsp;=&nbsp; <span style="color: #c678dd;line-height: 26px;">new</span>&nbsp;Date(nowMillis); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">if</span>(ttlMillis== <span style="color: #c678dd;line-height: 26px;">null</span>){ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ttlMillis=JwtUtil.JWT_TTL; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">long</span>&nbsp;expMillis&nbsp;=&nbsp;nowMillis&nbsp;+&nbsp;ttlMillis; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Date&nbsp;expDate&nbsp;=&nbsp; <span style="color: #c678dd;line-height: 26px;">new</span>&nbsp;Date(expMillis); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;Jwts.builder() <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.setId(uuid)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//唯一的ID</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.setSubject(subject)&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;主题&nbsp;&nbsp;可以是JSON数据</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.setIssuer( <span style="color: #98c379;line-height: 26px;">"sg"</span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;签发者</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.setIssuedAt(now)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;签发时间</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.signWith(signatureAlgorithm,&nbsp;secretKey)&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//使用HS256对称加密算法签名,&nbsp;第二个参数为秘钥</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.setExpiration(expDate); <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;生成加密后的秘钥&nbsp;secretKey</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;SecretKey&nbsp;<span style="color: #61aeee;line-height: 26px;">generalKey</span><span style="line-height: 26px;">()</span>&nbsp;</span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">byte</span>[]&nbsp;encodedKey&nbsp;=&nbsp;Base64.getDecoder().decode(JwtUtil.JWT_KEY); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SecretKey&nbsp;key&nbsp;=&nbsp; <span style="color: #c678dd;line-height: 26px;">new</span>&nbsp;SecretKeySpec(encodedKey,&nbsp; <span style="color: #d19a66;line-height: 26px;">0</span>,&nbsp;encodedKey.length,&nbsp; <span style="color: #98c379;line-height: 26px;">"AES"</span>); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;key; <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;jwt解密</span> <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;Claims&nbsp;<span style="color: #61aeee;line-height: 26px;">parseJWT</span><span style="line-height: 26px;">(String&nbsp;jwt)</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">throws</span>&nbsp;Exception&nbsp;</span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SecretKey&nbsp;secretKey&nbsp;=&nbsp;generalKey(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;Jwts.parser() <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.setSigningKey(secretKey) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.parseClaimsJws(jwt) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getBody(); <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br>} <br> </section></pre> <ul data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;font-size: 15px;color: rgb(89, 89, 89);list-style-type: circle;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> 写个单元测试,测试一下 </section></li> </ul> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/et9q9FvjBBqZBvomWB5E7RXxEwYD8leMQzEhibJibDVicnpeswsderxPcL15NAc4kNXpDRicm6YJ8LRHhe5Y1DKNXHW3DgNPr6icP/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span> <section style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);display: -webkit-box;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;font-size: 12px;letter-spacing: 0px;background: rgb(40, 44, 52);border-radius: 5px;margin-left: 0px;margin-right: 0px;"> <span style="color: #61aeee;line-height: 26px;">@Test</span> <br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">void</span>&nbsp;<span style="color: #61aeee;line-height: 26px;">test</span><span style="line-height: 26px;">()</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">throws</span>&nbsp;Exception&nbsp;</span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;token&nbsp;=&nbsp;JwtUtil.createJWT( <span style="color: #98c379;line-height: 26px;">"1735209949551763457"</span>); <br>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println( <span style="color: #98c379;line-height: 26px;">"Token:&nbsp;"</span>&nbsp;+&nbsp;token); <br>&nbsp;&nbsp;&nbsp;&nbsp;Date&nbsp;tokenExpirationDate&nbsp;=&nbsp;getTokenExpirationDate(token); <br>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(tokenExpirationDate); <br>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(tokenExpirationDate.toString()); <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">long</span>&nbsp;exp&nbsp;=&nbsp;tokenExpirationDate.getTime(); <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">long</span>&nbsp;cur&nbsp;=&nbsp;System.currentTimeMillis(); <br>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(exp); <br>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(cur); <br>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(exp&nbsp;-&nbsp;cur); <br>} <br> <span style="color: #5c6370;font-style: italic;line-height: 26px;">//&nbsp;解析令牌并获取过期时间</span> <br> <span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">public</span>&nbsp;<span style="color: #c678dd;line-height: 26px;">static</span>&nbsp;Date&nbsp;<span style="color: #61aeee;line-height: 26px;">getTokenExpirationDate</span><span style="line-height: 26px;">(String&nbsp;token)</span>&nbsp;</span>{ <br>&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">try</span>&nbsp;{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SecretKey&nbsp;secretKey&nbsp;=&nbsp;generalKey(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Claims&nbsp;claims&nbsp;=&nbsp;Jwts.parser() <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.setSigningKey(secretKey) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.parseClaimsJws(token) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.getBody(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">return</span>&nbsp;claims.getExpiration(); <br>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp; <span style="color: #c678dd;line-height: 26px;">catch</span>&nbsp;(ExpiredJwtException&nbsp;|&nbsp;SignatureException&nbsp;e)&nbsp;{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #c678dd;line-height: 26px;">throw</span>&nbsp; <span style="color: #c678dd;line-height: 26px;">new</span>&nbsp;RuntimeException( <span style="color: #98c379;line-height: 26px;">"Invalid&nbsp;token"</span>,&nbsp;e); <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br>} <br> </section></pre> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> Token有点长,就不放全部了 </section> <section style="margin-left: 0px;margin-right: 0px;"> <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" src="/upload/9226e056a33952cdce2b001a81ccc723.png" data-cropx1="0" data-cropx2="908.0968858131488" data-cropy1="0" data-cropy2="158.8235294117647" data-imgfileid="505884839" data-ratio="0.17400881057268722" src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/UtWdDgynLdZG1RobrBf5kvQ4vgmic1hTO1urEbcbTiayo25ODStjEichGKgZic8pdjaDG9BBtIOuMbj0gkruOC6S3A/640?wx_fmt=jpeg" data-type="jpeg" data-w="908" style="border-radius: 6px;display: block;margin: 20px auto;object-fit: contain;box-shadow: rgb(153, 153, 153) 2px 4px 7px;width: 486px;height: 85px;"> </figure> </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 可以看到我们的 exp 过期时间的毫秒数为 1703651262000 </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 可以看到我们的 cur 当前时间的毫秒数为 1703564863035 </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 我们将两者相减得到的值为 86398965ms,我们可以算一下一天的毫秒数是多少 1000 * 60 * 60 * 24 ms = 86400000ms </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 这样我们就能够拿到token的过期时间tokenExpirationDate了 </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 我们就可以通过在校验token的时候,如果token校验通过了,此时我们拿到该token的过期时间,以(过期时间 - 当前时间)进行判断 </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 如果说 (过期时间 - 当前时间) 小于约定的值,那么我们就重新根据token里面的信息,重新创建一个token,将新的token放到请求头中 </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 返回给前端,前端去进行本地存储更新token </section> <h2 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;font-weight: bold;color: black;font-size: 22px;border-bottom: 4px solid rgb(64, 184, 250);"><span style="display: flex;width: 20px;height: 20px;background-size: 20px 20px;background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/TLH3CicPVibrdibBMILeF8CicWeSvZ23v8btwtIMbkTwRmos7t5aawrahU9CianKiaXgAyc63QY0JtwjtS8QpWFORSaA/640?wx_fmt=png&amp;from=appmsg&quot;);margin-bottom: -22px;"></span><span style="display: flex;color: #40B8FA;font-size: 20px;margin-left: 25px;">前端token续约</span><span style="display: flex;width: 200px;height: 10px;border-top-left-radius: 20px;background: rgba(64, 184, 250, 0.5);color: rgb(255, 255, 255);font-size: 16px;letter-spacing: 0.544px;justify-content: flex-end;float: right;margin-top: -10px;"></span></h2> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> token的续约偏向于前端的解决方案,即由前端来进行token的过期时间的判断,首先前后端需要对接商量好一个token续约的接口, </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 当前端发现这个token快要过期的时候,向后端发送该token,然后后端将该token的过期时间延长。 </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> <strong style="color: rgb(53, 148, 247);">「前端采用的是双Token的方式,access-token 和 refresh-token即 AT 和 RT」</strong> </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> <strong style="color: rgb(53, 148, 247);">「而对于纯后端的方式,就是只有access-token这一个token」</strong> </section> <ul data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;font-size: 15px;color: rgb(89, 89, 89);list-style-type: circle;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;"><strong style="color: rgb(53, 148, 247);">「那么问题来了 AT 和 RT 到底有什么区别?为什么需要RT?」</strong></p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;"><strong style="color: rgb(53, 148, 247);">「在前端实现方案来说,RT是用来在AT即将过期的时候,用RT获取最新的token」</strong></p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">我解释一下我的观点:</p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">AT的暴露机会更多,每个请求都要携带,所以设置的过期时间短一点,<strong style="color: rgb(53, 148, 247);">「减少劫持风险」</strong></p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">RT只会暴露在auth服务中用来刷新at,设置的过期时间长一点,<strong style="color: rgb(53, 148, 247);">「增加便利性。」</strong></p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">AT 和 RT 是为了网络传输安全,网络传输中,容易暴露 AT,因为 AT 时间短,暴露后风险系数才低</p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;"><strong style="color: rgb(53, 148, 247);">「这种是标准的安全处理,其实已经无需探讨他的合理性,就好像 https 之于 http 一样」</strong></p> </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin: 30px 0px 15px;font-weight: bold;color: black;font-size: 22px;border-bottom: 4px solid rgb(64, 184, 250);"><span style="display: flex;width: 20px;height: 20px;background-size: 20px 20px;background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_png/TLH3CicPVibrdibBMILeF8CicWeSvZ23v8btwtIMbkTwRmos7t5aawrahU9CianKiaXgAyc63QY0JtwjtS8QpWFORSaA/640?wx_fmt=png&amp;from=appmsg&quot;);margin-bottom: -22px;"></span><span style="display: flex;color: #40B8FA;font-size: 20px;margin-left: 25px;">疑问及思考</span><span style="display: flex;width: 200px;height: 10px;border-top-left-radius: 20px;background: rgba(64, 184, 250, 0.5);color: rgb(255, 255, 255);font-size: 16px;letter-spacing: 0.544px;justify-content: flex-end;float: right;margin-top: -10px;"></span></h2> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 要是前端有一个表单页面,长时间不进行请求的发送,此时用户填写完表单了,再点击提交的时候,后端返回401了,怎么办? </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 也就是说,虽然你后端可以无感刷新Token,但是你后端无感刷新Token的前提是:前端得发请求,如果用户长时间不进行页面的交互, </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 即没有进行任何业务逻辑的跳转什么的,就单纯的往表单上面填东西,什么请求也没发的情况下,后端是无法感知Token过期的 </section> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> <strong style="color: rgb(53, 148, 247);">「这种情况怎么解决?」</strong> </section> <ul data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;font-size: 15px;color: rgb(89, 89, 89);list-style-type: circle;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">对于纯后端的解决方案,我是这样想的</p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">让前端在表单填写内容的时候做处理,如果提交返回的是401,那么前端就需要获取表单存在本地存储 然后跳转登录页,登录成功后</p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">返回这个页面,然后从本地存储取出来再回显到表单上面。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">对于前端的解决方案,我是这样想的</p> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">对于后端来说就是AT过期了,而对于前端来说就是AT和RT都过期了,怎么处理?</p> </section></li> </ul> <ol data-tool="mdnice编辑器" style="margin: 8px 0px;padding-left: 25px;font-size: 15px;color: rgb(89, 89, 89);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">需要监听refresh token的过期时间,在接近过期的时候向后端发起请求来刷新refresh token 或者是定期刷新一下refresh token</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;font-size: 14px;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;color: rgb(43, 43, 43);margin-top: 10px;margin-bottom: 10px;word-spacing: 2px;">和后端的解决方案一样,前端做一个类似草稿箱的功能对表单等元素进行保存</p> </section></li> </ol> <section style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin: 10px 0px;font-size: 14px;word-spacing: 2px;"> 关于无状态的 JWT 到底应该怎样用?参见: <code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">https://t.zsxq.com/15xye5IWG</code>。 </section>

惊呆:RocketMQ顺序消息,是“4把锁”实现的(顺序消费)

作者:じ☆ve宝贝

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">在40岁老架构师 尼恩的<strong>读者交流群</strong>(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多关于RocketMQ 的、很重要的面试题:</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgba(0, 0, 0, 0.05);padding: 10px 1em;border-left-color: rgb(221, 221, 221);margin-top: 1.2em;margin-bottom: 1.2em;border-left-width: 4px;color: rgb(119, 119, 119);quotes: none;"> <p style="padding-top: 8px;padding-bottom: 8px;text-wrap: wrap;text-size-adjust: auto;font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;line-height: 1.75em;">如何保证RocketMQ消息有序?</p> <p style="padding-top: 8px;padding-bottom: 8px;text-wrap: wrap;text-size-adjust: auto;font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;line-height: 1.75em;">RocketMQ 顺序消息,底层原理是什么?</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">这些题目是非常常见的面试题,回答的时候&nbsp;有两个层面</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;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> 第一个层面:应用 开发层 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> 第二个层面:底层 源码层 </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;"><span style="color: rgb(1, 1, 1);font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;letter-spacing: 0.578px;text-wrap: wrap;">第一个层面</span>开发层面的回答,参考答案请参见尼恩《技术自由圈》前面的一篇文章</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;"><a href="https://mp.weixin.qq.com/s?__biz=MzkxNzIyMTM1NQ==&amp;mid=2247500158&amp;idx=1&amp;sn=483783e762febb12f46b60b99ae1180b&amp;scene=21#wechat_redirect" style="color: rgb(217, 33, 66);border-bottom: 0px solid;" data-linktype="2">阿里面试:如何保证RocketMQ消息有序?如何解决RocketMQ消息积压?</a></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">一般来说,能够回答到上面的层次,已经非常牛掰了。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">但是,如果能够更上一层楼,去到<span style="color: rgb(1, 1, 1);letter-spacing: 0.034em;">第二个层面:底层 源码层,能</span><span style="letter-spacing: 0.034em;">从RocketMQ源码层去解答,那就更加让面试官 “不能自已、口水直流、震惊不已”,</span><strong style="letter-spacing: 0.034em;"><span style="letter-spacing: 0.034em;">当然,实现”offer直提”,“offer自由”。</span></strong></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">这里,尼恩这道面试题以及第二个层面的参考答案,也会收入咱们的 《<a href="https://mp.weixin.qq.com/s?__biz=MzkxNzIyMTM1NQ==&amp;mid=2247497474&amp;idx=1&amp;sn=54a7b194a72162e9f13695443eabe186&amp;scene=21#wechat_redirect" style="color: rgb(217, 33, 66);border-bottom: 0px solid;" data-linktype="2">尼恩Java面试宝典PDF</a>》V156版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。</p> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com"> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgba(0, 0, 0, 0.05);padding: 10px 1em;border-left-color: rgb(221, 221, 221);margin-top: 1.2em;margin-bottom: 1.2em;border-left-width: 4px;color: rgb(119, 119, 119);quotes: none;"> <p style="padding-top: 8px;padding-bottom: 8px;text-wrap: wrap;text-size-adjust: auto;font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;line-height: 1.75em;">特别提示,尼恩的3高架构宇宙,尼恩Java面试宝典,都是持续升级。</p> <p style="padding-top: 8px;padding-bottom: 8px;text-wrap: wrap;text-size-adjust: auto;font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;line-height: 1.75em;">最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,后台回复:领电子书</p> </blockquote> </section> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com"> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin: 10px auto 5px;border-top: 1px solid rgb(242, 242, 242);background-color: rgb(242, 242, 242);"><span style="display: none;"></span><span style="margin-top: -1px;padding-top: 14px;padding-bottom: 14px;padding-right: 5px;padding-left: 5px;font-size: 17px;border-top: 4px solid rgb(33, 33, 34);display: inline-block;line-height: 1.5;font-weight: normal;background-color: rgb(30, 30, 30);border-bottom-right-radius: 100px;color: rgb(255, 255, 255);padding-right: 20px;padding-left: 10px;">本文目录</span></h2> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- 尼恩说在前面</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- 回顾: 什么是顺序消息</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 1、分区有序消息</s

增加索引 + 异步 + 不落地后,从 12h 优化到 15 min

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">在开发中,我们经常会遇到这样的需求,将数据库中的图片导出到本地,再传给别人。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">一、一般我会这样做:</span></h2> <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;line-height: 26px;color: rgb(1, 1, 1);"> 通过接口或者定时任务的形式 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 读取Oracle或者MySQL数据库 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 通过FileOutputStream将Base64解密后的byte[]存储到本地 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 遍历本地文件夹,将图片通过FTP上传到第三方服务器 </section></li> </ol> <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="100024775" data-ratio="0.1684100418410042" src="/upload/9b41c28da17c7cfc6436b7f9aca8c4db.png" data-type="png" data-w="956" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">现场炸锅了!</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实际的数据量非常大,据统计差不多有400G的图片需要导出。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><span style="font-family:楷体;font-size:24px;color:#dd0000;">现场人员的反馈是,已经跑了12个小时了,还在继续,不知道啥时候能导完。</span></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">停下来呢?之前的白导了,不停呢?不知道要等到啥时候才能导完。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这不行啊,速度太慢了,一个简单的任务,不能被这东西耗死吧?</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/jJtbwFuzNwBaQz1bSicMCYEu1v1lVH1g6ZVP1YvoibBqibodibnkCSqTiaUFPwaAu2xLH4QPy2jz4sc3QAKoasovuFbeq8KBfG57l/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;"><span style="color: #61aeee;line-height: 26px;">@Value</span>(<span style=

阿里面试:Arthas原理和使用,大概说说吧?

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com"> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgba(0, 0, 0, 0.05);padding: 10px 1em;border-left-color: rgb(221, 221, 221);margin-top: 1.2em;margin-bottom: 1.2em;border-left-width: 4px;color: rgb(119, 119, 119);quotes: none;"> <p style="padding-top: 8px;padding-bottom: 8px;text-wrap: wrap;text-size-adjust: auto;font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;line-height: 1.75em;">Arthas原理和使用,大概说下吧?</p> </blockquote> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">Arthas 也是大家定位和解决线上问题,非常常用的一个工具。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">所以,这个题目如果答不上来, 说明平时没怎么解决过线上问题, 面试基本就挂。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">所以,这道题目,非常重要。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">这里,尼恩吧这道面试题以及参考答案,做了详细的梳理。 大家可以收藏起来, 是不是看看,做到温故而知新。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">同时,也收入咱们的 《<a href="https://mp.weixin.qq.com/s?__biz=MzkxNzIyMTM1NQ==&amp;mid=2247497474&amp;idx=1&amp;sn=54a7b194a72162e9f13695443eabe186&amp;scene=21#wechat_redirect" style="color: rgb(217, 33, 66);border-bottom: 0px solid;" data-linktype="2">尼恩Java面试宝典PDF</a>》V159版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;background: rgba(0, 0, 0, 0.05);padding: 10px 1em;border-left-color: rgb(221, 221, 221);margin-top: 1.2em;margin-bottom: 1.2em;border-left-width: 4px;color: rgb(119, 119, 119);quotes: none;"> <p style="padding-top: 8px;padding-bottom: 8px;text-wrap: wrap;text-size-adjust: auto;font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;line-height: 1.75em;">最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,后台回复:领电子书</p> </blockquote> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin: 10px auto 5px;border-top: 1px solid rgb(242, 242, 242);background-color: rgb(242, 242, 242);"><span style="display: none;"></span><span style="margin-top: -1px;padding-top: 14px;padding-bottom: 14px;padding-right: 5px;padding-left: 5px;font-size: 17px;border-top: 4px solid rgb(33, 33, 34);display: inline-block;line-height: 1.5;font-weight: normal;background-color: rgb(30, 30, 30);border-bottom-right-radius: 100px;color: rgb(255, 255, 255);padding-right: 20px;padding-left: 10px;">本文目录</span></h2> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- 尼恩说在前面</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- Arthas 在线排错工具的巨大价值</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">-&nbsp;<strong style="letter-spacing: 0.578px;">A</strong>rthas快速开始</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 方式一:使用 arthas-boot 启动arthas</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 方式二:使用 as.sh 脚本启动arthas</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">-&nbsp;<strong style="letter-spacing: 0.578px;">A</strong>rthas官方使用案例</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 1、启动 math-game</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 2、启动 arthas</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 3、dashboard 展示当前进程的信息</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 4、thread 命令Main Class</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 5、jad命令 来反编译 Main Class</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 6、watch</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 7、退出 arthas</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- Arthas的核心命令详解</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 1、基础命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 2、类操作命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 2.1 sc 命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - sc 命令 使用参考</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 2.2 sm 命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - 参数说明</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - 使用参考</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 2.3 jad命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - 参数说明参数说明</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 2.4 mc命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 2.6 retransform命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 2.7 dump命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - 参数说明</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - 使用参考</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 2.8 classloader命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - 使用参考</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 3、JVM操作的命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.1 dashboard命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 参数说明</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - 使用参考</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - 数据说明</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - JVM 内部线程</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.2 thread命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - cpu 使用率是如何统计出来的?</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - eg:支持一键展示当前最忙的前 N 个线程并打印堆栈</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - thread --all, 显示所有匹配的线程</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.3 jvm命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.4 sysprop命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.5 sysenv命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.6 vmoption命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.7 getstatic命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.8 ognl命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.9 heapdump命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.10 mbean命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 3.11 memory命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 4、字节码增强命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 4.1 watch命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 4.2 tt 命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 4.3 monitor命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 4.4 stack命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 4.5 trace命令</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 5、OGNL表达式</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 基本语法</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; - 参数说明</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - ①、调用静态属性</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - ②、调用静态方法</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - ③、调用构造方法</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; &nbsp; &nbsp; - ④、读取不同类型的值</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- Arthas线上常用场景</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 1、排查CPU占用过高问题</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 2、排查线程阻塞问题</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 3、排查死锁问题</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 4、排查方法执行过慢问题</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><span style="font-size: 14px;">&nbsp; - 5、动态修改线上代码</span></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- Arthas底层原理</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- 参考文献</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- 说在最后: “offer自由” 很容易的</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;">- 部分历史案例</span></strong></p> <p style="margin-bottom: 0px;letter-spacing: 0.578px;text-wrap: wrap;"><strong><span style="font-size: 14px;"><br></span></strong></p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin: 10px auto 5px;border-top: 1px solid rgb(242, 242, 242);background-color: rgb(242, 242, 242);"><span style="display: none;"></span><span style="margin-top: -1px;padding-top: 14px;padding-bottom: 14px;padding-right: 5px;padding-left: 5px;font-size: 17px;border-top: 4px solid rgb(33, 33, 34);display: inline-block;line-height: 1.5;font-weight: normal;background-color: rgb(30, 30, 30);border-bottom-right-radius: 100px;color: rgb(255, 255, 255);padding-right: 20px;padding-left: 10px;">Arthas 在线排错工具的巨大价值</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Arthas</code> 是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">Arthas 旨在解决这些问题。开发人员可以通过Arthas &nbsp;在线解决生产问题。通过Arthas ,无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img data-imgfileid="100017348" data-ratio="0.328125" src="/upload/73ed900672a7a3e0b8d73b03fab93004.png" data-type="png" data-w="320" style="display: block;margin-right: auto;margin-left: auto;"> <figcaption style="margin-top: 5px;text-align: center;color: #888;font-size: 14px;"> Arthas工具 </figcaption> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">Arthas中集成了大部分JDK工具的功能实现,因此,在线上情况时,可以通过它快速的帮助我们解决问题,如CPU占用过高、线程阻塞、死锁、代码动态修改、方法执行缓慢、排查<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">404</code>等。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">Arthas 官方文档(https://arthas.aliyun.com/doc/)</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;"><strong>当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决</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;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> <p style="padding-top: 8px;padding-bottom: 8px;color: black;margin-bottom: 16px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> <p style="padding-top: 8px;padding-bottom: 8px;color: black;margin-bottom: 16px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> <p style="padding-top: 8px;padding-bottom: 8px;color: black;margin-bottom: 16px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> <p style="padding-top: 8px;padding-bottom: 8px;color: black;margin-bottom: 16px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> <p style="padding-top: 8px;padding-bottom: 8px;color: black;margin-bottom: 16px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">是否有一个全局视角来查看系统的运行状况?</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> <p style="padding-top: 8px;padding-bottom: 8px;color: black;margin-bottom: 16px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">有什么办法可以监控到JVM的实时运行状态?</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> <p style="padding-top: 8px;padding-bottom: 8px;color: black;margin-bottom: 16px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">怎么快速定位应用的热点,生成火焰图?</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);font-size: 15px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;"> <p style="padding-top: 8px;padding-bottom: 8px;color: black;margin-bottom: 16px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">怎样直接从JVM内查找某个类的实例?</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Arthas</code>支持<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">JDK6+</code>,支持<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Linux/Mac/Winodws</code>,采用命令行交互模式,同时提供丰富的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Tab</code>自动补全功能,进一步方便进行问题的定位和诊断。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;text-align: left;">阿里提供的在线的<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Arthas Terminal</code>学习方式(Arthas Tutorials (aliyun.com)(https://arthas.aliyun.com/doc/arthas-tutorials.html?language=cn)),帮助大家快速上手。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;margin: 10px auto 5px;border-top: 1px solid rgb(242, 242, 242);background-color: rgb(242, 242, 242);"><span style="display: none;"></span><span style="margin-top: -1px;padding-top: 14px;padding-bottom: 14px;padding-right: 5px;padding-left: 5px;font-size: 17px;border-top: 4px solid rgb(33, 33, 34);display: inline-block;line-height: 1.5;font-weight: normal;background-color: rgb(30, 30, 30);border-bottom-right-radius: 100px;color: rgb(255, 255, 255);padding-right: 20px;padding-left: 10px;">Arthas快速开始</span></h2> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;margin: 10px auto 5px;background-color: rgb(252, 252, 252);"><span style="display: none;"></span><span style="margin-top: -1px;padding-top: 6px;padding-bottom: 6px;padding-right: 5px;padding-left: 5px;font-size: 17px;font-weight: normal;display: inline-block;line-height: 1.3;background-color: rgb(212, 224, 250);border-bottom-right-radius: 100px;color: rgb(30, 30, 30);padding-right: 20px;padding-left: 10px;">方式一:使用&nbsp;<code>arthas-boot</code> 启动arthas</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">推荐使用<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">arthas-boot</code>(推荐),下载<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">arthas-boot.jar</code>,然后用<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">java -jar</code>的方式启动:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/Tjnia6K0WAwyp1Niagp5xCN1ku7lqqdRDfcNua58LGckvFQ0LnMcJ51N3IcLzDIXmiaAuKDRMjRp7BAibXicicTk39HE7bnRG0MA6d/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">curl&nbsp;-O&nbsp;https://arthas.aliyun.com/arthas-boot.jar<br>java&nbsp;-jar&nbsp;arthas-boot.jar<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">打印帮助信息:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/Tjnia6K0WAwyp1Niagp5xCN1ku7lqqdRDfcNua58LGckvFQ0LnMcJ51N3IcLzDIXmiaAuKDRMjRp7BAibXicicTk39HE7bnRG0MA6d/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">java&nbsp;-jar&nbsp;arthas-boot.jar&nbsp;-h<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">arthas-boot启动过程中, 会下载一些依赖包,如果下载速度比较慢,可以使用aliyun的镜像:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/Tjnia6K0WAwyp1Niagp5xCN1ku7lqqdRDfcNua58LGckvFQ0LnMcJ51N3IcLzDIXmiaAuKDRMjRp7BAibXicicTk39HE7bnRG0MA6d/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;font-size: 12px;-webkit-overflow-scrolling: touch;padding-top: 15px;background: #282c34;border-radius: 5px;">java&nbsp;-jar&nbsp;arthas-boot.jar&nbsp;--repo-mirror&nbsp;aliyun&nbsp;--use-http<br></code></pre> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">arthas-boot 完成依赖下载,并且启动。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;">启动之后,这时候,arthas会列出当前检测到的java进程,</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Segoe UI&quot;, Arial, freesans, sans-serif;font-size: 15px;text-wrap: wrap;text-size-adjust: auto;line-height: 1.75em;"><code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Arthas</code>会将本机中所有的Java进程查询出来,类似于<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">jps/ps</code>的作用:</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="100017350" data-ratio="0.36666666666666664" src="/upload/46198820267b380da6bf0832707325de.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;margin-bottom: 16px;font-family: &quot;Helv

10亿数据高效插入MySQL最佳方案

作者:微信小助手

<section data-tool="markdown编辑器" data-website="https://markdown.com.cn/editor" style="font-size: 16px;padding: 25px 30px;word-break: break-word;margin-top: -10px;line-height: 1.25;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangTC-Light, PingFangSC-light, PingFangTC-light;letter-spacing: 2px;background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%), linear-gradient(360deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%);background-size: 20px 20px;background-position: center center;"> <p data-tool="markdown.com.cn编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;"><br></p> <hr data-tool="markdown.com.cn编辑器" style="margin-top: 10px;margin-bottom: 10px;height: 1px;border-right: none;border-bottom: none;border-left: none;border-top: 2px solid rgb(59, 170, 250);"> <h2 data-tool="markdown.com.cn编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 22px;border-bottom: 4px solid rgb(64, 184, 250);"><span style="display: flex;width: 20px;height: 20px;background-size: 20px 20px;background-image: none;margin-bottom: -22px;" data-remoteid="c1704732566009" data-cacheurl="https://imgkr.cn-bj.ufileos.com/15fdfb3c-b350-4da9-928e-5f8c506ec325.png"></span><span style="display: flex;color: #40B8FA;font-size: 20px;margin-left: 25px;">写在文章开头</span><span style="display: flex;width: 200px;height: 10px;border-top-left-radius: 20px;background: rgba(64, 184, 250, 0.5);color: rgb(255, 255, 255);font-size: 16px;letter-spacing: 0.544px;justify-content: flex-end;float: right;margin-top: -10px;"></span></h2> <p data-tool="markdown.com.cn编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">你好,我叫sharkchili,目前还是在一线奋斗的Java开发,经历过很多有意思的项目,也写过很多有意思的文章,是CSDN Java领域的博客专家,也是Java Guide的维护者之一,非常欢迎你关注我的公众号:<strong style="color: rgb(53, 148, 247);">「写代码的SharkChili」</strong>,这里面会有笔者精心挑选的并发、JVM、MySQL数据库专栏,也有笔者日常分享的硬核技术小文。</p> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkwODYyNTM2MQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/Wic5BjMXGvIPzn9nOU6xjiaUWqgsFIAibnHHLelHmTkia2ibGJU3FxRLc6dz67ibGibkMUqbavic8vdsolia7s19e0B1xiaw/0?wx_fmt=png" data-nickname="写代码的SharkChili" data-alias="sharkchili-1506" data-signature="Hi,我是sharkchili,CSDN Java 领域博客专家,开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助。" data-from="1" data-is_biz_ban="0"></mp-common-profile> </section> <h2 data-tool="markdown.com.cn编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 22px;border-bottom: 4px solid rgb(64, 184, 250);"><span style="display: flex;width: 20px;height: 20px;background-size: 20px 20px;background-image: none;margin-bottom: -22px;" data-remoteid="c1704732566010" data-cacheurl="https://imgkr.cn-bj.ufileos.com/15fdfb3c-b350-4da9-928e-5f8c506ec325.png"></span><span style="display: flex;color: #40B8FA;font-size: 20px;margin-left: 25px;">问题简介</span><span style="display: flex;width: 200px;height: 10px;border-top-left-radius: 20px;background: rgba(64, 184, 250, 0.5);color: rgb(255, 255, 255);font-size: 16px;letter-spacing: 0.544px;justify-content: flex-end;float: right;margin-top: -10px;"></span></h2> <p data-tool="markdown.com.cn编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">我们直接引入本文要探讨的问题,现在要求用最快的速度把10亿条左右的数据存到数据库中,对应的该实现有着以下几个要求:</p> <ol data-tool="markdown.com.cn编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;color: rgb(89, 89, 89);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 每条数据转为数据库数据大约 <code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">1k</code>。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 数据都存在 <code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">txt</code>文档中,需要进行解析后才能存到数据库中。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 所有数据对应的文档都存在 <code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">Hdfs</code>或 <code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">S3</code>分布式文件存储里。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 数据被分到100个文件中,通过文件的后缀确定这些数据的批次。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 要求保证有序导入,数据尽可能不重复。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 存储的数据库是 <code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">MySQL</code>。 </section></li> </ol> <h2 data-tool="markdown.com.cn编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 22px;border-bottom: 4px solid rgb(64, 184, 250);"><span style="display: flex;width: 20px;height: 20px;background-size: 20px 20px;background-image: none;margin-bottom: -22px;" data-remoteid="c1704732566011" data-cacheurl="https://imgkr.cn-bj.ufileos.com/15fdfb3c-b350-4da9-928e-5f8c506ec325.png"></span><span style="display: flex;color: #40B8FA;font-size: 20px;margin-left: 25px;">分析问题点</span><span style="display: flex;width: 200px;height: 10px;border-top-left-radius: 20px;background: rgba(64, 184, 250, 0.5);color: rgb(255, 255, 255);font-size: 16px;letter-spacing: 0.544px;justify-content: flex-end;float: right;margin-top: -10px;"></span></h2> <p data-tool="markdown.com.cn编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">针对需求整理分析,我们对该功能提出以下几个问题点:</p> <ol data-tool="markdown.com.cn编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;font-size: 15px;color: rgb(89, 89, 89);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 如何插入?是批量插入还是逐条插入? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 这种数据量是否需要考虑一下分库分表? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 插入时是用串行插入数据表还是并行插入数据表? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> MySQL存储引擎如何选择? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 如何实现快速读取数据? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 如何保证的写入时有序,即按照100个文件对应的数据顺序进行存储? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 我们希望将这个插入功能以任务为单位进行,要如何封装? </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-size: 14px;"> 如何实现分布式节点并发工作? </section></li> </ol> <figure data-tool="markdown.com.cn编辑器" style="margin-top: 10px;margin-bottom: 10px;"> <img class="rich_pages wxw-img" data-imgfileid="100000141" data-ratio="0.8967587034813925" src="/upload/d60ccbeee0008ece9c95ee7a8171fe32.png" data-type="png" data-w="833" style="width: auto;border-radius: 6px;display: block;margin: 20px auto;object-fit: contain;box-shadow: rgb(153, 153, 153) 2px 4px 7px;"> </figure> <h2 data-tool="markdown.com.cn编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 22px;border-bottom: 4px solid rgb(64, 184, 250);"><span style="display: flex;width: 20px;height: 20px;background-size: 20px 20px;background-image: none;margin-bottom: -22px;" data-remoteid="c1704732566012" data-cacheurl="https://imgkr.cn-bj.ufileos.com/15fdfb3c-b350-4da9-928e-5f8c506ec325.png"></span><span style="display: flex;color: #40B8FA;font-size: 20px;margin-left: 25px;">逐个击破</span><span style="display: flex;width: 200px;height: 10px;border-top-left-radius: 20px;background: rgba(64, 184, 250, 0.5);color: rgb(255, 255, 255);font-size: 16px;letter-spacing: 0.544px;justify-content: flex-end;float: right;margin-top: -10px;"></span></h2> <h3 data-tool="markdown.com.cn编辑器" style="color: black;font-size: 17px;font-weight: bold;text-align: center;margin-top: 20px;margin-bottom: 20px;"><span style="display: none;"></span><span style="border-bottom: 2px solid RGBA(79, 177, 249, .65);color: #2b2b2b;padding-bottom: 2px;"><span style="width: 30px;height: 30px;display: block;background-image: none;background-position: center center;background-size: 30px;margin: auto auto -8px;opacity: 1;background-repeat: no-repeat;" data-remoteid="c1704732566013" data-cacheurl="https://imgkr.cn-bj.ufileos.com/cdf294d0-6361-4af9-85e2-0913f0eb609b.png"></span>如何插入?批量插入还是单条插入?</span><span style="display: none;"></span></h3> <p data-tool="markdown.com.cn编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 10px;margin-bottom: 10px;font-size: 14px;word-spacing: 2px;">考虑到按照<code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">主键id</code>顺序自增顺序写入可以达到最快性能,所以我们的数据表的主键id一定是顺序自增的,其次我们都知道<code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">MySQL</code>插入操作中最耗时的操作是网络链接,所以我们采用的插入方式是批量插入。为了避免每次进行批量插入时,<code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">JDBC</code>需要预编译<code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">SQL</code>发起的网络<code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">IO</code>,我们使用<code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">Mybatis</code>进行插入时,会采用<code style="margin-right: 2px;margin-left: 2px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);display: inline-block;padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">ExecutorType.BATCH</code>的方式进行批处理插入,代码示例大抵如下所示:</p> <pre data-tool="markdown.com.cn编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;background: #282c34;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;letter-spacing: 0px;"><span style="color: #61aeee;line-height: 26px;">@Autowired</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">private</span>&nbsp;SqlSessionFactory&nbsp;sqlSessionFactory;<br><br><span style="color: #5c6370;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;session插入<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #61aeee;line-height: 26px;">@Test</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">void</span>&nbsp;<span style="color: #61aeee;line-height: 26px;">batchInsert</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">//获取一个BATCH的sqlSession&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SqlSession&nbsp;sqlSession&nbsp;=&nbsp;sqlSessionFactory.openSession(ExecutorType.BATCH);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BatchInsertTestMapper&nbsp;sqlSessionMapper&nbsp;=&nbsp;sqlSession.getMapper(BatchInsertTestMapper<span style="line-height: 26px;">.<span style="color: #c678dd;line-height: 26px;">class</span>)</span>;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">long</span>&nbsp;start&nbsp;=&nbsp;System.currentTimeMillis();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">for</span>&nbsp;(BatchInsertTest&nbsp;batchInsertTest&nbsp;:&nbsp;testList)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">//生成插入语句</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sqlSessionMapper.insert(batchInsertTest);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #5c6370;font-style: italic;line-height: 26px;">//使用批处理的方式一次性提交这批插入SQL</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sqlSession.commit();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #c678dd;line-height: 26px;">long</span>&nbsp;end&nbsp;=&nbsp;System.currentTimeMillis();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info(<span style="color: #98c379;line-height: 26px;">"批处理插入

自从用了灰度发布,睡觉真香!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;overflow-wrap: break-word;text-align: left;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">中有具体的介绍和代码的实现,从网关灰度到微服务之间灰度发布。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-imgfileid="100047554" data-ratio="0.4197994987468672" src="/upload/2733500a9cd3bc7b79d18195642d0aa7.png" data-type="png" data-w="798" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">灰度发布的定义</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">互联网产品需要快速迭代开发上线,又要保证质量,保证刚上线的系统,一旦出现问题可以很快控制影响面,就需要设计一套灰度发布系统</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">灰度发布系统的作用,可以根据配置,将用户的流量导到新上线的系统上,来快速验证新的功能,而一旦出现问题,也可以马上的修复,简单的说,就是一套A/B Test系统。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">灰度发布允许带着bug上线,只要bug不是致命的,当然这个bug是不知道的情况下,如果知道就要很快的改掉</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">简单灰度发布系统的设计</span></h2> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-imgfileid="100047553" data-ratio="0.48828125" src="/upload/03a0820009df92b7141618f869235bb6.jpg" data-type="jpeg" data-w="1024" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">灰度简单架构如上图所示,其中的必要组件如下:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: decimal;color: #f83929;font-size: 16px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">策略的配置平台,存放灰度的策略</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">灰度功能的执行程序</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">注册中心,注册的服务携带 <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">ip/Port/name/version</code></p> </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">有了上面三个组件,才算一个完整的灰度平台</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">灰度的策略</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">灰度必须要有灰度策略,灰度策略常见的方式有以下几种</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;list-style-type: decimal;color: #f83929;font-size: 16px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">基于Request Header进行流量切分</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">基于Cookie进行流量切分</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;text-align: left;font-weight: 500;color: #353535;"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">基于请求参数进行流量切分</p> </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">举例:根据请求中携带的用户uid进行取模,灰度的范围是百分之一,那么uid取模的范围就是100,模是0访问新版服务,模是1~99的访问老版服务。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">灰度发布策略分为两类,单策略和组合策略</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">单策略</strong>:比如按照用户的<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">uid</code>、<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">token</code>、<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">ip</code>进行取模</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">组合策略</strong>:多个服务同时灰度,比如我有A/B/C三个服务,需要同时对A和C进行灰度,但是B不需要灰度,这个时候就需要一个tag字段,具体实现在下文详述</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">灰度发布具体的执行控制</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">在上面的简单灰度发布系统架构中我们了解到,灰度发布服务分为上游和下游服务,上游服务是具体的执行灰度策略的程序,这个服务可以是nginx,也可以是微服务架构中的网关层/业务逻辑层,下面我们就来分析一下不同的上游服务,如何落地</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">Nginx</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">如果上游服务是nginx,那么就需要nginx通过Lua扩展nginx实现灰度策略的配置和转发,因为nginx本身并不具备灰度策略的执行</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">通过lua扩展实现了灰度策略的执行,但是问题又来了,nginx本身并不具备接收配置管理平台的灰度策略,这个时候应该怎么办呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">解决方案:本地部署Agent(需要自己开发),接收服务配置管理平台下发的灰度策略,更新nginx配置,优雅重启Nginx服务</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">网关层/业务逻辑层/数据访问层</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">只需要集成配置管理平台客户端SDK,接收服务配置管理平台下发的灰度策略,在通过集成的SDK进行灰度策略的执行即可</p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 22px;text-align: left;margin: 20px 10px 0px 0px;"><span style="display: none;"></span><span style="font-size: 18px;font-weight: 700;color: #222;display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">灰度发布复杂场景</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">下面举例两个稍微复杂的灰度发布场景,灰度策略假设都按照uid取模灰度百分之一的用户,看一下如何实现。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">场景1:调用链上同时灰度多个服务</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">功能升级涉及到多个服务变动,网关层和数据访问层灰度,业务逻辑层不变,这个时候应该如何进行灰度?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;"><strong style="font-weight: 700;color: rgb(248, 57, 41);">解决方案</strong>:</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">经过新版本网关层的请求,全部打上<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">tag T</code>,在业务逻辑层根据<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">tag T</code>进行转发, 标记<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">Tag T</code>的请求全部转发到新版数据访问层服务上,没有<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(271, 93, 108);">tag T</code>的请求全部转发到老版数据访问层上。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-imgfileid="100047556" data-ratio="1.4607703281027105" src="/upload/d50bf6727ac47e6b68e8cb9824a50689.jpg" data-type="jpeg" data-w="701" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;padding: 0px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">场景2:涉及数据的灰度服务</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">涉及到数据的灰度服务,一定会使用到数据库,使用到数据库就会涉及到你使用数据库前后的表字段不一致,我老版本是A/B/C三个字段,新版本是A/B/C/D四个字段。这时新版的灰度,就不能往老版的数据库进行修改了,这个时候就需要把数据copy一份出来做这个事情了</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">数据库其实并没有灰度的概念,这个时候我们只能把数据重新拷贝一份出来进行读和写,因为这时你的写必须是全量的(双写),不能说90%的数据写入到老版本,10%的数据写入到新版本,因为这个时候你会发现两个数据库的数据都不是全量的。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">离线全量复制数据的过程中一定会有数据丢失,这个时候就需要业务逻辑层写一份数据到MQ中,等数据同步完成之后,新版的数据访问层再将MQ的数据写入到新版本的DB中,实现数据的一致性,这个也是引入MQ的主要目的。</p> <figure data-tool="mdnice编辑器" style="margin: 0;margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-imgfileid="100047555" data-ratio="1.2768079800498753" src="/upload/c96991b41e30a4c354962053df91fbe1.jpg" data-type="jpeg" data-w="802" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin: 0.8em 0;font-size: 16px;color: #353535;">灰度过程中需要对两个数据库的数据进行对比,观察数据是否一致。这样不管是灰度失败,放弃新版DB,还是灰度成功切换到新版DB,数据都不会产生丢失。</p> <blockquote data-tool="mdnice编辑器" style="margin-top: 0px;margin-bottom: 20px;padding: 8px 10px 8px 15px;outline: 0px;border-left-width: 2px;border-left-color: rgb(239, 112, 96);color: rgb(106, 115, 125);font-size: 0.9em;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;text-align: left;text-wrap: wrap;word-spacing: 0.8px;border-top: none;border-right: none;border-bottom: none;overflow: auto;background: rgb(255, 249, 249);letter-spacing: 0.5444px;"></blockquote> </section>

GPT-SoVits:刚上线两天就获得了1.4k star的开源声音克隆项目!效果炸裂的跨语言音色克隆模型!

作者:微信小助手

<p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">就在两天前,<span style="font-style: italic;"><strong style="line-height: 1.75;color: rgb(0, 152, 116);">RVC变声器创始人</strong></span>&nbsp;(GitHub昵称:RVC-Boss)开源了一款跨语言音色克隆项目&nbsp;<strong style="line-height: 1.75;color: rgb(0, 152, 116);">GPT-SoVITS</strong>。项目一上线就引来了互联网大佬和博主的好评推荐,不到两天时间就已经在GitHub上获得了<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">1.4k</code>&nbsp;Star量。</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">据说,该项目是<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">RVC-Boss</code>&nbsp;同<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">Rcell</code>&nbsp;(AI音色转换技术Sovits开发者)共同研究,历时半年,期间遇到了很多难题而开发出来的一款全新的低成本的易用的音色克隆工具。</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">接下来小编带大家一起看看这款新型的音色克隆工具<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">RVC-Boss</code>有何特别之处!</p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010018" data-ratio="0.6468646864686468" data-s="300,640" src="/upload/120babadb08f295387baaf1de35e20e3.png" data-type="png" data-w="606" style=""></p> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 1.1em;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(0, 152, 116);color: rgb(63, 63, 63);">项目介绍</h3> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);"><code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">GPT-SoVITS</code>&nbsp;是一款强大的支持少量语音转换、文本到语音的音色克隆模型。支持中文、英文、日文的语音推理。</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">据开发者及各大博主测验,仅需提供 5 秒语音样本即可体验达到 80%~95% 像的声音克隆。若提供 1 分钟语音样本可以逼近真人的效果,且训练出高质量的 TTS 模型!</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">项目地址:<span style="line-height: 1.75;color: rgb(87, 107, 149);">https://github.com/RVC-Boss/GPT-SoVITS</span></p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">目前已获得&nbsp;<span style="font-style: italic;"><strong style="line-height: 1.75;color: rgb(0, 152, 116);">1.4k Star</strong></span>,看到很多人对其评价为目前最强中文语音克隆工具。</p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010019" data-ratio="0.5072697899838449" data-s="300,640" src="/upload/543a3a13952996010602e6efe692aff3.png" data-type="png" data-w="1238" style=""></p> <h4 style="font-size: 1em;letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-weight: bold;margin: 2em 8px 0.5em;color: rgb(0, 152, 116);">特征:</h4> <ul style="font-size: 14px;letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding-left: 1em;list-style: circle;color: rgb(63, 63, 63);" class="list-paddingleft-1"> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>•&nbsp;<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">零样本 TTS</code>:输入 5 秒语音样本并体验即时文本到语音转换。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>•&nbsp;<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">Few-shot TTS</code>:仅用 1 分钟的训练数据即可微调模型,以提高语音相似度和真实感。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>•&nbsp;<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">跨语言支持</code>:用与训练数据集不同的语言进行推理,目前支持英语、日语和中文。</p></li> <li style="text-align: left;line-height: 1.75;text-indent: -1em;display: block;margin: 0.2em 8px;"><p>•&nbsp;<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">WebUI工具</code>:集成工具包括语音伴奏分离、自动训练集分割、中文ASR和文本标注,帮助初学者创建训练数据集和GPT/SoVITS模型。</p></li> </ul> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 1.1em;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(0, 152, 116);color: rgb(63, 63, 63);">使用方式</h3> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">如果是 Windows 可直接开箱使用。</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">只需下载项目中的 prezip,解压并双击&nbsp;<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">go-webui.bat</code>文件 即可启动&nbsp;<span style="font-style: italic;"><strong style="line-height: 1.75;color: rgb(0, 152, 116);">GPT-SoVITS-WebUI</strong></span>,然后通过界面方式操作即可。</p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010020" data-ratio="0.6095890410958904" data-s="300,640" src="/upload/6938b5ee5ab444611ffaa926d0718581.png" data-type="png" data-w="730" style=""></p> <h4 style="font-size: 1em;letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-weight: bold;margin: 2em 8px 0.5em;color: rgb(0, 152, 116);">项目环境依赖:</h4> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);"><code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">GPT-SoVITS</code>&nbsp;依赖于开源音视频全能转码工具&nbsp;<strong style="line-height: 1.75;color: rgb(0, 152, 116);">FFmpeg</strong>。这个需要我们根据不同的系统进行手动安装。</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">conda 环境安装:</p> <pre style="color: rgb(171, 178, 191);background: rgb(40, 44, 52);font-size: 14px;letter-spacing: normal;text-align: left;line-height: 1.5;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;overflow-x: auto;border-radius: 8px;margin: 10px 8px;"><span style="padding: initial;display: block;height: 25px;background-color: transparent;background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/PdibpV1sFDHc5xia6ibXvDRhCzX9w4y3W8ZGXtHJKJMr5uJXT3sBibs7Jmy7752O9F20HKicS2sfZks7Dia58VbdbUnDd2GGgAqYRx/640?wx_fmt=svg&amp;from=appmsg&quot;);background-position: 14px 10px;background-repeat: no-repeat;background-size: 40px;"></span><code style="font-family: Menlo, &quot;Operator Mono&quot;, Consolas, Monaco, monospace;display: -webkit-box;padding: 0.5em 1em 1em;overflow-x: auto;line-height: 1.75;white-space-collapse: collapse;"><span>conda&nbsp;install&nbsp;ffmpeg</span></code></pre> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">Ubuntu/Debian 用户:</p> <pre style="color: rgb(171, 178, 191);background: rgb(40, 44, 52);font-size: 14px;letter-spacing: normal;text-align: left;line-height: 1.5;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;overflow-x: auto;border-radius: 8px;margin: 10px 8px;"><span style="padding: initial;display: block;height: 25px;background-color: transparent;background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/PdibpV1sFDHc5xia6ibXvDRhCzX9w4y3W8ZGXtHJKJMr5uJXT3sBibs7Jmy7752O9F20HKicS2sfZks7Dia58VbdbUnDd2GGgAqYRx/640?wx_fmt=svg&amp;from=appmsg&quot;);background-position: 14px 10px;background-repeat: no-repeat;background-size: 40px;"></span><code style="font-family: Menlo, &quot;Operator Mono&quot;, Consolas, Monaco, monospace;display: -webkit-box;padding: 0.5em 1em 1em;overflow-x: auto;line-height: 1.75;white-space-collapse: collapse;"><span>sudo&nbsp;apt&nbsp;install&nbsp;ffmpeg</span><br><span>sudo&nbsp;apt&nbsp;install&nbsp;libsox</span><span>-</span><span>dev</span><br><span>conda&nbsp;install&nbsp;</span><span>-</span><span>c&nbsp;conda</span><span>-</span><span>forge&nbsp;</span><span style="color: rgb(152, 195, 121);">'ffmpeg&lt;7'</span></code></pre> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">Mac 操作系统用户:</p> <pre style="color: rgb(171, 178, 191);background: rgb(40, 44, 52);font-size: 14px;letter-spacing: normal;text-align: left;line-height: 1.5;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;overflow-x: auto;border-radius: 8px;margin: 10px 8px;"><span style="padding: initial;display: block;height: 25px;background-color: transparent;background-image: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/PdibpV1sFDHc5xia6ibXvDRhCzX9w4y3W8ZGXtHJKJMr5uJXT3sBibs7Jmy7752O9F20HKicS2sfZks7Dia58VbdbUnDd2GGgAqYRx/640?wx_fmt=svg&amp;from=appmsg&quot;);background-position: 14px 10px;background-repeat: no-repeat;background-size: 40px;"></span><code style="font-family: Menlo, &quot;Operator Mono&quot;, Consolas, Monaco, monospace;display: -webkit-box;padding: 0.5em 1em 1em;overflow-x: auto;line-height: 1.75;white-space-collapse: collapse;"><span>brew&nbsp;install&nbsp;ffmpeg</span></code></pre> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">Windows操作系统用户:</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">需手动下载ffmpeg.exe和ffprobe.exe并将其放置在 GPT-SoVITS 根目录下。</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">ffmpeg.exe下载地址:<span style="line-height: 1.75;color: rgb(87, 107, 149);">https://huggingface.co/lj1995/VoiceConversionWebUI/blob/main/ffmpeg.exe</span></p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">ffprobe.exe下载地址:<span style="line-height: 1.75;color: rgb(87, 107, 149);">https://huggingface.co/lj1995/VoiceConversionWebUI/blob/main/ffprobe.exe</span></p> <h4 style="font-size: 1em;letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-weight: bold;margin: 2em 8px 0.5em;color: rgb(0, 152, 116);">预训练模型下载放置位置:</h4> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010021" data-ratio="0.2586002372479241" data-s="300,640" src="/upload/8c0b5e76105d182857ac5a8094a342a0.png" data-type="png" data-w="843" style=""></p> <h4 style="font-size: 1em;letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-weight: bold;margin: 2em 8px 0.5em;color: rgb(0, 152, 116);">具体使用步骤:</h4> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">1、打开项目根目录,将预置克隆音频放置根目录,然后双击<code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">go-webui.bat</code>&nbsp;运行项目。(可以发现它实际上执行了Python脚本webui.py)</p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010022" data-ratio="0.18267581475128644" data-s="300,640" src="/upload/5548c2ef2e8d297bbcee657b3390810e.png" data-type="png" data-w="1166" style=""></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010023" data-ratio="0.535870243293824" data-s="300,640" src="/upload/24bad358eb14288fb41424e9ee40beac.png" data-type="png" data-w="1603" style=""></p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">2、语音切割演示,将音频文件路径填入“音频自动切分输入路径”下,点击“开启语音切割”</p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010024" data-ratio="0.4476854787571338" data-s="300,640" src="/upload/392c09635a47647eb6d8e5e8ad997ae0.png" data-type="png" data-w="1577" style=""></p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">最终的切分结果会存放在项目Output下的slicer_opt目录下(切分成了20份)<br></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010025" data-ratio="1.0535714285714286" data-s="300,640" src="/upload/9c07c15f2037b5be6c85176776745130.png" data-type="png" data-w="616" style=""></p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">3、开始转写,将切分路径填入“中文批量离线ASR工具”输入路径下,转写结果文件会在<span style="color: rgb(63, 63, 63);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 14px;letter-spacing: 1.4px;text-align: left;text-wrap: wrap;">Output下的asr</span><span style="color: rgb(63, 63, 63);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 14px;letter-spacing: 1.4px;text-align: left;text-wrap: wrap;">_opt目录下生成</span></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010026" data-ratio="0.1556543837357052" data-s="300,640" src="/upload/eb0354b42d447be31e71208476eeb883.png" data-type="png" data-w="1574" style=""></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010027" data-ratio="0.5449826989619377" data-s="300,640" src="/upload/c8d8cbe9c33009d46bae35d4545d8a6a.png" data-type="png" data-w="1156" style=""></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010028" data-ratio="0.34688796680497924" data-s="300,640" src="/upload/e25c6bc241741b596f7311b3aca580d5.png" data-type="png" data-w="1205" style=""></p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">4、切换到GPT-SoVITS-TTS标签,填写模型名称(角色名),再分别填入之前生成的切分目录和转写目录路径,开启文本获取-开启SSL提取-语义Token提取(这3个步骤,一步一步来,一个完成之后再点击下一个),最后开启一键三连<br></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010029" data-ratio="0.5580808080808081" data-s="300,640" src="/upload/7e05cc26a13eca77cacef01508175674.png" data-type="png" data-w="1584" style=""></p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">然后转到“微调训练”,设置适合自己显卡的显存,“开启SoVits训练”,然后<span style="color: rgb(63, 63, 63);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 14px;letter-spacing: 1.4px;text-align: left;text-wrap: wrap;">SoVits</span><span style="color: rgb(63, 63, 63);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 14px;letter-spacing: 1.4px;text-align: left;text-wrap: wrap;">训练结束后,再“开启GPT训练”</span><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010030" data-ratio="0.4780952380952381" data-s="300,640" src="/upload/d3af2e8b5a6caef24e74f6da306b4820.png" data-type="png" data-w="1575" style=""></p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">5、选择“推理”标签栏,设置GPT和SoVits的模型,勾选“是否开启TTS推理WebUI”,等一会回自动跳转到一个新的“推理界面”<br></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010031" data-ratio="0.2538167938931298" data-s="300,640" src="/upload/1502519527ab17cdb485c161aa803796.png" data-type="png" data-w="1572" style=""></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010032" data-ratio="0.5500314663310258" data-s="300,640" src="/upload/498e5193ecdcf0f027e8a75baca0e78d.png" data-type="png" data-w="1589" style=""></p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">5、填写参考音频信息(音频文件、音频文本、语种)、合成音频信息(音频文本,语音),点击合成语音,最后就完成了语音转换。<br></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100010033" data-ratio="0.4407935523868568" data-s="300,640" src="/upload/0164fefceeafd1b14bdbc3d34e01e3e0.png" data-type="png" data-w="1613" style=""></p> <h3 style="letter-spacing: normal;text-wrap: wrap;text-align: left;line-height: 1.2;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 1.1em;font-weight: bold;margin-top: 2em;margin-right: 8px;margin-bottom: 0.75em;padding-left: 8px;border-left: 3px solid rgb(0, 152, 116);color: rgb(63, 63, 63);">总结</h3> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);"><code style="line-height: 1.75;font-size: 12.6px;color: rgb(221, 17, 68);background: rgba(27, 31, 35, 0.05);padding: 3px 5px;border-radius: 4px;word-break: break-all;">GPT-SoVITS</code>&nbsp;支持跨语言,集成了声音伴奏分离、自动训练集分割、中文ASR和文本标注等辅助工具。</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">仅需1分钟的训练数据,即可微调模型,提高语音相似性和真实感。</p> <p style="font-size: 14px;text-wrap: wrap;text-align: left;line-height: 1.75;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;margin: 1.5em 8px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">整体的体验还想相当不错的,希望未来应用的领域会越来越多,更新迭代会越来越完善。</p>

一键部署 SpringCloud 微服务,yyds!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-bottom: 0px;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-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;font-size: 16px;color: rgb(53, 53, 53);line-height: 1.5em;word-spacing: 0.8px;letter-spacing: 0.8px;word-break: break-word;text-align: left;padding: 5px;border-radius: 16px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">一键部署springcloud微服务,需要用到 Jenkins K8S Docker等工具,自行安装即可。</p> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">本文使用jenkins部署,流程如下图</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-imgfileid="100047827" data-ratio="0.7102330293819655" data-type="png" data-w="987" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/bea0b98a4615373fa354cfbbe5e6e0ef.png"> </figure> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 开发者将代码push到git </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 运维人员通过jenkins部署,自动到git上pull代码 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 通过maven构建代码 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 将maven构建后的jar打包成docker镜像 并 <code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">push docker</code>镜像到 <code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">docker registry</code> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 通过k8s发起 发布/更新 服务 操作 </section></li> </ul> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">其中 2~5步骤都会在jenkins中进行操作</p> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);line-height: 1.5em;letter-spacing: 0.5444px;font-weight: bold;display: block;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">1、开发者将代码PUSH到Git</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">这一步本文不做详细描述</p> <h2 data-tool="mdnice编辑器" style="margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(234, 84, 41);line-height: 1.5em;letter-spacing: 0.5444px;font-weight: bold;display: block;padding-bottom: 10px;border-bottom: 2px solid rgb(234, 84, 41);visibility: visible;">2、通过Jenkins部署,自动到Git上PULL代码</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">这里需要用到Jenkins 的 pipeline插件</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="color: rgb(34, 34, 34);line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">2.1、 配置SSH-KEY</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">因为jenkins需要pull git上的代码,正常来说,代码都是私有的,git clone操作的时候会需要密码,就不能完成自动化操作了。这里使用SSH-KEY 的方式,让<code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">git clone</code>操作无需密码就能完成克隆</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(0, 0, 0);line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">2.1.1、生成/添加SSH公钥</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">在jenkins所在环境里执行</p> <pre data-tool="mdnice编辑器" 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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewDZfMcOnV7PVkee6bIkI3aWNY0PEDxkGMLLUkoxibufLThWkrsJBD2DFksicoLcAkMk7ZX097bzrtUz8OGUHsRQB/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;font-size: 12px;">ssh-keygen&nbsp;-t&nbsp;ed25519&nbsp;-C&nbsp;<span style="color: #98c379;line-height: 26px;">"xxxxx@xxxxx.com"</span>&nbsp;&nbsp;<br></code></pre> <blockquote data-tool="mdnice编辑器" style="border-radius: 0px;width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);border-left-width: 2px;padding: 8px 10px 8px 15px;background: rgb(255, 249, 249);border-left-color: rgb(239, 112, 96);margin-top: 0px;margin-bottom: 20px;letter-spacing: 0.5444px;"> <span style="letter-spacing: 0em;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.8em;letter-spacing: 0em;color: rgb(53, 53, 53);font-size: 16px;margin-right: 10px;margin-left: 10px;">注意:这里的 xxxxx@xxxxx.com 只是生成的 sshkey 的名称,并不约束或要求具体命名为某个邮箱</p> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.8em;letter-spacing: 0em;color: rgb(53, 53, 53);font-size: 16px;margin-right: 10px;margin-left: 10px;">现网的大部分教程均讲解的使用邮箱生成,其一开始的初衷仅仅是为了便于辨识所以使用了邮箱</p> </blockquote> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">按照提示完成三次回车,即可生成 ssh key。通过查看 <code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">~/.ssh/id_ed25519.pub</code> 文件内容,获取到你的 <code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">public key</code></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img class="rich_pages wxw-img" data-imgfileid="100047829" data-ratio="0.6" data-type="png" data-w="1080" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/cb51dc5255a0cfe11aa63039c848ed73.png"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">得到公钥 public key 内容</p> <pre data-tool="mdnice编辑器" 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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewDZfMcOnV7PVkee6bIkI3aWNY0PEDxkGMLLUkoxibufLThWkrsJBD2DFksicoLcAkMk7ZX097bzrtUz8OGUHsRQB/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;font-size: 12px;">cat&nbsp;~/.ssh/id_ed25519.pub<br></code></pre> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">复制备用</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(0, 0, 0);line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">2.1.2、将公钥配置到git平台</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">git平台可以是github,gitee,也可以是自己搭建的gitlab等</p> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">我这里使用gitee</p> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">通过仓库主页 「管理」-&gt;「部署公钥管理」-&gt;「添加部署公钥」 ,添加生成的 <code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">public key</code> 添加到仓库中。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047828" data-ratio="0.5731481481481482" data-type="png" data-w="1080" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/0490715436e5366de2817ba4840c55d6.png"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">添加成功后,到jenkins所在环境运行</p> <pre data-tool="mdnice编辑器" 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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewDZfMcOnV7PVkee6bIkI3aWNY0PEDxkGMLLUkoxibufLThWkrsJBD2DFksicoLcAkMk7ZX097bzrtUz8OGUHsRQB/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;font-size: 12px;">ssh&nbsp;-T&nbsp;git@gitee.com<br></code></pre> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047830" data-ratio="0.16203703703703703" data-type="png" data-w="1080" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/b94d981926916fccb81019737d3566c5.png"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">首次使用需要确认并添加主机到本机SSH可信列表。若返回 <code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">Hi XXX! You’ve successfully authenticated, but Gitee.com does not provide shell access.</code> 内容,则证明添加成功</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(0, 0, 0);line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">2.1.3、测试</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">复制你项目的SSH链接</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047826" data-ratio="0.3333333333333333" data-type="png" data-w="564" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/fbcf72c83054aded5357b450eeddc549.png"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">在jenkins所在环境</p> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">执行<code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">git clone git@gitee.com:xxxx.git</code></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047832" data-ratio="0.20919881305637983" data-type="png" data-w="674" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/a6f8a23bf573fe48cd2c112cf6e9844d.png"> </figure> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="color: rgb(34, 34, 34);line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">2.2、配置Jenkins的pipeline 自动clone代码</span><span style="display: none;"></span></h3> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(0, 0, 0);line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">2.2.1、Jenkins创建任务</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">新建任务</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047831" data-ratio="1.196236559139785" data-type="png" data-w="372" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/6789864aea65d839a11169b662bf0752.png"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">选择流水线 确定</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047835" data-ratio="0.7583333333333333" data-type="png" data-w="1080" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/4e32b03aa407bdb70e4a864e54b0cc70.png"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">这里勾选参数化构建,选择字符参数,用于输入构建代码的版本</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047834" data-ratio="0.8101629913710451" data-type="png" data-w="1043" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/8cbfc910910d66478ce173f768d328c2.png"> <figcaption style="color: rgb(136, 136, 136);font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: center;margin-top: 5px;"> 在这里插入图片描述 </figcaption> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">默认值填master,根据自身项目实际填写</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047833" data-ratio="0.45185185185185184" data-type="png" data-w="1080" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/be533d2a7cd84b1c95ba184812c6e5c5.png"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">拉到最下面的流水线,写pipeline脚本,如果不知道怎么写,可以点击流水线语法进行参考</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047838" data-ratio="0.5287037037037037" data-type="png" data-w="1080" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/8e4f0bfb87243d1fda23baf16b331a93.png"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">这是我的脚本, REPOSITORY 填写项目的ssh地址,<code style="font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);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: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">REPOSITORY_VERSION</code>是刚刚配置的构建参数</p> <pre data-tool="mdnice编辑器" 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(&quot;https://mmbiz.qpic.cn/mmbiz_svg/A7sq8BD8oewDZfMcOnV7PVkee6bIkI3aWNY0PEDxkGMLLUkoxibufLThWkrsJBD2DFksicoLcAkMk7ZX097bzrtUz8OGUHsRQB/640?wx_fmt=svg&amp;from=appmsg&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;"></span><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;font-size: 12px;">pipeline&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;agent&nbsp;any<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;environment&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REPOSITORY=<span style="color: #d19a66;line-height: 26px;">"git@gitee.com:xxxxxx/cloud-demo.git"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;stages&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stage('拉代码')&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;steps&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;echo&nbsp;<span style="color: #d19a66;line-height: 26px;">"start&nbsp;fetch&nbsp;code&nbsp;from&nbsp;git:${REPOSITORY}"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deleteDir()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;git&nbsp;branch:&nbsp;<span style="color: #98c379;line-height: 26px;">"${REPOSITORY_VERSION}"</span>,&nbsp;url:&nbsp;<span style="color: #98c379;line-height: 26px;">"${REPOSITORY}"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">保存</p> <h4 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(0, 0, 0);line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">2.2.2、测试拉代码流程</span><span style="display: none;"></span></h4> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">返回Jenkins 首页,选择刚刚创建的项目</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047841" data-ratio="0.4842592592592593" data-type="png" data-w="1080" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/8aa65b8d27ff11a773ee999cacb9c27b.png"> <figcaption style="color: rgb(136, 136, 136);font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: center;margin-top: 5px;"> 在这里插入图片描述 </figcaption> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">点击右边的运行按钮</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047836" data-ratio="0.8523676880222841" data-type="png" data-w="359" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/5db49a6692aee9dfe5361675c0c51563.png"> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">输入代码分支版本</p> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;margin-top: 0.8em;margin-bottom: 0.8em;">点击开始构建</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;"> <img data-imgfileid="100047837" data-ratio="0.84" data-type="png" data-w="850" style="display: block;margin: 20px auto;max-width: 95%;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;height: auto !important;" src="/upload/ba1050abb04eddb2d716d9e2a6beddd4.png"> <figcaption style="color: rgb(136, 136, 136);font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: center;margin-top: 5px;"> 在这里插入图片描述 </figcaption> </figure> <p data-tool="mdnice编辑器" style="line-height: 1.75;letter-spacing: 0em

解锁Spring Boot新技巧,搞懂这些神奇注解,让你的项目更有料!

作者:微信小助手

<section data-role="outer" label="edit by 135editor"> <section style="letter-spacing: 0.578px;font-size: 16px;"> <section draggable="true" style="margin-top: 30px;margin-bottom: 10px;text-align: center;"> <section data-tools="135编辑器" data-id="114706"> <section style="margin: 20px auto;"> <section> <section style="display: flex;justify-content: flex-start;align-items: flex-end;"> <section style="padding-top: 25px;"> <section style="width: 100px;height: 1px;background-color: rgb(185, 214, 244);overflow: hidden;transform: rotate(-15deg);"> <br> </section> </section> </section> <section style="padding-right: 15px;padding-left: 15px;"> <section style="padding: 25px 15px;background-color: rgb(245, 250, 255);"> <section data-autoskip="1" style="text-align: justify;line-height: 1.75em;letter-spacing: 1.5px;font-size: 14px;color: rgb(51, 51, 51);background: transparent;"> <p style="vertical-align: inherit;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<span style="color: rgb(255, 76, 0);font-size: 18px;font-family: 黑体, SimHei;">成长不是瞬间的华丽转身</span></p> <p style="vertical-align: inherit;"><span style="color: rgb(255, 76, 0);font-size: 18px;font-family: 黑体, SimHei;">&nbsp; &nbsp; &nbsp;而是每天坚持努力的积累</span></p> </section> </section> </section> <section style="display: flex;justify-content: flex-end;align-items: flex-start;"> <section> <section style="width: 138px;height: 1px;background-color: rgb(185, 214, 244);transform: rotate(-15deg);"> <br> </section> </section> </section> </section> </section> </section> <section style="padding: 10px;display: inline-block;border-width: 1px;border-style: solid;border-color: rgb(192, 200, 209);background-color: rgb(255, 255, 255);width: 578px;box-shadow: rgb(220, 221, 221) 3.5px 3.5px 0px;height: auto;"> <section> <section style="padding: 0.1em 0.3em;display: inline-block;border-width: 2px;border-style: solid;border-color: rgb(255, 255, 255);background-color: rgb(254, 255, 255);color: rgb(13, 0, 21);font-size: 18px;"> <p><span style="color: rgb(122, 68, 66);"><strong>STRAT</strong></span></p> </section> </section> <section style="font-size: 24px;"> <p><span style="color: rgb(61, 170, 214);font-size: 22px;">乘风破浪 | 直挂云帆</span></p> </section> </section> </section> </section> <p style="margin: 24px 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">本篇来聊聊Springboot中的条件注解,这些条件注解使得开发者能够根据运行时环境的不同,动态的选择性地加载或配置一些 Bean 或组件。通过合理使用条件注解,</span><span style="letter-spacing: 1px;font-size: 16px;color: rgb(61, 170, 214);">可以实现更灵活和可配置的应用程序</span><span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">。</span></p> <section typography="classic"> <img class="rich_pages wxw-img" data-imgfileid="100002425" data-ratio="0.49537037037037035" src="/upload/68ae9d2da7f02b1247d1fee570d9096e.png" data-type="png" data-w="1080" style="vertical-align: inherit;width: 100%;" width="1767"> </section> <section typography="classic" style="letter-spacing: 0.578px;"> <section data-role="paragraph"> <section typography="classic"> <section typography="classic"> <section typography="classic"> <section typography="classic"> <ne-clipboard source="https%3A%2F%2Fwww.yuque.com%2Fdream_lsj%2Ftsyg0b%2Fgx548xggfabnf49k" style="letter-spacing: 0.578px;"></ne-clipboard> <section typography="classic" style="letter-spacing: 0.578px;height: 0px;overflow: hidden;"> <br> </section> </section> </section> </section> </section> </section> </section> <section typography="classic" style="letter-spacing: 0.578px;"> <section data-role="paragraph"> <section typography="classic"> <section typography="classic"> <section typography="classic"> <section typography="classic"> <section typography="classic"> <section data-role="title" data-tools="135编辑器" data-id="107354"> <section style="text-align: center;margin: 10px auto;"> <section style="display: flex;justify-content: flex-start;align-items: flex-start;"> <section style="width: 32px;height: 32px;background-color: rgb(255, 247, 245);border-radius: 50%;flex-shrink: 0;"> <section style="display: flex;justify-content: flex-start;align-items: flex-end;margin-top: 3px;margin-left: 10px;transform: skew(-30deg);"> <section style="width: 4px;height: 15px;background-color: rgb(242, 74, 91);overflow: hidden;"> <br> </section> <section style="width: 4px;height: 10px;background-color: rgb(100, 116, 206);margin-left: 3px;overflow: hidden;"> <br> </section> </section> </section> <section data-brushtype="text" style="font-size: 16px;letter-spacing: 1.5px;color: rgb(242, 74, 91);"> &nbsp; <span style="caret-color: red;font-size: 17px;color: rgb(122, 68, 66);">条件注解介绍以及解析</span> </section> </section> <section style="display: flex;justify-content: flex-end;"> <section style="width: 96%;height: 1px;background-color: rgb(107, 122, 206);margin-top: -5px;transform: rotate(0deg);max-width: 96% !important;" data-width="96%"> <br> </section> </section> </section> </section> <section data-role="paragraph"> <section typography="classic"> <ol class="list-paddingleft-1" style="padding-left: 30px;list-style-position: outside;margin-left: 8px;margin-right: 8px;"> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnBean</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:是否存在某个某类或某个名字的Bean。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnMissingBean</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:是否缺失某个某类或某个名字的Bean。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnSingleCandidate</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:是否符合指定类型的Bean只有一个。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnClass</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:是否存在某个类。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnMissingClass</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:是否缺失某个类。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnExpression</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:指定的表达式返回的是true还是false。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnJava</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:判断Java版本。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnJndi</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:JNDI指定的资源是否存在。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnWebApplication</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:当前应用是一个Web应用。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnNotWebApplication</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:当前应用不是一个Web应用。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnProperty</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:Environment中是否存在某个属性。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnResource</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:指定的资源是否存在。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnWarDeployment</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:项目是否以War包部署的方式运行。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 24px;margin-bottom: 24px;line-height: 1.75em;"> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ConditionalOnCloudPlatform</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">:<span style="color: rgb(96, 96, 96);font-size: 15px;letter-spacing: 1px;">是否</span>在某个云平台上。</span> </section></li> </ol> <section typography="classic"> <section style="min-height: 24px;margin: 24px 8px;line-height: 1.75em;"> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">当Spring在解析某一个条件注解的时候,首先会来匹配</span> <span style="letter-spacing: 1px;font-size: 16px;color: rgb(61, 170, 214);">SpringBootCondition中的matches方法</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">,该方法里面会根据不同的逻辑去处理。</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="java"><code><span class="code-snippet_outer"><span class="code-snippet__meta">@Override</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__function"><span class="code-snippet__keyword">public</span> <span class="code-snippet__keyword">final</span> <span class="code-snippet__keyword">boolean</span> <span class="code-snippet__title">matches</span><span class="code-snippet__params">(ConditionContext context, AnnotatedTypeMetadata metadata)</span> </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">// 条件注解写在了哪个类上,或哪个方法上</span></span></code><code><span class="code-snippet_outer"> String classOrMethodName = getClassOrMethodName(metadata);</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__comment">// 条件的判断结果</span></span></code><code><span class="code-snippet_outer"> ConditionOutcome outcome = getMatchOutcome(context, metadata);</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 如果log的日志级别为trace,那就直接记录当前条件的判断结果</span></span></code><code><span class="code-snippet_outer"> logOutcome(classOrMethodName, outcome);</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 将判断结果记录到ConditionEvaluationReport中</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">//ConditionEvaluationReportLoggingListener会在收到ContextRefreshedEvent事件后把判断结果用日志的方式打印出来</span></span></code><code><span class="code-snippet_outer"> recordEvaluation(context, classOrMethodName, outcome);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> outcome.isMatch();</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">catch</span> (NoClassDefFoundError ex) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">throw</span> <span class="code-snippet__keyword">new</span> IllegalStateException(<span class="code-snippet__string">"Could not evaluate condition on "</span> + classOrMethodName + <span class="code-snippet__string">" due to "</span></span></code><code><span class="code-snippet_outer"> + ex.getMessage() + <span class="code-snippet__string">" not found. Make sure your own configuration does not rely on "</span></span></code><code><span class="code-snippet_outer"> + <span class="code-snippet__string">"that class. This can also happen if you are "</span></span></code><code><span class="code-snippet_outer"> + <span class="code-snippet__string">"@ComponentScanning a springframework package (e.g. if you "</span></span></code><code><span class="code-snippet_outer"> + <span class="code-snippet__string">"put a @ComponentScan in the default package by mistake)"</span>, ex);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">catch</span> (RuntimeException ex) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">throw</span> <span class="code-snippet__keyword">new</span> IllegalStateException(<span class="code-snippet__string">"Error processing condition on "</span> + getName(metadata), ex);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> <section data-role="title" data-tools="135编辑器" data-id="107354"> <section style="text-align: center;margin: 10px auto;"> <section style="display: flex;justify-content: flex-start;align-items: flex-start;"> <section style="width: 32px;height: 32px;background-color: rgb(255, 247, 245);border-radius: 50%;flex-shrink: 0;"> <section style="display: flex;justify-content: flex-start;align-items: flex-end;margin-top: 3px;margin-left: 10px;transform: skew(-30deg);"> <section style="width: 4px;height: 15px;background-color: rgb(242, 74, 91);overflow: hidden;"> <br> </section> <section style="width: 4px;height: 10px;background-color: rgb(100, 116, 206);margin-left: 3px;overflow: hidden;"> <br> </section> </section> </section> <section data-brushtype="text" style="font-size: 16px;letter-spacing: 1.5px;color: rgb(242, 74, 91);"> &nbsp; <span style="caret-color: red;font-size: 17px;color: rgb(122, 68, 66);">各条件注解的原理&nbsp;</span> </section> </section> <section style="display: flex;justify-content: flex-end;"> <section style="width: 96%;height: 1px;background-color: rgb(107, 122, 206);margin-top: -5px;transform: rotate(0deg);max-width: 96% !important;" data-width="96%"> <br> </section> </section> </section> </section> <section data-tools="135编辑器" data-id="85927"> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="display: inline-block;padding: 0.3em 0.5em;width: 100%;font-size: 14px;color: rgb(254, 254, 254);background-color: rgb(204, 204, 204);" data-width="100%"> <strong><span style="color: #ffffff;" data-brushtype="text">@ConditionOnClass</span></strong> <span style="color: #ffffff;" data-brushtype="text"></span> </section> <section style="border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 10px;"> <section style="margin: 24px 8px;"> <span style="letter-spacing: 0.578px;caret-color: red;font-size: 16px;color: rgb(61, 170, 214);">判断某个类是否存在</span> <span style="letter-spacing: 0.578px;caret-color: red;font-size: 15px;color: rgb(96, 96, 96);">,执行OnClassCondition.getMatchOutcome方法。</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="kotlin"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {</span></code><code><span class="code-snippet_outer"> ClassLoader classLoader = context.getClassLoader();</span></code><code><span class="code-snippet_outer"> ConditionMessage matchMessage = ConditionMessage.empty();</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 拿到ConditionalOnClass注解中的value值,也就是要判断是否存在的类名</span></span></code><code><span class="code-snippet_outer"> List&lt;String&gt; onClasses = getCandidates(metadata, ConditionalOnClass.<span class="code-snippet__keyword">class</span>);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (onClasses != <span class="code-snippet__literal">null</span>) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 判断onClasses中不存在的类</span></span></code><code><span class="code-snippet_outer"> List&lt;String&gt; missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);</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> (!missing.isEmpty()) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.<span class="code-snippet__keyword">class</span>)</span></code><code><span class="code-snippet_outer"> .didNotFind(<span class="code-snippet__string">"required class"</span>, <span class="code-snippet__string">"required classes"</span>).items(Style.QUOTE, missing));</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"> matchMessage = matchMessage.andCondition(ConditionalOnClass.<span class="code-snippet__keyword">class</span>)</span></code><code><span class="code-snippet_outer"> .found(<span class="code-snippet__string">"required class"</span>, <span class="code-snippet__string">"required classes"</span>)</span></code><code><span class="code-snippet_outer"> .items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));</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">// 和上面类似,只不过是判断onMissingClasses是不是全部缺失,如果是则表示匹配</span></span></code><code><span class="code-snippet_outer"> List&lt;String&gt; onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.<span class="code-snippet__keyword">class</span>);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (onMissingClasses != <span class="code-snippet__literal">null</span>) {</span></code><code><span class="code-snippet_outer"> List&lt;String&gt; present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (!present.isEmpty()) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.<span class="code-snippet__keyword">class</span>)</span></code><code><span class="code-snippet_outer"> .found(<span class="code-snippet__string">"unwanted class"</span>, <span class="code-snippet__string">"unwanted classes"</span>).items(Style.QUOTE, present));</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.<span class="code-snippet__keyword">class</span>)</span></code><code><span class="code-snippet_outer"> .didNotFind(<span class="code-snippet__string">"unwanted class"</span>, <span class="code-snippet__string">"unwanted classes"</span>)</span></code><code><span class="code-snippet_outer"> .items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> ConditionOutcome.match(matchMessage);</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <section data-tools="135编辑器" data-id="7"> <section style="padding-left: 10px;border-left: 5px solid rgb(170, 170, 170);margin: 10px auto;"> <section style="font-size: 13px;letter-spacing: 1px;line-height: 1.75em;color: rgba(102, 102, 102, 0.8);"> <section data-role="list"> <ul class="list-paddingleft-1" style="padding-left: 30px;list-style-position: outside;margin-left: 8px;margin-right: 8px;"> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="color: rgb(102, 102, 102);font-size: 14px;letter-spacing: 1.5px;caret-color: red;margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;caret-color: red;color: rgb(96, 96, 96);letter-spacing: 1px;">获取ConditionalOnClass注解中的value值。</span> <ne-clipboard source="https%3A%2F%2Fwww.yuque.com%2Fdream_lsj%2Fyddpep%2Fpunmt1zhmirb2y8x%23Bvjvx"></ne-clipboard> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">判断是否存在不配的类,判断逻辑为</span> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);"> Class.<em>forName</em>(className);</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> 存在就会直接返回。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">不存在就会获取ConditionalOnMissingClass注解中的value值,然后处理逻辑与前面一直。</span> </section></li> </ul> </section> <section typography="classic" style="height: 0px;overflow: hidden;"> <br> </section> </section> </section> </section> <section typography="classic"> <section style="min-height: 24px;margin: 24px 8px;line-height: 1.75em;"> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">Spring中会去每个注解的解析,但是springBoot中会</span> <span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">提前将@ConditionalOnClass与@ConditionalOnClass</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">一起解析,如果发现不匹配能够快速的处理,但是如果都成立,那么这个回去重复执行。</span> </section> </section> </section> </section> </section> <section data-tools="135编辑器" data-id="85927"> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="display: inline-block;padding: 0.3em 0.5em;width: 100%;font-size: 14px;color: rgb(254, 254, 254);background-color: rgb(204, 204, 204);" data-width="100%"> <strong><span style="color: #ffffff;" data-brushtype="text">@ConditionOnBean</span></strong> <span style="color: #ffffff;" data-brushtype="text"></span> </section> <section style="border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 10px;"> <p style="margin: 24px 8px;line-height: 1.75em;"><span style="caret-color: red;font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">@ConditionalOnBean和@ConditionalOnClass的底层实现应该是差不多的,</span><span style="caret-color: red;letter-spacing: 1px;color: rgb(61, 170, 214);font-size: 16px;">一个是判断Bean存不存在,一个是判断类存不存在</span><span style="caret-color: red;font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">,事实上也确实差不多。&nbsp;&nbsp;</span> <ne-clipboard data="%7B%22type%22%3A%22fragment%22%2C%22name%22%3A%22%23fragment%22%2C%22children%22%3A%5B%7B%22type%22%3A%22text%22%2C%22id%22%3A%22u6c6eccf3%22%2C%22name%22%3A%22%23text%22%2C%22attrs%22%3A%7B%22fontsize%22%3A16%7D%2C%22data%22%3A%22%40ConditionalOnBean%E5%92%8C%40ConditionalOnClass%E7%9A%84%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0%E5%BA%94%E8%AF%A5%E6%98%AF%E5%B7%AE%E4%B8%8D%E5%A4%9A%E7%9A%84%EF%BC%8C%E4%B8%80%E4%B8%AA%E6%98%AF%E5%88%A4%E6%96%ADBean%E5%AD%98%E4%B8%8D%20%E5%AD%98%E5%9C%A8%EF%BC%8C%E4%B8%80%E4%B8%AA%E6%98%AF%E5%88%A4%E6%96%AD%E7%B1%BB%E5%AD%98%E4%B8%8D%E5%AD%98%E5%9C%A8%EF%BC%8C%E4%BA%8B%E5%AE%9E%E4%B8%8A%E4%B9%9F%E7%A1%AE%E5%AE%9E%E5%B7%AE%E4%B8%8D%E5%A4%9A%E3%80%82%20%20%22%7D%5D%2C%22attrs%22%3A%7B%7D%7D" source="https%3A%2F%2Fwww.yuque.com%2Fdream_lsj%2Fyddpep%2Fpunmt1zhmirb2y8x%23Bvjvx"></ne-clipboard></p> <p style="margin: 24px 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">@ConditionalOnBean和@ConditionalOnMissingBean对应的都是OnBeanCondition类, OnBeanCondition类也是继承了SpringBootCondition,所以SpringBootCondition类中的getMatchOutcome方法才是匹配逻辑。</span></p> <section typography="classic"> <img class="rich_pages wxw-img" data-imgfileid="100002424" data-ratio="0.4072978303747535" src="/upload/73211b44ad24fc19afa62c0ee1244033.png" data-type="png" data-w="1014" style="vertical-align: inherit;width: 100%;" width="1014"> </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="kotlin"><code><span class="code-snippet_outer"><span class="code-snippet__meta">@Override</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">public</span> ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {</span></code><code><span class="code-snippet_outer"> ConditionMessage matchMessage = ConditionMessage.empty();</span></code><code><span class="code-snippet_outer"> MergedAnnotations annotations = metadata.getAnnotations();</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 如果存在ConditionalOnBean注解</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (annotations.isPresent(ConditionalOnBean.<span class="code-snippet__keyword">class</span>)) {</span></code><code><span class="code-snippet_outer"> Spec&lt;ConditionalOnBean&gt; spec = new Spec&lt;&gt;(context, metadata, annotations, ConditionalOnBean.<span class="code-snippet__keyword">class</span>);</span></code><code><span class="code-snippet_outer"> MatchResult matchResult = getMatchingBeans(context, spec);</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 如果某个Bean不存在</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (!matchResult.isAllMatched()) {</span></code><code><span class="code-snippet_outer"> String reason = createOnBeanNoMatchReason(matchResult);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> ConditionOutcome.noMatch(spec.message().because(reason));</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">// 所有Bean都存在</span></span></code><code><span class="code-snippet_outer"> matchMessage = spec.message(matchMessage).found(<span class="code-snippet__string">"bean"</span>, <span class="code-snippet__string">"beans"</span>).items(Style.QUOTE, matchResult.getNamesOfAllMatches());</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">// 如果存在ConditionalOnSingleCandidate注解</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (metadata.isAnnotated(ConditionalOnSingleCandidate.<span class="code-snippet__keyword">class</span>.getName())) {</span></code><code><span class="code-snippet_outer"> Spec&lt;ConditionalOnSingleCandidate&gt; spec = new SingleCandidateSpec(context, metadata, annotations);</span></code><code><span class="code-snippet_outer"> MatchResult matchResult = getMatchingBeans(context, spec);</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// Bean不存在</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (!matchResult.isAllMatched()) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> ConditionOutcome.noMatch(spec.message().didNotFind(<span class="code-snippet__string">"any beans"</span>).atAll());</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">// Bean存在</span></span></code><code><span class="code-snippet_outer"> Set&lt;String&gt; allBeans = matchResult.getNamesOfAllMatches();</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__keyword">if</span> (allBeans.size() == <span class="code-snippet__number">1</span>) {</span></code><code><span class="code-snippet_outer"> matchMessage = spec.message(matchMessage).found(<span class="code-snippet__string">"a single bean"</span>).items(Style.QUOTE, allBeans);</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">else</span> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 如果有多个</span></span></code><code><span class="code-snippet_outer"> List&lt;String&gt; primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans, spec.getStrategy() == SearchStrategy.ALL);</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 没有主Bean,那就不匹配</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (primaryBeans.isEmpty()) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> ConditionOutcome.noMatch(</span></code><code><span class="code-snippet_outer"> spec.message().didNotFind(<span class="code-snippet__string">"a primary bean from beans"</span>).items(Style.QUOTE, allBeans));</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">// 有多个主Bean,那就不匹配</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (primaryBeans.size() &gt; <span class="code-snippet__number">1</span>) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> ConditionOutcome</span></code><code><span class="code-snippet_outer"> .noMatch(spec.message().found(<span class="code-snippet__string">"multiple primary beans"</span>).items(Style.QUOTE, primaryBeans));</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">// 只有一个主Bean</span></span></code><code><span class="code-snippet_outer"> matchMessage = spec.message(matchMessage)</span></code><code><span class="code-snippet_outer"> .found(<span class="code-snippet__string">"a single primary bean '"</span> + primaryBeans.<span class="code-snippet__keyword">get</span>(<span class="code-snippet__number">0</span>) + <span class="code-snippet__string">"' from beans"</span>)</span></code><code><span class="code-snippet_outer"> .items(Style.QUOTE, allBeans);</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"> <span class="code-snippet__comment">// 存在ConditionalOnMissingBean注解</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (metadata.isAnnotated(ConditionalOnMissingBean.<span class="code-snippet__keyword">class</span>.getName())) {</span></code><code><span class="code-snippet_outer"> Spec&lt;ConditionalOnMissingBean&gt; spec = new Spec&lt;&gt;(context, metadata, annotations,</span></code><code><span class="code-snippet_outer"> ConditionalOnMissingBean.<span class="code-snippet__keyword">class</span>);</span></code><code><span class="code-snippet_outer"> MatchResult matchResult = getMatchingBeans(context, spec);</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">//有任意一个Bean存在,那就条件不匹配</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (matchResult.isAnyMatched()) {</span></code><code><span class="code-snippet_outer"> String reason = createOnMissingBeanNoMatchReason(matchResult);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> ConditionOutcome.noMatch(spec.message().because(reason));</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"> matchMessage = spec.message(matchMessage).didNotFind(<span class="code-snippet__string">"any beans"</span>).atAll();</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> ConditionOutcome.match(matchMessage);</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <section data-tools="135编辑器" data-id="7"> <section style="padding-left: 10px;border-left: 5px solid rgb(170, 170, 170);margin: 10px auto;"> <section style="font-size: 13px;letter-spacing: 1px;line-height: 1.75em;color: rgba(102, 102, 102, 0.8);"> <section data-role="list"> <ul class="list-paddingleft-1" style="padding-left: 30px;list-style-position: outside;margin-left: 8px;margin-right: 8px;"> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="color: rgb(102, 102, 102);font-size: 14px;letter-spacing: 1.5px;caret-color: red;margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">当前在解析的类或方法上</span> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(96, 96, 96);">,</span> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(96, 96, 96);">是否有@ConditionalOnBean注解,如果有则生成对应的Spec对象,该对象中包含了 用户指定的,要判断的是否存在的Bean的类型。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="color: rgb(102, 102, 102);font-size: 14px;letter-spacing: 1.5px;caret-color: red;margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(96, 96, 96);">调用</span> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">getMatchingBeans方法进行条件判断</span> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(96, 96, 96);">&nbsp;。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="color: rgb(102, 102, 102);font-size: 14px;letter-spacing: 1.5px;caret-color: red;margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(96, 96, 96);">只要判断出来某一个Bean不存在,则return,表示条件不匹配 。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="color: rgb(102, 102, 102);font-size: 14px;letter-spacing: 1.5px;caret-color: red;margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(96, 96, 96);">只要所有Bean都存在,则继续执行下面代码 。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="color: rgb(102, 102, 102);font-size: 14px;letter-spacing: 1.5px;caret-color: red;margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">在解析的类或方法上是否有@ConditionalOnSingleCandidate注解</span> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(96, 96, 96);">,如果有则生成对应的 SingleCandidateSpec对象,该对象中包含了用户指定的,要判断的是否存在的Bean的类型(只能指定一个类 型),并且该类型的Bean只能有一个。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="color: rgb(102, 102, 102);font-size: 14px;letter-spacing: 1.5px;caret-color: red;margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">调用getMatchingBeans方法进行条件判断</span> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(96, 96, 96);">。</span> </section></li> </ul> </section> </section> </section> </section> </section> </section> </section> <section data-tools="135编辑器" data-id="85927"> <section style="margin-top: 10px;margin-bottom: 10px;"> <section style="display: inline-block;padding: 0.3em 0.5em;width: 100%;font-size: 14px;color: rgb(254, 254, 254);background-color: rgb(204, 204, 204);" data-width="100%"> <strong><span style="color: #ffffff;" data-brushtype="text">@ConditionOnMissingBean</span></strong> <span style="color: #ffffff;" data-brushtype="text"></span> </section> <section style="border-width: 1px;border-style: solid;border-color: rgb(204, 204, 204);padding: 10px;"> <p style="margin: 24px 8px;line-height: 1.75em;"><span style="caret-color: red;font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">@ConditionalOnMissingBean的作用是用来</span><span style="caret-color: red;letter-spacing: 1px;color: rgb(61, 170, 214);font-size: 16px;">判断某个Bean是否缺失</span><span style="caret-color: red;font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">,如果不存在某个Bean,那就符合该条件。</span> <ne-clipboard data="%7B%22type%22%3A%22fragment%22%2C%22name%22%3A%22%23fragment%22%2C%22children%22%3A%5B%7B%22type%22%3A%22element%22%2C%22id%22%3A%22u10d4a81c%22%2C%22name%22%3A%22p%22%2C%22attrs%22%3A%7B%7D%2C%22children%22%3A%5B%7B%22type%22%3A%22text%22%2C%22id%22%3A%22uaa45bdca%22%2C%22name%22%3A%22%23text%22%2C%22attrs%22%3A%7B%7D%2C%22data%22%3A%22%40ConditionalOnMissingBean%E7%9A%84%E4%BD%9C%E7%94%A8%E6%98%AF%E7%94%A8%E6%9D%A5%E5%88%A4%E6%96%AD%E6%9F%90%E4%B8%AABean%E6%98%AF%E5%90%A6%E7%BC%BA%E5%A4%B1%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%8D%E5%AD%98%E5%9C%A8%E6%9F%90%E4%B8%AABean%EF%BC%8C%E9%82%A3%E5%B0%B1%E7%AC%A6%E5%90%88%E8%AF%A5%E6%9D%A1%E4%BB%B6%22%7D%5D%7D%5D%2C%22attrs%22%3A%7B%7D%7D" source="https%3A%2F%2Fwww.yuque.com%2Fdream_lsj%2Fyddpep%2Fpunmt1zhmirb2y8x%23ySmLD"></ne-clipboard></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="ruby"><code><span class="code-snippet_outer">@Configuration(proxyBeanMethods = <span class="code-snippet__literal">false</span>)</span></code><code><span class="code-snippet_outer">@ConditionalOnClass({ Servlet.<span class="code-snippet__keyword">class</span>, Tomcat.<span class="code-snippet__keyword">class</span>, UpgradeProtocol.<span class="code-snippet__keyword">class</span> })</span></code><code><span class="code-snippet_outer">@ConditionalOnMissingBean(value = ServletWebServerFactory.<span class="code-snippet__keyword">class</span>, search = SearchStrategy.CURRENT)</span></code><code><span class="code-snippet_outer">static <span class="code-snippet__class"><span class="code-snippet__keyword">class</span> <span class="code-snippet__title">EmbeddedTomcat</span> {</span></span></code><code><span class="code-snippet_outer"> @Bean</span></code><code><span class="code-snippet_outer"> TomcatServletWebServerFactory tomcatServletWebServerFactory(</span></code><code><span class="code-snippet_outer"> ObjectProvider&lt;TomcatConnectorCustomizer&gt; connectorCustomizers,</span></code><code><span class="code-snippet_outer"> ObjectProvider&lt;TomcatContextCustomizer&gt; contextCustomizers,</span></code><code><span class="code-snippet_outer"> ObjectProvider&lt;TomcatProtocolHandlerCustomizer&lt;?<span class="code-snippet__meta">&gt;&gt; </span>protocolHandlerCustomizers) {</span></code><code><span class="code-snippet_outer"> TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__regexp">//</span> orderedStream()调用时会去Spring容器中找到TomcatConnectorCustomizer类型的Bean,</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"> factory.getTomcatConnectorCustomizers()</span></code><code><span class="code-snippet_outer"> .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));</span></code><code><span class="code-snippet_outer"> factory.getTomcatContextCustomizers()</span></code><code><span class="code-snippet_outer"> .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));</span></code><code><span class="code-snippet_outer"> factory.getTomcatProtocolHandlerCustomizers()</span></code><code><span class="code-snippet_outer"> .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));</span></code><code><span class="code-snippet_outer"><br></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> factory;</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></code></pre> </section> <section data-tools="135编辑器" data-id="7"> <section style="padding-left: 10px;border-left: 5px solid rgb(170, 170, 170);margin: 10px auto;"> <section style="font-size: 13px;letter-spacing: 1px;line-height: 1.75em;color: rgba(102, 102, 102, 0.8);" data-role="list"> <ul class="list-paddingleft-1" style="padding-left: 30px;list-style-position: outside;margin-left: 8px;margin-right: 8px;"> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="color: rgb(102, 102, 102);font-size: 14px;letter-spacing: 1.5px;caret-color: red;margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="caret-color: red;font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">如果用户自己没有定义</span> <span style="caret-color: red;font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">ServletWebServerFactory类型</span> <span style="caret-color: red;font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">的Bean,那代码中所定义的Bean就会生效。</span> </section></li> <li style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;"> <section style="color: rgb(102, 102, 102);font-size: 14px;letter-spacing: 1.5px;caret-color: red;margin-top: 8px;margin-bottom: 8px;line-height: 1.75em;"> <span style="caret-color: red;font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">如果用户自己定义了ServletWebServerFactory类型的Bean,那代码中定义的Bean就不生效 。</span> </section></li> </ul> </section> </section> </section> <section typography="classic"> <p style="min-height: 24px;margin: 24px 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;color: rgb(255, 76, 0);">SpringBoot利用该注解来决定到底用用户自己的Bean还是用SpringBoot自定配置的Bean。</span></p> <p style="min-height: 24px;margin: 24px 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">如果先解析自动配置然后再解析程序员定义的,那么就会发现该注解里面的判断是错误的,因为你首先来判断是否成立,是在还没有解析程序员定义的Bean之前,所以就会认为是符合该注解,</span><span style="font-size: 15px;letter-spacing: 1px;color: rgb(255, 76, 0);">实际上只是没有加载程序员定义的,所以必须先去解析程序员定义的,然后最后执行自动配置的</span><span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">。</span></p> </section> </section> </section> </section> <section style="margin-right: 16px;margin-left: 16px;clear: both;min-height: 1em;letter-spacing: 0.578px;line-height: 1.75em;"> <p style="text-align:justify;margin-right: 16px;margin-left: 16px;letter-spacing: 0.578px;line-height: 1.75em;"><br></p> <section style="text-align: justify;letter-spacing: 0.578px;line-height: 1.75em;margin: 24px 0px;"> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">今天先说到这里明天我们继续,这三个注解是我们自定义扩展SpringBoot相关知识点的重要内容。</span> </section> <section style="text-align: justify;letter-spacing: 0.578px;line-height: 1.75em;margin-left: 0px;margin-right: 0px;"> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">文章有帮助的话,&nbsp;<img class="rich_pages wxw-img" data-imgfileid="100002423" data-ratio="1" src="/upload/00591cd10d29240d23e0e432c72d560e.png" data-type="png" data-w="64" style="vertical-align: text-bottom;display: inline-block;width: 20px;">&nbsp;&nbsp;</span> <span style="color: rgb(61, 170, 214);font-size: 16px;letter-spacing: 1px;">点赞</span> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">、<span style="color: rgb(61, 170, 214);font-size: 16px;">在看</span>、<span style="color: rgb(61, 170, 214);font-size: 16px;">转发吧。</span></span> </section> <section style="text-align: justify;letter-spacing: 0.578px;line-height: 1.75em;margin-left: 0px;margin-right: 0px;"> <span style="font-size: 15px;color: rgb(96, 96, 96);letter-spacing: 1px;">一起讨论,谢谢支持哟</span> <img class="rich_pages wxw-img" data-imgfileid="100002421" data-ratio="1" src="/upload/1d550a991385b842a21e2b301725407e.png" data-type="png" data-w="64" style="vertical-align: text-bottom;display: inline-block;width: 20px;"> </section> <p style="text-align:justify;margin-right: 16px;margin-left: 16px;letter-spacing: 0.578px;line-height: 1.75em;"><br></p> <section data-tools="135编辑器" data-id="96672" style="letter-spacing: 0.578px;"> <section style="margin: 10px auto;"> <section style="display: flex;justify-content: space-between;align-items: center;"> <section style="width: 10px;"> <img src="/upload/28bc80024628ae6f7d65f73eddd7649f.png" data-type="png" data-imgfileid="100002422" style="width: 10px;display: block;vertical-align: inherit;transform: scaleX(-1);" data-ratio="0.9285714285714286" data-w="14"> </section> <section style="flex: 1 1 0%;border-bottom: 1px solid rgb(100, 48, 0);height: 1px;overflow: hidden;"> <br> </section> <section style="width: 10px;"> <img src="/upload/28bc80024628ae6f7d65f73eddd7649f.png" data-type="png" data-imgfileid="100002428" style="width: 10px;display: block;vertical-align: inherit;" data-ratio="0.9285714285714286" data-w="14"> </section> </section> <section style="padding-right: 1.5em;padding-left: 1.5em;display: flex;justify-content: center;align-items: center;"> <section style="width: 100px;"> <img class="rich_pages wxw-img" data-cropselx1="0" data-cropselx2="100" data-cropsely1="0" data-cropsely2="100" data-imgfileid="100002430" data-ratio="1" src="/upload/47de6afcb9a0ae6a20e3c09ec486223e.jpg" data-type="jpeg" data-w="258" data-width="100%" style="height: 100px;width: 100%;display: block;"> </section> <section style="margin-left: 12px;width: auto;"> <section data-brushtype="text" style="text-align: center;font-size: 14px;letter-spacing: 1.5px;color: rgb(102, 51, 0);"> 长按二维码关注我 </section> <section data-brushtype="text" style="text-align: center;font-size: 14px;letter-spacing: 1.5px;color: rgb(102, 51, 0);"> 每日分享干货 </section> <section style="width: 80px;"> <img class="rich_pages wxw-img" data-imgfileid="100002429" data-ratio="0.3870967741935484" src="/upload/35f14a13c17ff0d1bfac25027deb753b.png" data-type="gif" data-w="496" style="width: 80px;display: block;"> </section> </section> </section> <section style="display: flex;justify-content: space-between;align-items: center;transform: scaleY(-1);"> <section style="width: 10px;"> <img src="/upload/28bc80024628ae6f7d65f73eddd7649f.png" data-type="png" data-imgfileid="100002427" style="width: 10px;display: block;vertical-align: inherit;transform: scaleX(-1);" data-ratio="0.9285714285714286" data-w="14"> </section> <section style="flex: 1 1 0%;border-bottom: 1px solid rgb(100, 48, 0);height: 1px;overflow: hidden;"> <br> </section> <section style="width: 10px;"> <img src="/upload/28bc80024628ae6f7d65f73eddd7649f.png" data-type="png" data-imgfileid="100002426" style="width: 10px;display: block;vertical-align: inherit;" data-ratio="0.9285714285714286" data-w="14"> </section> </section> </section> </section> <p style="text-align:justify;letter-spacing: 0.578px;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</p> </section> </section> </section> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>

19.8K 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;height: auto;font-family: Optima, &quot;Microsoft YaHei&quot;, 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="line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;"><span style="font-size: 24px;font-weight: bold;letter-spacing: 0em;word-spacing: 0em;">Cloudreve:一个支持多家云存储的云盘系统</span><br></p> <p data-tool="mdnice编辑器" style="line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">Cloudreve 是一个开源的公有云文件系统,能助你以最低的成本快速搭建公私兼备的网盘系统。</p> <p data-tool="mdnice编辑器" style="line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">你可以使用它来搭建个人私有网盘、文件分享系统,亦或是针对大小团体的公有云系统。</p> <p data-tool="mdnice编辑器" style="line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">Cloudreve 在底层支持不同的云存储平台,用户在实际使用时无须关心物理存储方式。<img class="rich_pages wxw-img" data-imgfileid="100006214" data-ratio="0.6696629213483146" src="/upload/446a522516d1c1d137942218563eb70d.png" data-type="png" data-w="890" style="display: block;margin-right: auto;margin-left: auto;"></p> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">Cloudreve 的特点</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">Cloudreve 有以下几个主要的特点:</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(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持多种云存储平台。Cloudreve 支持将文件存储到本地存储、远程存储、七牛、阿里云 OSS、腾讯 COS、又拍云、OneDrive、S3 兼容 API 等多种云存储平台。用户可以根据自己的需求和成本选择合适的存储方式。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持直接传输和限速。Cloudreve 支持直接从云存储平台上传和下载文件,无需经过服务器中转,节省了服务器的流量和资源。同时,Cloudreve 也支持对上传和下载的速度进行限制,防止占用过多的带宽。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持离线下载和多节点分流。Cloudreve 集成了 Aria2,可以实现离线下载的功能,支持 HTTP、FTP、磁力链接、BT 种子等多种下载协议。此外,Cloudreve 还支持使用多个下载节点来分担下载的负载,提高下载的速度和稳定性。 </section></li> </ul> <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="100006215" data-ratio="0.7001499250374813" src="/upload/cbe12eb8b6448dd5443d7b81b6bc839d.png" data-type="png" data-w="2668" 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: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> </figure> <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(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持压缩和解压缩。Cloudreve 支持对文件和文件夹进行压缩和解压缩的操作,支持 ZIP、RAR、7Z 等多种压缩格式。用户可以一次性下载多个文件或文件夹,也可以在线查看压缩包的内容。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持 WebDAV 协议。Cloudreve 支持 WebDAV 协议,覆盖了所有的存储提供商。这意味着用户可以通过 WebDAV 将云盘挂载到电脑或其他设备上,直接对云盘的内容进行读写⁵。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持拖放上传和流式处理。Cloudreve 支持拖放文件或文件夹到网页上进行上传,无需点击按钮或选择文件。同时,Cloudreve 也支持流式处理上传,即边上传边处理文件,提高了上传的效率和体验。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持文件管理和分享。Cloudreve 支持拖放文件或文件夹来进行文件管理,可以方便地对文件进行复制、移动、重命名、删除等操作。此外,Cloudreve 还支持为文件和文件夹创建分享链接,可以设置过期时间和提取码,方便地与他人分享文件。 </section></li> </ul> <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="100006213" data-ratio="0.5378346915017462" src="/upload/31b9cd2fcdae9f15f3a9377d331fe2da.png" data-type="png" data-w="859" 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: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> </figure> <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(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持在线预览和编辑。Cloudreve 支持在线预览视频、图片、音频、ePub 文件等多种格式的文件。用户可以在网页上直接观看或听取文件的内容,无需下载到本地。Cloudreve 还支持在线编辑文本、Office 文档等文件,可以实现在线协作的功能。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持自定义主题和多语言。Cloudreve 支持自定义主题颜色,可以根据用户的喜好和场景选择合适的颜色。Cloudreve 还支持暗黑模式,可以保护用户的眼睛,提高用户的舒适度。Cloudreve 还支持 PWA 应用,可以让用户将网页添加到桌面,像使用原生应用一样使用 Cloudreve。Cloudreve 还支持 SPA,可以实现单页应用的效果,提高网页的加载速度和交互性。Cloudreve 还支持 i18n,可以根据用户的语言自动切换界面的语言。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 支持一键打包和部署。Cloudreve 支持一键打包,将所有的功能和依赖都打包到一个二进制文件中,方便用户下载和运行。Cloudreve 还支持 Docker 和 docker-compose,可以实现快速的部署和配置。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">Cloudreve 的安装和使用</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">Cloudreve 的安装和使用非常简单,只需几个步骤即可完成。</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(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 下载 Cloudreve 的二进制文件,根据自己的操作系统和 CPU 架构选择合适的版本。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 解压 Cloudreve 的二进制文件,给予执行权限,然后运行 Cloudreve。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 访问 Cloudreve 的网页,使用默认的用户名和密码(admin@admin.com / admin)登录 Cloudreve。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 在 Cloudreve 的后台管理页面,配置自己的存储策略、下载节点、离线下载等设置。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 开始使用 Cloudreve,享受云盘的便利和乐趣。 </section></li> </ul> <p data-tool="mdnice编辑器" style="line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">Cloudreve 是一个支持多家云存储的云盘系统,能助你以最低的成本快速搭建公私兼备的网盘系统。</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;outline: 0px;border-left-color: rgb(53, 179, 120);color: rgb(106, 115, 125);font-size: 0.9em;border-top: none;border-right: none;border-bottom: none;background: rgb(251, 249, 253);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;letter-spacing: 0.544px;text-align: left;text-wrap: wrap;overflow: auto;line-height: 27px;"></blockquote>