[关闭]
@heavysheep 2018-03-20T13:56:00.000000Z 字数 7022 阅读 1832

交接文档

未分类


本文档对简单的代码不做描述,重点讲一下一些架构的设计和原因及其建议的改进方案。因为代码你们肯定能看懂,但会不知道如此设计的历史原因。以方便后来者能没有坑的进行优化和改进。

推送系统

位置: apisvrv2
主要相关文件:
message_push.py: 消息分解和处理
push_script.py: 推送脚本及其相关维护的缓存等
push_manage.py: 相关的函数方法
爱思政、核心库的push_manage.py: 封装了消息向api推送的方法

message_push.py

因为项目分割,func_allotment里爱思政的可以删去,其他的基本都在工作状态中。
sms_push是短信发送方法
wx_push是微信发送方法

push_script.py

(2018.3.19测试和正式已经收入supervisor管理)
基于push_script.py的单进程模式,很多消息的去重/处理/缓存都可在此进行,从性能上来说是完全可以负载的,如果想改为多进程运行,将会面对很多队列去重/缓存保留等相关问题,要非常注意进程间通信。
SingletonListContainer是一个链表形式的单例模式。出于队列经常推送两条相同消息的情况,且我编写的mongodb_insert在某几个业务下依然不能保证100%检测,因此保持一定数量(我编写时是100)的消息签名保证去重。
priori_strategy先验策略库。某些比较特殊的需求只有在发送时间到期时才能确定是否发送,由此部分进行判断。此部分是背离设计初衷的,尽可能不要在此添加代码。
team_owner_message机构拥有者的每日推送。可以转移至push_manage.py
push_limit_strategy推送的控制策略,用于控制对用户的每日推送上限。
ak_maintain为了避免对ak的频繁调用,在内维护各个mp的ak和ak_timeout。
boss_message给老板推送的统计消息,统计方法已经滞后于现在销售端的版本,如果销售端要改,最好连带这里的统计方法一起改了。

push_manage.py

get_template处理各个公众号对模板消息的映射。你想问为什么不调用微信方法直接获取相应模板而要写死映射?因为截至2017年,该方法会有比较高的概率,在ak完全正确的情况下,报ak失效从而调取失败。而get_all_private_template方法不但数据量大,在内部循环也只能依靠名称判断,同名模板会有问题。
mongodb_insert数据处理后进入mongodb,主要目的是去重和不发送重复消息。

改进建议

  1. mongodb不稳定的问题很多,需要手动处理pymongo.errors.AutoReconnect等等错误,所以如上次开会所说,使用redis,且redis能解决大批量修改的问题。
  2. 做消息推送插件时使用数据库管理各个公众号的模板映射及配套参数,能彻底实现多公众号的可配。
  3. 从需求来看,先验的设计并不能高效的满足一些“在发送前决定是否发送或找到所有课程用户”之类的需求,用事务管理起来其实更能避免一些烂需求导致的架构混乱。

运维脚本

位置: apisvrv2
主要相关文件:
uclound_manage.py: message监控
uclound_manage_xuebanweb.py: 学伴web监控
命名为manage是因为本来想作为api使用,后来发现逻辑需要写的很少。以上两个文件除了添加和删除服务器的参数不同外,几乎是镜像的,拆分是为了分成独立的进程进行管理。

需要说明的部分:
* 可以通过_alarm_threshold和_alarm_open_id设置推送的阈值和接收人,open_id填微课堂的就行。
* get_metric这个方法,有时会回调一个DataSets为空的回调,所以我在后面做了一个处理。

改进建议

  1. 服务器的负载准确的来说应当计算平均值,在metric中支持一个列表的并行调取后求均值。目前是写死监控一台,这当然是不准确的。
  2. 相关操作都应当使用数据库维护并记录,方便检查。否则会造成重启后无法管理之前配置的服务器的问题,当然运维没有更大的需求时不必修改。

储值卡

类型: 75
位置: iMoocsSvrV2
主要相关文件:
stored_value_service.py: 储值卡的后台接口文件
其他相关方法:
export_user_stored_value_card_record: 涉及导出的入口。
stored_value_change: 涉及支付的入口。
微信项目-resource_share: 因为储值卡不是product,对推送做了特殊处理。
具体文档可见于小幺鸡

储值卡这块,考虑到产品设计需要接口返回不必要的和杂乱冗余的数据,且另有需要mongodb存储激活码这种冗余数据存储。为了避免查的时候左联、或需要同时查大量mysql-mongodb关联数据,将业务拆分成了三张表,且互有重复。相当于为了精密度和查的速度,牺牲了写的性能。
mysql-stored_value_card: 储值卡新建相关的数据。B端交界口主要涉及此表。
mongodb-user_stored_value_card: 激活码和卡的关联关系。发卡接口大多涉及此表。
mysql-user_stored_value_card:用户和储值卡的关联数据。统计接口大多从此表查。

需要说明的部分:
1. 关爱通因为抽奖的需求,要求以固定字符添加激活码前四位。

