记 Public Bookmark 的诞生

PHP 框架总要学一个的。在 N 年前浅尝了 ThinkPHP 之后,这回来玩一下 Laravel,这个被很多人点赞的框架。

当然,光看文档没有一点意思,更何况它的文档的详细程度和 Django 的文档完全不能比。那就只好由项目驱动,一点一点反啃文档。

Public Bookmark 的设计初衷是模仿虫部落的 快搜。快搜给人的感觉就是一套静态 HTML 页面,分为搜索版、学术版、素材版。如果单纯维护各个静态 HTML 页面,由于连接很多,会导致单个 html/json/xml 代码会很长,维护起来容易出错,并且加入一个版本就需要重新来一套页面。需求不复杂,“PHP + 数据库 + 在线管理” 正好可以拿来练手。

功能性方面,整个网站就是对 URL 的 CURD,加上对 Category 的 CURD,没了。这样功能太少了啊,那么仿照其它项目,做个 “公告板” 和 “升级日志” 出来。这两个都是单表的 CURD,可以先做来练手,然后再啃前面的级联操作(其实这两个表是可以合并成一个的,就像 WordPress 的万能的 posts 表一样)。另外,用邮件收集反馈信息比较麻烦,不如直接再开一个反馈信息表。前端方面,AdminLTE 基本可以满足需求。基本设想就这样出来了。

下面就开始写代码和学框架了。首先是数据库迁移、模型、大规模假数据的生成。最开始被 UUID 整得很惨:收到某个项目的影响,category、url、user 的 id 都想用 uuid 来玩。但是 Laravel 好像有个奇怪的机制:用 Database Seeder 生成数据后,uuid 类型的 id 会先被变成 int,然后再转成 string 存到数据库里。由于 uuid 转 int 不一定成功,而 php 里面 “转 int” 的策略是 “只保留转换成功的部分”,一个 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 的 uuid 转换出来可能就成了一个一位数字,或者成为一个空字符串。研究一天无果,放弃。但心里还是想要 UUID 的,毕竟可以防止一些无聊的人穷举。

Database Seeder 里面用到了 feaker,一个很强大的 “假数据生成” 类,能生成各种假数据,从合法网址、合法图片地址(关键是这些图片地址还都能 200),以假乱真的人名,随机单词、毫无意义的段落…… 真心大赞。一句 artisan db:seed ,原本空空如也的数据库里面瞬间填满数据,直接可以做分页了。

本来想用控制器默认路由的方式来玩,但是发现 5.2 版本去掉了这一个特性。于是几乎所有的路由都用了还算比较方便的资源路由。category 和 url 本来想做资源路由嵌套,因为写起来比较麻烦,就做成了 “大扁平” 模式。生成 URL 的时候有多种方式可选,但最终都用 action() 方式来生成:一是写法清晰一些,二是如果路由里面有变动,不需要到处乱改。

授权……5.2 版本自带了一个不明觉厉的授权体系。由于不需要做 RBAC,只是简单的 登录 而已,就直接用了。但是有一个设定:站点是 “发布型”,多用户没有意义,所以索性屏蔽掉了前台注册逻辑,改为拿 console 进行添加。但是在 console 里面也按照原本 web 的逻辑来玩,密码确认两遍、几个 “表单验证” 也都借调过来了,“有效避免手滑”。

记得从开始学 HTML 起,iframe 自动撑满剩余空间一直是个头疼的问题。Coderge 给了一个比较暴力但十分有效的解决办法:iframe 的每个父元素都设置 hetght=100% 的 inline style。但是我觉得这样太不清真了,搞了好久最终决定用 $("iframe").parents(element).css("height", "100%"); 来更暴力地解决问题。然后就被骂了。

从开始写到 V0.1 大概用了一星期。上线运行,发现一大堆 “用起来不舒服” 的地方,比如不能批量导入,比如操作成功后的跳转不合理,比如我想在后台进行排序自定义;除此之外还有一些诸如 “点击次数统计” 之类的功能。这才意识到 “需求不是一下子能发现完的”。陆陆续续发了几个版本之后,涉及到 PHP 的功能性的东西几乎都有了,相比 V0.1 版也更易用一些。人工点击一遍没什么问题,上线测试没什么问题,果断写 doc,发 V1.0 版,暂时弃坑。

为什么是 “人工点击” 而不是自动测试?因为不会啊……php 的单元测试真心看不懂,再加上单步调试环境一直搞不定,只能放弃。

说说这里面几个比较 “恶心人” 的设定:

  1. 链接点击次数。没什么用,但我就是觉得好玩。第一次实现的时候逻辑是这样的:所有链接都是内链,controller 里面统计点击次数,然后 redirect 到真实的 url。使用半天后发现某长城竟然会感知到这个跳转,某段时间竟一度将我的网站屏蔽掉了…… 这就囧了。并且这样多一次解析和跳转,等待时间比较长,体验不是很好。最后只能 ajax 出马,点击链接直接跳转,然后给后台发 ajax 统计点击次数。
  2. 访问频率限制。这是个好玩的东西。为了防止刷票,统计点击次数的时候,限制访问频率 60 次 / 分,超出后不再统计。一般没有人会达到这个限制吧。同样的,API 的访问频率也被限制在 60 次 / 分。
  3. 最上面说了,我很作死地加了个 “用户反馈”,这玩意儿极容易成为 Spam 重灾区,另外,我是拿 Markdown 渲染的 feedback,Markdown 可是支持 html 标签的啊亲,一不小心就被 xss 了。为解决这个问题,我做了两个限制:首先,限制 feedback 的 save 频率,每分钟 2 次,超出需等待 60 分;其次,过滤掉所有 html tag;最后,加入了 Google noCaptcha,以避免机器 spam。三方面下来,估计不会死得太惨。
  4. 做真删除而不是假删除。编号为 1 的 category 不能被删除,其它 category 删除的时候,其下的 url 被自动收容到 category 1 里面。
  5. API 里面,用 category 的 alia name 获取 category 里面的 url。为什么不用 category id 呢?因为我就是不想让你直接写 for 循环遍历啊!就和一开始想用 UUID 而不是 auto increasement int 做 id 是一个道理。
  6. 响应式方面。我压根儿没考虑多种客户端,只想着在电脑上访问了,最多在 pad 上面玩玩。AdminLTE 里面自带了一些屏幕适配,凑和用吧。

就这样了,等想如前端坑的时候再回来填它。启用右边栏,加入前端设置,自定义主题(AdminLTE 默认有很多颜色的主题可用呢),URL history,前端自定义排序,AngularJS 改写后端…… 各种想法都以后再慢慢说。

当然,如果你有兴趣,可以自己部署来一个玩玩。项目放在了 Github 上。你也可以来 这里 体验一下。目前已经录入了 100 多个 URL 了。

还有小半个暑假,转战其他领域去啦。

发表评论

电子邮件地址不会被公开。 必填项已用*标注