小布达拉 發表於 2019-10-16 16:25:00

使用.Net core3.0 开发斗图小程序后端+斗图小程序

<h2>为啥要写这么一个小程序?</h2>
<p>作为互联网的原住民。</p>
<p>90后程序员的我,从高中开始发QQ小表情。</p>
<p><img src="https://img2018.cnblogs.com/blog/653862/201910/653862-20191016140729162-1107852939.png" alt=""></p>
<p>&nbsp;</p>
<p>到之后的熊猫头,蘑菇头。</p>
<p><img src="https://img2018.cnblogs.com/blog/653862/201910/653862-20191016141400266-516765160.png" alt=""></p>
<p>&nbsp;</p>
<p><img src="https://img2018.cnblogs.com/blog/653862/201910/653862-20191016141413399-620603641.png" alt=""></p>
<p>&nbsp;</p>
<p>可以说表情包陪伴我从学校到社会,从青少年到中年。。</p>
<p>多年来,混迹于各个qq群,收集表情包多达三千多个,终于,在去年的某个工地跑路的时候,忘了带走我的表情包。。。然后就再也找不到之前的经典表情了。。</p>
<p>虽然QQ里面提成了斗图的功能,但是微信没有提成QQ那种怼图的功能。而且对我来说,QQ提供的表情都太高清了。。不如模糊的小标签有灵魂。。。</p>
<p>于是决定自己做一个这样的斗图小程序。自用,也给其他的朋友们提供方便。</p>
<hr>
<p>&nbsp;</p>
<h2>&nbsp;需要用到的技术栈</h2>
<p>服务器端:</p>
<ul>
<li>阿里云ubuntu1804最低配的的ECS一台</li>
<li>MySQL8.0社区版数据库</li>
<li>.Net Core 3.0&nbsp;</li>
<li>Dapper</li>
<li>HtmlAgilityPack</li>
<li>Lucene+jieba.net</li>
<li>阿里云对象存储+阿里云CDN</li>
</ul>
<p>小程序端:</p>
<ul>
<li>直接使用微信官方的那一套就行,客户端没啥复杂的,我使用的是uniapp开发的小程序。</li>
</ul>
<p>&nbsp;</p>
<p>选好技术类型后,就开始我们的斗图小程序开发过程</p>
<p>&nbsp;</p>
<hr>
<p>&nbsp;</p>
<h3>1.爬取图片数据</h3>
<p>百度搜搜表情包,会出现N个专门做表情包的网站。</p>
<p>我们使用HtmlAgilityPack来抓取图片地址和alt描述文字。</p>
<p>具体可以参考我的另一篇博文 【.Net Core下使用HtmlAgilityPack解析采集互联网数据】</p>
<p>爬取拿到图片地址后,使用HttpClient来下载图片,并上传到对象存储空间去,这里可以自己选择阿里云或者七牛云【不是广告,因为我最熟悉的就是阿里云和七牛云】</p>
<p>下载图片代码</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> result = <span style="color: rgba(0, 0, 255, 1)">await</span> <span style="color: rgba(0, 0, 255, 1)">new</span> HttpClient().GetStreamAsync(imgUrl);</pre>
</div>
<p>拿到流就可以直接下载了,然后上传了。</p>
<p>上传成功后,保存图片地址和图片的描述文字。</p>
<h3>2.开发web服务器接口</h3>
<p>在抓取完图片存到数据库后,我们手上已经有N多图片了。</p>
<p>然后就开发搜索图片的api。</p>
<p>第一步的时候,我们存储了图片地址和图片描述文字。我们搜索的就是这个描述文字。把搜索结果对应的图片列响应出去就好了。</p>
<p>假如你图片采集到几十万张。你单纯用like来搜索。一是慢的要死。二是搜索结果不尽人意。</p>
<p>有大佬估计会说直接上ElasticSearch或者Solr啊。。。</p>
<p>我的内心:服务器费用你给我出啊。我阿里云1h1g的机器,跑得起那玩意儿就怪了。。</p>
<p>这里我使用的是Lucene.Net 4.8+jieba.Net分词插件。</p>
<p>使用方式非常简单。把数据直接导入到Lucene就行了。【用Lucene的专业词语说叫添加到索引】</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">var</span> AppLuceneVersion =<span style="color: rgba(0, 0, 0, 1)"> LuceneVersion.LUCENE_48;
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> indexLocation = Path.Combine(System.IO.Directory.GetCurrentDirectory(), <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Indexs</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.Directory.Exists(indexLocation))
            {
                System.IO.Directory.CreateDirectory(indexLocation);
            }
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> dir =<span style="color: rgba(0, 0, 0, 1)"> FSDirectory.Open(indexLocation);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">create an analyzer to process the text</span>
            <span style="color: rgba(0, 0, 255, 1)">var</span> analyzer = <span style="color: rgba(0, 0, 255, 1)">new</span> JieBaAnalyzer(TokenizerMode.Search); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">new StandardAnalyzer(AppLuceneVersion);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">create an index writer</span>
            <span style="color: rgba(0, 0, 255, 1)">var</span> indexConfig = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> IndexWriterConfig(AppLuceneVersion, analyzer);
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> writer = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> IndexWriter(dir, indexConfig))
            {
               //todo 加载数据过来</span><span style="color: rgba(0, 0, 255, 1)">var</span> list = mysql.Query&lt;BiaoQingSearchModel&gt;(sql, <span style="color: rgba(0, 0, 255, 1)">new</span> { time, now =<span style="color: rgba(0, 0, 0, 1)"> nowTime }).ToList();
                </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; list.Count(); i++<span style="color: rgba(0, 0, 0, 1)">)
                {
                  </span><span style="color: rgba(0, 0, 255, 1)">var</span> item =<span style="color: rgba(0, 0, 0, 1)"> list;
                  </span><span style="color: rgba(0, 0, 255, 1)">var</span> doc = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Document();
                  doc.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> StringField(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">url</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, item.Url, Field.Store.YES));
                  doc.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> TextField(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Imgexplain</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, item.Imgexplain, Field.Store.YES));</span><span style="color: rgba(0, 0, 0, 1)">
                  writer.AddDocument(doc);
                }
                writer.Flush(triggerMerge: </span><span style="color: rgba(0, 0, 255, 1)">false</span>, applyAllDeletes: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
                writer.Commit();<br>       }</span></pre>