改进建议

  1. 不改需求的情况下,不太建议动这块,多个部分联系比较紧密,比较容易出问题。
  2. 换个不拍脑袋的产品经理

任务邀请卡

类型: 76
位置: iMoocsSvrV2
主要相关文件:
task_invite_service.py: 任务邀请卡后台接口文件
其他相关方法:
api项目-task_invite_message: 涉及到相关消息推送。
api项目-task_amount_increase: 涉及到推广任务对任务完成度的修改。
微信项目-resource_share: 因为任务邀请卡不是product,对推送做了特殊处理。
微信项目-task_invite: 设计到关注任务对任务完成度的修改。
具体文档可见于小幺鸡

需要说明的部分:
1. 任务上架后即不能修改,在此原则上,任务文本的join是基于上架做的。
2. 任务详情的id指向的是task_invite,其他用户任务相关都是user_task的id,具体见改进。
3. 整体来说,因为设计上需要用户接受任务时,该任务即不会受派送任务方修改/删除的影响,因此user_task是对task_invite表的一个复制和扩展,另外任务详情也是。

改进建议

  1. 设计时想多了,有一些为了兼容后期需求的字段,例如user_task表的cycle字段,设计以控制循环任务等,不需要的话可以删去。
  2. 因为任务详情是在后端完工后又新增的,导致该方面对移动前端非常不友好。具体表现为task_invite和user_task表的id对前端的不明确。在修改时可以将这两个表的id分别重命名以清晰指向。目前的原则是:未接受的任务都是task_invite的id,用户端拿到的任务都是user_task_id,文档也有较清晰的描述。

微信项目

消息配置

此部分说明扫码推送的逻辑。微信项目的消息配置对api项目消息系统的一个补充,之所以没有统一通过api进行处理,是因为微信要求即扫即推,而在api中面临最多5+n秒的空档期,体验会非常差。

流转流程:
1. 用户扫码后,进入weixin_open.py的WxOpenReceive._event_handler进行事件处理。
2. 根据二维码所带的参数不同(参数在核心库的svr_invitation_card_qrcode_create方法修改),进入qr_scan进行解析。如果此时有短链接,则一并在此处理。
3. 根据处理后的参数不同,分配进入相对应的ifelse进行接下来的推送数据处理并推送。需要注意,未关注扫码和关注扫码是两个不同的ifelse(之前的设计),需要在这两个部分都进行处理。

配置公众号菜单

当菜单按钮有事件按钮时,必须由后端进行所有按钮的配置。
可以用postman调用svr_mp_create_menu接口进行修改,在wx_mp_service的if name == "main"里有一些我之前构建参数的例子。

用户注册

weixin_manage.py中的user_subscribe。

改进建议

微信项目现在面临挺多的问题
1. weixin_open.py是负责事件分发的主要文件,目前代码非常的混乱和难读,而且参数的配置应该在顶部,接下来再进入未关注-关注和已关注-关注的逻辑,强烈建议重构。
2. 微信项目本身的回调式推送,由于处理结束后必须向微信发送确认的success,在服务器处理速度不够快的情况时,success发送会晚于微信自动重试时间,导致生成两条同样的信息。
目前为了解决这种情况,使用了客服消息而不是return的形式回调消息,并且使用SingletonListContainer链表容器加以管理,能保证95%以上的信息不会有重复消息的情况。
如果要彻底解决重复消息的问题,需要彻底重构项目,使其异步且快速的处理消息回调。
3. 所有事件的回调消息发送,应该被一整个类管理起来,否则难以扩展。

WeCastPush直播项目

目前直播项目的send_message接口效率非常低,一方面使用了融云的服务,回调比较慢,另一方面为了确定直播时间相对原点,mongodb和mysql的读取和写入次数较多,性能很差。目前使用运维脚本堆服务器来短期解决这个问题。

为了确定相对原点,试验出的逻辑如下:

  1. 当七牛直播开始,七牛立刻会通过回调方法进入核心库的live,此时自动创建qiniu_live_state文件,将start_time定位为七牛时间戳(基本准确)
  2. 在直播当中,live_time减少直播误差常量,录入数据库
  3. 当前端或七牛提示状态改变时,如果此时状态相同,不做任何操作--此步判断能避免大量前端/七牛莫名奇妙的发送
  4. 当前端提示直播断开时,此时的业务环境往往是用户主动断开,此时的误差常量依然存在,因此在set_live_state依然需要减去常量
  5. 在情况4下,合成视频会导致重复帧,反复检查大约刚好也是4秒,因此total_time计算不减去误差时间
  6. 当前端提示打开时,此时的误差依然存在,因此start_time减去此常量
  7. 当前端未通知关闭,而七牛通知关闭时,此时的业务场景应为断网状态,当七牛进行更新时,因其时间准确,不需要进行更新
  8. 如断网,且15秒内连上,此时无法判断,且会造成延迟,这也是目前考虑到唯一的问题

