聊聊HamZone社区的后端设计

共码了5308个字
文内使用到的标签:

HamZone 社区是国内的业余无线电爱好者交流社区,旨在提高国内业余无线电爱好者们的活跃程度,降低业余无线电入门门槛,沉淀好的交流内容,目前已经完成了初版项目大部分的开发,这个过程中学习了很多新的知识,怕会有闭门造车的现象,在此记录,如有不妥之处也请赐教。

简单介绍


访问HamZone服务时大致数据走向示意图

社区现有 V1 接口的设计,先由 PHP 承载访问流量进行简单的业务处理,如上图所示的用户信息、互动操作、工具箱等模块,再通过 ThriftRPC 调用 Go 语言异步协助处理流程,例如上图所示的脚本任务、内容安全检测、队列服务等。

社区服务器托管于阿里云,部分业务使用腾讯云服务,目前有上海,深圳,香港三地服务器。

除了运行 HamZone 所需的资源,还有配套使用了以下服务:

  • 搭建 Gitea 进行代码托管。
  • 使用 ElasticSearch, Kibana 管理分析日志。
  • 搭建 Bark, 企业微信进行消息推送。

HamZone 在构建的过程中,对接了不少第三方提供的接口,例如:

  • 使用了 Robohash 提供的算法生成了有趣的头像,在使用了原版的 Python 之后,为了方便调用,索性将代码转译成 Go 语言实现:点此查看项目
  • 使用了 QRZ.com 提供的接口,定期获取无线电爱好者们的活跃信息。
  • 定期获取 TLE 两行轨道数据进行卫星轨道预测。

开放接口

为了方便爱好者们进行二次开发,开放了一部分接口和数据,后期会逐渐完善这部分的内容,希望大家多多参与,共建 HamZone:

遇到的一些问题

如何保证站点内容健康

这是长期需要考虑的问题。对于一个社区论坛来说,持久运营,站点内容健康显得尤为重要,因此需要有个“保镖”持续保护。

在阅读信息安全工程师相关材料的时候,看到了信安入侵检测的基本策略是:保护-检测-响应,我想用到内容安全检测上面,也是合适的。


文章审核数据流

如上图所示,用户在发布文章之后,会唤起对应的内容检测服务,例如使用阿里云-内容安全腾讯云-文本内容安全提供的文本内容检测,比起自建的关键词识别,会有更好的效果。等待由云服务商提供的接口返回后,再对文章做出处理,记录对应的日志,对于疑似的结果,再推送到 Bark 或企业微信,及时提醒人工进行审核,处理结果则通过站内信通知用户。

而对于用户名等简单的文本检测,则使用 DFA 算法进行逐字匹配,建立本地敏感词库,降低云服务调用费用,对应的 PHP 实现:gist

文章排序

文章的排序情况会从另一个维度对站点进行运营起到重要的作用,它能够有效地凸显更优质的内容,简单的对站内信息进行过滤。

最开始发布的测试版本中,使用的是自定的排序方式,拟定了以下规则计算文章分数排序:

浏览量*0.2+感谢量*0.3+评论量*0.3+收藏量*0.2+举报量*-1+(现在时间-发布时间)*0.1

但是效果始终不理想,导致当前热门的文章会一直排在最前,而新发布的文章一直被排在中下的位置。

在经过一番信息收集之后,发现了阮一峰老师在 2012 年发布了《基于用户投票的排名算法》系列文章,总共六篇:可以点此查看。这给了我很大的帮助,因为有了具体的模型,站在巨人的肩膀上,可以更好的排序文章。

最后,参考阮一峰的《基于用户投票的排名算法(三):Stack Overflow》使用 Go 简单仿造了该算法:

//StackOverflowRank http://www.ruanyifeng.com/blog/2012/03/ranking_algorithm_stack_overflow.html
//问题浏览次数 / 回复数量 / Qscore(问题得分)= 赞成票-反对票 / 回答得分 Qage(距离问题发表的时间)和Qupdated(距离最后一个回答的时间)
func StackOverflowRank(views, Qansers, Qscore, Ascore, askDate, activeDate int64) float64 {
    Qage := float64(time.Now().Unix()-askDate) / 3600
    Qage, _ = decimal.NewFromFloat(Qage).Round(1).Float64()
    //Qupdated
    Qupdated := float64(time.Now().Unix()-activeDate) / 3600
    Qupdated, _ = decimal.NewFromFloat(Qupdated).Round(1).Float64()
    //dividend
    dividend := math.Log10(float64(views)*4) + (float64(Qansers)*float64(Qscore))*5 + float64(Ascore)
    //divisor
    divisor := math.Pow((Qage+1)-(Qage-Qupdated)/2, 1.5)
    //end
    e, _ := decimal.NewFromFloat((dividend / divisor) * 10000).Round(5).Float64()
    return e
}

实现方法还有待优化,最终的结果可能还不够理想,但是对比以前,已经非常接近想要的效果了。

用户 @Mention/@提及/at功能 功能要如何实现

@ 功能在如今已经非常常见了,对论坛来说也能提高站内用户的互动趣味性,所以就着手开发这项功能,设计之初,首先想到的是新浪微博的@方式:

在 F12 对微博的 @ 请求方式进行观察后,这让我很吃惊,因为实时性的返回对应的用户名及结果,而不是交付前端一次性渲染。由此就给我带来了几个问题:

  • 如何保证实时性
  • 如何提高响应速度
  • 如何进行模糊匹配
  • 是否能@全站用户,还是仅能@关注的用户

很显然,这个问题首要考虑数据库的匹配速度,毫无疑问的选用了 Redis 进行操作。

不得不吐槽一下,搜索引擎对 @ 这个符号的检索并不友好,尝试了关键词:@、at、小老鼠、提及、Mention,所以查阅资料的时候,花了不少时间。