</div>
<p>然后创建好索引之后,就可以在Indexs下看到一堆文件了。</p>
<p><img src="https://img2018.cnblogs.com/blog/653862/201910/653862-20191016154728542-596408164.png" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;大概就是如上图所示的样子。</p>
<p>然后就是搜索表情包的数据,数据搜索的时候,我们只搜索Lucene里面的图片描述列。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> AppLuceneVersion =<span style="color: rgba(0, 0, 0, 1)"> LuceneVersion.LUCENE_48;
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> indexLocation = Path.Combine(System.IO.Directory.GetCurrentDirectory(), <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Indexs</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.Directory.Exists(indexLocation))
            {
                System.IO.Directory.CreateDirectory(indexLocation);
            }
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> dir =<span style="color: rgba(0, 0, 0, 1)"> FSDirectory.Open(indexLocation);</span>
            <span style="color: rgba(0, 0, 255, 1)">var</span> analyzer = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> JieBaAnalyzer(TokenizerMode.Search);

            QueryParser qp </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> QueryParser(AppLuceneVersion, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Imgexplain</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, analyzer);
            Query query </span>=<span style="color: rgba(0, 0, 0, 1)"> qp.Parse(key);
            BooleanQuery b </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> BooleanQuery();
            b.Add(query, Occur.MUST);

            </span><span style="color: rgba(0, 0, 255, 1)">using</span> (IndexReader reader =<span style="color: rgba(0, 0, 0, 1)"> DirectoryReader.Open(dir))
            {
                </span><span style="color: rgba(0, 0, 255, 1)">var</span> searcher = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> IndexSearcher(reader);
                TopDocs hits </span>=<span style="color: rgba(0, 0, 0, 1)"> searcher.Search(query, Size);
                Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">一共:</span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)"> hits.TotalHits);
                Count </span>=<span style="color: rgba(0, 0, 0, 1)"> hits.TotalHits;
                </span><span style="color: rgba(0, 0, 255, 1)">var</span> list = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;BiaoQingSearchModel&gt;<span style="color: rgba(0, 0, 0, 1)">();
                </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> hit <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> hits.ScoreDocs)
                {
                  </span><span style="color: rgba(0, 0, 255, 1)">var</span> foundDoc =<span style="color: rgba(0, 0, 0, 1)"> searcher.Doc(hit.Doc);
                  </span><span style="color: rgba(0, 0, 255, 1)">var</span> model = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> BiaoQingSearchModel();
                  model.Url </span>= foundDoc.Get(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">url</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
                  model.Imgexplain </span>= foundDoc.Get(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Imgexplain</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);</span><span style="color: rgba(0, 0, 0, 1)">
                  list.Add(model);
                }
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> list;
            }</span></pre>