改进建议

  1. 项目初期由于需要尽可能的减少前端的改动,send_message接口有一些可以规避的查询(比如chatroom_id的确定),如果能让前端做一部分的修改,接口确实能少查询两次
  2. mongodb的响应比较慢也是一个问题
  3. 如大家所说,用web_socket替换rongyun相关的接口

新套餐

位置: iMoocsSvrV2
主要相关文件:
package_app_service.py: 主要接口文件
其他相关方法:
bpackage_user_add_balance: 购买套餐后写入team_info并分配服务。
具体文档可见于小幺鸡
需要说明的逻辑:

另外,此模块更改了console_v2的基类及weixin_base_page_v2.get_response_str的返回。

爬虫相关

位置: princecode/spider
主要相关文件:
litchi_spider.py: 荔枝微课爬虫
qianliao_spider.py: 千聊爬虫
qiniu_relate.py: 七牛方法
spider_manage.py: 会用到一些处理方法

相关的爬虫,都需要将文件下载到本地,并且依次上传至七牛、学伴数据库。
其中千聊爬虫因为其后端接口改变,有些功能已经不可使用。荔枝微课的依然可以使用,如果用户提供课程邀请码给予进入权限,则可完美拷贝到微学伴中。运行示例可实现多种用户的需求。

定时更新人气脚本

位置: iMoocsSvrV2
主要相关文件:
update_visite.py: 脚本文件
运行方式:目前由123.59.84.88通过crontab执行。
逻辑: 如果人气为0(新课)则在运行时随机设置人气为20-150;如果不为新课,80%的几率增长2%-5%,20%几率增长5%-10%

超时评价脚本

位置: iMoocsSvrV2
主要相关文件:
auto_grade.py: 脚本文件
运行方式:由team_days_datas运行时启动运行。
逻辑: 超时15天未进行评价的订单,会提供默认评价语句并给予五星好评。

改进建议

文案太烂了,应该通过一个语句列表随机选取,甚至可以尝试以假乱真打造高人气假象。

B端邀请有礼

iMoocsSvrV2-marketing_service.InviteInformation: B端邀请有礼接口
apisvrv2-invite_send: 邀请送相关
wx-weixin_manage.invite_member: 点击按钮的处理事件和扫码事件
另外wx项目中的teaminvited也是对此业务的处理

业务逻辑:

  1. 当机构创建,我找创建机构的人是否有邀请人,邀请人的来源包括但不限于B端邀请有礼等。
  2. 如果有邀请人,会找邀请人名下是否有机构,如果有,则找到一个/多个中创建最后的机构。
  3. 从某一天开始(记不得具体了,大约是10月中)邀请未大于20个,则发放余额。
  4. 给邀请人发送微信消息(如果邀请人48小时内操纵了我们的公众号)并发送短信。

这一块是早期写的,比较挫,有一段代码需要说明一下

  1. # marketing_service.InviteInformation
  2. key_8 = user_mapping_model().find_one(
  3. "SELECT key_8 FROM user_mapping WHERE user_id = ?", data["user_id"])
  4. if key_8:
  5. key_8 = key_8["key_8"]
  6. else:
  7. user_info = user_model.find_one(
  8. "SELECT * FROM `user` WHERE id = ?", data["user_id"])
  9. # 通过user表3必定存在数据生成映射,用以实现意外情况下的反查
  10. key_8 = "{0}{1}{2}".format(
  11. user_info["id"][-4:],
  12. user_info["login_pwd"][-2:] if user_info["login_pwd"] else "xx",
  13. str(user_info["add_time_int"])[-2:])

前端在推广时会生成一段链接附上user_id,这一段的目的是把user_id通过转换为映射,防止user_id泄露。

销售端

位置: iMoocsSvrV2
主要相关文件:
sale_service.py: 任务邀请卡后台接口文件

销售端是一块非常烫手的山芋,老板的需求奇葩而且往往不记得自己上次说了什么,一定要再三确定,最好录个音。

业务逻辑

老板的原话是“数据是否准确不重要,接口返回要快,在没有条件的时候查快而不准确的表”,所以能看到svr_boss_customer_listsvr_customer_list接口在没有查询条件时,team_sql变量选择取了team_cache表;在有查询条件时,查了team_days_datas表。

成交用户: 在指定时间内有任意一笔订单产生的机构
活跃用户: 在指定时间内有收入/流量/订单数/内容产品数有增加的机构
充值用户: 在指定时间内有任意充值进入微学伴
沉睡用户: 30天内非活跃数据的机构
流失用户: 180天以上非活跃用的机构(当时这里改了又改还没定,所以没做)

需要注意的是,以上逻辑因为实现问题/team_days_datas一些字段支持没跟上/老板心情变了,有一些细微的出入,具体由哪些已经不记得了。

此外还有一些客服沟通记录,指派客服等需求,这种比较简单就不说了。

改动建议

现在的SQL语句已经很复杂了,可以考虑使用一张新的缓存,然后看老板的心情重写

其他

其他例如新手任务、关注有礼、钉钉项目等,都太过简单且运行良好,就不再赘述。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注