高渐离の屋

一个不起眼的个人小站

0%

搭建一个短网址系统

前阵子,在Xice的推荐下,我入手了一个3位.eu域名u2b.eu。他作为一个老羊毛党了,给我推荐的域名价格自然也十分离谱,10年只要20刀,反过来看看我正在使用的gaojianli.me,一年12刀的价格完美阐释了什么叫做没有对比就没有伤害。

值得注意的是,.eu的域名要求欧盟(前)成员国居民的身份才能注册,因此一开始想当然拿了美国人身份注册的我险些打水漂,所幸最后通过一张PS的燃气账单侥幸蒙混过去,可谓是十分惊险。

虽然如此,作为冲动消费捡垃圾的典型,拿下了这个域名其实也不知道做什么。虽然不知道有什么用,但是太便宜了先买了吧。抱着这种思想我没少买奇奇怪怪的玩意儿,而这个三位的域名看起来也是这样一个“鸡肋”。说有用吧,虽然是3位域名,但是为了追求3位导致了其基本就是随机的字符串,含义不明;说没用那毕竟还是个3位的顶级域名,还能谐音碰瓷Youtube

在闲置了一阵子之后,姑且想到了一个用途,你不是短吗?那就来做个短网址系统吧!

在我印象中,短网址这种烂大街的东西有不少的开源实现,但是最近闲的也是闲的,与其用别人的,不如自己来造个轮子吧。

规划

首先来规划一下项目,难得能自己做个东西,便想用尽可能用一些新技术,因此没有选择劳模Node.js,而是打算尝鲜下微软的ASP .NET Core,这东西据说能完美跨平台运行在Linux上,满足我的需求不是问题。

因为要考虑到一个封装成API的可能,因此一定是前后端分离的架构。前端最初的时候是想尝试一下Razor的,C#编译成wasm的方式还挺新颖的。但是写了2行就发现这东西是巨坑,因为它是!基!于!Bootstrap的!!这是多少年前的UI库了,组件全部用CSS来描述,写得十分痛苦和不便,果然火不起来是有理由的,还是老老实实Vue.js吧。

在做这个的时候,恰逢Vue 3.X 出世,也本想尝试一下Composition API配合Vite体验一把抛弃Webpack的感觉,然而,当我兴致冲冲搭建好了脚手架之后遇到了一个更严重问题:没有组件库。这连BootStrap都没有,更凄惨了!写CSS是不可能写CSS的,这辈子也不可能手写的。 最后,还是选择了劳模组合Vue2+Vuetify,也算是一大缺憾。

在架构之前还是了解了一下网上的常见方案,在现有方案中有人提出了一些建议:

看了楼主代码.没上缓存(热点 url),没处理同一 url 连续两次转换出现不同结果的情况

对于上面的意见,分开来探讨一下吧:

  • 对于意见2,目前很多现存的短网址都不支持这种功能(例如goo.gl),其理由也非常简单:不同的用户每次提交的URL都是一次不同的业务,如果合并了难以完成统计,撤销等功能。同时还给添加徒增工作量,每次添加时都会进行一次全表查询,纵有索引也会极大地浪费性能。
  • 对于意见1,我的意见是请了解一下这个。现如今,是个项目就无脑上集群、Redis,完全不顾是否真的有这个需求,私以为也是一种提前优化。对于单用户来说,由于短链接跳转使用的是HTTP 301 永久性转移(Permanently Moved),第二次访问时会直接被浏览器所缓存;对于多用户来说,热点URL的概率同时被数百人访问的概率也非常低(每个人只需访问一次),更多的访问是离散的,增加Redis也不能增加太多性能。

总结

因此,总结一下所选解决方案如下:

分类 解决方案
后端 ASP .NET Core
数据库 MySQL
前端 Vue.js
组件库 Vuetify
缓存

新时代的ASP初体验

既然冒着踩坑的风险选择了
长久以来,提到ASP我想到的都是一众.asp.aspx结尾的网站,广泛分布在各大机关单位学校中,伴随着的关键词还有:卡,慢,IE6,大马等,,但是在实际开发中微软着实给我了一个惊喜,非复吴下阿软矣。

在实际的开发体验中,ASP.NET开发体验比我预想中的好得多,得益于宇宙第一IDE的加持,代码模板非常齐全,可以直接Controller为单位新建代码,以Controller的前缀命名的路由避免了写一大坨router.js,通过Attribute的传参更是免去了一大堆类似var a=request.body.a这种提取参数的代码。

