全部文章

<h1>Git 常用命令汇总</h1> <h2>一、Git 工作原理图</h2> <p><img src="//www.pcdeng.com/uploads/git-flow.png" alt="Git工作原理图" /></p> <h2>二、常用命令</h2> <ul> <li> <p>查看远程分支</p> <pre><code class="language-sh">git branch -a</code></pre> </li> <li> <p>查看本地分支</p> <pre><code class="language-sh">git branch</code></pre> </li> <li> <p>切换到<code>test</code>分支</p> <pre><code class="language-sh">git checkout test</code></pre> </li> <li> <p>创建<code>test</code>分支</p> <pre><code>git branch test</code></pre> </li> <li> <p>删除本地<code>test</code>分支</p> <pre><code class="language-sh">git branch -d test</code></pre> </li> <li> <p>推送<code>test</code>分支到远程服务器</p> <pre><code>git push origin test</code></pre> </li> <li> <p>添加<code>db</code>文件夹</p> <pre><code class="language-sh">git add db</code></pre> </li> <li> <p>提交信息</p> <pre><code class="language-sh">git commit -m '添加db文件夹'</code></pre> </li> <li> <p>将提交推送到远程<code>test</code>分支</p> <pre><code class="language-sh">git push --set-upstream origin test</code></pre> </li> <li> <p>查看当前的origin</p> <pre><code class="language-sh">git remote -v</code></pre> </li> <li> <p>删除远程分支</p> <pre><code class="language-sh">git push origin --delete <branchName> git push origin :<branchName></code></pre> </li> <li> <p>查看状态</p> <pre><code class="language-sh">git remote show origin</code></pre> </li> <li> <p>删除不存在对应远程分支的本地分支</p> <pre><code class="language-sh">git remote prune origin # stale不新鲜的;prune修剪,削减 git fetch -p</code></pre> </li> <li> <p>重命名本地分支(devel -> develop)</p> <pre><code class="language-sh">git branch -m devel develop</code></pre> </li> <li> <p>推送本地分支</p> <pre><code class="language-sh">git push origin develop</code></pre> </li> </ul>
详情
<h2>一、关于Google AdSense</h2> <p>上个星期我为自己的博客开通了Google Adsense。一方面是想学学Google AdSense;另一方面看看能有多少收入。 一共为了两个网站开通Google Adsense,一个已经顺利通过了;一个没有审核通过。没有通过的原因是<code>We’ve found some policy violations on your site which means your site isn’t ready to show ads yet. Make sure your site follows the AdSense Program Policies.</code> 现在还没有时间去修改网站。</p> <h2>二、注册 Google AdSense</h2> <p>注册 Google AdSense 是很容易的。前提是需一个你自己的网站和一个 Google 邮箱。按照注册页面提示填写所需信息即可。注册地址:<a href="https://www.google.com/adsense/signup/new/lead">https://www.google.com/adsense/signup/new/lead</a> 注册帮助文档:<a href="https://support.google.com/adsense/answer/7402253">https://support.google.com/adsense/answer/7402253</a></p> <h2>三、连接网站和 Google AdSense</h2> <ol> <li>上传 <code>ads.txt</code> 到网站根目录。帮助文档 <a href="https://support.google.com/adsense/answer/7532444?hl=zh-Hans">https://support.google.com/adsense/answer/7532444?hl=zh-Hans</a></li> <li>创建广告。 AdSense 提供了两种方式的广告。一种Auto ads(自动广告),一般是要复制一段JavaScript代码放置页面的 head 标签之间,AdSense 会分析你页面,在页面合适的位置放置广告。另一种是Ad unit(广告单元),你可以创建多个不同的广告单元,然后应用到网站的不同页面,推荐这种方式,这种方式更容易适应你的网站风格(不会破坏网页布局)。</li> </ol> <p>一开始,我用的是Auto ads,发现它撑破了页面的布局,极度不好看。Ad unit 有三种类型,分别为Square,Horizontal 和 Vertical。要按照网站布局方式选择。我选择了Horizontal的,因为它适合我的网页布局。 <img src="//www.pcdeng.com/uploads/ad-unit1.png" alt="Adunit1" /></p> <p>创建好 Ad unit 后就可以复制代码到你想要放置的地方。 <img src="//www.pcdeng.com/uploads/adunit2.png" alt="Adunit2" /></p> <p>例如我的</p> <pre><code class="language-javascript"><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <!-- horizontal --> <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-2862993259615109" data-ad-slot="6046279191" data-ad-format="auto" data-full-width-responsive="true"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script></code></pre> <h2>四、运行效果</h2> <p>经过两周的运行后,收益如下: <img src="//www.pcdeng.com/uploads/earnings.png" alt="收益" /> 注意:收益要满100刀才可以提现。</p> <h2>五、总结</h2> <p>通过开通Google AdSense 账户,我对它有了初步的认识。对其它网站提供的广告也没有那么反感了,希望看到这篇文章的你也不要反感这个网站的广告。</p>
详情
<h1>Laravel 5.2 实现浏览文章统计次数</h1> <h2>一、安装 weboAp/Visitor</h2> <p>先按官方提供的<a href="https://github.com/weboAp/Visitor">文档</a>安装好,并测试。 测试方法:</p> <pre><code class="language-php">// app/Http/Controllers/ArticleController.php use Weboap\Visitor\Facades\VisitorFacade; // 使用切面 class ArticleController extends Controller { /** * 文章详情页 * * @return \Illuminate\Http\Response */ public function show($id) { VisitorFacade::log(); } }</code></pre> <p>浏览器访问:<code>http://localhost/article/1</code> 看数据库表 <code>visitor_registry</code> 是否插入了一条数据。有则表明安装 <code>weboAp/Visitor</code> 成功,如果没有,请按官方文档再跑一遍。</p> <h2>二、定制</h2> <h3>改表名</h3> <p>官方预定的表名是 <code>visitor_registry</code>,我要改为的是 <code>article_clicks</code>.</p> <ol> <li> <p>改 <code>migration</code> 文件内容如下:</p> <pre><code class="language-php">// database/migrations/2014_02_09_225721_create_article_clicks.php <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; class CreateArticleClicks extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('article_clicks', function (Blueprint $table) { $table->increments('id'); $table->string('ip', 32); $table->string('country', 4)->nullable(); $table->string('city', 20)->nullable(); // 新增 $table->integer('article_id'); // 新增 $table->integer('clicks')->unsigned()->default(0); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('article_clicks'); } }</code></pre> <p>改完之后,手动去数据库删除 <code>visitor_registry</code> 表。重新执行 <code>php artisan migrate</code>。</p> </li> <li> <p>config/visitor.php</p> <pre><code class="language-php"><?php return [ 'table' => 'article_clicks', // 改这里 ]; </code></pre> </li> </ol> <h3>覆盖 <code>Visitor</code> 类的 <code>log</code> 方法,使其支持传入文章 id。改动后的使用方法为:<code>VisitorFacade::log($article->id)</code>;</h3> <ol> <li> <p>新建自定义 <code>Visitor</code> 类且继承 <code>Visitor</code></p> <pre><code class="language-php"><?php namespace App\Visitor; use Weboap\Visitor\Visitor as WeVisitor; use Carbon\Carbon as c; use App\ArticleClick; class Visitor extends WeVisitor { public function log($articleId = NULL) { $ip = $this->ip->get(); if (!$this->ip->isValid($ip)) { return; } if ($this->has($ip) && $this->hasArticle($articleId, $ip)) { $visitor = ArticleClick::where('ip', $ip) ->where('article_id', $articleId) ->whereDate('updated_at', '!=', c::today()) ->first(); if ($visitor) { $visitor->update(['clicks'=>$visitor->clicks + 1]); } return; } else { $geo = $this->geo->locate($ip); $country = array_key_exists('country_code', $geo) ? $geo['country_code'] : null; $city = array_key_exists('city', $geo) ? $geo['city'] : null; // ip doesnt exist in db $data = [ 'ip' => $ip, 'country' => $country, 'city' => $city, 'clicks' => 1, 'article_id' => $articleId, 'updated_at' => c::now(), 'created_at' => c::now(), ]; $this->storage->create($data); } // Clear the database cache $this->cache->destroy('weboap.visitor'); } public function hasArticle($id, $ip) { return count(ArticleClick::where('article_id', $id)->where('ip', $ip)->get()) > 0; } }</code></pre> </li> <li> <p>新建 <code>VisitorServiceProvider</code> 并继承 <code>VisitorServiceProvider</code></p> <pre><code class="language-php">// app/Providers/VisitorServiceProvider.php <?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Weboap\Visitor\VisitorServiceProvider as VS; use App\Visitor\Visitor; class VisitorServiceProvider extends VS { public function register() { parent::register(); } public function RegisterVisitor() { $this->app->singleton('visitor', function ($app) { return new Visitor( // 这是替换后的类 $app['Weboap\Visitor\Storage\VisitorInterface'], $app['Weboap\Visitor\Services\Geo\GeoInterface'], $app['ip'], $app['Weboap\Visitor\Services\Cache\CacheInterface'] ); }); $this->app->bind('App\Visitor\Visitor', function ($app) { // 关键在这里,狸猫换太子。 return $app['visitor']; }); } }</code></pre> </li> <li> <p>注册两个自定义的类。</p> <pre><code class="language-php">// composer.json "autoload": { "classmap": [ "database", "app/Visitor/Visitor.php" // 增加这行 ], "psr-4": { "App\\": "app/" } },</code></pre> <pre><code class="language-php"> // config/app.php providers: [ ... App\Providers\VisitorServiceProvider::class // 增加这行 ]</code></pre> <h3>最终结果</h3> <p><img src="//www.pcdeng.com/uploads/article-clicks.png" alt="统计结果" /></p> </li> </ol> <h2>参考</h2> <p><a href="https://learnku.com/articles/7637/statistics-of-the-number-of-times-in-laravel">Laravel 实现文章浏览量次数统计</a> <a href="https://stackoverflow.com/questions/47925618/laravel-5-5-override-vendor-class">Laravel 重写第三方类的方法</a></p>
详情
<h1>React Chat</h1> <h2>一共四个页面:</h2> <ol> <li>home 主页,也就是默认页。</li> <li>login 登录页</li> <li>register 注册页</li> <li>chat 聊天页</li> </ol> <h2>主要的功能有</h2> <ol> <li>注册</li> <li>登录</li> <li>聊天(可以换头像)</li> </ol> <h2>其中聊天的流程</h2> <ol> <li>登录成后,把 <code>accessToken</code> 和当前登录的用户信息()存在 <code>localstorage</code></li> <li>初始化 <code>websocket</code> 把 <code>accessToken</code> 通过查询参数(<code>queryString</code>) 传递到 <code>API</code>,<code>API</code> 根据 <code>accessToken</code> 认证用户。</li> <li>认证成功后,<code>websocket</code> 已经连接成功,然后服务器端通过 <code>websocket</code> 发送了三个事件到客户端。 <ol> <li><code>userin</code> 用户登入通知,事件的内容(数据)是登入的用户 <code>user</code>,这是广播事件(所有客户端都收到)</li> <li><code>onlineusers</code> 当前在线的所有用户,这是广播事件。</li> <li><code>groups</code> 当前用户所加入的群聊。单播(这个词或许用的不够准确。只有该用户自己收到)。</li> </ol></li> <li>当用户在输入框打字时,广播 <code>typing</code> 事件,事件内容(数据)为当前打字的用户,客户端收到该事件后,<code>显示 xxx 正在输入中...</code>,然后 <code>1.5s</code> 后隐藏该提示。</li> <li>输入完毕,按下回车键,发送 <code>message</code> 事件到服务端,服务端收到后,把这信息广播到所有客户端,客户端收到后把该信息压入 <code>messages</code> 数组,然后滚动条(如果有)滚动到底部。</li> </ol> <p>千言万语还不如一张图,还是直接上图吧。 <img src="//www.pcdeng.com/uploads/chat.png" alt="React Chat" /></p> <h2>主要依赖有</h2> <ul> <li><code>react</code> 前端框架,包括路由处理。</li> <li><code>axios</code> http 请求。</li> <li><code>date-fns</code> 日期、时间处理。</li> <li><code>lodash-es</code> 工具类。</li> <li><code>socket.io-client</code> websocket 客户端。</li> </ul> <h2>还没解决的问题有:</h2> <ol> <li>用户随意加入群聊(或者申请加入某个群聊)。</li> <li>用户创建群聊。</li> <li>用户切换群聊。</li> <li>单聊,怎样设计表结构?和群聊一样,群里只有两个人,如果采用这设计,群聊的名称和最后一条信息就要动态算了,有点麻烦?</li> <li>接收每个群新的信息包括最后一条和还有多少条未读。</li> <li>换头像后,要重新登录才能生效。</li> </ol> <p>有兴趣的朋友可以看 </p> <ul> <li><a href="https://github.com/paytondeng/chat">github</a> </li> <li><a href="http://chat.pcdeng.com">Live demo</a></li> </ul>
详情
<h1>MySQL</h1> <h2>历史与现状</h2> <p><code>MySQL</code> 是一个关系型数据库管理系统,由瑞典 <code>MySQL AB</code> 公司开发,2008 年被 <code>SUN</code> 公司收购,2009年 <code>SUN</code> 公司被 <code>Oracle</code> 收购,现它属于 <code>Oracle</code> 旗下产品。</p> <h2>个人的一些习惯</h2> <ul> <li>数据命名。数据库一般用项目的名字,如 <code>mini_clubs</code>,用 <code>_</code> 连接单词。</li> <li>表名。一般加前缀且单词用复数,如 <code>mc_users</code>,其中 <code>mc</code> 是由 <code>mini clubs</code> 的首字母组成。</li> <li>字符编码与字符集。字符编码一般使用 <code>utf8mb4</code>,字符集一般使用<code>utf8mb4_general_ci</code>。<code>utf8mb4</code> 是 <code>MySQL 5.5.3</code> 增加的,<code>mb4</code> 就是 <code>most bytes 4</code> 的意思,专门用来兼容四字节的<code>unicode</code></li> </ul> <h2>实例</h2> <p>以下实例都是我在做项目<a href="https://book.quest.ink">悦阅</a> 和 小咔吧微信小程序时总结下来的。</p> <ol> <li> <p>更新同表内的字段为另一字段 语法 <code>UPDATE 表名 SET 字段名=值</code></p> <pre><code class="language-sql">UPDATE book_borrowers SET created_at = (expire_at - 30 * 24 * 60 * 60); UPDATE books SET remark = REPLACE(remark, 'A', 'B'); UPDATE mc_users SET email = lower(email);</code></pre> </li> <li> <p>查询活动成员</p> <pre><code class="language-sql">SELECT u.NAME, u.email, DATE_FORMAT( FROM_UNIXTIME( m.joined_at ), '%Y-%m-%d %H:%i:%s' ) AS joinedAt FROM mc_activity_members AS m LEFT JOIN mc_users AS u ON u.id = m.user_id WHERE activity_id = 26 ORDER BY m.joined_at DESC;</code></pre> </li> <li> <p>导出数据库</p> <pre><code class="language-sql">mysqldump -uroot -p --databases clubs > clubs.backup20190825.sql</code></pre> </li> <li> <p>创建数据库</p> <pre><code class="language-sql">CREATE DATABASE IF NOT EXISTS clubs_backup DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;</code></pre> </li> <li> <p>借阅书 7 天后将过期</p> <pre><code class="language-sql">SELECT users.name AS '用户', books.title AS '图书', FROM_UNIXTIME( book_borrowers.created_at ) AS '借书日', FROM_UNIXTIME( book_borrowers.expire_at ) AS '最后还书日', TIMESTAMPDIFF( DAY, FROM_UNIXTIME( UNIX_TIMESTAMP( NOW())), FROM_UNIXTIME( book_borrowers.expire_at ) ) AS '还有几天到期', books.state AS '图书状态' FROM book_borrowers, books, users WHERE books.id = book_borrowers.book_id AND book_borrowers.user_id = users.id AND book_borrowers.deleted_at IS NULL AND book_borrowers.send_email_at IS NULL /*AND books.state = 2*/ AND book_borrowers.expire_at <= ( UNIX_TIMESTAMP( NOW()) + 7 * 24 * 60 * 60 ) ORDER BY expire_at ASC;</code></pre> </li> </ol> <p><img src="//www.pcdeng.com/uploads/MySQL.png" alt="MySQL基础" /></p> <h2>参考</h2> <p><a href="https://blog.csdn.net/zyj66666/article/details/74003041">MySQL的发展历程</a></p>
详情
<h2>for in 和 for of</h2> <h3>定义</h3> <pre><code>for (variable of iterable) { // statements }</code></pre> <p>其中 <code>iterable</code> 可以是 <code>Array</code>,<code>Map</code>,<code>Set</code>,<code>String</code>,<code>TypedArray</code>,<code>arguments</code> 包括显式实现可迭代协议的对象等等</p> <h3>for...of 和 for...in 的区别</h3> <pre><code class="language-javascript">Object.prototype.objCustom = function() {}; Array.prototype.arrCustom = function() {}; let iterable = [3, 5, 7]; iterable.foo = 'hello'; for (let i in iterable) { console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom" } for (let i in iterable) { if (iterable.hasOwnProperty(i)) { console.log(i); // logs 0, 1, 2, "foo" } } for (let i of iterable) { console.log(i); // logs 3, 5, 7 }</code></pre> <h2>结论:</h2> <ol> <li><code>for key in obj</code> 会遍历可枚举属性,包括继承下来的属性;</li> <li><code>for item of iterable</code> 会遍历 <code>iterable</code> 可迭代的值,这些是数组元素,而不是任何对象的属性。</li> </ol> <p>参考 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...of">for...of</a></p>
详情
<blockquote> <p>G2 是一套基于可视化编码的图形语法,以数据驱动,具有高度的易用性和扩展性,用户无需关注各种繁琐的实现细节,一条语句即可构建出各种各样的可交互的统计图表。</p> </blockquote> <p>上手教程:<a href="https://www.yuque.com/antv/g2-docs/get-started">快速上手</a> 示例:<a href="https://antv.alipay.com/zh-cn/g2/3.x/demo/index.html">G2 示例</a></p> <p>昨天接到一个任务要实现:<img src="//www.pcdeng.com/uploads/dashboard-design.png" alt="设计图" /></p> <p>以前用过 <a href="https://www.highcharts.com/demo/pie-basic">Hightcharts</a> 感觉相当麻烦。</p> <h2>Hightcharts</h2> <p>一个饼图的示例代码长这样子: 注:以下代码都假设你已经导入了相应的库。</p> <pre><code class="language-html"><div id="container"></div></code></pre> <pre><code class="language-javascript">Highcharts.chart('container', { chart: { plotBackgroundColor: null, plotBorderWidth: null, plotShadow: false, type: 'pie' }, title: { text: 'Browser market shares in January, 2018' }, tooltip: { pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>' }, plotOptions: { pie: { allowPointSelect: true, cursor: 'pointer', dataLabels: { enabled: true, format: '<b>{point.name}</b>: {point.percentage:.1f} %' } } }, series: [{ name: 'Brands', colorByPoint: true, data: [{ name: 'Chrome', y: 61.41, sliced: true, selected: true }, { name: 'Internet Explorer', y: 11.84 }, { name: 'Firefox', y: 10.85 }, { name: 'Edge', y: 4.67 }, { name: 'Safari', y: 4.18 }, { name: 'Sogou Explorer', y: 1.64 }, { name: 'Opera', y: 1.6 }, { name: 'QQ', y: 1.2 }, { name: 'Other', y: 2.61 }] }] });</code></pre> <p>个人感觉 <code>Highcharts</code> 的套路是配置式的,它内置好各种图表的默认配置,你要做的就是,配置它是什么类型图表,给 <code>series</code> 喂数据。</p> <h2>Antv G2</h2> <p>来看看 Antv G2 的套路,直接上代码。</p> <pre><code class="language-html"><div id="chart"></div></code></pre> <pre><code class="language-javascript">var data = [{ item: '事例一', count: 40, percent: 0.4 }, { item: '事例二', count: 21, percent: 0.21 }, { item: '事例三', count: 17, percent: 0.17 }, { item: '事例四', count: 13, percent: 0.13 }, { item: '事例五', count: 9, percent: 0.09 }]; var chart = new G2.Chart({ container: 'chart', forceFit: true, height: window.innerHeight }); chart.source(data, { percent: { formatter: function formatter(val) { val = val * 100 + '%'; return val; } } }); chart.coord('theta', { radius: 0.75 }); chart.tooltip({ showTitle: false, itemTpl: '<li><span style="background-color:{color};" class="g2-tooltip-marker"></span>{name}: {value}</li>' }); chart.intervalStack().position('percent').color('item').label('percent', { formatter: function formatter(val, item) { return item.point.item + ': ' + val; } }).tooltip('item*percent', function(item, percent) { percent = percent * 100 + '%'; return { name: item, value: percent }; }).style({ lineWidth: 1, stroke: '#fff' }); chart.render();</code></pre> <p>G2 的套路是,定义好数据,然后用 G2 的图形语法把数据转换为图表,它是以数据驱动的,同一数据结构可以用不同的图形语法画不同的图表。个人感觉更直观,更好操控。</p> <p>最后来看看用 G2 还原设计图 <img src="//www.pcdeng.com/uploads/dashboard-g2.png" alt="实现图" /></p> <p>结论:G2 更直观,更好操控图形的细节。</p>
详情
<p>今年是大学毕业的第六个年头了,我依然走在前端的路上。 简单总结一下这六年走过的路。</p> <table> <thead> <tr> <th>技能</th> <th>备注</th> </tr> </thead> <tbody> <tr> <td>Angular</td> <td>熟练</td> </tr> <tr> <td>Less/Sass</td> <td>熟练</td> </tr> <tr> <td>Rxjs</td> <td>熟练</td> </tr> <tr> <td>TypeScript</td> <td>熟练</td> </tr> <tr> <td>微信小程序</td> <td>熟练</td> </tr> </tbody> </table> <p>做了三个比较完整的微信小程序</p> <h2>小咔吧小程序</h2> <p><img src="//www.pcdeng.com/uploads/kaba.png" alt="小咔吧" /></p> <h2>嘉豪轩微信小程序</h2> <p><img src="//www.pcdeng.com/uploads/jhx.png" alt="嘉豪轩" /></p> <h2>Stayplease 微信小程序(未上线)</h2> <p><img src="//www.pcdeng.com/uploads/sp1.png" alt="SP" /></p>
详情
<p>我实验的条件 Mac 系统版本<code>10.14 (18A391)</code> 要了解 GPG 是什么,请移步 <a href="http://www.ruanyifeng.com/blog/2013/07/gpg.html">GPG 入门教程</a></p> <p>最终实现的效果看<a href="https://github.com/PaytonTang/paytontang.github.io/commits/master">这里</a>,看 commit <code>193eba8</code> 有个 <code>Verified</code> 标志。</p> <p>我这次只是想记录下步骤;</p> <ol> <li>去 <a href="https://sourceforge.net/p/gpgosx/docu/Download/">下载</a></li> <li>双击 <code>GnuPG-2.2.11.002.dmg</code> 安装 GnuPG。版本或许会有差异,我 2018-12-10 下载到的是 <code>2.2.11.002</code></li> <li>测试一下是否安装成功 <code>pgp2 --help</code> 如果有一大片信息出来,证明安装是成功的,如 <pre><code>gpg (GnuPG) 2.2.11 libgcrypt 1.8.4 Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.</code></pre></li> <li>生成秘钥 <pre><code>gpg2 --gen-key</code></pre> <p>按提示输入对应信息(名字和邮箱)</p></li> <li>导出公钥 <pre><code>gpg2 --armor --output public-key.txt --export [用户ID]</code></pre></li> <li>导入公钥到 <code>GitHub</code>。在当前目录打开 <code>public-key.txt</code> 复制全部内容到 '<a href="https://github.com/settings/keys">https://github.com/settings/keys</a>' 然后 <code>new GPG key</code> 粘贴 <code>GPG</code> 公钥到此输入框。</li> <li>参照 <a href="https://help.github.com/articles/telling-git-about-your-signing-key/">Telling Git about your GPG key</a> 告诉 <code>git</code> 你的 <code>GPG key</code></li> <li>然后试一下 <code>commit -S -m '测试 GPG 签名'</code> </li> <li>单独设置某一个项目提交时要签名 <pre><code class="language-sh">git config user.signingkey [GPG key] git config commit.gpgsign true</code></pre></li> </ol> <h2>参考</h2> <p><a href="http://www.ruanyifeng.com/blog/2013/07/gpg.html">http://www.ruanyifeng.com/blog/2013/07/gpg.html</a></p> <p><a href="https://www.yezhongqi.com/archives/1658.html">https://www.yezhongqi.com/archives/1658.html</a></p> <p><a href="https://help.github.com/articles/telling-git-about-your-signing-key/">https://help.github.com/articles/telling-git-about-your-signing-key/</a></p>
详情
<p>前台移除了 <code>bootstrap</code>,样式使用原生 <code>LESS</code> 来写。 因为本身页面和结构就很简单,我想尽量做的简单一些,毕竟 <code>Simple is the best.</code> 颜色搭配和字体参考了 <a href="http://www.ruanyifeng.com/blog">阮一峰的网络日志</a></p>
详情
<p><code>Node.js</code> LTS 版本目前已经是 <code>12.13.1</code>, 依稀记得第一次接触 <code>node.js</code> 是 <code>0.2.x</code>, 时间飞快,自从 <code>node.js</code> 问世以来,前端工具如雨后春笋,前端开发工程师能做的也更多。 下面我们来窥探一下 <code>node.js</code> 是如何创建一个 <code>http</code> 服务的。</p> <h1>快速创建一个HTTP服务</h1> <pre><code class="language-javascript">const http = require('http'); const hostname = '127.0.0.1'; const port = 3000; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Hello World\n'); }); server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); });</code></pre>
详情
<p>最近在做一个审批的项目,需求如下: 某个用户申请加入某一俱乐部,待俱乐部管理员审核过后才算正式加入该俱乐部。 审核记录表结构</p> <pre><code>CREATE TABLE `ClubMembers` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(11) unsigned NOT NULL, `club_id` int(11) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;</code></pre> <p>用户和俱乐部关系表结构</p> <pre><code>CREATE TABLE `JoinClubLogs` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(11) unsigned NOT NULL COMMENT '用户id', `action` int(11) unsigned NOT NULL COMMENT '动作,1:申请加入;2:部长拒绝;3:部长允许;4:用户退出俱乐部', `club_id` int(11) unsigned NOT NULL COMMENT '俱乐部id', `created_at` int(11) unsigned NOT NULL COMMENT '申请时间或者处理时间', `handler_id` int(10) unsigned DEFAULT NULL COMMENT '处理人id,俱乐部负责人id', `remark` varchar(255) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;</code></pre> <p>当 action 为3时,向 ClubMembers 表插入一条记录,表明用户已经正式加入俱乐部。 那么查询待审批记录应该怎样写?比如 A 俱乐部有多少人是在申请加入俱乐部的?</p> <pre><code>const joinLogs = await mysql.select('l.id', 'l.user_id as userId', 'l.club_id as clubId', 'c.name as clubName', 'u.name as userName') .from('JoinClubLogs as l') .innerJoin('Clubs as c', 'l.club_id', 'c.id') .innerJoin('Users as u', 'l.user_id', 'u.id') .joinRaw('left join (select distinct m.user_id, m.club_id, 1 as test_column from ClubMembers as m) as ll on (l.user_id = ll.user_id AND l.club_id = ll.club_id)') .where('l.action', 1) .whereNull('ll.test_column') .orderBy('l.created_at', 'desc');</code></pre> <p>以上是基于 <a href="https://knexjs.org/"><code>knex</code></a> 库写的查询语句,</p> <blockquote> <p>knex 是一个SQL查询构建器</p> </blockquote>
详情