moments 项目
moment项目需求
相关技术栈:redis、golang、MySQL、linux
做一个看板任务(照着微信朋友圈来,不过所有用户全部看成朋友即可)
- 用户可以登录,可以保持登录状态
- 用户可以发布一个想法,包含一段文字,0-4张图片(保存图片链接即可)
- 用户可以查看大家发布的说说,按时间顺序从新展示,需分页,一页20个
- 用户可以评论想法,也可以删除自己的评论
- 用户可以点赞/取消点赞想法
- 消息通知,点赞回复要通知所有和说说相关的人
不做界面,规划好接口,数据库,且完成即可
数据库尽可能满足 一对一 一对多 多对多 关系,不要求字段冗余
使用HTTP协议,框架自选(iris,beego等都行)
进度安排:
- 设计好数据库
- 规划好接口
- 做好登录认证
- 完成接口
数据库实现
实体,属性,关系;实体就是一个个对象,比如猫;属性就是实体所有的某个属性,比如猫的性别;关系就是实体和实体之间或者实体内部之间的关系。
在ER图中,矩形代表实体,椭圆代表属性,菱形代表关系,各个形状之间用线段连接。
在本项目中,实体有
- 内容发布者
- 朋友圈发布项
- 朋友圈的评论项
内容发布者对应属性:
- 用户ID(系统内部)(主键)
- 用户姓名(界面展示)
- 用户密码
朋友圈标志属性:
- 发布ID(每个发布者发布朋友圈时调用,自增1,本身具有时间顺序特性)(主键)
- 发布者用户ID(外键)
- 创建时间(显示在朋友圈下方)
- 照片列:照片url;照片url;照片url;照片url
- 配文字符串
评论属性:(评论归属于单个用户,并且此用户为接收者,而不是发布者)
- 用户ID(评论创建者,用来检索对应的用户名,以展示用户评论)(外键)
- 所属朋友圈,即发布ID(和用户ID组合作为主键)(外键)
- 评论创建时间(按时间顺序显示评论)
- 评论内容字符串
- 点赞与否
数据库设计思路
做一个看板任务(照着微信朋友圈来,不过所有用户全部看成朋友即可)
- 用户可以登录,可以保持登录状态
- 用户可以发布一个想法,包含一段文字,0-4张图片(保存图片链接即可)
- 用户可以查看大家发布的说说,按时间顺序从新展示,需分页,一页20个
- 用户可以评论想法,也可以删除自己的评论
- 用户可以点赞/取消点赞想法
- 消息通知,点赞回复要通知所有和说说相关的人
不做界面,规划好接口,数据库,且完成即可
数据库尽可能满足 一对一 一对多 多对多 关系,不要求字段冗余
使用HTTP协议,框架自选(iris,beego等都行)
微信朋友圈设计架构
微信朋友圈的数据有四个核心的表:
- 发布表,发布表记录了来自所有用户的所有Feed,比如一个用户发布了几张图片,每张图片的URL,图片在CDN里的URL,它有哪些元属性,谁可以看,谁不可以看等等。
- 相册,相册是每个用户独立的,记录了该用户所发布的所有内容,包括了与图片相关的文字,和无配图文字。
- 评论, 评论就是针对某个具体发布的朋友评论和点赞操作。
- 时间线,所谓“刷朋友圈”,就是刷时间线,就是一个用户所有的朋友的发布内容。
微信朋友圈的工作流程概述
比如有两个用户A和B, A和B各自都有各自的相册(可以理解为A和B各自的朋友圈内容),可能在同一台服务器上,也可能在不同服务器上。现在A上传了一张图片到自己的朋友圈。上传图片不经过微信服务器,而是直接上传到最近的CDN节点去,所以非常快。图片上传到CDN后,小王的微信客户端会通知,这里有一个新的发布(比如叫K2),这个发布的图片CDN URL是什么,谁能看这张图片等等此类的元数据。来把这些数据写入发布表中。
在发布表写完之后,会把K2的发布索引到A的相册表中,所以相册表记录得就是每个内容索引指针,相册表写好后会触发一个批处理动作,这个动作就是去跟A的每个好友说,A有一个新的发布,请把这个发布插入到每个好友的时间线里去。
现在B上朋友圈了,而B是A的一个好友,B拉自己的时间线的时候,时间线会获得K2的新发布通知,然后B的微信客户端就会取根据K2的元数据去获取发布表中的一些信息比如:CDN URL,把图片拉到本地。
在这个过程中,发布是很重要的,因为一方面要写一个自己的数据副本,并写入自己的时间线,还要把这个副本的指针插到所有好友的时间线里面去,如果一个用户有几百个好友的话,这个过程会比较慢一些。这是一个单数据副本写扩散的过程。但是相对应的,读取就很简单。每个用户只需要读取自己的时间线这一个动作就行,不需要去遍历所有的好友相册表。这个是否有一定得数据冗余??
参考文献:
数据交互逻辑
用户登录
匹配password字段—>若成功,登录操作(http协议)进入看板界面,推送Feed流信息,若失败,拒绝访问,“密码错误,请重新输入密码”,超时锁定。
刷朋友圈
登录成功—>推送publish,按publish_id从大到小排列,展示publish_id最大的20个publish。
刷朋友圈的行为即为刷取多个朋友圈信息(publish)对于单个publish:
- 获取创建时间—>拉取create_time在左下方显示;
- 获取照片—>拉取picture表中,publish_id对应的pic_url;
- 获取朋友圈文字—>拉取caption;
- 获取评论—>拉取comment表中,publish_id对应的comment,根据comment_id进行从大到小排序,根据usr_id,检索出usr_name(即评论人),拉取comments,和comment create_time(整体显示情况即为:评论人,评论,评论创建的时间);
- 获取点赞信息—>拉取like表中,publish_id对应的like项,并根据like_id,从大到小排序(整体显示情况即为:点赞人A,点赞人B,点赞人C…)
发布朋友圈
创建一个新的publish表项,publish_id自增1,根据当前用户,填入当前usr_id,录入当前时间到create_time,将朋友圈文字信息,录入到caption字段中;
若有照片,每个照片创建一个picture表项,录入pic_url和当前publish_id,pic_id自增1
评论朋友圈
创建一个新的comment表项,comment_id自增1,根据当前用户,填入当前usr_id,录入当前时间到create_time,将评论文字信息,录入到comments字段中,并录入当前评论的publish_id;
创建Notice,根据当前评论的publish_id,检索,当前Notice表中,具有同样publish_id的Notice,查看其中的from_id和to_id,即为所有相关的用户,创建对应的Notice,notice_id自增1,publish_id即为当前评论的publish_id,type为0(0为评论,1为点赞),from_id为当前评论的用户,to_id依次为检索到的所有用户ID,status为0(0未读,1已读);
点赞朋友圈
创建一个新的like表项,like_id自增1,根据当前用户,填入当前usr_id,填入当前点赞的publish_id;
创建Notice,根据当前评论的publish_id,检索,当前Notice表中,具有同样publish_id的Notice,查看其中的from_id和to_id,即为所有相关的用户,创建对应的Notice,notice_id自增1,publish_id即为当前评论的publish_id,type为1(0为评论,1为点赞),from_id为当前评论的用户,to_id依次为检索到的所有用户ID,status为0(0未读,1已读);
推送评论通知基本逻辑
- 接收通知的用户,通知详情需要主动获取
- 通知的主动推送,本质为当前用户定时,每秒查看,有多少条未读通知 (*)
- 根据Notice表,主动获取通知,定时获取to_id与当前用户usr_id相符,且status字段为0(未读)的Notice。通知格式为“您有N条消息未读”。并同时存储这些Notice的notice_id
- 当用户点击通知,读取通知时,检索存储的notice_id,将这些Notice的status字段置为1,并根据type获知通知类型,若type=0为评论,则查找comment表项,查找对应publish_id中
usr_id=from_id
的comments字段,获取评论详情(from_id+comments),上方要同时拉取对应publish_id的朋友圈详情(usr_id+pic+caption+create_time);若type=1为点赞,则拉取对应publish_id的朋友圈详情,并显示,”(from_id) like this”,上方要同时拉取对应publish_id的朋友圈详情(usr_id+pic+caption+create_time)
通知的几种特殊情况的考虑
- (未评论用户)取消点赞后,需要去掉该用户的Notice
- (未点赞用户)删除评论后,需要去掉该用户的Notice
- 阅读过通知后,通知需要保留,将状态置为已读,当后续还有人评论时,可以根据Notice表自动维护之后的通知。
- 每一次的通知都可以直接通过上一次通知进行维护更新。第一步,找到对应的publish_id中notice_id的最大的一项,找出对应的from_id,即找出了,本朋友圈上一次的通知,是从from_id,通知到了此用户的所有to_id。即所有最新的相关用户群,则更新的Notice为,from_id=当前usr_id,to_id=上一次的from_id+上一次的所有的to_id。
- 初次生成的通知会被立刻调用,所以也可以直接在生成的时候,直接推送。 (*)
MySQL设计规范
详情,点击标题跳转页面↑
接口设计
接口基础函数
Prepare
Beego框架中,Controller的Prepare函数都会自动在调用其之前先被调用。
所以Prepare函数,被我用来写验证相关的代码。
①假如当前是在进行非登录操作,则需要验证,session_id,若为空,则未登录,跳转到登录操作页面
②若不为空,则在redis中进行比对,看是否存在当前用户的session_id,K-VOUSMEVOYEZ结构为“K:session_id,VOUSMEVOYEZ:usr_name”,若查找当前session_id,得到的用户为空,则未登录,跳转到登录操作页面;得到的用户名非空,还需要验证一下用户表中是否存在该用户,不存在则显示为仿冒用户
③若不存在session_id的问题,执行完Prepare函数仍未被跳转到登录页面,则继续执行Controller
History
一个跳转函数,用来控制,当出现错误或者验证安全的问题时,及时做出反馈,当出现系统性错误时,停止系统的运行,当出现验证问题时,及时跳转到所需页面,如登录页面等。
msg:用来描述当前问题,并写入到跳转页面
url:用来描述跳转链接,当为空时,停止运行当前controller
Finish
Beego框架中,任何Controller的Finish函数都会自动在调用后,最后时刻被调用,所以Finish函数,被我用来写Session的控制。
①假如当前是在进行非登录登出操作,需要验证后,为当前Session_id,续期30分钟
②假如是登录操作,需要为当前用户创建一个session_id,存到redis中,并发送给客户端
登录接口
Login
路由:”/admin/login”
用户登录输入用户名密码,以Json格式发送至客户端,若存在Session,且和服务器内Session_id相符,则直接验证通过
1 | //UsrInfo 用户评价时提交的表单 |
输入:Body包含用户名密码的Json格式文档,或其他包含Session_id的Json格式文档
通过解析Json,解析出用户名,密码,和Session_id:
①假如Session_id非空,且在Redis中存在,则更新Redis,直接进入下一步;
②假如Session_id非空,但在Redis中不存在,则登录超时,要求重新登录,进入登录界面
③假如Session_id为空,则进入登录界面,解析客户端发送过来的用户名密码,若用户名密码匹配,则登录成功,并生成一个Session记录,保存在Redis,同时将Session_id作为输出发送给客户端。否则直接返回“用户名或密码错误”,要求重新登录
1 | //LoginResult 传递登录结果 |
输出:验证结果(true/false)+ session_id + usr_id(给客户端保存)
Logout
路由:”/admin/login”
用户退出登录的时候,需要清除掉当前用户的Session_id。然后跳转到登录页面。
输入:session_id
1 | //LogoutFeedback 退出登录反馈信息 |
输出:返回到登录界面
朋友圈接口
PostMoment(发布朋友圈)
分析:客户端发布朋友圈,通过表单发送给后台,返回一个publish_id,和发布结果;发布朋友圈时,所需要的信息是,用户名,文字信息,和图片,由此构建表单结构体
路由:”/feed/post”
1 | //PushMoment is 发送朋友圈时提交的表单 |
输入:朋友圈所需信息的表单。
1 | //PostResult 发布朋友圈后的反馈结果 |
输出:true or false 若为true 还需要返回一个当前创建成功后,数据库中publish表的publish_id,方便后期加评价和删除朋友圈。
ReadMoment(刷朋友圈)
分析:由于本需求中,所有人都为朋友,所以所有人的展示结果只跟发送展示请求的时间有关(当做一个锚点),读取朋友圈时,不要读取最新的朋友圈信息,即只刷取,锚点以前,请求时的当前数据库数据。如若不然,会导致分页重复,刷新朋友圈为动态操作(和刷朋友圈区分开,刷朋友圈是静态操作)。最新的朋友圈和评论产生时,只轮询读取到通知,而不拉取最新的朋友圈信息。当用户主动刷新的时候,再读取最新的信息。
路由:”/feed/circle”
用户登录成功跳转至朋友圈界面,查询当前publish列表,记录此时最新的publish(即为Publish_id最大值),记录为publishAnchor,通过锚点进行分页展示。
通过publish_id排列,查找20个最新的朋友圈,将所有的数据打包发送出去。
输入:publish_index(数据类型int,读取朋友圈信息时publish_id的开始处)
1 | //MomentFeed 朋友圈数据流 |
输出:锚点后的20条朋友圈信息(JSON)。创建一个朋友圈整体信息的结构体。
DelMoment(删除朋友圈)
根据前端发送来的pubish_id,进行对当前朋友圈的一系列删除,包括,相册表,评论表,点赞表和通知表。
路由:”/feed/del”
输入:publish_id
1 | //DelFeedInfo 删除朋友圈后的反馈信息 |
输出:反馈信息,true or false
评论接口
CreateComment(发布评论接口)
根据前端发送来的评论信息,建立数据库信息,因为前段发来的评论信息,要有,评论相关文字,评论所属对象,和评论所属朋友圈,故建立表单信息进行接收。
路由:”/feed/comment/create”
1 | //OneComment 用户评价时提交的表单 |
输入:form
1 | //CreateCommentFeedback 评论创建反馈 |
输出:反馈信息,返回告诉客户端,该评论的comment_id,以及评论成功与否,通知是否已经建立等确认信息。
DelComment(删除评论接口)
给出对应的comment_id,删除所有的相关信息。改动涉及:comment表和notice表
路由:”/feed/comment/del”
输入:comment_id
1 | //DelCommentFeedback 评论创建反馈 |
输出:反馈信息,返回告诉客户端,评论删除与否,评论涉及的通知信息删除与否。
点赞接口
LIke(点赞)
点赞过程中,需要在点赞表中建立一条新信息,相应的,点赞通知的建立,需要通过CreateNotice遍历的在Notice表中建立多条信息。所以需要usr_id ,usr_name ,publish_id 三个信息,以上信息以表单形式传递。
路由:”/feed/like/create
1 | //Like is 前端大赞操作发送来的表单 |
输入:点赞表单:Like结构体
1 | //CreateLikeFeedback 创建点赞后给出的反馈信息 |
输出:反馈信息,返回告诉客户端,创建的点赞的ID是多少,创建结果如何,通知是否创建成果的信息。
CancelLike(取消点赞)
取消点赞,得到取消点赞的对应like_id即可,删除like表中对应的信息,并同时删除对应的通知信息。
路由:”/feed/like/cancel
输入:like_id
1 | //CancelLikeFeedback 取消点赞反馈 |
输出:取消的结果,删除通知的结果,以及相关的反馈信息。以便后期对应做出维护。
通知接口
通知被设计成,创建通知,删除通知为基本函数,穿插在点赞和评论的基本操作当中,并非一个单独的接口,而获取通知被设计成一个接口,目的就是能够随时获得最新的通知,能随时调用这个接口,读取操作可以作为常用接口,而增删通知则不开放为一个接口来调用。
GetNotice(获得通知)
获得通知时无需发送多余的信息,直接通过Session即可
路由:feed/notice
输入:session_id (从redis中获得usr_name)
输出:通知信息。通知信息所需要的基本信息可以分析,通知信息的前端展示:
①朋友圈有更新的通知:如果有新的朋友圈,一个红点,如果没有,则没有红点;
②自己的朋友圈被评论的通知:XX(usr_name)评价了您的“XXX”(某条朋友圈的部分文字信息);
③自己参与的别人的朋友圈被其他人评论的通知:XX(usr_name)评价了“XXX”(某条朋友圈的部分文字信息);
④自己的朋友圈被点赞的通知:XX(usr_name)点赞了您的“XXX”(某条朋友圈的部分文字信息);
⑤自己参与的别人的朋友圈被其他人点赞的通知:XX(usr_name)点赞了“XXX”(某条朋友圈的部分文字信息);
前端展示如上五种通知,则需要知道的信息有:
- 该条通知,是通知有新的朋友圈还是新的评论还是有新的点赞?
- publish表单是否有新的内容
- 评论时,是谁评论了哪条朋友圈
- 点赞时,是谁点赞了哪条朋友圈
即为,publish_id,from_id,notice_type至于这条朋友圈的所属者是不是通知对象本身,可以由客户端自行判断。
因为通知的结构体如下:
1 | //OneNotice 单个通知信息 |
当客户端接收到的publish_id为0时,该条Notice意思是:有新的朋友圈信息。
当客户端接收到的publish_id不为0时,(如23),from_user_name:liuxing1,notice_type:1,该条信息的意思是:23号朋友圈有来自liuxing1的点赞
当客户端接收到的publish_id不为0时,(如23),from_user_name:liuxing1,notice_type:0,该条信息的意思是:23号朋友圈有来自liuxing1的评论
1 | //NoticeList 发送给用户的通知信息 |
故,以上通知列,可以告知用户一切相关的通知信息,点击这些信息的时候,再调取具体的通知信息给用户呈现。
GetInfo(根据通知,来获取相关信息的更新)
路由:feed/getinfo
以上五种通知,通知1不会单独呈现,只需要用小红点告知用户有最新动态,刷新朋友圈即可。通知2,3,4,5则会单独呈现,2,3点选的时候,会在上方展示相关朋友圈,下方会显示所有的点赞评论信息,即,将当前发生最新动态的朋友圈的最新状态整个从服务端拉取过来。点赞信息的更新类似。
输入:通过“session_id”可以得知usr_id,usr_name。只需要publish_id即可进行信息的更新。
1 | //NewMoment 回传的最新单条朋友圈详情 |
输出:单条朋友圈的最新状态。
实现所需知识树
Beego框架的使用
实例
samples/WebIM at master · beego/samples · GitHub
Session
由于需要保持登录状态,所以这里需要用到Session控制
Go基础学习记录之如何在Golang中使用Session - Go语言中文网 - Golang中文社区
Session control - beego: simple & powerful Go app framework
Redis
遇到的坑
Json解析首字母一定要大写
1 | //CreateCommentFeedback 评论创建反馈 |
编码JSON的时候,json:”-“可以让其不参与编码,不然还是会编码,不过不会更名。
1 | PublishID int `form:"publish_id" json:"-"` |
若为
1 | PublishID int `form:"publish_id"` |
编码的时候,还是会有这个字段,并且,显示的是,PublishID:XXX
redis的操作问题:
1 | func main() { |
输出:
1 | aaaaa |