分类:php

服务器再搬家

昨天刚好是7月13号,距离我把主服务器从 sakura vps 迁出刚好整一年。这一年里我使用的是论坛成员推荐的海星云服务器。虽然存在比较大的丢包情况,但在七牛 cdn 的加成下总体也还算比较平稳。但在上个月,因为备案失效而被迫从中国大陆地区迁出的口袋维基迁移到这台服务器的时候,每个月 1TB 的流量配额就显得特别捉襟见肘了。

VPS 6月流量总图

为此,我还特意把剩余的几个月加钱升级了高配,就为了多买一点流量。临近一年整,靠升配续命终归不是正道,于是服务器再次搬家就成为了一个议题。

这不,好容易有个闲暇时间,赶紧把家搬了。

这次选用的是 Wenjing network 旗下的 HostKVM 。看 IP 分配,感觉跟之前海星在同一个机房,但丢包情况得到了不少改善。按照老规矩,观察几天,看看情况。

替换 Discuz! 6 的默认验证码

PTB 今年已经是第 11 年了,代码也是 10 年前的代码了。
这次又遇到什么问题了呢?就是我们的验证码机制被攻破了。
而且其实挺遗憾的,因为 PTB 最初的安全设计是严进宽出,即你注册需要有相当繁琐的步骤和漫长的验证期,之后几乎没有什么限制。

然后很尴尬,我们被撞库了。

就是前一分钟我们还在微信群里面谈笑风生一分钟之后刷出来发现对面那个人的 ID 在论坛灌了 1000 多个台湾找小姐这样。

然后我就按照之前双子的思路打开了验证码。

但是很尴尬, Discuz 6 有一个很严重的逻辑漏洞。这其实也不是逻辑漏洞,是当年大家对 ajax 的理解都不到位而已。

按照惯例,我们来读代码。

DISCUZ_ROOT./ajax.php:64


