首页
留言
动态
归档
推荐
音乐
工具
Search
1
Emby公益服-上万部电影电视剧免费看
61,846 阅读
2
openwrt-docker部署lxk0301京东自动签到脚本
12,523 阅读
3
QuantumultX-京东签到撸京东豆
10,867 阅读
4
LXK0301京东签到脚本-自动提交互助码
9,310 阅读
5
微信-域名被封监测以及自动更换被封域名
8,986 阅读
随便写写
科学上网
Web开发
瞎折腾
Search
标签搜索
quantumultx
laravel
openwrt
laravel nova
laradock
telegram
薅羊毛
google adsense
jd_scripts
京东签到
ubuntu
oh-my-zsh
web开发环境
nginx
工具
shadowsocks shadowsocksR
RBAC
权限管理
内网穿透
Python
orzlee
累计撰写
44
篇文章
累计收到
595
条评论
首页
栏目
随便写写
科学上网
Web开发
瞎折腾
页面
留言
动态
归档
推荐
音乐
工具
搜索到
44
篇与
orzlee
的结果
2019-01-28
微信-域名被封监测以及自动更换被封域名
前言 微信已经成为大多数中国人的一种依赖,发展好了自然管理就更加严格,对于不符合微信营销规范的的网站统统不允许访问,就连对手的网址也无法在微信浏览器中打开。 最近公司有个需求,在各个平台大量投放广告,微信内域名频繁被封,需要域名监测和替换的内部系统功能。微信开发做过不少,但是这种明显是找漏子钻。 想不到就上网找吧!把Google翻个底朝天,全TMD是广告,各种出租微信域名防封检测API,真的一条有用的信息都没有。这下真得靠自己了。 那些出售API的到底是用的什么方法? 我琢磨了一会,想不到还能又什么方法能检测域名是否被微信封掉,只有在微信APP或桌面微信内置浏览器才行啊。抓包看看请求域名前有没有什么检测API,也没有发现什么有价值的信息。后来想想也不可能写个程序,人家请求API检测域名就用程序在桌面微信打开链接判断是否被封吧。真的有点悬,这难道有内部接口? 我是如何找到方法的? 在年中的时候我发现一个微信很坑爹的问题,那时候在高铁上改代码历历在目。“微信短链接”把orzlee坑惨了,在公司内部系统做了个短链接功能,每次手动替换被封域名都会自动生成微信短链接,这下倒好,生成的短链接不到一小时就封,替换了好几个域名,最后用长连接明显存活时间更长。 最坑爹的还不是这个,微信短链接跳转域名被封,短链接在任何浏览器打开都会如下提示: 功能编写思路 Nice,有办法了,我把需要检测的域名都生成一个微信短链接,然后隔段时间访问短链接判断是否重定向正确的域名或者判断重定向域名是否weixin110.qq.com。 整套功能编写思路: 域名都在阿里云,充分利用域名分组功能,总共分三组域名池-正常使用域名-弃用域名。整套域名管理使用API处理 域名池 域名池域名无任何指向,完全未使用的正常域名(没有被微信封,有指向也可以,那就再替换域名逻辑要做好处理了)。 正常使用域名 目前正常使用的域名 弃用域名 已经被微信封或者域名备案丢失 每隔一段时间检测域名是否正常,这个有两种情况: 域名正常 皆大欢喜(^_^)。 域名被封(备案丢失) 替换被封域名 说说被封的处理思路,因为公司内部系统已经自动化站点配置,现在加入阿里云API简直如虎添翼。域名替换使用固定短链接,要求使原域名链接依然可以正常访问。这点很好做,302重定向即可。域名使用记录表中记录新域名,旧域名被访问(使用以一个域名专门用作跳转域名,不做其他使用,携带参数能找到数据库中的旧域名。这个域名最好自己注册备案一个),重定向至新域名,固定链接不会变动,使用缓存减轻服务器压力。 移动被封域名至弃用域名分组 域名池取出域名移动至正常使用域名 分组 添加域名解析 修改nginx配置新域名 记录已经替换的新域名(同一条数据多一个new_domain字段,一旦换新域名就替换。也可以直接替换,只是之前配置的旧域名就找不到了,看业务需求吧) 这样整个思路就很清晰了。 核心功能 有了思路就简单多了,准备开搞。 我把监测功能写成laravel command,方便自己使用,任务调度也没问题。 在测试监测代码时发现一个问题,访问频率太快容易403。尝试替换User Agent,client-ip等头部信息没用。最后只有一个办法了,HTTP代理。网上虽然有很多免费的,但是这东西不稳定那就是自己刨坑,公司有几台服务器,全部搭建HTTP代理,都是以前购买的阿里云服务器,就为了备案而买的。 搭建HTTP代理 安装tinyproxy sudo apt-get update sudo apt-get install tinyproxy 编辑配置文件: Port 8888 代理端口可自己修改 Allow 127.0.0.1 填写需要使用代理的服务器IP,只有该IP的服务器才能访问 nano /etc/tinyproxy.conf Ctrl+o 保存 Enter 确认 Ctrl+x 退出编辑器 启动重启停止命令 service tinyproxy start service tinyproxy restart service tinyproxy stop 阿里云服务器无备案检测 阿里云服务器配置未备案服务器会返回403状态码,按照状态码判断是否无备案。这里就需要保证域名池中的域名是无解析或者有解析但是配置正确,绝对不能自己搞出个403误判,这点应该是没问题的。 代码编写 use GuzzleHttp\Client; use GuzzleHttp\Exception\ConnectException; use Illuminate\Console\Command; use Illuminate\Http\Response; use Illuminate\Support\Facades\Event; use Lijianmin\DomainDetection\Events\BanDomainEvent; use Lijianmin\DomainDetection\Models\Domain; class DomainMonitorCommand extends Command { /** * The console command name. * * @var string */ protected $name = 'domain-detection:monitor'; /** * The console command description. * * @var string */ protected $description = 'monitor domain name is ban.'; const SLEEP_TIME = 2; const USER_AGENTS = [ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)", "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)", "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20", "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", ]; const PROXY = [ '', //本机 ... //自己搭建的代理 ]; /** * Execute the console command. * * @return void */ public function handle(Client $client) { //找出正在使用的域名,阿里云域名API获取域名列表,存入数据库。表中'detail'字段我保留了API返回的当前域名原始信息。 //'short_url'字段为保存时生成的微信短链接 $query = Domain::whereNotNull('short_url') ->where(\DB::raw("detail->'$.DomainGroupId'"), config('domain_detection.ali.group_id')) ->where('status', true); foreach ($query->cursor() as $index => $domain) { $result = $client->get($domain->short_url, [ 'allow_redirects' => false, 'headers' => [ 'USER-AGENT' => array_random(self::USER_AGENTS), //获取随机'USER-AGENT' ], 'proxy' => array_get(self::PROXY, ($index % count(self::PROXY))), //循环使用使用代理 'http_errors' => false, //屏蔽4xx 5xx状态码异常 ]); //访问微信短链接,获取重定向域名 $redirect_domain = $this->extractDomain($result->getHeaderLine('Location')); //如果重定向域名是否与当前数据域名匹配,否则被封了 if (strpos($redirect_domain, $domain->domain) !== false) { try { //微信没封再来检测备案 if ($client->get($redirect_domain, [ 'http_errors' => false, 'allow_redirects' => false, //如果无备案域名配置在当前服务器,自己访问是正常的,一定要其他主机访问才会返回403 'proxy' => last(self::PROXY), ])->getStatusCode() != Response::HTTP_FORBIDDEN) { $domain->touch(); //更新updated_at 时间戳,刷新最近检测时间 sleep(self::SLEEP_TIME); //HTTP代理比较少,频率低点比较好 continue; } } catch (ConnectException $exception) { if ( !$exception->getCode()) { $domain->touch(); sleep(self::SLEEP_TIME); continue; }; throw $exception; } } //被封了或者掉备案了,标记状态 $domain->status = false; $domain->save(); //这个事件主要处理域名更换操作,移动阿里云域名分组等等 Event::fire(new BanDomainEvent($domain)); sleep(self::SLEEP_TIME); } } // 提取连接中域名 public function extractDomain($url) { if (empty($url)) { return ''; } return array_get(parse_url($url), 'host'); } } 阿里云API看文档就好了,阿里云API-PHP-composer,对照文档使用。 提醒下API类: 例如http://domain.aliyuncs.com,看域名前缀找API。找到需要的API类后链式调用就API名称就好了,其实蛮友好的,只是文档没有说明。 //http://domain.aliyuncs.com AlibabaCloud::domain()->v20180129()->updateDomainToDomainGroup() //http://domainIntl.aliyuncs.com AlibabaCloud::domainIntl()->v20171218() 结语 幸好被微信短链接坑过,才会长记性,遇到其他问题居然把坑给利用起来了。其实核心就一个,用微信短链接来判断域名是否正常是目前想到的最简单的放法了,也不知道那些出售API的是不是有更好的方法,如果需求量大的话就找更多的HTTP代理,不然还是有点担心代理不够用,到时候全是403,不过403解封状态也很快,但是这种硬杠的做法还是不好,代理越多保证万无一失。
2019年01月28日
8,986 阅读
0 评论
2 点赞
2019-01-28
Google adsense 人工验证PIN-西联汇款-收款
前言 Google adsense邮寄第三封PIN码已经过去三个月了,已经不抱任何希望了。最后还是通过人工验证跳过PIN,这几天去办了张光大银行的储蓄卡,方便接收西联汇款。今天已经收到Google adsense签发的西联汇款,说说具体流程吧。 国内无法收到PIN码,人工验证 国内收到PIN码的几率非常小,有的人可以收到,但是绝大部分我相信这只是个传说,国内情况就不多说了,基本当废纸卖。 在第三封PIN码邮寄超过一个月就可以通过人工验证的方式跳过PIN码了。附加文件就上传身份证正反面好了,拍照扫描都行。广告发布商ID可以在账号-设置-账号信息中查看。 提交人工验证表单后,Google会很快审核,审核通过会发送邮件通知。刷新页面后,页面顶部红色验证PIN警告会消失,但是首页卡片还在,只是PIN输入次数变成了0次。 付款方式 过了PIN就简单了,毕竟PIN耽误了两个多月时间了。在国内最优的方式肯定是西联汇款,速度快还没有任何手续费,皆大欢喜。目前中国可以办理西联汇款的银行蛮多的,不过最方便的orzlee还是推荐中国邮政-西连汇款、光大银行-西联汇款,其中光大银行最方便,网银或柜台收汇一次后,可以手机APP收汇,真的非常方便。orzlee还是推荐先办卡后在来填写收款方式。 点击付款新增付款方式,选择西联汇款,名字和姓名输入自己姓名全拼。 至于大小写问题,以光大银行为例,登陆网银或者手机银行(手机银行搜索西连汇款),网银选择出国金融-西连汇款-西连收汇里面就可以看到收汇信息,这时按照银行填写最准确。如果没有看到信息,打银行电话转人工设置即可。 收汇 orzlee是在下午3点半左右填写的付款方式,Google adsense在次日凌晨2:28分签发汇款。 点击付款,选择最近的交易 点击说明文字查看西联汇款单 以光大银行西联收汇为例,第一次收汇必须使用网银或者前往柜台,网银选择出国金融-西连汇款-西连收汇,仔细看西连汇款单,信息全部都已经给你了,发汇人国家看汇款单最后一行,监控号码和汇款金额,其他信息自己补充填写,收汇人职业就写自己的职业好了。后面需要填写居住信息,非常简单。之后就可以使用手机APP收汇了。 结语 收美金不是第一次,收Google的美金还是第一次,整体体验和效率都很棒,关键Google adsense收入真的非常高。有很多事情只有自己尝试了才能深有体会,orzlee还是希望大家多多动手,实践比理论收益的经验更多。
2019年01月28日
4,821 阅读
0 评论
0 点赞
2019-01-22
Laravel 5.7 Broadcast + laravel echo 实现 WebSocket C/S 实时通信
前言 laravel Broadcast 平时项目中接触得比较少,最近公司年会活动需要做一个抽奖项目。要求用一太手机控制其他在抽奖页面的用户同时开始抽奖以及显示抽奖结果。提出项目需求时第一个想到的就是WebSocket,相对于使用AJAX轮询请求来说,WebSocket的实时性要高出不少,而且AJAX轮询请求服务器有太多不必要的请求,对于搞开发的来说这种不必要的请求就应该避免,不做多余操作。以前在项目中使用的WebSocket,也是抽奖项目,但是不是基于laravel框架,这次项目确实动力十足。 实现流程 使用laravel Broadcast广播系统(使用Redis) 触发Broadcast广播事件 laravel-echo-server 通过Redis收听到Broadcast广播事件 laravel-echo-server 使用socket.io下发给laravel-echo laravel-echo接收事件对象 实现步骤 配置 在app.php中启用广播系统服务提供者 /* * Application Service Providers... */ ... App\Providers\BroadcastServiceProvider::class, ... 修改.env中BROADCAST_DRIVER配置位置redis ... BROADCAST_DRIVER=redis ... laravel redis客户端扩展: 查看database.php中redis['client'] 如果使用predis请安装composer包 composer require predis/predis 如果使用phpredis就需要自己编译PHP的reids扩展。 phpredis效率要高于predis,按自己项目需求取舍 自定义配置文件config/lottery.php: 在该项目中使用的是扩展包开发,配置文件中有些命名空间应该修改为自己对应的,可以先定义,看完文章后再回过头来就明白了。 return [ /* * 频道名称 */ 'channel_name' => env('LOTTERY_CHANNEL_NAME','sweepstakes'), /* * 路由路径 */ 'route' => 'lottery', /* * 控制器 */ 'controller' => \Lijianmin\Lottery\Contollers\LotteryController::class, /* * 频道验证 */ 'broadcast' => \Lijianmin\Lottery\Broadcasting\LotteryChannel::class, /* * 缓存 */ 'cache' => [ 'prefix' => 'lottery-cache', 'expiration_time' => 5, //默认过期时间,小时 'redis_cache_db' => env('REDIS_CACHE_DB',1), ], ]; 注册广播事件 项目中事件包括 活动开始、入场、抽奖开始、开奖。 由于合作开发,我将它写出扩展包,定制接口方便对接。 首先创建出所需事件,如果不需要扩展包开发可以: php artisan make:event event-name 或者 在app/Providers/EventServiceProvider.php成员变量$listen内追加: /** * The event listener mappings for the application. * * @var array */ protected $listen = [ 'App\Events\EventName' => [ 'App\Listeners\ListenserName', ], ... ]; ... 然后执行生成事件命令 php artisan event:generate 事件注册完毕后需要实现Illuminate\Contracts\Broadcasting\ShouldBroadcast接口,需要实现broadcastOn方法,该方法返回一个频道或者一个频道数组。broadcastAs方法自定义广播名称,我希望还是自己定义,不然后面laravel-echo会很尴尬: use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class ActivityStart implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $option; public function __construct($option = []) { $this->option = $option; } public function broadcastOn() { return new PresenceChannel(config('lottery.channel_name')); } public function broadcastAs(){ return 'activityStart'; } } config('lottery.channel_name')应该修改为自己定义的频道名称,或者也可以像orzlee一样使用配置文件定义,方便修改。 每个事件都需要创建对应的文件,如果只是为了通知前端可以不需要事件处理器。 对于频道分为三种: Channel-普通频道。 PrivateChannel-私有频道,需要自定义授权认证。 PresenceChannel-类似于私有频道,但是比私有频道更加高级。 频道授权 首先自己创建一个频道: php artisan make:channel LotteryChannel 在routes/channels.php中添加频道(主要是频道授权验证): Broadcast::channel(config('lottery.channel_name'),config('lottery.broadcast')); 实现授权验证方法: PrivateChannel私有频道只需要返回true或者false,PresenceChannel频道在验证成功就需要返回一个用户信息数组,验证失败返回false或者null。 class LotteryChannel { ... /** * Authenticate the user's access to the channel. * * @param \App\User $user * @return array|bool */ public function join() { if(\Auth::check()){ $user = \Auth::user(); return [ 'user' => [ 'id' => $user->id, 'name' => $user->names, ], 'isLotteryDraw' => $this->lotteryService->isLotteryDraw() ]; } return false; } } 搭建laravel-echo-server服务: 安装最新版NodeJS和npm(如果已经安装可以跳过): curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash - sudo apt-get install -y nodejs ##如果需要指定版本修改链接中的数字即可 curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - 进入项目目录执行: 先把需要安装的一次性装完,orzlee已经写成command,部署服务器一行命令就全部安装好了 npm install -g laravel-echo-server npm i --save socket.io-client npm i --save laravel-echo npm install 初始化laravel-echo-server: 按实际情况填写,选错了也不要紧,到时候在修改 laravel-echo-server init 项目下会生成laravel-echo-server.json配置文件: 注意authHost,devMode,port,protocol几个重要参数就好了。 { "authHost": "http://127.0.0.1:8000", "authEndpoint": "/broadcasting/auth", "clients": [], "database": "redis", "databaseConfig": { "redis" : {}, "sqlite": { "databasePath": "/database/laravel-echo-server.sqlite" } }, "devMode": true, "host": null, "port": "6001", "protocol": "http", "socketio": {}, "sslCertPath": "", "sslKeyPath": "", "sslCertChainPath": "", "sslPassphrase": "", "subscribers": { "http": true, "redis": true }, "apiOriginAllow": { "allowCors": false, "allowOrigin": "", "allowMethods": "", "allowHeaders": "" } } 启动laravel-echo-server: laravel-echo-server start 前端laravel-echo对接 修改resources/js/bootstrap.js(5.6文件在public目录下): 默认内容是注释了的,而且使用的是pusher,说实话pusher是付费服务,而且服务器都在国外,对国内实在是不友好。有想法的可以去了解下,每天有免费请求限制,主要是用于开发调试。 /** * Echo exposes an expressive API for subscribing to channels and listening * for events that are broadcast by Laravel. Echo and event broadcasting * allows your team to easily build robust real-time web applications. */ import Echo from 'laravel-echo' window.io = require('socket.io-client'); // window.Pusher = require('pusher-js'); window.Echo = new Echo({ broadcaster: 'socket.io', host: window.location.hostname + ':6001' }); 其实做完这一步就可以打包编译NodeJS了: prod代表生产环境,如果还需要调试可以使用dev或者watch,watch模式文件改动会自动编译。 npm run prod 前端页面配置事件监听: 注意监听事件名称,如果在上文中使用了broadcastAs命名事件名称,那么只需要在名称前加入.就好了,不然你需要把命名空间以.分割,例如:App.Lottery.ActivityStart,实在是太罗嗦了。 <!-- Scripts --> <script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script> <script src="{{ asset('js/app.js') }}"></script> <script> let lottery_server = Echo.join('{{ config('lottery.channel_name') }}'); lottery_server.here(function (users) { //本来使用私有频道,但是找了半天也没有找到连接成功的事件才改用`PresenceChannel`频道, //在`here`事件会在连接成功返回所有当前频道的用户信息 console.log(e) }); lottery_server.joining(function (user) { //新用户加入频道返回用户信息 console.log(e) }); lottery_server.listen('.activityStart', (e) => { //活动开始 console.log('activityStart',e); }); lottery_server.listen('.admission', (e) => { //入场,其实这个事件在`PresenceChannel`频道下已经多此一举了 console.log('admission',e); }); lottery_server.listen('.newcomerAdmission', (e) => { //新用户加入,这个也是多余的 console.log('newcomerAdmission',e); }); lottery_server.listen('.lottery', (e) => { //摇奖事件 console.log('lottery',e); }); lottery_server.listen('.lotteryResult', (e) => { //摇奖结果事件 console.log('lotteryResult',e); }); </script> 到此整个前对接已经完成。 后端事件触发 其实最简单的就是后端。例如用户入场,用户登陆后触发AdmissionEvent事件,在定义事件时构造函数中设定前端所需的参数,数组也好对象也好,都会传给前端。 发送入场事件: event(new AdmissionEvent()); 每个事件我都会在构造函数中添加option数组,除了必须传递的参数以外方便附加其他参数。 结语 像这种类似于C/S模式开发要考虑的情况非常多,用户已经入场,关闭浏览器再次入场情况,首先名单不能重复添加,已中奖用户在入场时必须实时传递,在开奖状态中用户离开页面,然后在进入页面也应该保持前端正在开奖的效果,所以开奖状态要实时传递。还有很多很多需要考虑的问题,然后经过不停的测试。在年会上需要开31个奖,真是心惊胆战的,测试是还是有些问题的,还好在年会上没有出大漏子(真心不是后端问题,前端开奖结果效果问题),包括后端都去调试前端开奖动画效果,真心累。自从写好laravel-echo部署上线,真的没有出现过问题,还是很稳定的。测试时,最多30多台手机,活动开始之前有个图片遮罩,活动开始事件触发全部向上拉开遮罩,真的很壮观。多台手机之间延迟非常小,体验很不错,年会现场效果也是非常棒。
2019年01月22日
5,281 阅读
0 评论
0 点赞
2019-01-10
广告投放,广告收入
前言 Google给我邮寄的PIN已经发出第三封了,我估计国内能妥投的概率非常低(都当废纸卖了),等月底了上传资料验证身份好了。这几天一直在研究广告,包括广告收入和广告投放。之前没接触过广告这一块,不过多操作几次还是能得到不少经验的。 广告收入 网站有些访问量了,闲着没事干想着申请了Google Adsense投入广告。其实站点访问量并不大,但是好奇想着试试网站盈利是一种怎样的体验... Google Adsense的收入对于orzlee来说只是一个尝试,其中出现了很多简写单词: CPC - Cost PerClick 每次点击费用,即点击单价。 CPM - Cost Per Mile 千次展示费用,广告展示一千次需要支付的费用。 eCPM - Effective Cost Per Mille 千次展示收入。 RPM - Revenue Per Mille 千次展示收入(与eCPM同意,只是不同的广告联盟名称有所不同)。和CPM类似,RPM是针对广告展示商(比如Adsense商户)而言的。 CTR - Click-throughRate 点击率,点击次数占展示次数的百分比。 作为广告流量主来说这些最基本的还是要了解。Google Adsense目前自动广告虽然已经非常聪明了,但是还达不到完美,如果懒得折腾确实非常方便。很多样式并不是特别友好,甚至有些体验很糟糕。 orzlee就在一个以手机流量为主的站点使用过自动广告,经常展示广告会把整个网站向下顶,广告显示在网站顶部。说实话我都不想用了,其实是网站前端代码一塌糊涂,Google也无能为力。自动广告主要还是要靠前端布局规范(说实话我真的不喜欢折腾HTML,特别是css,这些才是真正的无聊),那样自动广告就会非常融洽,怎么也不会那么突兀。 最近也试了一下Google Adsense广告单元,只是可以稍微自定样式,这样出来的广告就比较完美了。而且Google Adsense的广告很聪明,移动端会自适应布局,不会乱七八糟。 Google Adsense几乎是所有广告联盟中收益最高的了,但是也非常严格。orzlee也试过其他的广告商,如popcash、exoclick这些,说真的一天收入不如Google Adsense几次点击。貌似他们只算展示量,点击次数用来计算CTR,而CTR会提高你的eCPM。也就是说点击率高,你的千次展示收入就会越高,纯靠展示量获得收入,如果展示量很高但是点击率低,那你网站流量对该广告没有起到什么作用。归根结底就是展示广告目的就是引导用户广告发布商的网站(貌似现在没什么注册、安装的广告收入了,不过这类广告确实恶心),网站流量大,广告没人点,自然没什么收入。就连Google AdsenseRPM也是如此,一直在不停的波动,只不过Google Adsense计算点击收益,这个收益真的非常可观,我见过一次点击1.x美金,真是豪。 还忽略了一点,是我最喜欢也是最讨厌的,那就是AdBlock。这估计是所有站长的克星,现在用AdBlock的人真的非常多,这减少站长一笔收入,很烦。这东西真的没什么好的办法,Pornhub 使用WebSocket绕过广告屏蔽插件确实很犀利,但是对于一般的站长都是依赖广告联盟,想绕过广告联盟展示广告没什么资源。也有很多站长会提示自己的用户,要求对自己网站关掉AdBlock,甚至有些更加严格,网站都不让用户看。其实做站不容易,屏蔽掉哪些恶心的弹窗,入侵式广告就好了。 广告投放 广告投放只是小试牛刀,体验一下。我在popcash上投放的广告,最低0.5美金千次展示。 刚开始几乎没有怎么过滤流量,但是设置了单日最大金额,我设置的是1美金,创建一个Campaigns,一直在pending。等我过了两分钟再刷新立马1.3美金消费,我都设置单日最大限额1美金了,居然都拦不住,再去看站点流量真是猛,流量非常大,但是因为投放没有过滤,进来的流量几乎都是无效流量,短短几分钟就退回平常值了。我暂停了Campaigns,重新编辑过滤,限制非常严格,让流量更加精准有效。现在几乎一小时花0.01美金,流量不会瞬间上去了。 观察了几天流量小了很多,但是质量要高不少,最起码进来的流量停留时间要比开始长得多。毕竟不是做垃圾站,量多不如量精。首先我限制了流量语言,orzlee的站点并不是多语言站,先限制中文,国家也只选了几个中文用户较多的。还有一个就是流量来源,popcash做的不是很好,只有比较简单的来源筛选,其他的就看自己了。比如说浏览器、操作系统、设备。 其实广告投放还是谨慎点,多花点时间观测流量趋势,如果太多流量质量不好赶紧修改广告计划,不求流量大,只求流量精准。像个人站长广告投放可不能像大公司一样财大气粗,最重要的还是要自己站点质量,要留的住客户才行。 结语 什么事情都自己尝试一下,经验收获比看书看文章强多了。做网站运营广告是第一收入来源,当然不包括收费站点。终于知道站长有多恨AdBlock了,但这个是双向的,如果以前广告不那么恶心,也不至于逼得作者开发AdBlock,友好的广告也没人讨厌。中国垃圾站群真是多如牛毛,超恶心,搜索引擎一搜全是垃圾站。原创文章也越来越少了,写文章很花时间,多尊重作者吧。自己尝试了就明白他人的难处,总之多动手,学到的经验比现在赚到的钱价值高多了。现在学到的知识,以后会用金钱成倍的返还给你。
2019年01月10日
7,059 阅读
2 评论
1 点赞
2018-12-28
逗比根据地打不开,doub.io无法解析,最新逗比根据地镜像地址
前言 doub.io从12月7号就发现已经无法解析域名,显然出事了。在逗比根据地看过不少文章,很可惜。 备份站 orzlee在网上找到一些doub.io的备份站点: 镜像站 doubmirror.cf 镜像站 doubibackup.com 逗比云备份镜像站 softsmirror.cf 逗比云备份镜像站 doubiyunbackup.com 逗比根据地的GitHub地址 ToyoDAdoubi 以上地址可能大多数被墙,需要代理访问。 结语 陆陆续续感觉有好多大佬都销声匿迹了,还记得2013年的时候GoAgent科学上网,每隔几天就找代理IP,到现在自己搭建科学上网服务器,途中大佬们项目都被迫删除,希望平安。
2018年12月28日
6,988 阅读
0 评论
0 点赞
2018-12-18
laravel nova 权限管理工具
前言 前几个月一直在折腾laravel nova,不得不说它的扩展性非常强。laravel nova packages每天都会有很多新鲜的扩展包,生态很好。spatie laravel-permission是非常不错的Role-based access control RBAC扩展包,orzlee在laravel 5.3就在使用了,这里主要说说spatie laravel-permission的laravel nova权限管理工具。 Larval nova packages 目前有三款laravel nova packages包是基于spatie laravel-permission开发的,功能上大同小异,各有特色。 1. vyuldashev/nova-permission: 这是最早出现基于spatie laravel-permission的laravel nova packages。支持自定义资源,本地化支持。 orzlee一开始也是使用的这款扩展包,但是没几天我就发现了一个问题,因为laravel nova的Relationships并不会调用spatie laravel-permission自带的权限或角色附加、分离方法,所以在给用户添加权限时不会触发权限缓存更新。这就必须等待缓存过期或者手动清理缓存。 这个问题一直没有解决,orzlee也关注了Issues:Cache not invalidated on attaching a role or permission to user好长一段时间,然而并没有进展。最后还是提问者重新创建了一个新的包insenseanalytics/laravel-nova-permission。 2. insenseanalytics/laravel-nova-permission: 这款包主要是使用ForgetCachedPermissions中间件来判断当前路由是否为附加或分离请求(attach,detach),然后过滤出附加或分离权限的请求,执行缓存清除。 分离时没有问题,但是附加在路由判断上有点问题。orzlee在测试的时候发现附加怎么也无法清除缓存,经过调试发现url没有匹配到。 class ForgetCachedPermissions { /** * Handle the incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * * @return \Illuminate\Http\Response */ public function handle($request, $next) { $response = $next($request); if ($request->is('nova-api/*/detach') || $request->is('nova-api/*/*/attach/*')) { ##作者判断了attach,但是orzlee的请求路由为:http://127.0.0.1:8000/nova-api/admins/1/attach-morphed/permissions $permissionKey = (Nova::resourceForModel(app(PermissionRegistrar::class)->getPermissionClass()))::uriKey(); if ($request->viaRelationship === $permissionKey) { app(PermissionRegistrar::class)->forgetCachedPermissions(); } } return $response; } } orzlee已经向作者提交Issues了。 orzlee在给作者RP包语言本地化时有一个权限搜索翻译的功能没有被作者合并。作者觉得使用自定义资源功能让用户自行实现,这样也不错,不过度化设计。 权限是由开发者添加,而使用的时候基本上是管理员在操作。现在添加权限是一个搜索选择器,管理员需要给某个用户或管理员添加权限就必须搜索。权限在添加时为了多语言化必用标识,管理员在搜索时按照本地化语言搜索会方便很多。 orzlee还是把实现方法分享出来吧: GuardFilterHandelTrait主要功能是按guard_name过滤掉不属于附加用户的权限,防止多guard_name权限混乱。 use Laravel\Nova\Http\Requests\NovaRequest; trait GuardFilterHandelTrait { /** * Override the applyFilters method to add the guard_name condition when filtering * * @param \Laravel\Nova\Http\Requests\NovaRequest $request * @param \Illuminate\Database\Eloquent\Builder $query * @param array $filters * * @return \Illuminate\Database\Eloquent\Builder */ protected static function applyFilters(NovaRequest $request, $query, array $filters) { $query = parent::applyFilters($request, $query, $filters); if ($model = head($request->__memoized)) { $guard_name = $model->guard_name ?? self::getGuardForModel(get_class($model)); $query->where('guard_name', $guard_name); } return $query; } /** * @param string model * * @return string|null */ public static function getGuardForModel(string $model) { return collect(config('auth.guards')) ->map(function ($guard) { return config("auth.providers.{$guard['provider']}.model"); })->search($model); } } PermissionSearchTranslationTrait搜索权限时会按照本地化语言反向找出对应权限。 trait PermissionSearchTranslationTrait { use GuardFilterHandelTrait; /** * Override the applyFilters method,title field translation */ public function title() { return __('laravel-nova-permission::permissions.display_names.'.$this->name); } /** * Rewrite the applySearch method to apply translation field search * * @param \Illuminate\Database\Eloquent\Builder $query * @param string $search * @return \Illuminate\Database\Eloquent\Builder */ protected static function applySearch($query, $search) { return $query->where(function ($query) use ($search) { if (is_numeric($search) && in_array($query->getModel()->getKeyType(), ['int', 'integer'])) { $query->orWhere($query->getModel()->getQualifiedKeyName(), $search); } $model = $query->getModel(); $connectionType = $query->getModel()->getConnection()->getDriverName(); $likeOperator = $connectionType == 'pgsql' ? 'ilike' : 'like'; $trans_search = array_keys(preg_grep("/$search/",array_dot(__('laravel-nova-permission::permissions.display_names')))); foreach (static::searchableColumns() as $column) { $qualify_column = $model->qualifyColumn($column); foreach ($trans_search as $t_search){ $query->orWhere($qualify_column, $likeOperator, '%'.$t_search.'%'); } $query->orWhere($qualify_column, $likeOperator, '%'.$search.'%'); } }); } } 自定义Role & Permission资源(注意命名空间替换成自己项目对应的): use App\Traits\Resources\GuardFilterHandelTrait; use \Insenseanalytics\LaravelNovaPermission\Role as BaseRole; class Role extends BaseRole { use GuardFilterHandelTrait; } use App\Traits\Resources\PermissionSearchTranslationTrait; use \Insenseanalytics\LaravelNovaPermission\Permission as BasePermission; class Permission extends BasePermission { use PermissionSearchTranslationTrait; } 按照作者文档自定义角色和权限类: // in app/Providers/NovaServiceProvider.php public function tools() { return [ // ... \Insenseanalytics\LaravelNovaPermission\LaravelNovaPermission::make() ->roleResource(CustomRole::class) ->permissionResource(CustomPermission::class), ]; } 如果你还没有发布语言文件请执行: php artisan vendor:publish --provider="Insenseanalytics\LaravelNovaPermission\NovaPermissionServiceProvider" 创建自己本地化语言文件,例如在项目路径./resources/lang/vendor/laravel-nova-permission/创建一个zh-CN文件夹,复制en中的所有文件到zh-CN,然后自己翻译。display_names就是权限name对应的本地化翻译。 'display_names' => [ 'test' => '测试', 'delete' => '删除', 'edit' => '编辑', 'create' => '创建', ], 现在应该支持本地化权限搜索了。或者可以直接使用包jianminlee/laravel-nova-filter。 该包还支持基于laravel nova的权限验证,非常方便,在nova资源中使用PermissionsBasedAuthTrait Trait,然后定义一个静态变量: class YourNovaResource extends Resource { use \Insenseanalytics\LaravelNovaPermission\PermissionsBasedAuthTrait; public static $permissionsForAbilities = [ 'viewAny' => 'view products', 'view' => 'view products', 'create' => 'create products', 'update' => 'update products', 'delete' => 'delete products', 'restore' => 'restore products', 'forceDelete' => 'forceDelete products', 'addAttribute' => 'add product attributes', 'attachAttribute' => 'attach product attributes', 'detachAttribute' => 'detach product attributes', ]; .... } 详细文档可以取项目地址查看。insenseanalytics/laravel-nova-permission 3. DigitalCloud/nova-permission-tool: 今天刚刚发布的的,该包根据资源自动生成对应权限,听上去高大上,但是会多出不少多余的权限。因为我们的资源不一定会实现所有功能,到时候添加权限就会眼花缭乱了。orzlee认为还是自定义的好。 作者还自定义角色和权限的字段,在操作上体验好很多。凡是都有两面性,如果项目比较大(权限多)的情况下体验就可想而知了。 结语 这三款包都各有特色,同一功能的包就有这么多,这些还都只是基于spatie laravel-permission的,还有其他作者自己实现的权限管理工具。回过头来,还是得靠自己,工具再多也不一定都适合自己的项目,好好学Vue吧!
2018年12月18日
5,684 阅读
0 评论
0 点赞
2018-12-05
tmux强大的终端复用软件
前言 tmux功能非常强大,可以让用户在一个终端内管理多个分离的会话,窗口及面板,对于同时使用多个命令行,或多个任务时非常方便。orzlee其实一直把他作为后台工具来使用。 安装 tmux安装非常简单: apt update apt install tmux 像GCP服务器就自带tmux。 基本使用 创建一个新会话: tmux [new -s 会话名 -n 窗口名] ########### tmux new -s frp 大多数情况没有使用过-n参数。 列出所有会话: tmux ls 恢复会话: tmux at [-t 会话名] ########### tmux at -t frp 关闭会话: tmux kill-session -t 会话名 ########### tmux kill-session -t frp 组合键命令(crtl+b) 以下命名都必须在crtl+b组合键按下之后输入: 会话 :new<回车> 启动新会话 s 列出所有会话 $ 重命名当前会话 窗口 (标签页) c 创建新窗口 w 列出所有窗口 n 后一个窗口 p 前一个窗口 f 查找窗口 , 重命名当前窗口 & 关闭当前窗口 调整窗口排序 swap-window -s 3 -t 1 交换 3 号和 1 号窗口 swap-window -t 1 交换当前和 1 号窗口 move-window -t 1 移动当前窗口到 1 号 分割窗格 % 垂直分割 " 水平分割 o 交换窗格 x 关闭窗格 ⍽ 左边这个符号代表空格键 - 切换布局 q 显示每个窗格是第几个,当数字出现的时候按数字几就选中第几个窗格 { 与上一个窗格交换位置 } 与下一个窗格交换位置 z 切换窗格最大化/最小化 同步命令到其他窗格 可以将 :setw synchronize-panes on/off 其他 d 退出 tmux(tmux 仍在后台运行) t 窗口中央显示一个数字时钟 ? 列出所有快捷键 : 命令提示符 其他不常用的就不写了。 一般使用: ##创建会话 tmux new -s test ##ctrl+b % 垂直分割窗口 ##ctrl+b " 水平分割窗口 ... ##ctrl+b q [1/2/3/4/5]切换窗格 ##ctrl+b d 让tmux后台 ##恢复会话 tmux at -t test ##挨个执行 exit 或者 kill tmux kill-session -t test 结语 tmux功能强大,熟练操作后真的很方便。特别是退出会话后恢复,进程不会终止,一直在后台运行。至于更多功能可以去看看Tmux 使用手册。
2018年12月05日
3,264 阅读
0 评论
0 点赞
2018-12-05
frp内网穿透
前言 在项目中经常涉及到需要验证域名的API开发,这些API开发需要在线调试。通常我们可以解析域名到本地,但是国内运营商封端口、无外网ip,线上调试又相对麻烦。frp是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp, http, https 协议。让它实现内网穿透,域名解析到服务器,让服务器通过其他端口访问本地web服务,轻松实现API调试。 安装使用 搭建frp服务需要一台服务器,基本无配置要求。 下载frp(frp发布地址): wget https://github.com/fatedier/frp/releases/download/v0.21.0/frp_0.21.0_linux_amd64.tar.gz 解压tar.gz: tar -zxvf frp_0.21.0_linux_amd64.tar.gz 编辑服务端配置文件: cd frp_0.21.0_linux_amd64 nano frps.ini 如果不许要其他功能,frps.ini保留下面三行即可(端口可自行更改) ############### [common] bind_port = 4443 ##frp服务端口 vhost_http_port = 80 ##http协议访问端口 启动服务: ./frps -c ./frps.ini 后台运行可以使用tmux工具,安装使用可以看orzlee的另一篇文章tmux强大的终端复用软件 客户端(windows) windows 32位 windows 64位 包含服务端和客户端。 编辑客户端配置文件: 解压zip后进入目录,编辑frpc.ini(推荐编辑器Sublime Text)。 [common] server_addr = x.x.x.x ###服务端ip地址或域名 server_port = 4443 ###服务端端口 [web] type = http ###http https local_port = 8000 ###本地web服务访问端口 custom_domains = www.yourdomain.com ###服务器域名地址 启动客户端: ./frpc -c ./frpc.ini 现在通过浏览器访问 http://www.yourdomain.com 即可访问到处于内网机器上的 web 服务。 结语 有了内网穿透,对于验证域名的API开发要省事不少,本地调试修改代码都非常方便,提高开发效率。而部署frp比较简单,当然还有更多功能,可以看看中文文档。
2018年12月05日
3,352 阅读
0 评论
1 点赞
2018-12-03
Python打包exe和解包
前言 之前自己接触过Python一段时间,不过那只是自己无聊瞎折腾。Python使用空格缩进划分代码块,强制规范了代码得可读性。最近公司有个项目,由Python打包的windows可执行程序,项目已经有了,只不过由于没有更新导致无法使用,需要解包调整代码后再打包。程序不是orzlee写的,也没有多少Python经验,不过会一种编程语言,其他的语言也就是语法差异了。 解包 Python解包使用pyinstxtractor.py,将pyinstxtractor.py和将要解包的.exe放到同一文件夹后执行: python pyinstxtractor.py example.exe 解包后会在当前目录生成example.exe_extracted文件夹。 如果Python打包的可执行程序是Python 2.7版本,那么example.exe_extracted文件夹中应该直接解包出了pyc文件,其他版本还需要一步。 在example.exe_extracted文件夹中找到没有后缀的文件(在文件夹一级目录,不用找其他目录),一般就是主要的pyc文件了(orzlee这个解包后就是main)。 由于其他版本的Python解包后缺少文件头,所以需要加上: 需要十六进制编辑器(orzlee随便在网上找了一个Hxd)。 先将main文件复制一份,重命名为main.pyc 先打开文件夹中其他pyc后缀的文件,复制文件头。 打开main.pyc将文件头粘贴后保存。 安装uncompyle6: pip install uncompyle6 转换pyc文件: uncompyle6 main.pyc > main.py 至此已经解包成功。 打包 修改完代码后再打包回exe可执行文件,需要pyinstaller。 安装pyinstaller: pip install pyinstaller 还有其他方法pyinstaller Downloads。 打包: pyinstaller -F main.py 一定要加上-F参数,不然不会打包成一个文件。 部分参数说明: -F 生成单个可执行文件; -D –onedir 创建一个目录,包含exe文件,但会依赖很多文件(默认选项)。 -w 表示去掉控制台窗口,这在GUI界面时非常有用。命令行程序请无视!; -c –console, –nowindowed 使用控制台,无界面(默认); -p 自定义需要加载的类路径,一般用不到; -i 表示可执行文件的图标。 结语 python打包成exe可执行文件还是第一次见,打包、解包又长见识了。还好这个python程序非常简单,不然难度真的很大。总之经验是累积起来的,只要有Google在,这点困难还是能应付的。
2018年12月03日
5,295 阅读
0 评论
3 点赞
2018-12-01
oh-my-zsh强大的zsh配置管理
前言 大多数linux系统都是使用bash作为Unix shell,但是zsh比bash更为强大。zsh有很多快捷功能,命令提示、智能补全、快速跳转等等。zsh拥有很多开源插件、主题、还有很多“让你尖叫的东西”。 安装 在安装oh-my-zsh之前,先安装zsh: apt install zsh 然后切换成zsh: chsh -s /bin/zsh ##切换回bash chsh -s /bin/bash 现在我们来安装oh-my-zsh: ##curl sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" ##wget sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)" 现在应该是这个样子 oh-my-zsh自带了很多主题,真的好多。oh-my-zsh Themes主题预览选一个自己喜欢的主题,记下名字,修改配置文件: nano ~/.zshrc 请注意:如果你以前是使用bash,切记把bash环境变量加载:去掉高亮语句前面的# 或者文件末尾添加 source ~/.profile 保存ctrl+o 敲回车键 退出ctrl+x 重新应用配置文件: source ~/.zshrc 推荐两款插件: zsh-autosuggestions zsh-syntax-highlighting 插件:zsh-autosuggestions zsh-autosuggestions是一款能记住历史命令的插件,在输入命令时会提示历史输入过的命令。 oh-my-zsh插件安装都非常简单: git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions 然后编辑配置文件,启用插件: nano ~/.zshrc ##ctrl+w 搜索 plugins= ##添加插件名 plugins=( git zsh-autosuggestions ) 保存ctrl+o 敲回车键 退出ctrl+x 然后重新应用zsh配置文件: source ~/.zshrc 安装完以后,输入过的命令都会被记录,当下次再输入时会提示历史的命令。按下->方向键就可以选用。 插件:zsh-syntax-highlighting zsh-syntax-highlighting是一款语法高亮插件,在输入错误的命令时会显示错误语法。 安装插件步骤和上文一样: clone 插件到oh-my-zsh插件目录 git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting 添加插件到zsh配置文件 plugins=( [plugins...] zsh-syntax-highlighting) 应用配置 source ~/.zshrc 在输入错误的命令时会高亮红色提示,当输入正确后会取消高亮显示。 常见问题 其使用中还蛮多坑的,例如 wget https://xxx.xxx.xxx "zsh: no matches found: https://xxx.xxx.xxx" ,这是由于 zsh 导致的,在缺省的情况下,zsh 始终自动解释命令后的参数,传不到给 wget 解释: nano ~/.zshrc ... setopt no_nomatch ##在文件中加入这行 ... 保存ctrl+o 敲回车键 退出ctrl+x 然后重新应用zsh配置文件: source ~/.zshrc 小键盘失效解决办法: cat >> ~/.zshrc << EOF ########### ## Keypad ## 0 . Enter bindkey -s "^[Op" "0" bindkey -s "^[Ol" "." bindkey -s "^[OM" "^M" ## 1 2 3 bindkey -s "^[Oq" "1" bindkey -s "^[Or" "2" bindkey -s "^[Os" "3" ## 4 5 6 bindkey -s "^[Ot" "4" bindkey -s "^[Ou" "5" bindkey -s "^[Ov" "6" ## 7 8 9 bindkey -s "^[Ow" "7" bindkey -s "^[Ox" "8" bindkey -s "^[Oy" "9" ## + - * / bindkey -s "^[Ok" "+" bindkey -s "^[Om" "-" bindkey -s "^[Oj" "*" bindkey -s "^[Oo" "/" ############## EOF source ~/.zshrc 国内安装 使用gitee镜像:sh -c "$(curl -fsSL https://gitee.com/mirrors/oh-my-zsh/raw/master/tools/install.sh \ | sed 's|^REPO=.*|REPO=${REPO:-mirrors/oh-my-zsh}|g' \ | sed 's|^REMOTE=.*|REMOTE=${REMOTE:-https://gitee.com/${REPO}.git}|g')" 安装插件,自动补全、高亮、建议 zsh-syntax-highlighting zsh-autosuggestions zsh-completions:git clone https://gitee.com/yuhldr/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting && git clone https://gitee.com/yuhldr/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions && git clone https://gitee.com/yuhldr/zsh-completions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-completions 添加插件等操作按照上文操作即可。 结语 有了oh-my-zsh,看到的终端终于不再是单调的颜色了,而且更加聪明了,在输入错误的命令后也不用盯着一个个字母去找了。当然这都只是oh-my-zsh的冰山一角,还有很多强大的功能等着你去发现。
2018年12月01日
5,330 阅读
0 评论
1 点赞
1
...
3
4
5