从设计模式上来说,数据库的访问应该与Controller分离,实现封装、解耦。但是得益于LINQ的强大威力,数据库的访问被压缩到了一行语句,考虑到本项目复杂度,此时再进行封装反而有过度设计的嫌疑了,因此直接LINQ写到Controller里算了。如下是实现短网址跳转的代码,可见不论是查询数据库还是中URL中传参都是十分简洁的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[HttpGet("{shorten}")]
public void Forward(string shorten, [FromServices] UrlContext dbContext)
{
var longUrl = from urls in dbContext.Urls
where urls.shortUrl == shorten
select urls.longUrl;
if (longUrl.Count() == 0)
{
Response.Redirect(Configuration.GetSection("SiteSettings")["homePage"]);
}
else
{
Response.Redirect(longUrl.Single());
}
}

总而言之,ASP.NET Core我相当看好,尽管是微软的技术所以前景不容乐观,各位开发者还是学习Java前途更加光明。但是作为简单的项目来说其比Koa完善,同时配置比Java的Spring方便(不用写XML真是人间之鉴),Nuget还能直接高速访问不用代理,好处真是太多了。我花了2天时间通过自己摸索+VS的自动提示便基本熟悉了此框架并完成了此项目(有够简单就是了),换做是Spring上手应该很难这么快的吧。

短网址Hash算法

关于这个网上也有很多讨论了,我对比后选择了“62进制法”。其核心是将长网址存入数据库后获得一个id,将id从10进制转换为62进制字符串,再加上一些随机字符串防止碰撞,最后得到缩短后的地址。

由于我直接将id设置为MySQL里的自增字段,因此连随机字符串都不需要了,直接把id拿来用就行了,反正也不存在碰撞的可能。进制转换代码是我从网上抄的Java代码改写的,众所周知C#曾经有个名字叫J++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static char[] alphaBet = { 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Z', 'X', 'C', 'V', 'B', 'N', 'M' };
public static string getShorted(long id)
{
var rest = id;
Stack<char> stack = new Stack<char>();
StringBuilder result = new StringBuilder(0);
while (rest != 0)
{
stack.Push(alphaBet[rest - (rest / 62) * 62]);
rest = rest / 62;
}
for (; stack.Count() != 0;)
{
result.Append(stack.Pop());
}
return result.ToString();
}

至于前端部分就是普普通通的Vue.js应用,没什么好说的。写完之后编译、部署一气呵成,成果如下,欢迎体验:

值得注意的是Debian 10源中的Mariadb是链接yaSSL编译的,不支持TLS1.2及以上版本,在.NET Core 3.0以上版本的runtime上会报错,其解决方法是安装Mariadb官方源中的版本,其使用OpenSSL编译可以正常支持TLS1.2。

Update 2020.12.13:
懒癌终于治好了之后终于弄好了docker部署,接下来直接运行下面命令就可以部署啦:

1
2
3
4
5
6
7
8
docker run -d -e ConnectionStrings__SqlConnection="server=<ip to the db>;port=<db port>;database=shortUrl;uid=shorturl;pwd=<pwd>;CharSet=utf8" \
-e SiteSettings__origin="https://short.u2b.eu" \
-e SiteSettings__homePage="https://short.u2b.eu" \
-e SiteSettings__prefix="https://u2b.eu" \
-p 5000:5000 \
--name url_shorter \
--restart=always \
url_shorter

では、諸君は。

附录

项目源代码

Codespaces

由于偷懒,本文没有使用常规的VSCode写作,而是直接使用了Github的在线VSCode:Codespaces,虽然很早我就排到了Codespaces的使用权,但是实际使用这是第一次,这里就顺便谈谈感想。

先说优点吧,其本质上是一个Ubuntu 18.04的容器,分配了1c4g的资源,里面运行了vscode remote server,然后浏览器远程连接之。好处是可以不用克隆代码和折腾Git,随到随写,哪怕是iPad也可以随时随地编程。

但是,理想很美好,现实很骨感,实际体验那叫一个差。后悔,总之就是非常后悔,除了随时随地的掉线(2秒掉一次)外(Update 2020.12.13:别让它失焦,否则必定掉线),延迟也难以忍受。输入可能有优化和提前渲染,输入的时候完全体会不到延迟,但是一旦要编辑那就是噩梦。每次退格键都要触发一次服务器通讯,大概延迟在2s左右,在这等待期间如果你移动了光标,不好意思,你的删除内容会马上跳转到现光标所在位置上,导致意外删除。换言之,你的所有操作并不是按顺序进入队列中,而是按照js的微任务执行顺序来操作。好家伙,同步的文本编辑任务在它这里变成异步了,因此每当网络波动你就不得不停下来,等待操作执行完毕,以免发生意外操作。

关于这个功能,我觉得要么是大陆网络的问题,要么就只是个体验版,白嫖要什么自行车。猜想Github实际设计应该是让企业self hosted,然后再在内网中使用吧,就结果而言,完全不具备安装成PWA应用使用的可能性。