...
} elseif($action == 'checkseccode') {

	if($seclevel) {
		$tmp = $seccode;
	} else {
		$key = $seccodedata['type'] != 3 ? '' : $_DCACHE['settings']['authkey'].date('Ymd');
		list($tmp, $expiration, $seccodeuid) = explode("\t", authcode($_DCOOKIE['secc'], 'DECODE', $key));
		if($seccodeuid != $discuz_uid || $timestamp - $expiration > 600) {
			showmessage('submit_seccode_invalid');
		}
	}
	seccodeconvert($tmp);
	strtoupper($seccodeverify) != $tmp && showmessage('submit_seccode_invalid');
...

以及

DISCUZ_ROOT./include/global.func.php:888


function submitcheck($var, $allowget = 0, $seccodecheck = 0, $secqaacheck = 0) {
	if(empty($GLOBALS[$var])) {
		return FALSE;
	} else {
		global $_SERVER, $seclevel, $seccode, $seccodedata, $seccodeverify, $secanswer, $_DCACHE, $_DCOOKIE, $timestamp, $discuz_uid;
		if($allowget || ($_SERVER['REQUEST_METHOD'] == 'POST' && $GLOBALS['formhash'] == formhash() && (empty($_SERVER['HTTP_REFERER']) ||
			preg_replace("/https?:\/\/([^\:\/]+).*/i", "\\1", $_SERVER['HTTP_REFERER']) == preg_replace("/([^\:]+).*/", "\\1", $_SERVER['HTTP_HOST'])))) {
        		if($seccodecheck) {
        			if(!$seclevel) {
        				$key = $seccodedata['type'] != 3 ? '' : $_DCACHE['settings']['authkey'].date('Ymd');
        				list($seccode, $expiration, $seccodeuid) = explode("\t", authcode($_DCOOKIE['secc'], 'DECODE', $key));
        				if($seccodeuid != $discuz_uid || $timestamp - $expiration > 600) {
        					showmessage('submit_seccode_invalid');
        				}
        				dsetcookie('secc', '', -86400 * 365);
        			} else {
        				$tmp = substr($seccode, 0, 1);
        			}
        			seccodeconvert($seccode);
        			if(strtoupper($seccodeverify) != $seccode) {
        				showmessage('submit_seccode_invalid');
        			}
				$seclevel && $seccode = random(6, 1) + $tmp * 1000000;
        		}
			if($secqaacheck) {
        			if(!$seclevel) {
        				list($seccode, $expiration, $seccodeuid) = explode("\t", authcode($_DCOOKIE['secq'], 'DECODE'));
        				if($seccodeuid != $discuz_uid || $timestamp - $expiration > 600) {
        					showmessage('submit_secqaa_invalid');
        				}
        				dsetcookie('secq', '', -86400 * 365);
        			}
        			require_once DISCUZ_ROOT.'./forumdata/cache/cache_secqaa.php';
        			if(md5($secanswer) != $_DCACHE['secqaa'][substr($seccode, 0, 1)]['answer']) {
        			        showmessage('submit_secqaa_invalid');
        			}
				$seclevel && $seccode = random(1, 1) * 1000000 + substr($seccode, -6);
        		}
			return TRUE;
		} else {
			showmessage('submit_invalid');
		}
	}
}

不难看出,其实对于验证码的过期,后台的判断只是基于时间(600秒)和是否是属于这个用户。
这其实是有很大的问题的,因为没有重试次数限制,对于不复杂的非中文验证码,一秒钟发出几千个请求cover掉所有数字和大部分字母组合是很轻松的事情。
因此干脆就把它从代码里面摘掉。

主要修改的是下面几个文件。因为现在就是这么做的,因此具体的细节就不展开说了。

include/javascript/post_editor.js
摘掉里面对原有验证码的前端校验

template/seccheck.htm
摘掉原有验证码的展示代码,同位置增加极验的前端代码

ajax.php
屏蔽掉前端校验接口
global.func.php
common.inc.php
摘掉原有验证码后端校验的内容部分,保留 Cookies 校验部分
增加极验后端校验代码

总结一下,其实 discuz 6 在代码设计上真的是完美无缺的。这么老的代码改起来也没有什么不适感(最起码比在公司里吃的那些 s**t 要好得多太多了)。基本就是搜“submit_seccode_invalid”就可以解决这次的问题。
总之双站都是靠极验老版本去 cover 机器人了,毕竟防御价值没那么高,就不去针对 geetest v3 做适配升级了。
屏蔽掉机器人就已经解决绝大部分问题了。

一次古典互联网服务器迁移记录

严格上说,我们这一代前站长(因为现在事实上没有高活跃度会员),赶上的是 Web2.0 刚刚开始起步的那段时间。
谁能想到两三年前 PHP 已经被划作前端范畴了,而最近这一两年前端技术又这么发达了呢。
然而既然决定了保留这些互联网遗迹,那就得让他们在目前为止最好的工作状态下工作。

2007 年春,口袋根据地站长 nfopo 宣布暂时关站,同期,我们筹划成立口袋社区 Poke The BBS (当时英文名叫 Pokemon The BBS)。那时候开一个网站的成本是非常低的。互联网刚刚跨进 Web2.0 ,人人分享的平台还是每人在计算机上创建自己的网站。 CNNIC 联合国内的域名服务商推出了 1 元甚至 0 元购买 .cn 域名的活动。口袋社区最早是以 poketb.cn 作为主域名的,后来购入了同名的 .com 域名,并沿用至今。
poketb.cn whois
最早的口袋社区是 Discuz! 4.1F ,服务器是现在仍然在僵尸的梦游科技美国合租。彼时还没有智能 DNS 和 CDN 这样的高科技玩意儿,以至于现在 PTB 的代码里面还有这样的历史遗迹。
ptb 分站列表
其实后来 PTB 的命途也比较坎坷。无非就是我们比口袋根据地的站长年纪小了几分,最后大家都没有逃过高考这一关。从梦游科技迁出后, PTB 先后托管在 iFastNet 、 vultr vps 上。因为国内互联网环境日趋复杂,导致总有一些会员无法成功访问网站。这也就是导致论坛会员日趋减少的一个重要原因。
我是大概 2013 年的时候决定重新将网站开放出来的。当时的想法也比较简单,就是无论自己当年多么中二,这段记忆总还是要在的。毕竟还是一个可以向后人吹牛逼的资本:“看,你爹当年就是对这个玩意儿上瘾了” —— by liuyanghejerry
之后在潘达的帮助下我将服务器迁移到了日本的 sakura vps 。樱花当年的速度是真好,服务也比较稳定。
sakura vps
只不过美中不足的是,樱花也是个古典 vps 提供商。它们的账户甚至还需要人工去确认日本本地地址。而 vps 除了稳定之外,在性价比上更是一塌糊涂。前一年我使用单台机器承载了所有流量。这在一台双核 1G 内存的机器上来说真是个大型的挑战。要知道 MySQL + PHP-fastcgi 可是吃内存磁盘的大户。机器常常被拖得十分卡顿。于是在大概一年以前我拜托潘先生另外购买了一台同机房的 sakura 节点用于单独跑 MySQL ,并拿来做 SS 节点。
现在来看这个决策是很正确的,虽然多花了一点钱,但是可以有效承载更大的用户流量。这也为成功托管口袋双子星打下了一个良好的基础。
然后一年前接手了口袋双子星,并为其在口袋社区服务器的 php5.6 环境下运行做了大量的改动。
近几年,互联网,特别是移动互联网进一步普及,使得中国的互联网环境空前复杂。面试、考试中简单的从一台计算机 A 连接到服务器 B 这样理想的情况已经不复存在(虽然看上去还是那样的)。于是在去年晚些时候我又花了一些时间为双子、口袋社区部署了一个用于 CDN 的域名 suicune.cn 并进行了加速。
然而,还是很慢。发现原因是 sakura vps 到大陆的速度变的越来越差,于是就有了这次迁移。
这次迁移出于平衡的考虑(因为数据库节点不涉及到访问中国大陆,因此只要在日本就好),我选择了一台仍然在日本但是速度相对较好的 vps 。经过测试后有中文客服,并且速度还不错的海星云成为了我的首选。
接下来的事情就是测试一下稳定性,以决定是否继续使用了。

============2018.06.04更新。

先说结论吧:总体来说服务的稳定性没有 sakura 好。
毕竟是二道贩子嘛,比不过自建线路的地头蛇的。刚好在敏感时期,刚迁移完就发现有奇怪的问题。这次迁移之后我对 vps 本身的改动是有三个:其一,升级了 linux 内核到 4.3 ,默认开了 bbr ;其二,调整了 php5.6-fpm / php7-fpm 的编译参数,这个测试没有什么影响; 其三,测试几个小流量站点直接 Caddy – PHP ,去掉了中间的 nginx。第三条这个测试后来摘掉了,因为发现维护起来实在是太麻烦了。
总体来说还是维持 外部流量 – Caddy – Nginx – php5.6/7 – 代码 的这个逻辑。按理来说迁移应该是无感的, 因为环境配置和代码位置甚至临时文件位置都是一样的。但是依然出现了奇怪的丢包。
现在在怀疑是特殊时期导致的,等这两天过去之后再确认一下吧。

UGC 和街机音乐游戏

2011 年下半年我升入了大学。在高中被压抑了几年的打太鼓的兴趣被解放出来。
那会儿的互联网已经比较发达了。虽然还是 Web2.0 初期,但是人们已经热衷于把各种线下的情报搬到线上。
比如说,大型游戏机爱好者异地出勤(在外地去当地的游戏机厅)的时候,需要的位置信息。
当时,我们也做了一个用户主动发布记录的游戏厅位置记录网站,叫做“太鼓太鼓”。
基于ThinkPHP 3.0 做的。
啊,ThinkPHP 3.0 已经是 6 年前的事情了吗?

源代码:https://github.com/Woodu/taiko.tk

在同时期,除了贴吧、人人网等公开的信息渠道之外,还有诸如中国太鼓联盟论坛(cntaiko,已关站)等一大批基于已有程序的网站。
刚刚举的这些例子分别是几种不同的 UCG 平台,以“太鼓太鼓”为代表的程序,包括通过 mediawiki 等 wiki 建站程序建立的情报网站,是单纯的信息交换平台,这类网站没有任何社交属性,只是单纯的提供各地游戏机厅分布信息等的储存和展示。而人人网(当时)提供的公共主页之类的更类似于现在的微博金V 号,由管理者人工筛选一些跟主题相关的话题(比如游戏内容、游戏新情报或者单纯的地区交友贴),然后发动成员(会员)进行交流。

至于贴吧、 cntaiko 之类的论坛(公告板)性质的网站,其存在的社交性质是最浓厚的。这类平台是大部分玩家进行游戏外交流的主要途径。当然,除了贴吧之外的其他论坛更多的是以“大神分享——玩家讨论大神分享”为主要内容。直到今天的 BEMANICN 、 sows 等论坛,也是同样的模式,只不过现在的“大神”已经不是一个人,而是一个组织了。

上面所说的这些渠道所存在的核心价值,在于其本身能提供的信息价值能否保证会员持续留存。比如说 wiki ,里面的信息是否及时和准确,如果是作为参与者能否获得满足其需要的回报(论坛积分),再比如 BEMANICN 和 sows 提供的积分和积分所可以换取的服务(网络服务、游戏程序资源)。

并不是说论坛到现在为止就彻底失去了其意义,只是人们讨论非资源相关的吹水活动已经不可逆转地转移到了更方便发言和更难以追踪的即时通讯工具上了。

到了新时期 ( 2012 年之后 ) ,经历过论坛关站潮和贴吧清理之后,很多玩家,特别是以地区为划分的区域性玩家群体已经转为相对比较封闭的 QQ 群等方式进行日常的交流。这种方式让玩家们之间有了明确的界限,玩家之间的阶层划分变得更加明确。此时,玩家主要的交流也从简单的游戏信息交流转变成了以游戏为主题的综合社交。

由此可见,玩家之间,特别是同时期的玩家之间逐渐由公开转变为封闭社交。这带来的结果就是新、旧玩家之间的信息交流进一步割裂。除此之外,因为“熟人效应”,新玩家,特别是水平不够高或者跟老玩家认知不同的玩法的新玩家更容易遭到老玩家的排斥和抵触(舞萌游戏机“拆机”(游玩时比较用力导致机器损耗程度变快的玩法)事件、乐动魔方圈插队吵架,等等),甚至是群起而攻之。这也导致很多玩家“拒绝混圈”。

另一方面,在大型游戏机的老家日本,因为新款的游戏机台已经逐渐网游化,之前很多民间维护的信息已经由官方信息进行了取代。比如世嘉社的 maimai 和 chunithm 在 Aime.net 上都可以很方便地查找到每一台机器的所在地,这也使得最初很多民间维护的网站失去了意义。所以很多地区交流的玩家转为了直接从官方的网站分享自己的游玩记录,这也使得一些玩家间的交流变成了单纯的游戏记录分享。

梳理清楚这条脉络之后,我们也要认识到,每个人对于同一个事物的认知是有先后的。以《太鼓达人》为例,虽然目前大陆通行的太鼓达人街机依然以 10 年前的《太鼓达人 12 亚洲版》为主,依然有新的玩家进入并参与讨论。这本身是街机游戏的性质所致,即游戏本身玩法确定,以总参与人数胜过服务某一批特定玩家。其原因是街机机台本身是摆放在公共空间的,公共主页、群等作为在其之上派生出的交流渠道不可能覆盖每一个游戏机台的玩家。那么在这种情况下,每个在这些交流渠道上产生的内容它们的价值又在哪里呢?

我认为,价值主要体现在两个方面。

第一,游戏玩家确实需要一定程度的游戏外社交。大型游戏街机发展几十年,早于家用机一辈,从出现在公共场所(游戏机厅)的第一天开始,就承载了一定的社交角色。从最早的游戏机房内的社交,转变到至今的网络刷卡,在线对战,不变的核心仍然是其保留的竞技性。竞技性促使玩家投入更多的金钱(游戏币)来精进自己的技术,从而获得其他玩家的对自己的认可。这期间增加的花费也是大型游戏机设计的本意。

当然,这里面不包含给庄家送钱的垃圾赌博机。

第二,用户(游戏玩家)产生的内容可以作为游戏运营的参考。虽然整个日系游戏机已经完全退出中国市场,通过华立等代理商来进行运营,但是我们也可以看到,最初舞萌的官方运营微博——精文世嘉舞萌maimai 官方账号确实在微博上作为一个活跃度很高的存在来让各地玩家感受到游戏官方的运营诚意。即使精文世嘉已经结束运营这么多年,通过海外渠道传播进来的同款游戏的情报,也让 maimai 成为了国内为数不多的活跃且广泛的音乐游戏。

简单整理之后不难发现,一款游戏里,它们的受众是如何反馈自己从游戏处获得的信息,可以决定一款游戏的传播广度与深度。假如中国的大型游戏机市场还有第二春的话,我很期待新入局的人是怎么经营用户生产数据的。

读源码

http://www.yiichina.com/doc/guide/2.0/structure-controllers

在“控制器ID”一节中这一特性被通过命名规范的方式规定。
错怪yii了。

还是要多看官网。

==16:51更新,以上为更新全文

最近由于工作(因为业余依旧沉迷阴阳师),开始正式研究php7和yii2。
今天试图通过pretty-url将所有/api/v1/action的请求转发到/apiv1下面来进行处理。
于是配置如下:

'urlManager' => [
            'enablePrettyUrl' => true,
            'enableStrictParsing' => false,
            'showScriptName' => false,
            'rules' => array('api/v1/' => 'apiv1/', '' => 'site/index','/'=>'/',)
        ],

然后,在apiv1的controller里面写了这样一个函数:

public function actionFinanceInfo()
    {
        $userObj = new \app\models\User();
        return $userObj::find()->where(['id' => 1])
            ->one();
    }

理论上是没问题的,但是死活就是404。无奈中翻看yii2的源码,发现路由这块的实现是:

public function createAction($id)
    {
        if ($id === '') {
            $id = $this->defaultAction;
        }
        $actionMap = $this->actions();
        if (isset($actionMap[$id])) {
            return Yii::createObject($actionMap[$id], [$id, $this]);
        } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) {
            $methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
            if (method_exists($this, $methodName)) {
                $method = new \ReflectionMethod($this, $methodName);
                if ($method->isPublic() && $method->getName() === $methodName) {
                    return new InlineAction($id, $this, $methodName);
                }
            }
        }

        return null;
    }

竟然是一个将驼峰处理成连字符的函数。文档里貌似就没提到这一条啊。坑。