对于前端的实现方式,发现了几项开源项目:

以及以下对于@功能的几项讨论,但都没有说明后端实现的方案:

在看完以上的帖子之后,着手尝试通过缓存用户名的方式,来提高检索的速度,以下是 Go 语言的缓存代码设计片段:

for _, v := range userList {
        var temp []string
        DD := []rune(v.Username)
        for i := 0; i < len(DD); i++ {
            temp = append(temp, string(DD[i]))
        }
        //temp e.g. [E m i n] [L e s l i e]
        // fmt.Println(temp)
        if len(temp) == 0 {
            continue
        }
        beforeWord := ""
        for k, val := range temp {
            beforeWord += val
            //用户名最后一个字做记号 并标记用户ID及头像
            if len(temp)-1 == k {
                beforeWord = beforeWord + "" + fmt.Sprintf("%d%s", v.ID, v.AvatarURL)
                redisTemp = append(redisTemp, redis.Z{Score: 0, Member: beforeWord})
                continue
            }
            redisTemp = append(redisTemp, redis.Z{Score: 0, Member: beforeWord})
        }
    }

Redis内使用 $ 标记区分,并放置了 UID 、用户名、用户对应的头像,提高体验,zset 存储:

202) "Eminl"
203) "Eminli"
204) "Eminlin73https://avatars2.githubusercontent.com/u/20252561?v=4"

检索 Redis 内已经缓存好的上述格式,则是使用 PHP 完成了匹配,贴出部分代码,仅供参考:

           positon = Redis::zRank(redisKey, query);
           //如果是空的,则随机返回几个用户
           if( empty(query) ){
                temp = ["a","b"];key = temp[ array_rand(temp, 1) ];
            }
           positon = Redis::zRank(redisKey, query);
            if (empty(positon)){
                return CommonController::simpleSuccessJsonReponse([]);
            }
            res = Redis::zRange(redisKey, positon, -1);temp = [];
            i=0;
            foreach(res as k=>v){
                if( mb_stristr(v, "") ){
                    i++;
                    //限制返回的个数
                    if (i>10){
                        break;
                    }
                    info = explode("",v);temp[] = [
                        "text" => info[0],
                        "value" =>info[1],
                        "img" => $info[2]
                    ];
                }
            }

当用户提交对应的文章或动态请求时候,再通过正则表达来匹配内容中出现的 @用户名空格 格式,发送对应用户的站内信通知:

//MentionParse @功能解析
func MentionParse(text string) {
    re, err := regexp.Compile("@(.*?)(\\s|\u3000)")
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    fmt.Println(re.FindAllString(text, -1))
}

最后,对应的效果录了一段视频,展示效果如下:


@功能效果演示

用户关注动态功能

关注表设计:

字段 类型 说明
id int 关注用户 ID
follow_uid int 关注用户 ID
be_followed_uid int 被关注用户 ID
status tinyint 关注状态
update_time int 更新时间
created_time int 创建时间

动态表设计:

字段 类型 说明
id int 关注用户 ID
uid int 用户 ID
content varchar 动态内容,限制长度
status tinyint 关注状态
update_time int 更新时间
created_time int 创建时间

对于用户的动态获取,采用了拉模式。

用户的关注、取关、共同关注功能,则是利用了 Redis 的交集、并集、差集。

安全设计

接口安全:移动端接口采用 RSA+DES 加密,搭配 Nonce+Timestamp 预防重放攻击,这部分需要等待更多的验证。

图片服务

站内的图片功能是必不可少的,需要考虑例如下面几个问题:

  • 内容安全性
  • 存储空间
  • 请求图片响应速度
  • 防盗链

阿里云的上传速率虽然可观入网带宽最大为10 Mbit/s,综合考虑之后,但是还是选择了 OSS 进行存储。

为了保证上传的内容可控,最终找到了 OSS 直传回调方案,在阿里云 OSS 服务端直传的概述中有提及:

采用服务端签名后直传方案有个问题:大多数情况下,用户上传数据后,应用服务器需要知道用户上传了哪些文件以及文件名;如果上传了图片,还需要知道图片的大小等,为此OSS提供了上传回调方案。


阿里云 OSS 服务端直传签名回调示意图

用户直传图片到 OSS 的方案,能够提高上传的速度、降低服务器带宽及存储压力、提高访问速度,通过回调,还能控制对应的图片内容,是个比较折中的方案。

代码自动发布

在日常开发的过程中,代码发布可能是最普遍的工作之一,碎片化发布非常频繁,需要不断的调试代码、对接前端,因为没有过多的精力,所以不采用 Docker 等方式。最终选择了配合内网搭建的 Gitea,通过 Webhook,使用 Go 开发了一套 autoDeploy,后面有机会再开源。

大致流程:开发者在 Push 代码到指定分支,触发Webhook ,唤起 Repo 内的 .sh 进行拉去代码自动部署、单元测试。部署结果推送至 Telegram 或 企业微信,并记录详细日志情况。

后台设计

后台功能,主要是方便管理员快速的对站点进行操作,例如:文章管理、用户管理、评论管理、审核、站点设置等功能。


HamZone管理后台-文章列表界面

其它

至此,简单的介绍了 HamZone 后端开发的一些情况和问题,当然一个项目的上线还有其他的内容,例如:App积分服务、站内规则、商标软著知识产权、应用商店审核、移动端消息推送、资质问题、备案审核、前端对接、服务器选购、运维部署、管理后台……

一个人的力量是很渺小的,但是坚持做下去,就会有所积累,再次引用绝命毒师里听到的那句话:

All that you’ve done, it’s a part of you.

Prev:
Next:

发表回复

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