1 Star 0 Fork 0

小陈/blog

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
search.xml 151.97 KB
一键复制 编辑 原始数据 按行查看 历史
小陈 提交于 2023-10-24 15:38 . Site updated: 2023-10-24 15:38:20
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Hexo部署踩坑</title>
<url>/blog/2023/03/09/Hexo%E9%83%A8%E7%BD%B2%E8%B8%A9%E5%9D%91/</url>
<content><![CDATA[<h2 id="Hexo部署踩坑"><a href="#Hexo部署踩坑" class="headerlink" title="Hexo部署踩坑"></a>Hexo部署踩坑</h2><p>记录踩坑和解决方法</p>
<h3 id="部署Gitee服务器无法显示样式"><a href="#部署Gitee服务器无法显示样式" class="headerlink" title="部署Gitee服务器无法显示样式"></a>部署Gitee服务器无法显示样式</h3><h4 id="问题:本地部署是正常的,Gitee服务器上无法显示,下图为错误信息"><a href="#问题:本地部署是正常的,Gitee服务器上无法显示,下图为错误信息" class="headerlink" title="问题:本地部署是正常的,Gitee服务器上无法显示,下图为错误信息"></a>问题:本地部署是正常的,Gitee服务器上无法显示,下图为错误信息</h4><p><img src="https://foruda.gitee.com/images/1678354680887337314/775f8b99_7715734.png"><br><img src="https://foruda.gitee.com/images/1678354853895051133/5774daf3_7715734.png"></p>
<h4 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h4><p>F12查看发现语法错误,但是本地无问题,而且查看一下配置后并无语法问题,随后怀疑上传服务器后路径问题,检查Hexo的配置文件,发现url和root路径错误,应该填入的是gitee的路径地址<br><img src="https://foruda.gitee.com/images/1678354488245714057/53bf4f02_7715734.png"></p>
<h4 id="解决方案:"><a href="#解决方案:" class="headerlink" title="解决方案:"></a>解决方案:</h4><p>(1)上传服务器代码前,首先确保静态文件语法等正确,推送仓库路径是否正确<br>(2)root后面根路径要设置博客后的/blog/ ,然后保存</p>
<h3 id="部署后hexo-generator-search搜索框样式混乱"><a href="#部署后hexo-generator-search搜索框样式混乱" class="headerlink" title="部署后hexo-generator-search搜索框样式混乱"></a>部署后hexo-generator-search搜索框样式混乱</h3><h4 id="问题:本地部署正常,上传服务器后搜索框,如下图"><a href="#问题:本地部署正常,上传服务器后搜索框,如下图" class="headerlink" title="问题:本地部署正常,上传服务器后搜索框,如下图"></a>问题:本地部署正常,上传服务器后搜索框,如下图</h4><p><img src="https://foruda.gitee.com/images/1678355362311260764/1376c088_7715734.png"></p>
<h4 id="解决思路-1"><a href="#解决思路-1" class="headerlink" title="解决思路"></a>解决思路</h4><p>F12查看并无报错信息,怀疑配置错误或缺失插件,后面发现并没有,网上查找一番发现答案</p>
<h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><p>(1)shift+F5刷新(个人也不是很懂Gitee为什么会出现这样的错误,个人归咎为服务器缓存)</p>
]]></content>
<categories>
<category>Hexo部署</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
<entry>
<title>Hexo部署踩坑2</title>
<url>/blog/2023/03/09/Hexo%E9%83%A8%E7%BD%B2%E8%B8%A9%E5%9D%912/</url>
<content><![CDATA[<h1 id="Hexo部署踩坑"><a href="#Hexo部署踩坑" class="headerlink" title="Hexo部署踩坑"></a>Hexo部署踩坑</h1><p>记录踩坑和解决办法</p>
<h2 id="服务上传文章图片"><a href="#服务上传文章图片" class="headerlink" title="服务上传文章图片"></a>服务上传文章图片</h2><h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>需要找服务器上传文章图片,不然会无法显示</p>
<h3 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h3><p>作为学生第一要义是考虑经济因素,首先需要的是免费的,其次是方便一点的图片服务器,我第一次是放CSDN里面,因为CSDN是自带Markdown格式编辑器,按道理是十分方便的还能上传图片,但是第一篇文章的图片上传后一天左右,我发现自己的博客无法调用CSDN博客的图片,F12调试报403错误,后面搞了一会和网上找了挺久都没办法解决(报403权限拒绝),换了个思路,既然Gitee是可以当做仓库存放的,那为何不能存放图片呢?去Gitee<br>查看并创建仓库后可行,如图<br><img src="https://foruda.gitee.com/images/1678356602335104712/2a9eef17_7715734.png"><br><img src="https://foruda.gitee.com/images/1678356648065299072/32731bde_7715734.png"></p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>(1)CSDN也能写博客和输入图片,但是第二天自己博客上就会报403权限拒绝,无法显示图片(可能个人原因)<br>(2)Gitee上建设一个仓库,专门存放图片,这是暂时发现的最优解,后续找到方便存放会更新。</p>
]]></content>
<categories>
<category>Hexo部署</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<url>/blog/2023/03/05/hello-world/</url>
<content><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><pre><code class="bash">$ hexo new &quot;My New Post&quot;
</code></pre>
<p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><pre><code class="bash">$ hexo server
</code></pre>
<p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><pre><code class="bash">$ hexo generate
</code></pre>
<p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><pre><code class="bash">$ hexo deploy
</code></pre>
<p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>
]]></content>
</entry>
<entry>
<title>第一篇文章测试</title>
<url>/blog/2023/03/05/%E7%AC%AC%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E6%B5%8B%E8%AF%95/</url>
<content><![CDATA[<p>第一篇文章,多多包容,测试搭建中,内容不多后续会出博客搭建详细过程</p>
<h3 id="Create-a-new-java"><a href="#Create-a-new-java" class="headerlink" title="Create a new java"></a>Create a new java</h3><pre><code class="bash">$ public class Hello&#123;
public static void main(String args[])&#123;
System.out.println(&quot;Hello World&quot;);
&#125;
&#125;
</code></pre>
]]></content>
<tags>
<tag>hello</tag>
</tags>
</entry>
<entry>
<title>枚举类的学习</title>
<url>/blog/2023/03/11/java%E5%AD%A6%E4%B9%A01/</url>
<content><![CDATA[<h1 id="枚举类学习"><a href="#枚举类学习" class="headerlink" title="枚举类学习"></a>枚举类学习</h1><h2 id="enum关键字的使用"><a href="#enum关键字的使用" class="headerlink" title="enum关键字的使用"></a>enum关键字的使用</h2><p>说明:定义的枚举类默认继承于java.lang.Enum类</p>
<h3 id="Enum的常用方法"><a href="#Enum的常用方法" class="headerlink" title="Enum的常用方法"></a>Enum的常用方法</h3><p>values()方法:返回枚举类型的对象数组。该方法可以很方便的遍历所有的枚举值<br>valuesOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象<br>toString:返回当前枚举类对象常量的名称</p>
<h3 id="enum的使用方法:"><a href="#enum的使用方法:" class="headerlink" title="enum的使用方法:"></a>enum的使用方法:</h3><pre><code> 1.提供当前枚举类的对象,多个对象之间用“,”隔开,末尾对象“;&quot;结束
</code></pre>
<pre><code class="java"> SPRING(&quot;春天&quot;, &quot;春暖花开&quot;),
SUMMER(&quot;夏天&quot;, &quot;夏日炎炎&quot;),
AUTUMN(&quot;秋天&quot;, &quot;秋意浓浓&quot;),
WINTER(&quot;冬天&quot;,&quot;寒冬已至&quot;);
</code></pre>
<pre><code>2.声明Season对象的属性:private final修饰
</code></pre>
<pre><code class="java"> private final String seasonName;
private final String seasonDesc;
</code></pre>
<pre><code>3.私有化类的构造器,并给对象属性赋值
</code></pre>
<pre><code class="java">private Season1(String seasonName, String seasonDesc) &#123;
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
&#125;
</code></pre>
<pre><code>4. 其它诉求:获取枚举类对象的属性
</code></pre>
<pre><code class="java"> public String getSeasonName() &#123;
return seasonName;
&#125;
public String getSeasonDesc() &#123;
return seasonDesc;
&#125;
//默认继承Enum类,可以根据需求确认是否重写
@Override
public String toString() &#123;
return &quot;Season1&#123;&quot; +
&quot;seasonName=&#39;&quot; + seasonName + &#39;\&#39;&#39; +
&quot;, seasonDesc=&#39;&quot; + seasonDesc + &#39;\&#39;&#39; +
&#39;&#125;&#39;;
&#125;
</code></pre>
<pre><code> 5.测试
</code></pre>
<pre><code class="java"> public static void main(String[] args) &#123;
System.out.println(Season.AUTUMN);//toString()
//输出Season1&#123;seasonName=&#39;秋天&#39;, seasonDesc=&#39;秋意浓浓&#39;&#125;
//value()
Season1[] values = Season1.values();
for (int i=0;i&lt;values.length;i++)&#123;
System.out.println(values[i]);
&#125;
// 输出
// Season1&#123;seasonName=&#39;春天&#39;, seasonDesc=&#39;春暖花开&#39;&#125;
// Season1&#123;seasonName=&#39;夏天&#39;, seasonDesc=&#39;夏日炎炎&#39;&#125;
// Season1&#123;seasonName=&#39;秋天&#39;, seasonDesc=&#39;秋意浓浓&#39;&#125;
// Season1&#123;seasonName=&#39;冬天&#39;, seasonDesc=&#39;寒冬已至&#39;&#125;
&#125;
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Linux系统中CPU占用高的解决思路</title>
<url>/blog/2023/03/13/Linux%E5%AD%A6%E4%B9%A01/</url>
<content><![CDATA[<h1 id="当在-linux系统中,java项目cpu占用很高的时候,有哪些思路可以去解决"><a href="#当在-linux系统中,java项目cpu占用很高的时候,有哪些思路可以去解决" class="headerlink" title="当在 linux系统中,java项目cpu占用很高的时候,有哪些思路可以去解决"></a>当在 linux系统中,java项目cpu占用很高的时候,有哪些思路可以去解决</h1><p>解决思路:<br>1.查看java进程信息,ps命令可以展示所有进程信息</p>
<pre><code class="linux">ps-ef |grep java
</code></pre>
<p>2.查看线程的堆栈相关信息</p>
<pre><code class="linux">jstack[pid]
</code></pre>
<p>3.查看CPU占用搞的线程</p>
<pre><code class="linux">rop -H
</code></pre>
<p>4.查看代码是否有死循环,或者借助一些外部工具提供其他信息</p>
]]></content>
<categories>
<category>运维部署踩坑</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title>集合的学习</title>
<url>/blog/2023/03/15/java%E5%AD%A6%E4%B9%A02/</url>
<content><![CDATA[<h1 id="集合框架"><a href="#集合框架" class="headerlink" title="集合框架"></a>集合框架</h1><h2 id="Collection接口:单例集合,用来存储一个一个的对象"><a href="#Collection接口:单例集合,用来存储一个一个的对象" class="headerlink" title="Collection接口:单例集合,用来存储一个一个的对象"></a>Collection接口:单例集合,用来存储一个一个的对象</h2><p>List接口:存储有序的、可重复的数据。—&gt;“动态数组”ArrayList、LinkedList、Vector<br>Set接口:存储无序的、不可重复的。—&gt;HashSet、LinkedHashSet、TreeSet<br>Map接口:双列集合,用来存储一对(key-value)一对的数据。—&gt;HashMap、LinkedHashMap、TreeMap、Hashtable、Properties</p>
<h2 id="ArrayList方法"><a href="#ArrayList方法" class="headerlink" title="ArrayList方法"></a>ArrayList方法</h2><pre><code class="java"> Collection arrayList = new ArrayList();
arrayList.add(&quot;aa&quot;);
arrayList.add(&quot;22&quot;);
arrayList.add(123);//自动装箱
arrayList.add(new Date());
//size():获取添加元素的个数
System.out.println(arrayList.size());
//addAll(Collection arrayList):将arrayList集合中的元素添加到当前的集合中
Collection arrayList2 = new ArrayList();
arrayList2.add(21);
arrayList2.add(&quot;21&quot;);
arrayList.addAll(arrayList2);
System.out.println(arrayList.size());
System.out.println(arrayList);
//clear:清空集合元素
arrayList.clear();
//isEmpty():判断当前集合是否为空
System.out.println(arrayList.isEmpty());
</code></pre>
<p>输出</p>
<pre><code class="java">4
6
[aa, 22, 123, Wed Mar 15 19:56:40 CST 2023, 21, 21]
true
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Nginx部署命令</title>
<url>/blog/2023/03/19/Linux%E5%AD%A6%E4%B9%A02/</url>
<content><![CDATA[<h1 id="jar包运行命令"><a href="#jar包运行命令" class="headerlink" title="jar包运行命令"></a>jar包运行命令</h1><h2 id="方式一:-java-jar-xxx-jar"><a href="#方式一:-java-jar-xxx-jar" class="headerlink" title="方式一: java -jar xxx.jar"></a>方式一: java -jar xxx.jar</h2><p>最常用的启动jar包命令,特点:当前ssh窗口被锁定,可按CTRL + C打断程序运行,或直接关闭窗口,程序退出。</p>
<h2 id="方式二:-java-jar-xxx-jar-amp"><a href="#方式二:-java-jar-xxx-jar-amp" class="headerlink" title="方式二: java -jar xxx.jar &amp;"></a>方式二: java -jar xxx.jar &amp;</h2><p>&amp;代表在后台运行 ,ctrl+c 后程序也会继续运行。</p>
<h2 id="方式三:-nohup-java-jar-xxx-jar-amp"><a href="#方式三:-nohup-java-jar-xxx-jar-amp" class="headerlink" title="方式三: nohup java -jar xxx.jar &amp;"></a>方式三: nohup java -jar xxx.jar &amp;</h2><p>nohup 即 no hang up 不挂断 ,关闭SSH客户端连接,程序不会中止运行。默认下,相关的输出都是定向至nohup.out的文件中</p>
<h2 id="方式四:nohup-java-jar-xxx-jar-gt-aaa-log-amp"><a href="#方式四:nohup-java-jar-xxx-jar-gt-aaa-log-amp" class="headerlink" title="方式四:nohup java -jar xxx.jar &gt;aaa.log &amp;"></a>方式四:nohup java -jar xxx.jar &gt;aaa.log &amp;</h2><p>指向定向输出地址,例如aaa.log</p>
<h1 id="Nginx部署运行"><a href="#Nginx部署运行" class="headerlink" title="Nginx部署运行"></a>Nginx部署运行</h1><h2 id="安装(网上教程)"><a href="#安装(网上教程)" class="headerlink" title="安装(网上教程)"></a>安装(网上教程)</h2><h2 id="开放nginx的80端口"><a href="#开放nginx的80端口" class="headerlink" title="开放nginx的80端口"></a>开放nginx的80端口</h2><pre><code class="java">\\查看端口列表
firewall-cmd --list-all
\\设置80端口
firewall-cmd --add-service=http –permanent
firewall-cmd --add-port=80/tcp --permanent
\\重启防火墙(可设置默认关闭状态)
firewall-cmd –reload
</code></pre>
<h2 id="启动服务"><a href="#启动服务" class="headerlink" title="启动服务"></a>启动服务</h2><p>进入目录 /usr/local/nginx/sbin/nginx 启动服务 ./nginx<br><img src="https://foruda.gitee.com/images/1679223782532858953/fcd9f4d1_7715734.png"></p>
]]></content>
<categories>
<category>运维部署踩坑</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title>迭代器和foreach循环的学习</title>
<url>/blog/2023/03/20/java%E5%AD%A6%E4%B9%A03/</url>
<content><![CDATA[<h1 id="迭代器"><a href="#迭代器" class="headerlink" title="迭代器"></a>迭代器</h1><p>在 Java 中,迭代器通常用于遍历集合类(如 ArrayList、LinkedList 等)中的元素。通过调用集合对象的 iterator() 方法,可以获得一个迭代器对象,然后使用该对象的 hasNext() 和 next() 方法依次获取集合中的每个元素。<br>总的来说,Java 迭代器的原理就是通过为每个集合对象提供一个迭代器对象,并在迭代器对象中封装集合对象的内部实现,从而使得我们能够以一种标准化的方式来遍历集合中的元素,而不需要关心集合的具体实现细节</p>
<p>示例代码:</p>
<pre><code class="java"> ArrayList&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;Java&quot;);
list.add(&quot;Python&quot;);
list.add(&quot;C++&quot;);
list.add(&quot;Ruby&quot;);
Iterator&lt;String&gt; itr = list.iterator();
while (itr.hasNext()) &#123;
String element = itr.next();
System.out.println(element);
&#125;
/**这个程序创建了一个 ArrayList,添加了四个字符串元素,并使用 iterator() 方法获取迭代器对象。
然后,使用 while 循环和 hasNext() 和 next() 方法遍历整个列表,并将每个元素打印到控制台中。*/
//用法二:迭代器删除元素
if (fruit.equals(&quot;Java&quot;)) &#123;
iterator.remove(); // 删除集合中的 &quot;Java&quot;
&#125;
itr = list.iterator();
while (itr.hasNext())&#123;
System.out.println(itr.next());//重新输出
&#125;
/*我们首先获取了一个 ArrayList 集合的迭代器对象,然后通过 while 循环遍历集合中的每个元素。
当遍历到 &quot;java&quot; 元素时,我们调用迭代器的 remove() 方法将其从集合中删除*/
</code></pre>
<h1 id="foreach循环"><a href="#foreach循环" class="headerlink" title="foreach循环"></a>foreach循环</h1><p>Java中的foreach语法是一种用于遍历数组和集合的简便方式</p>
<pre><code class="java">for (type element : array/collection) &#123;
// 循环体
&#125;
/*其中, type 是数组或集合元素的数据类型, element 是循环变量,
array/collection 是要遍历的数组或集合对象*/
</code></pre>
<p> 遍历数组和集合对象的示例代码</p>
<pre><code class="java">//遍历一个整型数组并输出其中的所有元素,可以使用以下代码
int[] numbers = &#123;1, 2, 3, 4, 5&#125;;
for (int number : numbers) &#123;
System.out.println(number);
&#125;
/*这段代码会依次输出数组 numbers 中的每个元素。
需要注意的是,foreach语法只适用于数组和实现了Iterable接口的集合类,
比如ArrayList和LinkedList等。如果要遍历其他类型的集合,可以考虑使用迭代器(Iterator)来实现*/
</code></pre>
<pre><code class="java">//集合实现 foreach 循环的示例代码:
List&lt;String&gt; fruits = new ArrayList&lt;&gt;();
fruits.add(&quot;apple&quot;);
fruits.add(&quot;banana&quot;);
fruits.add(&quot;orange&quot;);
// 使用 foreach 循环遍历整个集合
for (String fruit : fruits) &#123;
System.out.println(fruit);
&#125;
&#125;
/*创建了一个 List 集合对象 fruits,并向其中添加了三个字符串元素。
然后,我们使用 foreach 循环来遍历整个集合,并将每个元素存储在变量 fruit 中。
在每次迭代中,我们输出当前元素的值。需要注意的是,因为 List 实现了 Iterable 接口,所以它可以被 foreach 循环遍历。
如果你想在自己的类中实现 foreach 循环遍历功能,你需要在类中实现 Iterable 接口并提供 iterator() 方法的实现。*/
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>集合的学习2</title>
<url>/blog/2023/03/23/java%E5%AD%A6%E4%B9%A04/</url>
<content><![CDATA[<h1 id="chatGPT辅助学习的内容"><a href="#chatGPT辅助学习的内容" class="headerlink" title="chatGPT辅助学习的内容"></a>chatGPT辅助学习的内容</h1><p>chatGPT真的太强大了!一个20分钟的学习视频它可以总结出几百文字让你理解的语言!!不会让我找不到工作吧哭哭哭😭😭</p>
<h1 id="ArrayList常用方法"><a href="#ArrayList常用方法" class="headerlink" title="ArrayList常用方法"></a>ArrayList常用方法</h1><ul>
<li><p>add(Object obj): 将指定的元素添加到列表的末尾。</p>
</li>
<li><p>add(int index, Object obj): 将指定的元素插入到列表的指定位置。</p>
</li>
<li><p>remove(Object obj): 从列表中删除指定元素的第一个出现。</p>
</li>
<li><p>remove(int index): 从列表中删除指定位置的元素。</p>
</li>
<li><p>get(int index): 返回列表中指定位置的元素。</p>
</li>
<li><p>set(int index, Object obj): 用指定元素替换列表中指定位置的元素。</p>
</li>
<li><p>size(): 返回列表中的元素数。</p>
</li>
<li><p>isEmpty(): 如果列表中没有元素,则返回 true。</p>
</li>
<li><p>indexOf(Object obj): 返回指定元素在列表中第一次出现的位置。</p>
</li>
<li><p>lastIndexOf(Object obj): 返回指定元素在列表中最后一次出现的位置。</p>
</li>
<li><p>clear(): 从列表中删除所有元素。</p>
</li>
<li><p>toArray(): 将列表转换为数组。<br>实现案例:</p>
<pre><code class="java">// 创建一个空的ArrayList
ArrayList&lt;String&gt; list = new ArrayList&lt;&gt;();
// 添加元素
list.add(&quot;Java&quot;);
list.add(&quot;Python&quot;);
list.add(&quot;C++&quot;);
// 获取元素
System.out.println(&quot;第二个元素是:&quot; + list.get(1));
// 修改元素
list.set(1, &quot;JavaScript&quot;);
System.out.println(&quot;修改后的第二个元素是:&quot; + list.get(1));
// 删除元素
list.remove(0);
System.out.println(&quot;删除第一个元素后,列表中的元素有:&quot;);
for (String s : list) &#123;
System.out.println(s);
&#125;
// 判断是否包含某个元素
if (list.contains(&quot;C++&quot;)) &#123;
System.out.println(&quot;列表中包含C++&quot;);
&#125;
// 获取列表大小
System.out.println(&quot;列表中有&quot; + list.size() + &quot;个元素&quot;);
&#125;
</code></pre>
<h1 id="HashSet常用方法"><a href="#HashSet常用方法" class="headerlink" title="HashSet常用方法"></a>HashSet常用方法</h1><p>不允许重复元素:</p>
</li>
</ul>
<blockquote>
<p>不可重复性指的是内容不同,即在HashSet中不能包含相同的元素。HashSet是基于哈希表实现的,它使用元素的哈希值来确定元素在集合中的位置,如果两个元素的哈希值相同,那么它们会被认为是相同的元素。因此,HashSet中的元素必须实现equals()和hashCode()方法,以确保元素的内容唯一性。</p>
</blockquote>
<p>元素无序:</p>
<blockquote>
<p>HashSet的特性是无序的,但是在Java8之后,对于小于等于16个元素的HashSet会使用一个特殊的数据结构——链表数组。这个数据结构会按照元素的哈希值进行排序,所以在输出时看起来是有序的</p>
</blockquote>
<p>支持快速查询:</p>
<ul>
<li>add(E e):向集合中添加元素,如果元素已经存在则不添加,返回false。</li>
<li>remove(Object o):从集合中移除指定元素,如果元素不存在则返回false。</li>
<li>contains(Object o):判断集合中是否包含指定元素,如果包含则返回true,否则返回false。</li>
<li>clear():清空集合中的所有元素。</li>
<li>size():返回集合中元素的个数。</li>
<li>isEmpty():判断集合是否为空,如果为空则返回true,否则返回false。</li>
<li>iterator():返回一个迭代器,用于遍历集合中的元素。</li>
<li>addAll(Collection&lt;? extends E&gt; c):将指定集合中的所有元素添加到当前集合中。</li>
<li>removeAll(Collection&lt;?&gt; c):从当前集合中移除指定集合中的所有元素。</li>
<li>retainAll(Collection&lt;?&gt; c):从当前集合中仅保留指定集合中的元素,移除其他元素。</li>
<li>toArray():将集合中的元素转换为数组。</li>
<li>toArray(T[] a):将集合中的元素转换为指定类型的数组。<br>实现案例:<pre><code class="java"> // 创建HashSet对象
HashSet&lt;String&gt; set = new HashSet&lt;&gt;();
// 添加元素
set.add(&quot;1&quot;);
set.add(&quot;1&quot;);
set.add(&quot;2&quot;);
set.add(&quot;3&quot;);
//遍历集合
for (String s :set)&#123;
System.out.println(s);
&#125;
// 判断元素是否存在
System.out.println(set.contains(&quot;1&quot;));
//删除元素
set.remove(1);
System.out.println(&quot;删除元素后.....&quot;+set);
// 集合大小
System.out.println(set.size());
// 清空集合
set.clear();
// 判断集合是否为空
System.out.println(set.isEmpty());
</code></pre>
</li>
</ul>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>集合的学习3</title>
<url>/blog/2023/03/26/java%E5%AD%A6%E4%B9%A05/</url>
<content><![CDATA[<h1 id="Map的常用实现方式"><a href="#Map的常用实现方式" class="headerlink" title="Map的常用实现方式"></a>Map的常用实现方式</h1><p>Map 是一种常用的数据结构,用于将一个值 (value) 映射到另一个值 (key) 上。它通常用于存储键值对,并允许通过键有效地查找值。<br>常用方法:</p>
<blockquote>
<p>添加元素: 使用 put(key, value) 方法添加元素 (键值对)<br>获取元素: 使用 get(key) 方法获取指定键对应的值<br>删除元素: 使用 remove(key) 方法删除指定的键值对<br>判断键是否存在: 使用 containsKey() 方法判断指定键是否存在于 map 中<br>获取所有键: 使用 keySet() 方法获取 map 中所有的键<br>获取所有值: 使用 values() 方法获取 map 中所有的值</p>
</blockquote>
<p>Map 的特点包括:</p>
<blockquote>
<p>键必须唯一: 每个键只能对应一个值,且不允许出现重复的键。 value是可重复的,collection存储所有的value<br>元素无序: 存储的键和值没有固定的顺序。<br>动态增长: Map的大小是可以动态增加或减少的。</p>
</blockquote>
<blockquote>
<p>HashMap 是一种基于哈希表实现的键值对存储结构,它的底层实现原理主要包括两个部分: </p>
<ul>
<li>哈希函数:将键(Key)映射到哈希表中的桶(Bucket),使得不同的键能够分散地散落在不同的桶内,从而保证哈希表的查找、插入、删除等操作的效率。</li>
<li> 链表/红黑树结构:当多个不同的键散落到同一个桶中时,采用链表或红黑树等数据结构进行存储和查找,以避免冲突。如果链表过长,则会转化为红黑树以提高效率。</li>
<li>HashMap 在 Java 中是线程不安全的,因此在多线程环境下需要采取相应的措施保证线程安全,例如使用 ConcurrentHashMap</li>
</ul>
</blockquote>
<p> Java 示例代码,说明了如何创建一个 HashMap 对象并使用它</p>
<pre><code class="java"> HashMap&lt;String, Integer&gt; hashMap = new HashMap&lt;&gt;();
// 向 Map 中添加一些数据
hashMap.put(&quot;11&quot;,212);
hashMap.put(&quot;3&quot;,21);
hashMap.put(&quot;8&quot;,45);
hashMap.put(&quot;9&quot;,34);
hashMap.put(&quot;0&quot;,2123);
// 查找 Map 中的元素
System.out.println(hashMap.get(&quot;11&quot;));
System.out.println(hashMap.get(&quot;3&quot;));
// 修改 Map 中的元素
hashMap.put(&quot;11&quot;,100);
//遍历 Map 中的所有元素
for (Map.Entry&lt;String,Integer&gt; entry :hashMap.entrySet())&#123;
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + &quot;:&quot; + value);
&#125;
// 删除指定的元素
hashMap.remove(&quot;0&quot;);
System.out.println(hashMap.containsKey(&quot;0&quot;));
// 获取所有值
Collection&lt;Integer&gt; values = hashMap.values();
System.out.println(values);
// 获取所有key
Set&lt;String&gt; strings = hashMap.keySet();
System.out.println(strings);
</code></pre>
<h1 id="LinkedHashMap"><a href="#LinkedHashMap" class="headerlink" title="LinkedHashMap"></a>LinkedHashMap</h1><p>基于哈希表实现的 Map 数据结构,它还使用了双向链表来维护插入顺序或访问顺序。</p>
<blockquote>
<p>基于哈希表的数据结构:用于高效地查找键值对。<br>双向链表: 用于维护插入顺序或访问顺序。<br>具体来说,在 HashMap 的基础上,每个 Entry 节点会保存前驱节点和后继节点的引用,这样就构成了一个双向链表,并且这个链表也是一个 LRU 队列。当新元素被加入到 LinkedHashMap 中,它会被添加到双向链表的尾部;当旧元素访问时,它会被移动到双向链表的尾部(即更新访问顺序),因此可以根据双向链表的顺序轻松实现 LRU。</p>
</blockquote>
<p>简单的 Java 代码示例,演示了如何使用 LinkedHashMap</p>
<pre><code class="java"> // 创建一个保存整型键和值的 LinkedHashMap
LinkedHashMap&lt;Integer, Integer&gt; map = new LinkedHashMap&lt;&gt;();
map.put(3, 9);
map.put(2, 4);
map.put(4, 16);
map.put(1, 1);
// 打印出所有键值对及其插入的顺序
for (Map.Entry&lt;Integer, Integer&gt; entry : map.entrySet()) &#123;
System.out.println(entry.getKey() + &quot;=&gt;&quot; + entry.getValue());
&#125;
输出:键值对的顺序保持了插入的顺序。
3=&gt;9
2=&gt;4
4=&gt;16
1=&gt;1
</code></pre>
<h1 id="Collections"><a href="#Collections" class="headerlink" title="Collections"></a>Collections</h1><p> Collections是一个实用类库,包含了许多静态方法来操作集合(Collection)和映射(Map)等数据结构</p>
<blockquote>
<ul>
<li>sort(List<T> list):对列表进行排序。 </li>
<li>binarySearch(List&lt;?&gt; list, Object key):使用二分查找算法在列表中查找指定元素。</li>
<li>reverse(List<T> list):反转列表中元素的顺序。</li>
<li>shuffle(List&lt;?&gt; list):随机排列列表中的元素。</li>
<li>max(Collection&lt;? extends T&gt;coll):返回集合中最大的元素。</li>
<li>min(Collection&lt;? extends T&gt; coll):返回集合中最小的元素。 </li>
<li>frequency(Collection&lt;?&gt; c, Object o):统计集合中某个元素出现的次数。 </li>
<li>disjoint(Collection<?> c1, Collection<?> c2):判断两个集合是否没有共同元素。</li>
</ul>
<p>Java程序演示如何使用上述Collections方法:</p>
</blockquote>
<pre><code class="java"> @Test
public void test1()&#123;
List&lt;Integer&gt; list = new ArrayList&lt;Integer&gt;();
list.add(5);
list.add(2);
list.add(10);
// 对list进行排序
Collections.sort(list);
System.out.println(&quot;Sorted List: &quot; + list);
// 在list中查找元素2
int index = Collections.binarySearch(list, 2);
System.out.println(&quot;Index of 2: &quot; + index);
// 反转list中元素的顺序
Collections.reverse(list);
System.out.println(&quot;Reversed List: &quot; + list);
// 随机排列list中的元素
Collections.shuffle(list);
System.out.println(&quot;Shuffled List: &quot; + list);
// 返回集合中最大的元素
Integer max = Collections.max(list);
System.out.println(&quot;Maximum Element: &quot; + max);
// 统计集合中某个元素出现的次数
int freq = Collections.frequency(list, 5);
System.out.println(&quot;Frequency of 5: &quot; + freq);
// 判断两个集合是否没有共同元素
List&lt;Integer&gt; list2 = new ArrayList&lt;Integer&gt;();
list2.add(1);
boolean disjoint = Collections.disjoint(list, list2);
System.out.println(&quot;Disjoint: &quot; + disjoint);
/*
输出结果为:
sorted List: [2, 5, 10]
Index of 2: 0
Reversed List: [10, 5, 2]
Shuffled List: [5, 10, 2]
Maximum Element: 10
Minimum Element: 2
Frequency of 5: 1
Disjoint: true*/
&#125;
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>利用AI生成ppt</title>
<url>/blog/2023/03/27/chatGPT%E4%BD%BF%E7%94%A8/</url>
<content><![CDATA[<h1 id="AI工具生成ppt"><a href="#AI工具生成ppt" class="headerlink" title="AI工具生成ppt"></a>AI工具生成ppt</h1><p>首先最基本的是要会拥用ChatGPT账户,超级简单!!(3.27暂时不收费)</p>
<h2 id="描述需求"><a href="#描述需求" class="headerlink" title="描述需求"></a>描述需求</h2><p>比如我这里有一个作业需要PPT汇报,登入ai官网并输入相关要求(必须markdown格式输出),并复制输出内容。<br><img src="https://foruda.gitee.com/images/1679919947521532685/a4a20c5e_7715734.png"><br><img src="https://foruda.gitee.com/images/1679919114703345545/75bc1834_7715734.png"></p>
<h2 id="生成PPT"><a href="#生成PPT" class="headerlink" title="生成PPT"></a>生成PPT</h2><p>登陆<code>https://mindshow.fun/#/edit/61674</code>,并导入相关格式生成下载。<br><img src="https://foruda.gitee.com/images/1679919459218787980/c57fd852_7715734.png"></p>
]]></content>
<categories>
<category>ChatGPT工具使用</category>
</categories>
<tags>
<tag>GPT</tag>
</tags>
</entry>
<entry>
<title>泛型的学习</title>
<url>/blog/2023/03/29/java%E5%AD%A6%E4%B9%A06/</url>
<content><![CDATA[<h1 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h1><blockquote>
<p>泛型在Java中主要用于提高代码的可重用性和类型安全,可以定义不同类型的对象,在使用时可以根据需要指定具体的类型。</p>
</blockquote>
<ul>
<li>类型安全:泛型在编译时可以检查类型的合法性,从而避免在运行时出现类型不匹配的错误,提高了程序的可靠性。</li>
<li>可重用性:泛型可以定义不同类型的对象,在使用时可以根据需要指定具体的类型,从而提高了代码的可重用性。</li>
<li>灵活性:通过使用泛型,可以使代码更加通用和灵活,同时也提高了代码的可读性和可维护性。</li>
<li>性能损失:泛型会带来一定的性能损失,因为需要进行类型擦除和强制类型转换等操作。</li>
</ul>
<p>泛型在Java中实现方法的示例代码</p>
<pre><code class="java">// 定义一个泛型方法
public static &lt;T&gt; void text(T t)&#123;
System.out.println(t);
&#125;
@Test
public void test4()&#123;
// 调用泛型方法
GenericsTest.&lt;String&gt;text(&quot;hello&quot;);
&#125;
* 我们定义了一个泛型方法text,并且使用了类型参数T。然后,在Test4方法中,
* 我们通过调用GenericDemo.&lt;String&gt;text(&quot;hello world&quot;)来指定具体的类型参数为String,并打印出字符串。
</code></pre>
<p>泛型在Java中实现类的示例代码</p>
<pre><code class="java"> // 定义一个泛型类
public class MyGenericClass&lt;T&gt;&#123;
private T element;
public T getElement() &#123;
return element;
&#125;
public void setElement(T element) &#123;
this.element = element;
&#125;
&#125;
//调用泛型方法
@Test
public void test1()&#123;
MyGenericClass&lt;Integer&gt; myGenericClass = new MyGenericClass&lt;&gt;();
myGenericClass.setElement(10);
System.out.println(myGenericClass.getElement());
MyGenericClass&lt;String&gt; myGenericClass2 = new MyGenericClass&lt;&gt;();
myGenericClass2.setElement(&quot;你好阿&quot;);
System.out.println(myGenericClass2.getElement());
&#125;
* 在MyGenericClass类中,我们使用了类型参数T来表示该类可以处理的数据类型。然后,我们定义了一个属性element和其对应的getter和setter方法,用于保存和获取泛型数据。
* 在test1方法中,我们实例化了两个MyGenericClass对象,分别指定了不同的类型参数Integer和String。然后,我们通过调用setElement方法来设置不同类型的数据,并通过调用getElement方法来获取相应的数据并打印输出。
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>使用Vercel托管平台搭建免费Chatgpt</title>
<url>/blog/2023/04/05/chatGPT%E4%BD%BF%E7%94%A82/</url>
<content><![CDATA[<h1 id="使用Vercel托管平台搭建免费Chatgpt并集成至个人网站"><a href="#使用Vercel托管平台搭建免费Chatgpt并集成至个人网站" class="headerlink" title="使用Vercel托管平台搭建免费Chatgpt并集成至个人网站"></a>使用Vercel托管平台搭建免费Chatgpt并集成至个人网站</h1><pre><code>前提:
需要一个github账户,进行对开源项目进行Fork
</code></pre>
<!-- 科学上网
openAI账户 -->
<p>打开网址:<a href="https://github.com/ourongxing/chatgpt-vercel">https://github.com/ourongxing/chatgpt-vercel</a><br>点击右上角的Fork按钮,如下图<br><img src="https://valiant-long.github.io/img/chatgpt/1.jpg" alt="在这里插入图片描述"><br>Fork后把 chatgpt-vercel 托管到vercel平台(github操作结束)。</p>
<blockquote>
<p>打开网址:<a href="https://vercel.com/">https://vercel.com/</a> 需要你把github账户关联到vercel平台 进入后,点击 Add<br>new,选择project如下图,然后进行import</p>
</blockquote>
<p><img src="https://foruda.gitee.com/images/1680665845403007061/0be18ebc_7715734.png" alt="在这里插入图片描述"></p>
<blockquote>
<p>获取chatgpt apikey 网址:<a href="https://platform.openai.com/">https://platform.openai.com/</a><br>点击由上角头像,点击Manage Account-&gt;API Keys-&gt;Create New Secret Key, 然后复制下来保存备用。输入key<br><img src="https://foruda.gitee.com/images/1680666165543059081/846b18de_7715734.png" alt="在这里插入图片描述"></p>
</blockquote>
<p>成功后会返回一个网站,如本站的AI所示,现在,你已经成功拥有了自己的一个私人AI了!</p>
]]></content>
<categories>
<category>ChatGPT工具使用</category>
</categories>
<tags>
<tag>GPT</tag>
</tags>
</entry>
<entry>
<title>序列化的学习理解</title>
<url>/blog/2023/04/10/java%E5%AD%A6%E4%B9%A07/</url>
<content><![CDATA[<h1 id="Java-序列化"><a href="#Java-序列化" class="headerlink" title="Java 序列化"></a>Java 序列化</h1><blockquote>
<p>Java 序列化的目的是将 Java 对象转换为字节流,以便在网络中传输或将其保存在磁盘上,从而实现对象的持久化、远程调用和缓存等功能</p>
</blockquote>
<ul>
<li>对象持久化:将 Java对象转换为字节流,并将其保存到文件或数据库中。之后,我们可以重新读取该文件或数据库记录,通过反序列化操作将其还原为原始对象,从而实现对象持久化的功能。</li>
<li>远程调用:通过 Java 序列化,我们可以将 Java 对象通过网络传输到远程机器上,然后在远程机器上进行相关操作,最终再将结果序列化并传回本地机器。这种方式被称为 Java 远程方法调用(Java RMI)。</li>
<li>缓存机制:Java 序列化可以将 Java 对象缓存在内存或其他高速存储介质中,在需要时快速读取,这可以提高程序的性能和响应速度。</li>
</ul>
<p>Java 序列化的示例代码</p>
<pre><code class="java"> @Test
public void test1() throws IOException, ClassNotFoundException &#123;
// 创建 Person 对象
Person person = new Person(12, &quot;小时&quot;);
// 将 Person 对象序列化到文件中
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(&quot;hello.txt&quot;));
objectOutputStream.writeObject(person);
objectOutputStream.close();
// 从文件中读取序列化的 Person 对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream(&quot;hello.txt&quot;));
Person newPerson = (Person)in.readObject();
in.close();
System.out.println(newPerson.getName());
System.out.println(newPerson.getAge());
&#125;
class Person implements Serializable&#123;
private int age;
private String name;
public Person(int age, String name) &#123;
this.age = age;
this.name = name;
&#125;
public Person() &#123;
&#125;
public int getAge() &#123;
return age;
&#125;
public void setAge(int age) &#123;
this.age = age;
&#125;
public String getName() &#123;
return name;
&#125;
public void setName(String name) &#123;
this.name = name;
&#125;
运行上述代码后,会将 Person 对象序列化到 hello.txt 文件中,然后再从该文件中读取出序列化的对象,并输出其姓名和年龄。
</code></pre>
<p>最近偷懒了….</p>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>反射的学习1</title>
<url>/blog/2023/04/12/java%E5%AD%A6%E4%B9%A08/</url>
<content><![CDATA[<h1 id="反射"><a href="#反射" class="headerlink" title="反射"></a>反射</h1><blockquote>
<p>Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。Java属于先编译再运行的语言,程序中对象的类型在编译期就确定了,但Java反射机制的作用主要是在程序运行时才获取类的相关信息,可以在程序运行时动态地加载、检查、调用和修改类中的方法、构造方法、字段以及访问权限等。</p>
</blockquote>
<p>以下是一个简单的Java反射机制示例,演示了如何使用反射机制动态获取类名、属性值和调用方法:</p>
<pre><code class="java"> 需要先创建一个Person类
//反射方式
@Test
public void test2() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException &#123;Person ss = new Person(&quot;ss&quot;, 2);
// 创建Person类的对象
Class cons = Person.class;
Constructor constructor = cons.getConstructor(String.class, int.class);
Object instance = constructor.newInstance(&quot;Tom&quot;, 12);
Person p =(Person)instance;
System.out.println(p);
//调用属性值
Field age = cons.getDeclaredField(&quot;age&quot;);
age.setAccessible(true);
age.set(p,10);
System.out.println(p);
//调用方法
Method eat = cons.getDeclaredMethod(&quot;eat&quot;);
eat.invoke(p);
&#125;
通过getDeclaredField()方法获取属性值,并通过get()方法获取实际的属性值或者set()修改p值,通过getDeclaredMethod()方法获取方法信息,并使用invoke()方法调用该方法
</code></pre>
<p>此外,反射机制也会在一定程度上影响程序的性能,因此应该尽量避免过多地使用反射机制</p>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Lambda表达式</title>
<url>/blog/2023/04/15/java%E5%AD%A6%E4%B9%A09/</url>
<content><![CDATA[<h1 id="Lambda表达式语法的使用"><a href="#Lambda表达式语法的使用" class="headerlink" title="Lambda表达式语法的使用"></a>Lambda表达式语法的使用</h1><blockquote>
<p>Lambda表达式是Java 8中引入的一个新功能,它是一种匿名函数,可以作为参数传递给方法或存储在变量中。Lambda表达式可以简化代码<br>(parameters) -&gt; expression<br><br>(parameters) -&gt; { statements; }<br>其中,parameters表示Lambda表达式的参数列表,可以是空的或非空的;expression或statements表示Lambda表达式的执行体,可以是一个表达式或一段代码块。<br>Lambda表达式的参数列表和执行体之间用箭头符号-&gt;连接。如果参数列表为空,则使用空括号()表示;如果执行体只有一条语句,则可以省略花括号{}和分号;。如果执行体包含多条语句,则需要使用花括号{}将语句块括起来,并且每条语句必须以分号;结束。<br> 其中,parameters表示Lambda表达式的参数列表,可以是空的或非空的;expression或statements表示Lambda表达式的执行体,可以是一个表达式或一段代码块。</p>
</blockquote>
<p>Lambda表达式语法可以用在Java中的List的各种场景,例如:</p>
<pre><code class="sql"> @Test
public void test1()&#123;
ArrayList&lt;Object&gt; arrayList = new ArrayList&lt;&gt;();
arrayList.add(&quot;1&quot;);
arrayList.add(&quot;22&quot;);
arrayList.add(&quot;3s2&quot;);
arrayList.add(&quot;42&quot;);
arrayList.add(&quot;43&quot;);
arrayList.add(null);
// 遍历List
arrayList.forEach(ss -&gt; System.out.println(ss));
// 条件过滤List
System.out.println(arrayList.stream().filter(s -&gt; s != &quot;1&quot;).collect(Collectors.toList()));
// 去重
System.out.println(arrayList.stream().distinct().collect(Collectors.toList()));
// 对List进行去除null
System.out.println(arrayList.stream().filter(Objects::nonNull).collect(Collectors.toList()));
// 对List进行分组
System.out.println(arrayList.stream().collect(Collectors.groupingBy(Objects::nonNull)));
&#125;
// 使用Lambda表达式创建线程1
Thread thread1 = new Thread(() -&gt; &#123;
for (int i = 0; i &lt; 5; i++) &#123;
System.out.println(&quot;Thread 1 is running: &quot; + i);
&#125;
&#125;);
thread1.start();
// 使用Lambda表达式创建线程2
Thread thread2 = new Thread(() -&gt; &#123;
for (int i = 0; i &lt; 5; i++) &#123;
System.out.println(&quot;Thread 2 is running: &quot; + i);
&#125;
&#125;);
thread2.start();
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Lambda表达式2</title>
<url>/blog/2023/04/16/java%E5%AD%A6%E4%B9%A010/</url>
<content><![CDATA[<h1 id="SteamAPI"><a href="#SteamAPI" class="headerlink" title="SteamAPI"></a>SteamAPI</h1><p>StreamAPI是Java 8中新增的一个功能,它是一种用于操作集合(List、Set、Map等)和数组的工具,可以让我们以函数式编程的方式来处理数据。<br> StreamAPI可以应用于很多场景中,以下是一些常见的使用场景:</p>
<blockquote>
<p> 数据筛选和过滤:使用filter方法可以筛选出符合条件的数据,比如筛选出所有年龄大于18岁的人。<br>数据转换和映射:使用map方法可以将数据进行转换或映射,比如将所有字符串转换为大写。<br>数据排序和去重:使用sorted和distinct方法可以对数据进行排序和去重操作。<br>数据分组和聚合:使用groupingBy和reduce方法可以对数据进行分组和聚合操作,比如按照性别分组并计算每组人数。<br>并行处理数据:使用parallelStream方法可以将数据分成多个部分并行处理,提高处理效率。<br>数据统计和分析:使用count、sum、average等方法可以对数据进行统计和分析,比如统计所有人的平均年龄。<br>数据匹配和查找:使用anyMatch、allMatch、noneMatch和findAny等方法可以进行数据匹配和查找操作,比如查找是否有年龄大于60岁的人。</p>
</blockquote>
<p>使用StreamAPI的示例代码:</p>
<pre><code class="java"> class Person &#123;
private String name;
private String gender;
private int age;
public Person(String name, String gender, int age) &#123;
this.name = name;
this.gender = gender;
this.age = age;
&#125;
public String getGender() &#123;
return gender;
&#125;
public String getName() &#123;
return name;
&#125;
public int getAge() &#123;
return age;
&#125;
&#125;
@Test
public void test1()&#123;
List&lt;Integer&gt; list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 筛选出所有大于5的数据
list.stream()
.filter(n -&gt; n &gt; 5)
.forEach(System.out::println);
&#125;
@Test
public void test2()&#123;
List&lt;String&gt; list = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;orange&quot;, &quot;grape&quot;, &quot;watermelon&quot;);
// 将所有字符串转换为大写
list.stream()
.map(String::toUpperCase)
.forEach(System.out::println);&#125;
@Test
public void test3()&#123;
List&lt;Integer&gt; list = Arrays.asList(1, 3, 2, 5, 4, 2, 6, 8, 7, 9, 10);
// 对数据进行排序并去重
list.stream()
.sorted()
.distinct()
.forEach(System.out::println);
&#125;
@Test
public void test4()&#123;
List&lt;Person&gt; list = Arrays.asList(
new Person(&quot;张三&quot;, &quot;&quot;, 20),
new Person(&quot;李四&quot;, &quot;&quot;, 18),
new Person(&quot;王五&quot;, &quot;&quot;, 25),
new Person(&quot;赵六&quot;, &quot;&quot;, 22),
new Person(&quot;钱七&quot;, &quot;&quot;, 30)
);
// 按照性别分组并计算每组人数
Map&lt;String, Long&gt; result = list.stream()
.collect(Collectors.groupingBy(Person::getGender, Collectors.counting()));
System.out.println(result);
&#125;
@Test
public void test5()&#123;
List&lt;Person&gt; list = Arrays.asList(
new Person(&quot;张三&quot;, &quot;&quot;, 20),
new Person(&quot;李四&quot;, &quot;&quot;, 18),
new Person(&quot;王五&quot;, &quot;&quot;, 25),
new Person(&quot;赵六&quot;, &quot;&quot;, 22),
new Person(&quot;钱七&quot;, &quot;&quot;, 30)
);
// 查找是否有年龄大于60岁的人
boolean result = list.stream()
.anyMatch(person -&gt; person.getAge() &gt; 60);
System.out.println(result);
// 筛选出年龄大于10岁的人并输出他们的信息
list.stream().filter(person -&gt; person.getAge()&gt;10).
forEach(person -&gt; System.out.println(person.getName()+&quot;&quot;+person.getAge()));
&#125;
@Test
public void test6()&#123;
List&lt;Person&gt; list = Arrays.asList(
new Person(&quot;张三&quot;, &quot;&quot;, 20),
new Person(&quot;李四&quot;, &quot;&quot;, 18),
new Person(&quot;王五&quot;, &quot;&quot;, 25),
new Person(&quot;赵六&quot;, &quot;&quot;, 22),
new Person(&quot;钱七&quot;, &quot;&quot;, 30)
);
// 计算所有人的平均年龄
double averageAge = list.stream()
.mapToInt(Person::getAge)
.average()
.orElse(0);
System.out.println(averageAge) ;
&#125;
@Test
public void test7()&#123;
List&lt;Person&gt; list = Arrays.asList(
new Person(&quot;张三&quot;, &quot;&quot;, 20),
new Person(&quot;李四&quot;, &quot;&quot;, 18),
new Person(&quot;王五&quot;, &quot;&quot;, 25),
new Person(&quot;赵六&quot;, &quot;&quot;, 22),
new Person(&quot;钱七&quot;, &quot;&quot;, 30)
);
// 查找是否有年龄大于60岁的人
boolean result = list.stream()
.anyMatch(person -&gt; person.getAge() &gt; 60);
System.out.println(result);
&#125;
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>springboot(yaml语法和属性赋值)</title>
<url>/blog/2023/04/21/java%E5%AD%A6%E4%B9%A011/</url>
<content><![CDATA[<h1 id="Springboot(YAML语法和属性赋值)"><a href="#Springboot(YAML语法和属性赋值)" class="headerlink" title="Springboot(YAML语法和属性赋值)"></a>Springboot(YAML语法和属性赋值)</h1><p>YAML(YAML Ain’t MarkupLanguage)是一种常用于存储配置文件、数据序列化、消息传递等的标记语言。相对于XML和JSON格式,YAML更加人性化、易读、可维护,并且支持关系、表达式等更多复杂的数据结构。</p>
<blockquote>
<p>使用YAML格式的注意事项如下:</p>
<p>1.缩进非常重要 - 对于需要嵌套的关系,必须适时的进行缩进;<br>2.每一个列表元素都必须以同样的缩进方式开始;<br>3.引号用法 - 在使用冒号、方括号、大括号等特殊字符时一定要加上双引号或单引号,例如:”this is a string”;<br>4.控制空行和注释 - 避免混淆以及提高可读性,可以用“#”符号注释代码,并尽量避免在一个块内出现多个空行的情况;<br>5.尽量避免在键中使用特殊字符(如 . ,:-)。</p>
</blockquote>
<ul>
<li>@ConfigurationProperties(prefix = “person”) 是一个注解,用于绑定配置文件中以person为前缀的属性。它通常用在一个被Spring容器管理的Bean中,以将应用程序的配置信息绑定到该Bean的属性上。</li>
<li>Spring Boot将自动扫描使用@Component注释并加载到ApplicationContext上下文中的此类bean,并将配置文件中对应的键值映射到这些属性上。在此示例中,SpringBoot会读取person前缀的属性的名称和值,并将它们分别注入到此类Person 的 name 和 age域上。需要注意的是,在读取属性时必须保证属性名称和字段名称相同。如果出现命名冲突导致无法绑定,可以使@Value(“${person.name}”)实现手动绑定。另外,属性值必须与其数据类型匹配,否则Spring 抛出异常。</li>
<li>@Component 是SpringFramework中的一个基本注解,通常用于标识被Spring容器所管理的Bean。在应用程序扫描包中发现带有@Component注解的bean时,默认会自动实例化并装配,使其变成可供应用程序其他部分使用的对象。</li>
<li>@Autowired 是SpringFramework用于注入bean依赖关系的注解之一。它对类所需要的其他Bean进行会自动的装配,并且负责调用setter方法,构造器或者Fields,以将所需的依赖关系连接到目标Bean中。</li>
<li>@Validated是Spring框架中的一个注解,可以用来验证方法参数、方法返回值和类字段等。该注解基于JSR-303规范的@javax.validation.Valid注解扩展,并结合了Spring框架的特性,能够更好地支持Spring的功能。</li>
</ul>
<blockquote>
<p>@NotNull:被注释的元素必须不为null。<br>@NotEmpty:被注释的字符串必须非空。<br>@NotBlank:被注释的字符串必须非空且不能只包含空格符。<br>@Size:被注释的元素大小必须在指定的范围内。<br>@Digits:被注释的元素必须是一个数字,并且其值的位数必须在指定的范围内。<br>@Past:被注释的元素必须是一个过去的日期或时间。<br>@Future:被注释的元素必须是一个将来的日期或时间。<br>@Max:被注释的元素必须是数字,并且其值小于或等于指定的最大值。<br>@Min:被注释的元素必须是数字,并且其值大于或等于指定的最小值。<br>@Pattern:被注释的字符串必须符合指定的正则表达式。等等</p>
</blockquote>
<p>示例代码:</p>
<pre><code class="java"> // yaml格式文件
person:
name: ss$&#123;random.uuid&#125;
age: $&#123;random.int&#125;
happy: true
birth: 2022/22/1
maps: &#123;k1: ss,k2: ww&#125;
Dog:
name: xiaochen$&#123;person.hello:jello&#125;_xiaoO
age: 22
lists:
- boy
- girl
- ss
spring:
profiles:
active: dev
logging:
level:
root: WARN
org:
springframework: INFO
server:
port: 8080
servlet:
context-path: /demo
myapp:
name: myAppName
version: 1.0.0
以上是Spring Boot应用程序中的一个典型的YAML配置文件,包含了应用程序环境、日志级别、服务器配置以及自定义应用程序参数。其中,每个键值对的“:”后面必须有一个空格,否则会导致语法错误。
</code></pre>
<pre><code class="java">@Component
@ConfigurationProperties(prefix = &quot;person&quot;)
//@PropertySource(value = &quot;&quot;)
@Validated
public class Person &#123;
// @NotBlank(message = &quot;用户名不能为空&quot;)
private String name;
// @Min(value = 0, message = &quot;年龄不能小于0&quot;)
// @Max(value = 120, message = &quot;年龄不能大于120&quot;)
private Integer age;
private Boolean happy;
private Date birth;
private Map&lt;String,Object&gt; maps;
private List&lt;Object&gt; lists;
private Dog dog;
//get\set等忽略
&#125;
&#125;
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Spring Boot集成Security框架</title>
<url>/blog/2023/04/27/java%E5%AD%A6%E4%B9%A012/</url>
<content><![CDATA[<h1 id="Spring-Boot集成Security框架"><a href="#Spring-Boot集成Security框架" class="headerlink" title="Spring Boot集成Security框架"></a>Spring Boot集成Security框架</h1><p>Spring Security是Spring Framework生态系统中一个功能强大且灵活的权限框架,主要用于确保Web应用程序安全。它提供了基于角色、权限等进行授权认证的功能,帮助我们实现用户身份验证、授权和其他的安全性功能。</p>
<blockquote>
<p>主要作用和应用场景:</p>
<p>用户身份认证:它允许您对用户进行身份验证,并跟踪其登录状态、会话或其他安全性属性。</p>
<p>授权策略管理:它允许您控制哪些用户有权进行哪些操作以及如何限制他们的访问。</p>
<p>数据安全性:它提供了标准的密钥管理和加密技术,可以在数据库层面或字段级别上保护敏感数据,保障应用程序的数据安全性。</p>
<p>简化开发过程:Spring Security可与Spring<br>Boot快速集成,并提供许多现成的权限组件和API,简化了开发人员的工作量,降低了出现安全问题的风险。</p>
<p>保护Web API:Spring Security不仅可以保护Web页面的安全性,还能够保护Web<br>API的安全性,并限制哪些客户端已被授权访问。</p>
<p>综上所述,SpringSecurity的主要作用是帮助开发者实现Web应用程序的安全性,包括身份认证、授权管理和数据安全等方面,是一个十分重要的权限框架,广泛应用于各种类型的Web应用程序中。</p>
</blockquote>
<h1 id="Spring-Boot集成Security框架的基本步骤:"><a href="#Spring-Boot集成Security框架的基本步骤:" class="headerlink" title="Spring Boot集成Security框架的基本步骤:"></a>Spring Boot集成Security框架的基本步骤:</h1><h2 id="1-在pom-xml文件中添加spring-boot-starter-security依赖项。"><a href="#1-在pom-xml文件中添加spring-boot-starter-security依赖项。" class="headerlink" title="1.在pom.xml文件中添加spring-boot-starter-security依赖项。"></a>1.在pom.xml文件中添加spring-boot-starter-security依赖项。</h2><pre><code class="bash">&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<h2 id="2-创建一个配置类来配置Security策略。例如:"><a href="#2-创建一个配置类来配置Security策略。例如:" class="headerlink" title="2.创建一个配置类来配置Security策略。例如:"></a>2.创建一个配置类来配置Security策略。例如:</h2><pre><code class="bash">@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter &#123;
@Override
protected void configure(HttpSecurity http) throws Exception &#123;
// 允许所有用户访问&quot;/&quot;&quot;/home&quot;
http.authorizeRequests()
.antMatchers(&quot;/&quot;, &quot;/home&quot;).permitAll()
// 其它资源需要认证后才能访问
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage(&quot;/login&quot;)
.permitAll()
.and()
.logout()
.permitAll();
&#125;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception &#123;
auth.inMemoryAuthentication()
.withUser(&quot;user&quot;).password(&quot;&#123;noop&#125;password&quot;).roles(&quot;USER&quot;);
&#125;
&#125;
</code></pre>
<h2 id="3-创建一个控制器,测试包含经过身份验证的请求处理逻辑:"><a href="#3-创建一个控制器,测试包含经过身份验证的请求处理逻辑:" class="headerlink" title="3.创建一个控制器,测试包含经过身份验证的请求处理逻辑:"></a>3.创建一个控制器,测试包含经过身份验证的请求处理逻辑:</h2><pre><code class="bash">@RestController
public class HomeController &#123;
@GetMapping(&quot;/&quot;)
public String greeting() &#123;
return &quot;Hello, World!&quot;;
&#125;
@GetMapping(&quot;/home&quot;)
public String home() &#123;
return &quot;Welcome Home!&quot;;
&#125;
@GetMapping(&quot;/admin&quot;)
public String admin() &#123;
return &quot;Welcome Admin!&quot;;
&#125;
&#125;
</code></pre>
<h2 id="4-启动Spring-Boot应用程序,然后在浏览器中访问”http-localhost-8080-home-quot-,并使用配置的用户凭据进行身份验证。"><a href="#4-启动Spring-Boot应用程序,然后在浏览器中访问”http-localhost-8080-home-quot-,并使用配置的用户凭据进行身份验证。" class="headerlink" title="4.启动Spring Boot应用程序,然后在浏览器中访问”http://localhost:8080/home&quot;,并使用配置的用户凭据进行身份验证。"></a>4.启动Spring Boot应用程序,然后在浏览器中访问”<a href="http://localhost:8080/home&quot;%EF%BC%8C%E5%B9%B6%E4%BD%BF%E7%94%A8%E9%85%8D%E7%BD%AE%E7%9A%84%E7%94%A8%E6%88%B7%E5%87%AD%E6%8D%AE%E8%BF%9B%E8%A1%8C%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81%E3%80%82">http://localhost:8080/home&quot;,并使用配置的用户凭据进行身份验证。</a></h2>]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Spring Boot集成</title>
<url>/blog/2023/05/05/java%E5%AD%A6%E4%B9%A013/</url>
<content><![CDATA[<h1 id="Spring-Boot集成"><a href="#Spring-Boot集成" class="headerlink" title="Spring Boot集成"></a>Spring Boot集成</h1><h2 id="一、swagger"><a href="#一、swagger" class="headerlink" title="一、swagger"></a>一、swagger</h2><blockquote>
<p>Swagger是一个开源的API文档生成工具,可以通过自动生成API文档、测试API接口等功能来提升团队开发效率。在实际应用中,Swagger可应用于各类Web API的开发中,包括RESTful服务、微服务架构等。对于SpringBoot框架下的应用程序,可以通过引入Swagger相关依赖和配置类,快速地实现API文档和测试功能的生成</p>
</blockquote>
<p>示例如下:</p>
<h3 id="步骤1:pom-xml文件中添加如下依赖:"><a href="#步骤1:pom-xml文件中添加如下依赖:" class="headerlink" title="步骤1:pom.xml文件中添加如下依赖:"></a>步骤1:pom.xml文件中添加如下依赖:</h3><pre><code class="bash">&lt;dependency&gt;
&lt;groupId&gt;io.springfox&lt;/groupId&gt;
&lt;artifactId&gt;springfox-swagger2&lt;/artifactId&gt;
&lt;version&gt;2.9.2&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;io.springfox&lt;/groupId&gt;
&lt;artifactId&gt;springfox-swagger-ui&lt;/artifactId&gt;
&lt;version&gt;2.9.2&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<h3 id="步骤2:增加Swagger2的配置类"><a href="#步骤2:增加Swagger2的配置类" class="headerlink" title="步骤2:增加Swagger2的配置类"></a>步骤2:增加Swagger2的配置类</h3><pre><code class="bash">可以在一个@Configuration注解的类中定义Swagger2的配置,代码如下:
@Configuration
@EnableSwagger2//开启swagger2
public class swaggerConfig &#123;
/**
配置Swagger2相关信息
*/
@Bean
public Docket api(Environment environment) &#123;
//判断是否在开发环境或者测试环境
Profiles profiles = Profiles.of(&quot;dev&quot;, &quot;test&quot;);
boolean acceptsProfiles = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(acceptsProfiles)//表示是否启动swagger
.select()
//指定扫描的包
.apis(RequestHandlerSelectors.basePackage(&quot;com.v1.controller&quot;))
// PathSelectors.any()则表示所有路径都需要被文档化。
.paths(PathSelectors.any())
.build();
&#125;
/**
* 配置API文档相关信息
*/
private ApiInfo apiInfo() &#123;
return new ApiInfoBuilder()
.title(&quot;API文档&quot;)
.description(&quot;供了用户管理系统的所有接口信息&quot;)
.version(&quot;1.0.0&quot;)
.build();
&#125;
&#125;
</code></pre>
<p>有可能出现问题路径不正确,需要再yml文件进行配置:</p>
<pre><code class="bash">spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
</code></pre>
<h2 id="二、异步任务"><a href="#二、异步任务" class="headerlink" title="二、异步任务"></a>二、异步任务</h2><blockquote>
<p>Spring Boot是一个基于Spring框架的开源项目,可以帮助我们快速构建可靠性高的Java应用程序。Spring Boot中的异步和同步操作是指Java方法执行完毕之后是否会立即返回结果</p>
</blockquote>
<p>基于Spring Boot使用异步操作的示例:</p>
<h3 id="步骤一:添加EnableAsync"><a href="#步骤一:添加EnableAsync" class="headerlink" title="步骤一:添加EnableAsync"></a>步骤一:添加EnableAsync</h3><pre><code class="bash">@SpringBootApplication
@EnableAsync//开启异步
@EnableScheduling//开启定时任务
public class SpringbootMybatisApplication &#123;
public static void main(String[] args) &#123; SpringApplication.run(SpringbootMybatisApplication.class, args);
&#125;
&#125;
</code></pre>
<h3 id="步骤二:"><a href="#步骤二:" class="headerlink" title="步骤二:"></a>步骤二:</h3><pre><code class="bash">@Service
public class AsyncService &#123;
private static final Logger logger = LoggerFactory.getLogger(AsyncService.class);
//使用了Spring Boot的异步注解@Async,意味着该方法会在另外一个独立的线程中执行,不会阻塞主线程。
@Async
public void asyncMethod() throws InterruptedException &#123;
logger.info(&quot;异步方法开始执行....&quot;);
Thread.sleep(5000);
logger.info(&quot;异步方法执行完毕....&quot;);
&#125;
&#125;
</code></pre>
<p>步骤三:</p>
<pre><code class="bash"> @Autowired
AsyncService asyncService;
@GetMapping(&quot;/Async&quot;)
public String asyncMethod() throws InterruptedException &#123;
asyncService.asyncMethod();
return &quot;ok&quot;;
</code></pre>
<p>异步方法被定义为asyncMethod(),其中模拟实现了一个5秒钟的耗时操作:Thread.sleep(5000)。当调用该方法时,日志将会输出「异步方法开始执行….」,并在5秒钟后输出「异步方法执行完毕….」</p>
<h2 id="三、redis"><a href="#三、redis" class="headerlink" title="三、redis"></a>三、redis</h2><blockquote>
<p>Redis主要作用是数据缓存、分布式锁、消息队列和计数器/排行榜等功能。它在高性能、可靠性等方面都具有优势,适用于互联网企业和各种大型应用场景。</p>
</blockquote>
<h3 id="步骤1:添加依赖"><a href="#步骤1:添加依赖" class="headerlink" title="步骤1:添加依赖"></a>步骤1:添加依赖</h3><pre><code class="bash">&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<h3 id="步骤2:配置Redis连接信息(application-yml)"><a href="#步骤2:配置Redis连接信息(application-yml)" class="headerlink" title="步骤2:配置Redis连接信息(application.yml)"></a>步骤2:配置Redis连接信息(application.yml)</h3><pre><code class="bash"> # redis 配置
redis:
# 地址
# host:
host: 127.0.0.1
# 端口,默认为6379
port: 6379
# 数据库索引
database: 1
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
</code></pre>
<h3 id="步骤3-使用RedisTemplate或StringRedisTemplate进行操作"><a href="#步骤3-使用RedisTemplate或StringRedisTemplate进行操作" class="headerlink" title="步骤3:使用RedisTemplate或StringRedisTemplate进行操作"></a>步骤3:使用RedisTemplate或StringRedisTemplate进行操作</h3><pre><code class="bash">@Component
public class RedisCache &#123;
@Resource
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public &lt;T&gt; void setCacheObject(final String key, final T value) &#123;
redisTemplate.opsForValue().set(key, value);
&#125;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public &lt;T&gt; void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) &#123;
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
&#125;
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) &#123;
return expire(key, timeout, TimeUnit.SECONDS);
&#125;
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) &#123;
Boolean isTrue =false;
try &#123;
isTrue = redisTemplate.expire(key, timeout, unit);
&#125;catch (Exception e)&#123;
e.printStackTrace();
throw new BusinessException(ErrorCodeEnum.REDIS_CONNECT_FAILED);
&#125;
if(StringUtils.isNull(isTrue))&#123;
isTrue=false;
&#125;
return isTrue;
&#125;
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public &lt;T&gt; T getCacheObject(final String key) &#123;
T t =null;
try &#123;
ValueOperations&lt;String, T&gt; operation = redisTemplate.opsForValue();
t = operation.get(key);
&#125;catch (Exception e)&#123;
throw new BusinessException(ErrorCodeEnum.REDIS_CONNECT_FAILED);
&#125;
return t;
&#125;
/**
* 删除单个对象
*/
public void deleteObject(final String key) &#123;
try &#123;
redisTemplate.delete(key);
&#125;catch (Exception e)&#123;
e.printStackTrace();
throw new BusinessException(ErrorCodeEnum.REDIS_CONNECT_FAILED);
&#125;
&#125;
/**
* 删除集合对象
*
* @param collection 多个对象
*/
public void deleteObject(final Collection collection) &#123;
try &#123;
redisTemplate.delete(collection);
&#125;catch (Exception e)&#123;
e.printStackTrace();
throw new BusinessException(ErrorCodeEnum.REDIS_CONNECT_FAILED);
&#125;
&#125;
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public &lt;T&gt; long setCacheList(final String key, final List&lt;T&gt; dataList) &#123;
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
&#125;
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public &lt;T&gt; List&lt;T&gt; getCacheList(final String key) &#123;
return redisTemplate.opsForList().range(key, 0, -1);
&#125;
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public &lt;T&gt; BoundSetOperations&lt;String, T&gt; setCacheSet(final String key, final Set&lt;T&gt; dataSet) &#123;
BoundSetOperations&lt;String, T&gt; setOperation = redisTemplate.boundSetOps(key);
Iterator&lt;T&gt; it = dataSet.iterator();
while (it.hasNext()) &#123;
setOperation.add(it.next());
&#125;
return setOperation;
&#125;
/**
* 获得缓存的set
*
* @param key
* @return
*/
public &lt;T&gt; Set&lt;T&gt; getCacheSet(final String key) &#123;
return redisTemplate.opsForSet().members(key);
&#125;
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public &lt;T&gt; void setCacheMap(final String key, final Map&lt;String, T&gt; dataMap) &#123;
if (dataMap != null) &#123;
redisTemplate.opsForHash().putAll(key, dataMap);
&#125;
&#125;
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public &lt;T&gt; Map&lt;String, T&gt; getCacheMap(final String key) &#123;
return redisTemplate.opsForHash().entries(key);
&#125;
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection&lt;String&gt; keys(final String pattern) &#123;
return redisTemplate.keys(pattern);
&#125;
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>最近一段时间的感悟</title>
<url>/blog/2023/05/20/java%E9%A1%B9%E7%9B%AE%E6%80%BB%E7%BB%93/</url>
<content><![CDATA[<p>好久没写博客了,这段时间我一直在忙于写一个基础框架以巩固自己的基础知识。对于这个小项目,我选择使用springboot作为基础,同时还运用了许多其他的前端和后端技术。其中,我用到了redis来进行登录验证,通过jwt认证、动态路由以及密码加密等技术来提高项目的安全性。另外,在接口编写过程中,我使用swagger来编写文档,这无疑能够提升项目的可维护性。<br>Spring项目还用到MyBatis-Plus这个非常流行的ORM框架。相对于原生的Mybatis框架,MyBatis-Plus不仅能够简化开发流程,同时还提供了很多实用的功能,例如基于注解的自动代码生成,方便的分页查询,强大的条件构造器等等。<br>当然,任何项目都有其不足之处。由于我不擅长vue,因此前端代码基本上参考了其他博主的作品,这也导致了与后端接口调试过程中出现了一些问题。不过这并没有太大关系,实际上,背诵技术栈不如理解逻辑、并能够开发出实际业务的代码更为重要!</p>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>IDEA新建springboot项目时不能导入maven依赖(右边没有maven窗口)</title>
<url>/blog/2023/05/26/java%E5%AD%A6%E4%B9%A0%E8%B8%A9%E5%9D%911/</url>
<content><![CDATA[<p>1.问题:在本地下载了一个springboot项目,但是用idea打开后发现不能导入maven依赖,并且在IDEA右边也没有出现maven窗口,如下图<br><img src="https://foruda.gitee.com/images/1685065731398237909/9927a249_7715734.png" alt="在这里插入图片描述"><br> 2.解决方法:右键点击pom.xml文件,然后点击“Add as Maven Project”即可<br><img src="https://foruda.gitee.com/images/1685065596969777042/99cd12c4_7715734.png" alt="在这里插入图片描述"></p>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
<entry>
<title>Spring Boot集成Elasticsearch</title>
<url>/blog/2023/06/30/java%E5%AD%A6%E4%B9%A014/</url>
<content><![CDATA[<h1 id="Elasticsearch集成springboot项目"><a href="#Elasticsearch集成springboot项目" class="headerlink" title="Elasticsearch集成springboot项目"></a>Elasticsearch集成springboot项目</h1><blockquote>
<p>lasticsearch是一个开源的分布式搜索和分析引擎,主要用于实时搜索、存储和分析大规模数据。它基于Apache<br>Lucene引擎构建,具备高度可伸缩性和强大的全文搜索功能。</p>
</blockquote>
<h2 id="1-安装"><a href="#1-安装" class="headerlink" title="1.安装"></a>1.安装</h2><p>docker直接安装运行(需要放行5601和9200端口)<br><img src="https://foruda.gitee.com/images/1688030625534265611/de02f14b_7715734.png" alt="在这里插入图片描述"></p>
<p><em>1、idea项目导入依赖</em></p>
<pre><code class="bash"> &lt;dependency&gt;
&lt;groupId&gt;org.elasticsearch.client&lt;/groupId&gt;
&lt;artifactId&gt;elasticsearch-rest-high-level-client&lt;/artifactId&gt;
&lt;version&gt;7.4.2&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>2、<em>编写配置</em></p>
<pre><code class="bash">@Configuration
public class MallElasticSearchConfig &#123;
public static final RequestOptions COMMON_OPTIONS;
static &#123;
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
// builder.addHeader(&quot;Authorization&quot;, &quot;Bearer &quot; + TOKEN);
// builder.setHttpAsyncResponseConsumerFactory(
// new HttpAsyncResponseConsumerFactory
// .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
COMMON_OPTIONS = builder.build();
&#125;
@Bean
public RestHighLevelClient esRestClient()&#123;
// RestHighLevelClient client = new RestHighLevelClient(
// RestClient.builder(new HttpHost(&quot;175.178.182.120&quot;, 9200, &quot;http&quot;)));
RestClientBuilder builder =null;
builder= RestClient.builder(new HttpHost(&quot;127.0.0.1&quot;, 9200, &quot;http&quot;));
RestHighLevelClient client =new RestHighLevelClient(builder);
return client;
&#125;
&#125;
或配置application.yaml
spring:
application:
name: mall-search
cloud:
nacos:
discovery:
server-addr: 192.168.163.131:8848
</code></pre>
<p>3、测试</p>
<pre><code class="bash"> @Autowired
private RestHighLevelClient client;
@Test
public void searchData() throws IOException &#123;
//1、创建索引请求
SearchRequest searchRequest = new SearchRequest();
//指定索引
searchRequest.indices(&quot;bank&quot;);
//指定SDL,检索条件
SearchSourceBuilder searchRequestBuilder = new SearchSourceBuilder();
searchRequestBuilder.query(QueryBuilders.matchQuery(&quot;address&quot;,&quot;mill&quot;));
System.out.printf( searchRequestBuilder.toString());
searchRequest.source(searchRequestBuilder);
//2、执行检索
SearchResponse search = client.search(searchRequest, MallElasticSearchConfig.COMMON_OPTIONS);
//3、分析结果
System.out.println(search.toString());
//输出结果
&#123;&quot;query&quot;:&#123;&quot;match&quot;:&#123;&quot;address&quot;:&#123;&quot;query&quot;:&quot;mill&quot;,&quot;operator&quot;:&quot;OR&quot;,&quot;prefix_length&quot;:0,&quot;max_expansions&quot;:50,&quot;fuzzy_transpositions&quot;:true,&quot;lenient&quot;:false,&quot;zero_terms_query&quot;:&quot;NONE&quot;,&quot;auto_generate_synonyms_phrase_query&quot;:true,&quot;boost&quot;:1.0&#125;&#125;&#125;&#125;&#123;&quot;took&quot;:15,&quot;timed_out&quot;:false,&quot;_shards&quot;:&#123;&quot;total&quot;:1,&quot;successful&quot;:1,&quot;skipped&quot;:0,&quot;failed&quot;:0&#125;,&quot;hits&quot;:&#123;&quot;total&quot;:&#125;&#125;&#125;
&#125;
</code></pre>
<p>以下是Elasticsearch的简单使用方法,可以根据实际业务要求调整</p>
<pre><code class="bash">
GET /bank/_search
&#123;
&quot;query&quot;: &#123;
&quot;match_all&quot;: &#123;&#125;
&#125;,
&quot;sort&quot;: [
&#123;
&quot;account_number&quot;: &quot;asc&quot;
&#125;
]
&#125;
# query 查询条件
# sort 排序条件
GET bank/_search
&#123;
&quot;query&quot;: &#123;
&quot;match_all&quot;: &#123;&#125;
&#125;,
&quot;from&quot;: 0,
&quot;size&quot;: 5,
&quot;sort&quot;: [
&#123;
&quot;account_number&quot;: &#123;
&quot;order&quot;: &quot;desc&quot;
&#125;,
&quot;balance&quot;: &#123;
&quot;order&quot;: &quot;asc&quot;
&#125;
&#125;
]
&#125;
# match_all 查询类型【代表查询所有的所有】,es中可以在query中组合非常多的查询类型完成复杂查询;
# from+size 限定,完成分页功能;从第几条数据开始,每页有多少数据
# sort 排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准;
GET bank/_search
&#123;
&quot;query&quot;: &#123;
&quot;match&quot;: &#123;
&quot;account_number&quot;: 20
&#125;
&#125;
&#125;
# 查找匹配 account_number 为 20 的数据 非文本推荐使用 term
GET bank/_search
&#123;
&quot;query&quot;: &#123;
&quot;multi_match&quot;: &#123;
&quot;query&quot;: &quot;mill&quot;,
&quot;fields&quot;: [
&quot;city&quot;,
&quot;address&quot;
]
&#125;
&#125;
&#125;
# 检索 city 或 address 匹配包含 mill 的数据,会对查询条件分词
GET bank/_search
&#123;
&quot;query&quot;: &#123;
&quot;bool&quot;: &#123;
&quot;must&quot;: [
&#123;
&quot;match&quot;: &#123;
&quot;address&quot;: &quot;mill&quot;
&#125;
&#125;
],
&quot;filter&quot;: &#123;
&quot;range&quot;: &#123;
&quot;age&quot;: &#123;
&quot;gte&quot;: &quot;10&quot;,
&quot;lte&quot;: &quot;30&quot;
&#125;
&#125;
&#125;
&#125;
&#125;
&#125;
# 这里先是查询所有匹配 address 包含 mill 的文档,
# 然后再根据 10000&lt;=balance&lt;=20000 进行过滤查询结果
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Spring Boot集成redis、SpringCache</title>
<url>/blog/2023/07/04/java%E5%AD%A6%E4%B9%A015/</url>
<content><![CDATA[<h1 id="redis集成springboot项目"><a href="#redis集成springboot项目" class="headerlink" title="redis集成springboot项目"></a>redis集成springboot项目</h1><h2 id="缓存-redis"><a href="#缓存-redis" class="headerlink" title="缓存 - redis"></a>缓存 - redis</h2><p>虚拟机初始化时已安装 Redis。</p>
<pre><code class="bash">&lt;!-- redis --&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><pre><code class="bash">spring:
redis:
host: 192.168.18.211
port: 6379
</code></pre>
<h2 id="业务中加入缓存"><a href="#业务中加入缓存" class="headerlink" title="业务中加入缓存"></a>业务中加入缓存</h2><pre><code class="bash">@Autowired
StringRedisTemplate redisTemplate;
@Override
public Map&lt;String, List&lt;Catalogs2Vo&gt;&gt; getCatalogJson() &#123;
// 1.从缓存中读取分类信息
String catalogJSON = redisTemplate.opsForValue().get(&quot;catalogJSON&quot;);
if (StringUtils.isEmpty(catalogJSON)) &#123;
// 2. 缓存中没有,查询数据库
Map&lt;String, List&lt;Catalogs2Vo&gt;&gt; catalogJsonFromDB = getCatalogJsonFromDB();
// 3. 查询到的数据存放到缓存中,将对象转成 JSON 存储
redisTemplate.opsForValue().set(&quot;catalogJSON&quot;, JSON.toJSONString(catalogJsonFromDB));
return catalogJsonFromDB;
&#125;
return JSON.parseObject(catalogJSON, new TypeReference&lt;Map&lt;String, List&lt;Catalogs2Vo&gt;&gt;&gt;()&#123;&#125;);
&#125;
/**
* 加缓存前,只读取数据库的操作
*
* @return
*/
public Map&lt;String, List&lt;Catalogs2Vo&gt;&gt; getCatalogJsonFromDB() &#123;
System.out.println(&quot;查询了数据库&quot;);
// 性能优化:将数据库的多次查询变为一次
List&lt;CategoryEntity&gt; selectList = this.baseMapper.selectList(null);
//1、查出所有分类
//1、1)查出所有一级分类
List&lt;CategoryEntity&gt; level1Categories = getParentCid(selectList, 0L);
//封装数据
Map&lt;String, List&lt;Catalogs2Vo&gt;&gt; parentCid = level1Categories.stream().collect(Collectors.toMap(k -&gt; k.getCatId().toString(), v -&gt; &#123;
//1、每一个的一级分类,查到这个一级分类的二级分类
List&lt;CategoryEntity&gt; categoryEntities = getParentCid(selectList, v.getCatId());
//2、封装上面的结果
List&lt;Catalogs2Vo&gt; catalogs2Vos = null;
if (categoryEntities != null) &#123;
catalogs2Vos = categoryEntities.stream().map(l2 -&gt; &#123;
Catalogs2Vo catalogs2Vo = new Catalogs2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName().toString());
//1、找当前二级分类的三级分类封装成vo
List&lt;CategoryEntity&gt; level3Catelog = getParentCid(selectList, l2.getCatId());
if (level3Catelog != null) &#123;
List&lt;Catalogs2Vo.Category3Vo&gt; category3Vos = level3Catelog.stream().map(l3 -&gt; &#123;
//2、封装成指定格式
Catalogs2Vo.Category3Vo category3Vo = new Catalogs2Vo.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return category3Vo;
&#125;).collect(Collectors.toList());
catalogs2Vo.setCatalog3List(category3Vos);
&#125;
return catalogs2Vo;
&#125;).collect(Collectors.toList());
&#125;
return catalogs2Vos;
&#125;));
return parentCid;
&#125;
</code></pre>
<h2 id="缓存穿透"><a href="#缓存穿透" class="headerlink" title="缓存穿透"></a>缓存穿透</h2><blockquote>
<p>缓存穿透是指 查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的 null<br>写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。 在流量大时,可能 DB 就挂掉了,要是有人利用不存在的<br>key 频繁攻击我们的应用,这就是漏洞。 解决方法:缓存空结果、并且设置短的过期时间。</p>
</blockquote>
<h2 id="缓存雪崩"><a href="#缓存雪崩" class="headerlink" title="缓存雪崩"></a>缓存雪崩</h2><blockquote>
<p>缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。<br>解决方法:原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。</p>
</blockquote>
<h2 id="缓存击穿"><a href="#缓存击穿" class="headerlink" title="缓存击穿"></a>缓存击穿</h2><blockquote>
<p>对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。<br>这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所有对这个 key 的数据查询都落到<br>db,我们称为缓存击穿。<br>解决方法:加锁。大量并发只让一个人去查,其他人等待,查到之后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去查数据库。</p>
</blockquote>
<h1 id="缓存-SpringCache"><a href="#缓存-SpringCache" class="headerlink" title="缓存 - SpringCache"></a>缓存 - SpringCache</h1><h2 id="引入依赖"><a href="#引入依赖" class="headerlink" title="引入依赖"></a>引入依赖</h2><pre><code class="bash">&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-cache&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<h2 id="添加配置"><a href="#添加配置" class="headerlink" title="添加配置"></a>添加配置</h2><pre><code class="bash">spring.cache.type=redis
#spring.cache.cache-names=qq,毫秒为单位
spring.cache.redis.time-to-live=3600000
#如果指定了前缀就用我们指定的前缀,如果没有就默认使用缓存的名字作为前缀
#spring.cache.redis.key-prefix=CACHE_
spring.cache.redis.use-key-prefix=true
#是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
</code></pre>
<pre><code class="bash">@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig &#123;
/**
* 配置文件的配置没有用上
* 1. 原来和配置文件绑定的配置类为:@ConfigurationProperties(prefix = &quot;spring.cache&quot;)
* public class CacheProperties
* &lt;p&gt;
* 2. 要让他生效,要加上 @EnableConfigurationProperties(CacheProperties.class)
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) &#123;
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// config = config.entryTtl();
//序列化机制,将结果转为json格式返回
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中所有的配置都生效
if (redisProperties.getTimeToLive() != null) &#123;
config = config.entryTtl(redisProperties.getTimeToLive());
&#125;
if (redisProperties.getKeyPrefix() != null) &#123;
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
&#125;
if (!redisProperties.isCacheNullValues()) &#123;
config = config.disableCachingNullValues();
&#125;
if (!redisProperties.isUseKeyPrefix()) &#123;
config = config.disableKeyPrefix();
&#125;
return config;
&#125;
&#125;
</code></pre>
<blockquote>
<p>常用注解<br> ● @Cacheable :触发将数据保存到缓存的操作;<br> ● @CacheEvict : 触发将数据从缓存删除的操作;<br> ●@CachePut :不影响方法执行更新缓存;<br> ● @Cacheing:组合以上多个操作;<br> ● @CacheConfig:在类级别共享缓存的相同配置;</p>
</blockquote>
<pre><code class="bash"> /**
* 1、每一个需要缓存的数据我们都来指定要放到那个名字的缓存。【缓存的分区(按照业务类型分)】
* 2、@Cacheable 代表当前方法的结果需要缓存,如果缓存中有,方法都不用调用,如果缓存中没有,会调用方法。最后将方法的结果放入缓存
* 3、默认行为
* 3.1 如果缓存中有,方法不再调用
* 3.2 key是默认生成的:缓存的名字::SimpleKey::[](自动生成key值)
* 3.3 缓存的value值,默认使用jdk序列化机制,将序列化的数据存到redis中
* 3.4 默认时间是 -1:
*
* 自定义操作:key的生成
* 1. 指定生成缓存的key:key属性指定,接收一个 SpEl
* 2. 指定缓存的数据的存活时间:配置文档中修改存活时间 ttl
* 3. 将数据保存为json格式: 自定义配置类 MyCacheManager
* &lt;p&gt;
* 4、Spring-Cache的不足之处:
* 1)、读模式
* 缓存穿透:查询一个null数据。解决方案:缓存空数据
* 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;使用sync = true来解决击穿问题
* 缓存雪崩:大量的key同时过期。解决:加随机时间。加上过期时间
* 2)、写模式:(缓存与数据库一致)
* 1)、读写加锁。
* 2)、引入Canal,感知到MySQL的更新去更新Redis
* 3)、读多写多,直接去数据库查询就行
* &lt;p&gt;
* 总结:
* 常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):写模式(只要缓存的数据有过期时间就足够了)
* 特殊数据:特殊设计
* &lt;p&gt;
* 原理:
* CacheManager(RedisCacheManager)-&gt;Cache(RedisCache)-&gt;Cache负责缓存的读写
*
* @return
*/
@Cacheable (value = &#123;&quot;category&quot;&#125;,key = &quot;#root.method.name&quot;)//代表当前方法的结果需要缓存,如果缓存中有,方法不用调用。如果缓存中没有,会调用方法,最后将方法结果缓存
@Override
public List&lt;CategoryEntity&gt; getLevel1Categorys() &#123;
System.out.println(&quot;getLevel1Categorys........&quot;);
long l = System.currentTimeMillis();
List&lt;CategoryEntity&gt; categoryEntities = this.baseMapper.selectList(
new QueryWrapper&lt;CategoryEntity&gt;().eq(&quot;parent_cid&quot;, 0));
System.out.println(&quot;消耗时间:&quot; + (System.currentTimeMillis() - l));
return categoryEntities;
&#125;
/**
* 级联更新所有关联的数据
*
* @CacheEvict:失效模式
* @CachePut:双写模式,需要有返回值
* 1、同时进行多种缓存操作:@Caching
* 2、指定删除某个分区下的所有数据 @CacheEvict(value = &quot;category&quot;,allEntries = true)
* 3、存储同一类型的数据,都可以指定为同一分区
* @param category
*/
// @Caching(evict =&#123;
// @CacheEvict(value = &quot;category&quot;, key = &quot;&#39;getLevel1Categorys&#39;&quot;)
// @CacheEvict(value = &quot;category&quot;, key = &quot;&#39;getCatalogJson&#39;&quot;)
//
// &#125;)
@CacheEvict(value = &quot;category&quot;,allEntries = true)
@Transactional
@Override
public void updateCascade(CategoryEntity category) &#123;
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
&#125;
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>RabbitMQ</title>
<url>/blog/2023/07/17/java%E5%AD%A6%E4%B9%A016/</url>
<content><![CDATA[<h1 id="RabbitMQ"><a href="#RabbitMQ" class="headerlink" title="RabbitMQ"></a>RabbitMQ</h1><blockquote>
<p>是一种开源的消息队列中间件,它基于AMQP(Advanced Message QueuingProtocol)协议,用于在应用程序之间传递消息。下面是一个简单的例子,以帮助新手理解RabbitMQ的基本原理:</p>
<p>假设有一个在线商城系统,包括商品服务和订单服务。商品服务负责管理商品信息,订单服务负责处理用户的订单。为了实现系统解耦和提高可伸缩性,我们可以使用RabbitMQ作为商品服务和订单服务之间的消息传递中间件。<br>安装和配置RabbitMQ:首先,我们需要在服务器上安装并配置RabbitMQ。这是一个独立的服务,商品服务和订单服务都要连接到这个RabbitMQ服务。<br>定义消息格式:我们定义一个名为”NewOrder”的消息格式,用于在订单服务中创建新订单时发送给商品服务。消息格式可以包含订单的相关信息,比如订单号、商品ID、数量等。<br>发送消息:当有用户下订单时,订单服务会将”NewOrder”消息发送给RabbitMQ服务。订单服务通过指定消息的目标队列(Queue)来发送消息,比如”new_order_queue”。<br>接收消息:商品服务会连接到RabbitMQ服务,并注册对”new_order_queue”队列的订阅。当RabbitMQ接收到”NewOrder”消息时,它会将该消息放入”new_order_queue”队列中等待消费。<br>处理消息:商品服务中有一个单独的线程或进程,一直监听”new_order_queue”队列。当队列中有消息时,商品服务就会接收并处理该消息。比如,它可以根据订单信息更新库存数量,生成发货单等操作。<br>通过RabbitMQ,商品服务和订单服务之间的通信实现了解耦。订单服务只需要发送消息到RabbitMQ,而不需要关心商品服务的具体实现。而商品服务则可以通过订阅相应的队列,灵活地处理和消费消息。</p>
</blockquote>
<h1 id="RabbitMQ的基本原理包括以下几个要点:"><a href="#RabbitMQ的基本原理包括以下几个要点:" class="headerlink" title="RabbitMQ的基本原理包括以下几个要点:"></a>RabbitMQ的基本原理包括以下几个要点:</h1><p>消息的生产者将消息发送到队列中,而不是直接发送给消费者。</p>
<blockquote>
<p>消息队列充当了生产者和消费者之间的缓冲区,确保消息能够可靠地传递。 消费者通过订阅队列并从队列中接收消息来消费。 RabbitMQ负责管理队列和消息的传递,确保消息达到消费者。<br>这种模式使得生产者和消费者可以异步进行,并且不需要实时连接。生产者可以继续生产消息,而消费者可以按照自己的速度来消费消息,提高了系统的弹性和伸缩性。 需要注意的是,这只是RabbitMQ的基本原理,实际使用中还可以应用更多高级特性,如消息确认、消息持久化、消息路由等,以满足更复杂的业务需求。</p>
</blockquote>
<h1 id="Docker-安装-RabbitMQ"><a href="#Docker-安装-RabbitMQ" class="headerlink" title="Docker 安装 RabbitMQ"></a>Docker 安装 RabbitMQ</h1><pre><code class="bash">docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
</code></pre>
<p>镜像中没有RabbitMQ贼默认下载最新版本</p>
<blockquote>
<p>4369,25672(Erlang发现&amp;集群端口)<br>5672,5671(AMQP端口)<br>15672 (web管理后台端口)<br>61613,61614(STOMP协议端口)<br>1883,8883(MQTT协议端口)</p>
</blockquote>
<h1 id="Springbooot整合RabbitMQ"><a href="#Springbooot整合RabbitMQ" class="headerlink" title="Springbooot整合RabbitMQ"></a>Springbooot整合RabbitMQ</h1><h3 id="1、导入pom文件"><a href="#1、导入pom文件" class="headerlink" title="1、导入pom文件"></a>1、导入pom文件</h3><pre><code class="bash"> &lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-amqp&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<h3 id="2、开启MQ服务"><a href="#2、开启MQ服务" class="headerlink" title="2、开启MQ服务"></a>2、开启MQ服务</h3><pre><code class="bash">配置文件写入mqhost地址等信息,账号密码默认写入(其他根据业务实际)
spring.rabbitmq.host=192.168.18.211
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
主配置类添加开启:
@EnableRabbit
</code></pre>
<h3 id="3、测试使用"><a href="#3、测试使用" class="headerlink" title="3、测试使用"></a>3、测试使用</h3><pre><code class="bash">
@Autowired
AmqpAdmin amqpAdmin;
@Autowired
RabbitTemplate rabbitTemplate;
//创建了一个名为&quot;hello-java-change&quot;的Direct Exchange,并通过amqpAdmin.declareExchange()方法声明了该交换机。
@Test
public void creatChange() &#123;
DirectExchange directExchange = new DirectExchange(&quot;hello-java-change&quot;, true, false);
amqpAdmin.declareExchange(directExchange);
log.info(&quot;交换机创建完成&quot;,&quot;java&quot;);
&#125;
//创建了一个名为&quot;hello-java-queue&quot;的队列,并通过amqpAdmin.declareQueue()方法声明了该队列。
@Test
public void creatQueue() &#123;
Queue queue = new Queue(&quot;hello-java-queue&quot;,true,false,false);
amqpAdmin.declareQueue(queue);
log.info(&quot;队列创建完成&quot;,&quot;java-queue&quot;);
&#125;
//创建了一个绑定关系,将队列&quot;hello-java-queue&quot;绑定到交换机&quot;hello-java-change&quot;上,绑定的路由键是&quot;hello.java&quot;
@Test
public void creatChangeQueue() &#123;
Binding binding = new Binding(&quot;hello-java-queue&quot;,
Binding.DestinationType.QUEUE,
&quot;hello-java-change&quot;,
&quot;hello.java&quot;,null);
amqpAdmin.declareBinding(binding);
log.info(&quot;Binding创建完成&quot;,&quot;java-binding&quot;);
&#125;
//使用rabbitTemplate.convertAndSend()方法将消息发送到名为&quot;hello-java-change&quot;的交换机,并指定了路由键为&quot;hello.java&quot;。消息的内容是reasonEntity对象
@Test
public void sendMessage() &#123;
//1、发送消息
OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
reasonEntity.setId(1L);
reasonEntity.setCreateTime(new Date());
reasonEntity.setName(&quot;thy&quot;);
//1、发送的消息要求使用对象必须实现序列化Serializable
//2、
String s = &quot;hello World&quot;;
rabbitTemplate.convertAndSend(&quot;hello-java-change&quot;,&quot;hello.java&quot;,reasonEntity);
log.info(&quot;消息发送完成&quot;,reasonEntity);
&#125;
//通过设置@RabbitListener注解的queues属性为&#123;&quot;hello-java-queue&quot;&#125;,表示该方法会监听名为&quot;hello-java-queue&quot;的队列。
当有消息到达该队列时,receiveMessage()方法会被自动调用,并将消息作为参数传入。在方法内部,你打印了接收到的消息内容和类型。
@RabbitListener(queues = &#123;&quot;hello-java-queue&quot;&#125;)
public void recieveMessage(Object message)&#123;
System.out.println(&quot;接受内容为:&quot;+message+&quot;==》类型&quot;+message);
&#125;
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>webSocket</title>
<url>/blog/2023/08/09/java%E5%AD%A6%E4%B9%A017/</url>
<content><![CDATA[<h1 id="webSocket"><a href="#webSocket" class="headerlink" title="webSocket"></a>webSocket</h1><blockquote>
<p>WebSocket 是一种在 Web 应用程序中实现双向通信的协议。它提供了一个持久化的连接,允许服务器主动向客户端推送数据,而无需客户端发起请求。WebSocket旨在解决传统的 HTTP 请求-响应模型无法实时更新数据的限制。 与传统的 HTTP 请求-响应模型不同,WebSocket在建立连接之后,客户端和服务器之间可以进行双向通信。这意味着服务器可以直接向客户端发送消息,并且客户端也可以向服务器发送消息,而不需要每次都重新建立连接。<br>总之,WebSocket 是一种强大的协议,使得 Web 应用程序能够实现实时、双向的通信,为构建实时应用提供了便利。</p>
</blockquote>
<p>1、项目中引入maven</p>
<pre><code class="bash">&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-websocket&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<p>2、注册配置类</p>
<pre><code class="bash">/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration &#123;
@Bean
public ServerEndpointExporter serverEndpointExporter() &#123;
return new ServerEndpointExporter();
&#125;
&#125;
</code></pre>
<p>3、webSocket服务</p>
<pre><code class="bash">
/**
* WebSocket服务
*/
@Component
@ServerEndpoint(&quot;/ws/&#123;sid&#125;&quot;)
public class WebSocketServer &#123;
//存放会话对象
private static Map&lt;String, Session&gt; sessionMap = new HashMap();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam(&quot;sid&quot;) String sid) &#123;
System.out.println(&quot;客户端:&quot; + sid + &quot;建立连接&quot;);
sessionMap.put(sid, session);
&#125;
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam(&quot;sid&quot;) String sid) &#123;
System.out.println(&quot;收到来自客户端:&quot; + sid + &quot;的信息:&quot; + message);
&#125;
/**
* 连接关闭调用的方法
*
* @param sid
*/
@OnClose
public void onClose(@PathParam(&quot;sid&quot;) String sid) &#123;
System.out.println(&quot;连接断开:&quot; + sid);
sessionMap.remove(sid);
&#125;
/**
* 群发
*
* @param message
*/
public void sendToAllClient(String message) &#123;
Collection&lt;Session&gt; sessions = sessionMap.values();
for (Session session : sessions) &#123;
try &#123;
//服务器向客户端发送消息
session.getBasicRemote().sendText(message);
&#125; catch (Exception e) &#123;
e.printStackTrace();
&#125;
&#125;
&#125;
&#125;
</code></pre>
<p>4、测试</p>
<pre><code class="bash">@Component
public class WebSocketTask &#123;
@Autowired
private WebSocketServer webSocketServer;
/**
* 通过WebSocket每隔5秒向客户端发送消息
*/
@Scheduled(cron = &quot;0/5 * * * * ?&quot;)
public void sendMessageToClient() &#123;
webSocketServer.sendToAllClient(&quot;这是来自服务端的消息:&quot; + DateTimeFormatter.ofPattern(&quot;HH:mm:ss&quot;).format(LocalDateTime.now()));
&#125;
&#125;
//需要配置前端页面请求使用
&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;title&gt;WebSocket Demo&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;input id=&quot;text&quot; type=&quot;text&quot; /&gt;
&lt;button onclick=&quot;send()&quot;&gt;发送消息&lt;/button&gt;
&lt;button onclick=&quot;closeWebSocket()&quot;&gt;关闭连接&lt;/button&gt;
&lt;div id=&quot;message&quot;&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
var websocket = null;
var clientId = Math.random().toString(36).substr(2);
//判断当前浏览器是否支持WebSocket
if(&#39;WebSocket&#39; in window)&#123;
//连接WebSocket节点
websocket = new WebSocket(&quot;ws://localhost:8080/ws/&quot;+clientId);
&#125;
else&#123;
alert(&#39;Not support websocket&#39;)
&#125;
//连接发生错误的回调方法
websocket.onerror = function()&#123;
setMessageInnerHTML(&quot;error&quot;);
&#125;;
//连接成功建立的回调方法
websocket.onopen = function()&#123;
setMessageInnerHTML(&quot;连接成功&quot;);
&#125;
//接收到消息的回调方法
websocket.onmessage = function(event)&#123;
setMessageInnerHTML(event.data);
&#125;
//连接关闭的回调方法
websocket.onclose = function()&#123;
setMessageInnerHTML(&quot;close&quot;);
&#125;
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function()&#123;
websocket.close();
&#125;
//将消息显示在网页上
function setMessageInnerHTML(innerHTML)&#123;
document.getElementById(&#39;message&#39;).innerHTML += innerHTML + &#39;&lt;br/&gt;&#39;;
&#125;
//发送消息
function send()&#123;
var message = document.getElementById(&#39;text&#39;).value;
websocket.send(message);
&#125;
//关闭连接
function closeWebSocket() &#123;
websocket.close();
&#125;
&lt;/script&gt;
&lt;/html&gt;
</code></pre>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>社区版本idea出现的debug测试</title>
<url>/blog/2023/08/30/java%E5%AD%A6%E4%B9%A0%E8%B8%A9%E5%9D%912/</url>
<content><![CDATA[<h2 id="社区版本idea出现的debug测试"><a href="#社区版本idea出现的debug测试" class="headerlink" title="社区版本idea出现的debug测试"></a>社区版本idea出现的debug测试</h2><blockquote>
<p><strong>ERROR: transport library not found: dt_socket ERROR: JDWP Transport dt_socket failed to initialize, TRANSPORT_LOAD(509) JDWP exit error<br>AGENT_ERROR_TRANSPORT_LOAD(196): No transports initialized<br>[debugInit.c:750] FATAL ERROR in native method: JDWP No transports<br>initialized, jvmtiError=AGENT_ERROR_TRANSPORT_LOAD(196) Process<br>finished with exit code 1</strong></p>
</blockquote>
<h2 id="原因分析"><a href="#原因分析" class="headerlink" title="原因分析"></a>原因分析</h2><p>后期在idea出现问题后,JAVA_HOME配置过程中,jre环境与jdk环境设置不一样,导致debug测试出现问题</p>
<h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>调配jre环境变量,就可以尝试的再次进行debug测试,项目配置要与本机环境变量一致<br><img src="https://foruda.gitee.com/images/1693407905187308150/fb0deded_7715734.png" alt="在这里插入图片描述"></p>
<p><img src="https://foruda.gitee.com/images/1693407951295657629/7e4bc607_7715734.png" alt="在这里插入图片描述"></p>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>踩坑</tag>
</tags>
</entry>
<entry>
<title>大文件上传(断点续传)</title>
<url>/blog/2023/09/05/java%E5%AD%A6%E4%B9%A018/</url>
<content><![CDATA[<h1 id="断点续传"><a href="#断点续传" class="headerlink" title="断点续传"></a>断点续传</h1><h2 id="什么是断点续传"><a href="#什么是断点续传" class="headerlink" title="什么是断点续传"></a>什么是断点续传</h2><p>通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传需求。HTTP协议本身对上传文件大小没有限制,但是客户的网络环境之类、电脑硬件环境等参差不齐,如果一个大文件快上传完了,但是突然断网了,没有上传完成,需要客户重新上传,那么用户体验就非常差。所以对于大文件上传的最基本要求就是断点续传<br>流程如下</p>
<ol>
<li>前端上传前先把文件分成块</li>
<li>一块一块的上传,上传中断后重新上传。已上传的分块则不用再上传</li>
<li>各分块上传完成后,在服务端合并文件<h2 id="分块与合并测试"><a href="#分块与合并测试" class="headerlink" title="分块与合并测试"></a>分块与合并测试</h2>为了更好的理解文件分块上传的原理,下面用Java代码测试文件的分块与合并</li>
</ol>
<p><strong>文件分块的流程如下</strong></p>
<ol>
<li>获取源文件长度</li>
<li>根据设定的分块文件大小,计算出块数(向上取整,例如33.4M的文件,块大小为1M,则需要34块)</li>
<li>从源文件读取数据,并依次向每一个块文件写数据</li>
</ol>
<p><strong>文件分块测试代码如下</strong></p>
<pre><code class="bash"> MinioClient minioClient =
MinioClient.builder()
.endpoint(&quot;http://127.0.0.1:9000&quot;)
.credentials(&quot;minioadmin&quot;, &quot;minioadmin&quot;)
.build();
//分块测试
@Test
public void videoChunkTest() throws IOException &#123;
// // 源文件
File file = new File(&quot;C:\\Users\\哈欠\\Desktop\\blogImage\\3.mp4&quot;);
//分块文件存储路径
String chunkFilePath = &quot;C:\\Users\\哈欠\\Desktop\\blogImage\\chunk\\&quot;;
//分块文件大小
int chunkSize = 1024 * 1024 * 1;
//分块文件个数
double chunkNum = Math.ceil(file.length() * 1.0 / chunkSize);
RandomAccessFile raf_r = new RandomAccessFile(file, &quot;r&quot;);
//缓存区
byte[] bytes = new byte[1024];
for (int i = 0; i &lt; chunkNum; i++) &#123;
File chunkFile = new File(chunkFilePath + i);
RandomAccessFile raf_rw = new RandomAccessFile(chunkFile, &quot;rw&quot;);
int len = -1;
while ((len = raf_r.read(bytes)) != -1) &#123;
raf_rw.write(bytes, 0, len);
if (chunkFile.length() &gt;= chunkSize) &#123;
break;
&#125;
&#125;
&#125;
&#125;
</code></pre>
<p><strong>文件合并流程</strong></p>
<ol>
<li>找到要合并的文件并按文件分块的先后顺序排序</li>
<li>创建合并文件</li>
<li>依次从合并的文件中读取数据冰箱合并文件写入数据</li>
</ol>
<p><strong>文件合并的测试代码</strong></p>
<pre><code class="bash"> @Test
public void videoMergeCTest() throws IOException &#123;
//块文件目录
File chunkFolder = new File(&quot;C:\\Users\\哈欠\\Desktop\\blogImage\\chunk&quot;);
File sourceFile = new File(&quot;C:\\Users\\哈欠\\Desktop\\blogImage\\1.mp4&quot;);
File mergeFile = new File(&quot;C:\\Users\\哈欠\\Desktop\\blogImage\\222.mp4&quot;);
File[] files = chunkFolder.listFiles();
List&lt;File&gt; fileList = Arrays.asList(files);
Collections.sort(fileList, new Comparator&lt;File&gt;() &#123;
@Override
public int compare(File o1, File o2) &#123;
return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());
&#125;
&#125;);
//向合并文件写的流
RandomAccessFile raf_rw = new RandomAccessFile(mergeFile, &quot;rw&quot;);
//缓存区
byte[] bytes = new byte[1024];
//遍历分块件,向合并的文件写
for (File file : fileList) &#123;
RandomAccessFile raf_r = new RandomAccessFile(file, &quot;r&quot;);
int len = -1;
while ((len = raf_r.read(bytes)) != -1) &#123;
raf_rw.write(bytes, 0, len);
&#125;
raf_r.close();
&#125;
raf_rw.close();
FileInputStream fileInputStream_mergeFile = new FileInputStream(mergeFile);
FileInputStream fileInputStream_sourceFile = new FileInputStream(sourceFile);
String md5_merge = DigestUtils.md5Hex(fileInputStream_mergeFile);
String md5_source = DigestUtils.md5Hex(fileInputStream_sourceFile);
if (md5_source == md5_merge) &#123;
System.out.println(&quot;文件合并成功&quot;);
&#125;
&#125;
</code></pre>
<p><strong>视频上传分块</strong></p>
<pre><code class="bash"> @Test
public void uploadChunk() throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException &#123;
for (int i = 0; i &lt; 11; i++) &#123;
//上传文件的参数信息
UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
.bucket(&quot;testbucket&quot;)//桶
.filename(&quot;C:\\Users\\哈欠\\Desktop\\blogImage\\chunk\\&quot; + i) //指定本地文件路径
// .object(&quot;1.mp4&quot;)//对象名 在桶下存储该文件
.object(&quot;chunk/&quot; + i)//对象名 放在子目录下
// .contentType(mimeType)//设置媒体文件类型
.build();
minioClient.uploadObject(uploadObjectArgs);
System.out.println(&quot;成功&quot; + i);
&#125;
&#125;
</code></pre>
<p><strong>合并分块</strong></p>
<pre><code class="bash"> /*合并*/
@Test
public void testMerge() throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException &#123;
List&lt;ComposeSource&gt; list = new ArrayList&lt;&gt;();
for (int i = 0; i &lt; 11; i++) &#123;
//上传文件的参数信息
ComposeSource composeSource = ComposeSource.builder()
.bucket(&quot;testbucket&quot;)//桶
.object(&quot;chunk/&quot; + i)//对象名 放在子目录下
.build();
list.add(composeSource);
&#125;
ComposeObjectArgs composeObjectArgs = ComposeObjectArgs
.builder()
.bucket(&quot;testbucket&quot;)
.object(&quot;merge02.mp4&quot;)
.sources(list)
.build();
minioClient.composeObject(composeObjectArgs);
// System.out.println(&quot;成功&quot; + i);
&#125;
</code></pre>
<h2 id="上传大文件逻辑"><a href="#上传大文件逻辑" class="headerlink" title="上传大文件逻辑"></a>上传大文件逻辑</h2><ol>
<li><strong>前端上传文件前,请求媒资接口层检查文件是否存在</strong><br>若存在,则不再上传<br>若不存在,则开始上传,首先对视频文件进行分块</li>
<li><strong>前端分块进行上传,上传前首先检查分块是否已经存在</strong><br>若分块已存在,则不再上传<br>若分块不存在,则开始上传分块</li>
<li>前端请求媒资管理接口层,请求上传分块</li>
<li>接口层请求服务层上传分块</li>
<li>服务端将分块信息上传到MinIO</li>
<li>前端将分块上传完毕,请求接口层合并分块</li>
<li>接口层请求服务层合并分块</li>
<li>服务层根据文件信息找到MinIO中的分块文件,下载到本地临时目录,将所有分块下载完毕后开始合并,合并完成后,将合并后的文件上传至MinIO</li>
</ol>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>MinIO分布式文件系统</title>
<url>/blog/2023/09/08/java%E5%AD%A6%E4%B9%A019/</url>
<content><![CDATA[<h1 id="分布式文件系统"><a href="#分布式文件系统" class="headerlink" title="分布式文件系统"></a>分布式文件系统</h1><h2 id="什么是分布式文件系统"><a href="#什么是分布式文件系统" class="headerlink" title="什么是分布式文件系统"></a>什么是分布式文件系统</h2><ul>
<li>文件系统的定义</li>
</ul>
<blockquote>
<p>文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。文件系统由三部分组成:文件系统的接口,对对象操纵和管理的软件集合,对象及属性。从系统角度来看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。</p>
</blockquote>
<p>文件系统是负责管理和存储文件和系统软件,操作系统通过文件系统提供的借口去存取文件,用户通过操作系统访问磁盘上的文件<br>常见的文件系统:FAT16/FAT32、NTFS、HFS、UFS、APFS、XFS、Ext4等</p>
<ul>
<li>分布式文件系统的定义</li>
</ul>
<blockquote>
<p>分布式文件系统(Distributed File System,DFS)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点(可简单的理解为一台计算机)相连;或是若干不同的逻辑磁盘分区或卷标组合在一起而形成的完整的有层次的文件系统。DFS为分布在网络上任意位置的资源提供一个逻辑上的树形文件系统结构,从而使用户访问分布在网络上的共享文件更加简便。单独的 DFS共享文件夹的作用是相对于通过网络上的其他共享文件夹的访问点</p>
</blockquote>
<p>可以简单的理解为:一个计算机无法存储海量的文件,通过网络将若干计算机组织起来共同去存储海量的文件,去接收海量用户的请求,这些组织起来的计算机通过计算机网络通信</p>
<ul>
<li>市面上有哪些分布式文件系统的产品 </li>
</ul>
<ol>
<li>NFS</li>
</ol>
<blockquote>
<p>NFS是基于UDP/IP协议的应用,其实现主要是采用远程过程调用RPC机制,RPC提供了一组与机器、操作系统以及低层传送协议无关的存取远程文件的操作。RPC采用了XDR的支持。XDR是一种与机器无关的数据描述编码的协议,他以独立与任意机器体系结构的格式对网上传送的数据进行编码和解码,支持在异构系统之间数据的传送。</p>
<p>特点 在客户端上映射NFS服务器的驱动器 客户端通过万国访问NFS服务器的硬盘完全透明<br>2. HDFS<br>Hadoop分布式文件系统(HDFS)是指被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统(Distributed File System)。它和现有的分布式文件系统有很多共同点。但同时,它和其他的分布式文件系统的区别也是很明显的。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。HDFS放宽了一部分POSIX约束,来实现流式读取文件系统数据的目的。HDFS在最开始是作为Apache Nutch搜索引擎项目的基础架构而开发的。HDFS是Apache Hadoop Core项目的一部分。<br>HDFS有着高容错性(fault-tolerant)的特点,并且设计用来部署在低廉的(low-cost)硬件上。而且它提供高吞吐量(high throughput)来访问应用程序的数据,适合那些有着超大数据集(large data set)的应用程序。HDFS放宽了(relax)POSIX的要求(requirements)这样可以实现流的形式访问(streaming access)文件系统中的数据。<br><em>HDFS采用主从结构,一个HDFS集群由一个名称节点和若干数据节点组成<br>名称节点存储数据的元信息,一个完整的数据文件分成若干块存储在数据节点<br>客户端从名称节点获取数据的元信息及数据分块的信息,得到信息客户端即可从数据块来存储数据</em><br>3. 云计算厂家<br>阿里云的oss,百度的对象存储BOS等</p>
</blockquote>
<h2 id="MinIO"><a href="#MinIO" class="headerlink" title="MinIO"></a>MinIO</h2><p><em>MinIO构建分布式文件系统,MinIO是一个非常轻量的服务,可以很简单的和其他应用结合使用。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等</em><br>它的一大特点就是轻量,使用简单、功能强大,支持各种平台,单个文件最大5TB,兼容提供了Java、Python、GO等多版本SDK支持<br><strong>官网:<a href="https://min.io/%EF%BC%8C">https://min.io/,</a><br>中文:<a href="https://www.minio.org.cn/">https://www.minio.org.cn/</a><a href="http://docs.minio.org.cn/docs/">http://docs.minio.org.cn/docs/</a></strong><br>MinIO采用去中心化共享架构,每个节点是对等关系,通过Nginx可对MinIO进行负载均衡访问<br>去中心化有什么好处?<br>在大数据领域,通常的设计理念都是无中心和分布式。MinIO分布式模式可以帮助你搭建一个高可用的对象存储服务,你可以使用这些存储设备,而不用考虑其真实物理位置<br>它将分布在不同服务器上的多块硬盘组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式MinIO避免了单点故障<br>**MinIO下载地址:<a href="https://dl.min.io/server/minio/release/**%EF%BC%88%E8%BF%99%E9%87%8C%E4%BD%BF%E7%94%A8%E6%9C%AC%E5%9C%B0%E4%BD%9C%E4%B8%BA%E5%AE%9E%E9%AA%8C%E7%8E%AF%E5%A2%83%E6%BC%94%E7%A4%BA%EF%BC%89">https://dl.min.io/server/minio/release/**(这里使用本地作为实验环境演示)</a></p>
<ul>
<li>安装完毕后,CMD进入minio.exe所在目录,执行下面的命令,会在D盘创建4个目录,模拟4个硬盘<pre><code class="bash">minio.exe server D:\develop\minio_data\data1 D:\develop\minio_data\data2 D:\develop\minio_data\data3 D:\develop\minio_data\data4
</code></pre>
<img src="https://foruda.gitee.com/images/1693900861633260970/048ad7a4_7715734.png" alt="在这里插入图片描述"><br>默认账号密码均为minioadmin,访问localhost:9000进行登录</li>
<li>然后创建两个buckets<br> mediafiles:普通文件<br> video:视频文件<br><img src="https://foruda.gitee.com/images/1693900929750461863/ade8e9bf_7715734.png" alt="在这里插入图片描述"></li>
<li>桶的权限修改为public<br><img src="https://foruda.gitee.com/images/1693902568010693182/46734448_7715734.png" alt="在这里插入图片描述"><h3 id="java项目中演示"><a href="#java项目中演示" class="headerlink" title="java项目中演示"></a><strong>java项目中演示</strong></h3></li>
</ul>
<h4 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h4><p> Java 1.8或更高版本</p>
<h4 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h4><ol>
<li>在pom文件工程中添加依赖</li>
</ol>
<pre><code class="bash">&lt;dependency&gt;
&lt;groupId&gt;io.minio&lt;/groupId&gt;
&lt;artifactId&gt;minio&lt;/artifactId&gt;
&lt;version&gt;8.4.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;com.squareup.okhttp3&lt;/groupId&gt;
&lt;artifactId&gt;okhttp&lt;/artifactId&gt;
&lt;version&gt;4.8.1&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<ol start="2">
<li>从官方文档中看到,需要三个参数才能连接到minio服务</li>
</ol>
<blockquote>
<p>Endpoint Access Secret Key</p>
</blockquote>
<ol start="3">
<li><p>官方文档给出的示例代码</p>
<pre><code class="bash">public class FileUploader &#123;
public static void main(String[] args)
throws IOException, NoSuchAlgorithmException, InvalidKeyException &#123;
try &#123;
// Create a minioClient with the MinIO server playground, its access key and secret key.
// 创建MinIO客户端,连接参数就是上述表格中的三个参数,127.0.0.1:9000、minioadmin、minioadmin
MinioClient minioClient =
MinioClient.builder()
.endpoint(&quot;https://play.min.io&quot;)
.credentials(&quot;Q3AM3UQ867SPQQA43P2F&quot;, &quot;zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG&quot;)
.build();
// Make &#39;asiatrip&#39; bucket if not exist.
// 由于backet我们已经手动创建了,所以这段代码可以删掉
boolean found =
minioClient.bucketExists(BucketExistsArgs.builder().bucket(&quot;asiatrip&quot;).build());
if (!found) &#123;
// Make a new bucket called &#39;asiatrip&#39;.
minioClient.makeBucket(MakeBucketArgs.builder().bucket(&quot;asiatrip&quot;).build());
&#125; else &#123;
System.out.println(&quot;Bucket &#39;asiatrip&#39; already exists.&quot;);
&#125;
// Upload &#39;/home/user/Photos/asiaphotos.zip&#39; as object name &#39;asiaphotos-2015.zip&#39; to bucket
// &#39;asiatrip&#39;.
// 将 &#39;/home/user/Photos/asiaphotos.zip&#39; 文件命名为 &#39;asiaphotos-2015.zip&#39;
// 并上传到 &#39;asiatrip&#39; 里(示例代码创建的bucket)
minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(&quot;asiatrip&quot;)
.object(&quot;asiaphotos-2015.zip&quot;)
.filename(&quot;/home/user/Photos/asiaphotos.zip&quot;)
.build());
// 这段输出也没有用,可以直接删掉
System.out.println(
&quot;&#39;/home/user/Photos/asiaphotos.zip&#39; is successfully uploaded as &quot;
+ &quot;object &#39;asiaphotos-2015.zip&#39; to bucket &#39;asiatrip&#39;.&quot;);
&#125; catch (MinioException e) &#123;
System.out.println(&quot;Error occurred: &quot; + e);
System.out.println(&quot;HTTP trace: &quot; + e.httpTrace());
&#125;
&#125;
&#125;
</code></pre>
</li>
<li><p>yml进行配置三个参数</p>
<pre><code class="bash">minio:
endpoint: http://127.0.0.1:9000
accessKey: minioadmin
secretKey: minioadmin
bucket:
files: mediafiles
videofiles: video
</code></pre>
</li>
<li><p>创建Minio配置类,实现bean容器管理</p>
<pre><code class="bash">Configuration
public class MinioConfig &#123;
@Value(&quot;$&#123;minio.endpoint&#125;&quot;)
private String endpoint;
@Value(&quot;$&#123;minio.accessKey&#125;&quot;)
private String accessKey;
@Value(&quot;$&#123;minio.secretKey&#125;&quot;)
private String secretKey;
@Bean
public MinioClient minioClient() &#123;
MinioClient minioClient =
MinioClient.builder()
.endpoint(&quot;http://127.0.0.1:9000&quot;)
.credentials(accessKey, secretKey)
.build();
return minioClient;
&#125;
</code></pre>
</li>
<li><p>业务实现(控制层)</p>
</li>
</ol>
<pre><code class="bash"> @ApiOperation(&quot;上传图片&quot;)
@RequestMapping(value = &quot;/upload/coursefile&quot;, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart(&quot;filedata&quot;) MultipartFile filedata) throws IOException &#123;
//准备上传文件的信息
UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
//原始文件名称
uploadFileParamsDto.setFilename(filedata.getOriginalFilename());
//文件大小
uploadFileParamsDto.setFileSize(filedata.getSize());
//文件类型
uploadFileParamsDto.setFileType(&quot;001001&quot;);
//创建一个临时文件
File tempFile = File.createTempFile(&quot;minio&quot;, &quot;.temp&quot;);
filedata.transferTo(tempFile);
Long companyId = 1232141425L;
//文件路径
String localFilePath = tempFile.getAbsolutePath();
//调用service上传图片
UploadFileResultDto uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, localFilePath);
return uploadFileResultDto;
&#125;
</code></pre>
<ol start="7">
<li>业务实现(业务层)</li>
</ol>
<pre><code class="bash"> //存储普通文件
@Value(&quot;$&#123;minio.bucket.files&#125;&quot;)
private String bucket_mediafiles;
//存储视频
@Value(&quot;$&#123;minio.bucket.videofiles&#125;&quot;)
private String bucket_video;
//根据扩展名获取mimeType
private String getMimeType(String extension)&#123;
if(extension == null)&#123;
extension = &quot;&quot;;
&#125;
//根据扩展名取出mimeType
ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流
if(extensionMatch!=null)&#123;
mimeType = extensionMatch.getMimeType();
&#125;
return mimeType;
&#125;
/**
* 将文件上传到minio
* @param localFilePath 文件本地路径
* @param mimeType 媒体类型
* @param bucket 桶
* @param objectName 对象名
* @return
*/
public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName)&#123;
try &#123;
UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
.bucket(bucket)//桶
.filename(localFilePath) //指定本地文件路径
.object(objectName)//对象名 放在子目录下
.contentType(mimeType)//设置媒体文件类型
.build();
//上传文件
minioClient.uploadObject(uploadObjectArgs);
log.debug(&quot;上传文件到minio成功,bucket:&#123;&#125;,objectName:&#123;&#125;,错误信息:&#123;&#125;&quot;,bucket,objectName);
return true;
&#125; catch (Exception e) &#123;
e.printStackTrace();
log.error(&quot;上传文件出错,bucket:&#123;&#125;,objectName:&#123;&#125;,错误信息:&#123;&#125;&quot;,bucket,objectName,e.getMessage());
&#125;
return false;
&#125;
//获取文件默认存储目录路径 年/月/日
private String getDefaultFolderPath() &#123;
SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy-MM-dd&quot;);
String folder = sdf.format(new Date()).replace(&quot;-&quot;, &quot;/&quot;)+&quot;/&quot;;
return folder;
&#125;
//获取文件的md5
private String getFileMd5(File file) &#123;
try (FileInputStream fileInputStream = new FileInputStream(file)) &#123;
String fileMd5 = DigestUtils.md5Hex(fileInputStream);
return fileMd5;
&#125; catch (Exception e) &#123;
e.printStackTrace();
return null;
&#125;
&#125;
@Override
public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) &#123;
//文件名
String filename = uploadFileParamsDto.getFilename();
//先得到扩展名
String extension = filename.substring(filename.lastIndexOf(&quot;.&quot;));
//得到mimeType
String mimeType = getMimeType(extension);
//子目录
String defaultFolderPath = getDefaultFolderPath();
//文件的md5值
String fileMd5 = getFileMd5(new File(localFilePath));
String objectName = defaultFolderPath+fileMd5+extension;
//上传文件到minio
boolean result = addMediaFilesToMinIO(localFilePath, mimeType, bucket_mediafiles, objectName);
if(!result)&#123;
XueChengPlusException.cast(&quot;上传文件失败&quot;);
&#125;
//入库文件信息
MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);
if(mediaFiles==null)&#123;
XueChengPlusException.cast(&quot;文件上传后保存信息失败&quot;);
&#125;
//准备返回的对象
UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
return uploadFileResultDto;
&#125;
/**
* @description 将文件信息添加到文件表
* @param companyId 机构id
* @param fileMd5 文件md5值
* @param uploadFileParamsDto 上传文件的信息
* @param bucket 桶
* @param objectName 对象名称
* @return com.xuecheng.media.model.po.MediaFiles
* @author Mr.M
* @date 2022/10/12 21:22
*/
@Transactional
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName)&#123;
//将文件信息保存到数据库
MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
if(mediaFiles == null)&#123;
mediaFiles = new MediaFiles();
BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
//文件id
mediaFiles.setId(fileMd5);
//机构id
mediaFiles.setCompanyId(companyId);
//桶
mediaFiles.setBucket(bucket);
//file_path
mediaFiles.setFilePath(objectName);
//file_id
mediaFiles.setFileId(fileMd5);
//url
mediaFiles.setUrl(&quot;/&quot;+bucket+&quot;/&quot;+objectName);
//上传时间
mediaFiles.setCreateDate(LocalDateTime.now());
//状态
mediaFiles.setStatus(&quot;1&quot;);
//审核状态
mediaFiles.setAuditStatus(&quot;002003&quot;);
//插入数据库
int insert = mediaFilesMapper.insert(mediaFiles);
if(insert&lt;=0)&#123;
log.debug(&quot;向数据库保存文件失败,bucket:&#123;&#125;,objectName:&#123;&#125;&quot;,bucket,objectName);
return null;
&#125;
return mediaFiles;
&#125;
return mediaFiles;
&#125;
</code></pre>
<p>至此完成上传图片功能。</p>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>redis中实现分布式锁</title>
<url>/blog/2023/10/24/java%E5%AD%A6%E4%B9%A020/</url>
<content><![CDATA[<h1 id="redis中实现分布式锁"><a href="#redis中实现分布式锁" class="headerlink" title="redis中实现分布式锁"></a>redis中实现分布式锁</h1><ul>
<li> <strong>锁(Lock)</strong> :锁是一种同步机制,用于确保在任意时刻只有一个节点(进程或线程)可以访问共享资源。锁可以防止竞态条件和数据不一致问题。<br>分布式系统中的关键概念,用于解决多个节点同时访问共享资源可能引发的并发问题。</li>
<li> <strong>共享资源(Shared Resource)</strong> :共享资源是多个节点需要访问或修改的数据、文件、服务等。在分布式系统中,多个节点可能同时尝试访问这些共享资源,从而引发问题。</li>
<li> <strong>死锁(Deadlock)</strong> :死锁是多个节点因相互等待对方释放资源而陷入无限等待的状态。在分布式系统中,多个节点可能同时竞争资源,如果没有良好的协调机制,就可能出现死锁情况。</li>
</ul>
<p>下述是实际业务代码中的分布式加锁操作:</p>
<pre><code>/**
* 加锁
*
* @param name
* @param expire
* @return
*/
public String tryLock(String name, long expire) &#123;
name = name + &quot;_lock&quot;;
String token = UUID.randomUUID().toString();
RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory();
RedisConnection conn = factory.getConnection();
try &#123;
//参考redis命令:
//set key value [EX seconds] [PX milliseconds] [NX|XX]
Boolean result = conn.set(
name.getBytes(),
token.getBytes(),
Expiration.from(expire, TimeUnit.MILLISECONDS),
RedisStringCommands.SetOption.SET_IF_ABSENT //NX
);
if (result != null &amp;&amp; result)
return token;
&#125; finally &#123;
RedisConnectionUtils.releaseConnection(conn, factory,false);
&#125;
return null;
&#125;
@Scheduled(cron = &quot;0 */1 * * * ?&quot;)
public void refresh() &#123;
String token = cacheService.tryLock(&quot;FUTRUE_TASK_SYNC&quot;, 1000 * 30);
if (StringUtils.isNotBlank(token)) &#123;
log.info(&quot;未来数据定时刷新---定时任务&quot;);
Set&lt;String&gt; futureKeys = cacheService.scan(ScheduleConstants.FUTURE + &quot;*&quot;);
futureKeys.forEach(item -&gt; &#123;
String topicKey = item.split(ScheduleConstants.FUTURE)[1];
Set&lt;String&gt; tasks = cacheService.zRangeByScore(item, 0, System.currentTimeMillis());
if (!tasks.isEmpty()) &#123;
cacheService.refreshWithPipeline(item, topicKey, tasks);
log.info(&quot;成功的将&quot; + item + &quot;刷新了&quot; + topicKey);
&#125;
&#125;);
&#125;
&#125;
</code></pre>
<p>在上述代码中,我们通过使用<code>expire</code>命令为锁设置了一个过期时间,以防止节点在获取锁后崩溃或异常退出,导致锁一直无法释放。设置合理的过期时间可以避免锁长时间占用而导致的资源浪费。<br>个人感觉在实际业务中数据量不大或者说使用人数不是特别多,一般不需要,因为服务分布式锁的引入会增加系统的复杂性和性能开销,因此在使用分布式锁时需要考虑其对系统性能的影响。</p>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>kafka中实现订阅主题监听</title>
<url>/blog/2023/10/24/java%E5%AD%A6%E4%B9%A021/</url>
<content><![CDATA[<h1 id="kafka技术实现文章上下架"><a href="#kafka技术实现文章上下架" class="headerlink" title="kafka技术实现文章上下架"></a>kafka技术实现文章上下架</h1><h3 id="点对点"><a href="#点对点" class="headerlink" title="点对点"></a>点对点</h3><p>生产者生产消息发送到Queue,消费者从Queue取出数据,并消费数据,数据被消费,Queue不再存储,Queue支持多个消费者,一条消息只能被消费一次(只有一个消费者可以消费到)</p>
<h3 id="发布-订阅(一对多)"><a href="#发布-订阅(一对多)" class="headerlink" title="发布/订阅(一对多)"></a>发布/订阅(一对多)</h3><p>生产者发送消息到topic中,多个消费者订阅topic,和点对点不同,发布到topic的消息会被所有订阅者消费</p>
<ul>
<li><p> Producer:消息生产者,向kafka broker发送消息</p>
</li>
<li><p> Consumer:消息消费者,从kafka broker取消息</p>
</li>
<li><p>Consumer Group:多个consumer组成,消费者组内不同消费者负责消费不同分区的数据,kafka的topic下的一条消息,只能被同一个消费者组的一个消费者消费到</p>
<ul>
<li> consumer group下可以有一个或多个consumer instance,consumer instance可以是一个进程,也可以是一个线程</li>
<li> group.id是一个字符串,唯一标识一个consumer group</li>
<li> <strong>consumer group下订阅的topic下的每个分区只能分配给某个group下的一个consumer(当然该分区还可以被分配给其他group)</strong></li>
</ul>
</li>
<li><p> Broker:一台服务器就是一个broker,一个集群由多个broker组成,一个broker可以容纳多个topic</p>
</li>
<li><p> Topic:主题(队列)</p>
</li>
<li><p> Partition:分区,kafka的扩展性体现,一个庞大的topic有很多分区(partition),可以分不到多个broker上去,每个 partition 是一个有序的队列, partition 中的每条消息 都会被分配一个有序的 id (offset) kafka 只保证按一个 partition 中的顺序将消息发给consumer ,不保证一个 topic的整体(多个 partition 间)的顺序;</p>
</li>
<li><p> Replica:副本,当集群某个节点故障时,该节点的partitiion数据不丢失,kafka的副本机制,一个topic的每个分区有多个副本,一个leader和follower</p>
</li>
<li><p> follower:每个分区的多个副本的“从”,实时从leader中同步数据,保持leader数据的同步</p>
</li>
<li><p> leader:每个分区副本的主,生产者发送数据的对象,消费者消费数据的对象</p>
</li>
<li><p> Offset:每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到partition中。partition中的每个消息都有一个连续的序列号叫做offset,用于partition唯一标识一条消息;kafka 的存储文件都是按照 offset.kafka来命名,用 offset 名字的好处是方便查。例如你想位于 2049,只要找到2048.kafka的文件即可。当然 the first offsetthe 就 是 00000000000.kafka ;</p>
</li>
<li><p> zookeeper:保存offset数据(0.9版本之前),保证高可用,0.9版本之后offset数据存放在kafka的系统特定topic中;</p>
</li>
</ul>
<h2 id="项目中集成kafka实现文章上下架"><a href="#项目中集成kafka实现文章上下架" class="headerlink" title="项目中集成kafka实现文章上下架"></a>项目中集成kafka实现文章上下架</h2><pre><code>/**
* 通过重新注册KafkaStreamsConfiguration对象,设置自定配置参数
*/
@Setter
@Getter
@Configuration
@EnableKafkaStreams
@ConfigurationProperties(prefix=&quot;kafka&quot;)
public class KafkaStreamConfig &#123;
private static final int MAX_MESSAGE_SIZE = 16* 1024 * 1024;
private String hosts;
private String group;
@Bean(name = KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
public KafkaStreamsConfiguration defaultKafkaStreamsConfig() &#123;
Map&lt;String, Object&gt; props = new HashMap&lt;&gt;();
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, hosts);
props.put(StreamsConfig.APPLICATION_ID_CONFIG, this.getGroup()+&quot;_stream_aid&quot;);
props.put(StreamsConfig.CLIENT_ID_CONFIG, this.getGroup()+&quot;_stream_cid&quot;);
props.put(StreamsConfig.RETRIES_CONFIG, 10);
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
return new KafkaStreamsConfiguration(props);
&#125;
```
server:
port: 9991
spring:
application:
name: kafka-demo
kafka:
bootstrap-servers: 175.178.218.98:9092
producer:
retries: 10
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
consumer:
group-id: $&#123;spring.application.name&#125;-test
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
kafka:
hosts: 175.178.218.98
group: $&#123;spring.application.name&#125;&#125;
</code></pre>
<p>上述配置完成可以直接运行kafka,以下为实际业务中使用kafka监听上下架文章,主要使用订阅主题获</p>
<pre><code>业务中加入kafka发送主题,需要以json格式转换
@Autowired
private KafkaTemplate&lt;String,String&gt; kafkaTemplate;
@Override
public ResponseResult downOrUp(WmNewsDto dto) &#123;
if (dto.getId() == null) &#123;
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
&#125;
WmNews wmNews = getById(dto.getId());
// WmNews wmNews = wmNewsMapper.selectById(dto.getId());
if (wmNews==null)&#123;
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
&#125;
if (!wmNews.getStatus().equals(WmNews.Status.PUBLISHED.getCode())) &#123;
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,&quot;当前文章不是发布状态&quot;);
&#125;
if(dto.getEnable()!=null&amp;&amp;dto.getEnable()&gt;-1&amp;&amp;dto.getEnable()&lt;2) &#123;
wmNews.setEnable(dto.getEnable());
updateById(wmNews);
if (wmNews.getArticleId() != null) &#123;
//发送消息,通知article修改文章配置
HashMap&lt;String, Object&gt; map = new HashMap&lt;&gt;();
map.put(&quot;articleId&quot;, wmNews.getArticleId());
map.put(&quot;enable&quot;, dto.getEnable());
kafkaTemplate.send(WmNewsMessageConstants.WM_MEWS_UP_OR_DOWN_TOPIC, JSON.toJSONString(map));
&#125;
&#125;
return ResponseResult.okResult(wmNews);
&#125;
</code></pre>
<p>创建监听器订阅主题获取消息</p>
<pre><code>@Component
@Slf4j
public class ArticleIsDownListener &#123;
@Autowired
private ApArticleConfigService apArticleConfigService;
@KafkaListener(topics = WmNewsMessageConstants.WM_MEWS_UP_OR_DOWN_TOPIC)
public void onMessage(String message)&#123;
if (StringUtils.isNotBlank(message))&#123;
Map map = JSON.parseObject(message, Map.class);
apArticleConfigService.updateByMap(map);
&#125;
&#125;
&#125;
</code></pre>
<p>根据键获取相关消息并更改文章状态</p>
<pre><code>/**
* 修改文章
* @param map
*/
@Override
public void updateByMap(Map map) &#123;
//0 下架 1 上架
Object enable = map.get(&quot;enable&quot;);
boolean isDown = true;
if(enable.equals(1))&#123;
isDown = false;
&#125;
//修改文章
update(Wrappers.&lt;ApArticleConfig&gt;lambdaUpdate().eq(ApArticleConfig::getArticleId,map.get(&quot;articleId&quot;))
.set(ApArticleConfig::getIsDown,isDown));
&#125;
</code></pre>
<p>这为kafka应用于实际业务中的一个小例子,他的功能十分强大,<strong>Kafka运行时很少有大量读磁盘的操作,主要是定期批量写磁盘操作,因此操作磁盘很高效</strong>,更实用于大数据量的情况</p>
]]></content>
<categories>
<category>java的学习</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
</search>
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/chen_nengshou/blog.git
git@gitee.com:chen_nengshou/blog.git
chen_nengshou
blog
blog
master

搜索帮助