</div>
<p>&nbsp;</p>
<p>然后就能正确的搜索出我们需要的表情包了。</p>
<p>做好这一堆事情,基本上我们的服务器端开发工作完成了80%。</p>
<p>然后就是用asp.net core写一个简单的api然后调用Lucene的搜索方法,就完成了。</p>
<h3>服务器部署工作</h3>
<p>我使用的是Ubuntu1804,也推荐大家使用Ubuntu server。ubuntu比centos好用几条街。。有人说centos稳定,同样是Linux,为啥ubuntu server就不稳定了?</p>
<p>1. 在ubuntu服务器上使用apt命令安装好nginx,mysql,Supervisor三个软件</p>
<p>迁移本地采集的数据到ubuntu上的数据库。【迁移数据可以直接使用mysqldump出sql,然后到服务器上执行就行了】</p>
<p>2.申请域名和SSL证书,因为微信小程序要求必须https。【阿里云可以一条龙服务】</p>
<p>3.在nginx中绑定域名和https证书</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">server {
      listen </span><span style="color: rgba(128, 0, 128, 1)">443</span><span style="color: rgba(0, 0, 0, 1)"> ssl;
      server_name 你的域名
      ssl on;
      ssl_certificate </span>/<span style="color: rgba(0, 0, 255, 1)">var</span>/ssl/xxx<span style="color: rgba(0, 0, 0, 1)">.pem;
      ssl_certificate_key </span>/<span style="color: rgba(0, 0, 255, 1)">var</span>/ssl/xxx<span style="color: rgba(0, 0, 0, 1)">.key;
      ssl_session_timeout 5m;
      ssl_protocols TLSv1 TLSv1.</span><span style="color: rgba(128, 0, 128, 1)">1</span> TLSv1.<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
      ssl_ciphers ECDHE</span>-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!<span style="color: rgba(0, 0, 0, 1)">DHE;
      ssl_prefer_server_ciphers on;

      location </span>/<span style="color: rgba(0, 0, 0, 1)"> {
                proxy_pass http:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">localhost:5000;</span>
                proxy_http_version <span style="color: rgba(128, 0, 128, 1)">1.1</span><span style="color: rgba(0, 0, 0, 1)">;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection keep</span>-<span style="color: rgba(0, 0, 0, 1)">alive;
                proxy_set_header Host $host;
                proxy_cache_bypass $http_upgrade;
                }
      }</span></pre>
</div>
<p>4.使用Supervisor以守护进程的模式启用.net core开发的webapi站点。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">
command</span>=<span style="color: rgba(0, 0, 0, 1)">dotnet xxx.dll
directory</span>=/<span style="color: rgba(0, 0, 255, 1)">var</span>/www/<span style="color: rgba(0, 0, 0, 1)">
user</span>=<span style="color: rgba(0, 0, 0, 1)">root
autostart</span>=<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
autorestart</span>=<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
startsecs</span>=<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">
stderr_logfile</span>=/usr/log/doutu/<span style="color: rgba(0, 0, 0, 1)">err.log
stdout_logfile</span>=/usr/log/doutu/<span style="color: rgba(0, 0, 255, 1)">out</span>.log</pre>
</div>
<p>5.启动站点服务</p>
<p>  启用supervisor,使用supervisorctl&nbsp;reload 然后supervisorctl status查看守护进程状态</p>
<p><img src="https://img2018.cnblogs.com/blog/653862/201910/653862-20191016160626012-1348158324.png" alt=""></p>
<p>&nbsp;</p>
<p>  然后启动nginx,正常情况下,就可以通过你绑定的域名访问到你的webapi了。</p>
<p>6.性能优化建议【不是必须】</p>
<p>  因为我们穷,买不起高配的服务器。所以只能通过做更多的功课来加速访问,提高我们的小水管服务器的性能。</p>
<p>  把图片上传到oss去,保证我们的webapi只响应webapi的文本数据,这样1m的小水管,撑住20个人同时访问,压力还是不大的。</p>
<p>  使用cdn加速访问。cdn是内容分发网络的简称。简单的理解就是用户访问距离他最近的节点就能获取到数据。举个栗子:你服务器在上海,你用户在新疆,如果你用户通过网线来找你的服务器上的图片,</p>
<p>  如果不是直连的话,需要经过n个路由器和交换鸡。中间可能会找不到你的服务器,然后也许你就丢失了一个用户了!cdn就是帮我们分发图片到全国各大节点。然后无死角让你提升用户体验。</p>
<p>  开启nginx的gzip,压缩你的数据包。之前10k一个数据包,压缩后2k,你服务器的并发性能瞬间提高几个档次【当然压缩需要消耗cpu,cpu不行就算求了】</p>
<hr>
<p>以上内容虽然只是短短一篇文章,但是实际上消耗了我近两周的业余时间【还不算上备案域名的时间】,因为中间关于全文检索的知识点我也不太了解,踩了很多坑才爬上岸。</p>
<p>如果有大佬需要交流,或者想自己不想这么麻烦,想用现成的接口,也可以找我要api接口。联系方式:<strong>QQ群:545594312</strong>,也可以点击下边的链接进群。</p>
<h1><span style="color: rgba(255, 0, 0, 1)">接下来就是广告时间了!!!!,欢迎微信扫码体验!!!【转载请不要去掉我的广告,谢谢大佬们】</span></h1>
<p><img src="https://img2018.cnblogs.com/blog/653862/201910/653862-20191016162317577-1669505403.jpg" alt=""></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>  </p>
<p>  </p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/boxrice/p/11685275.html
頁: [1]
查看完整版本: 使用.Net core3.0 开发斗图小程序后端+斗图小程序