w8-网络遗书demo

首先最近搜集到关于一些https的资料,先记录下来,做个备份,这个早晚要用到

网上流传的所谓「支付宝偷偷添加根证书,将造成安全隐患」的说法是否正确?

数字证书及CA的扫盲介绍

Demo(数据库)

继续上周的demo,目前我先做数据库的表设计,这个表我打算先在demo中统一用一张表来测试,然后最后在正式版通过拆分来提高效率。

数据库的表项目上周有以上部分

{
    'name':name, //用户名
    'password':password, //密码
    'note':note, //遗书内容
    'reciver':reciver, //接收人
    'heatbeatmail':heatbeatmail, //心跳邮箱
    'heatbeatrate':heatbeatrate, //心跳频率
    'heatbeatdelay':heatbeatdelay //心跳过期
}

现在来认真重新设计这个表,首先是用户名密码。

用户名和密码,应该是通过平台注册来的,且用户名应该是个id号而不是用户的真实姓名,所以应该拆分成id、name、password三项。

{
    'id':id, //用户id
    'password':password, //密码
    'name':name //用户名
}

另外,平台应该支持第三方的认证系统,这样的话,用户就可以不设置平台自身密码,而只用id就可以了,所以需要确认下author2.0中数据库中的字段怎么设计,这个有点麻烦可以先不放在demo中做。

再来是遗书部分,遗书应该可以不止能够写一封,一个人应该可以写多封遗书,而且应该有个上限,避免可能出现的不必要的麻烦,暂定上限为10的话,所以遗书的字段应该是note01~note10,随机加的,先测试时候暂时用一个note1和note2。

然后应该可以指定接收人接收指定的留言内容,这里面应该是个嵌套,比如说contact01:{mail:'[email protected]',phone:'13821647463',name:'小白'},然后还应有note01:[content01,content02]

{
    'note01':note, //遗书内容
    'noteLink':['content01','content02'], //遗书关联
    'contact01':{
        mail:'[email protected]',
        phone:'13821647463',
        name:'小白'
        }, //接收人1
    'contact02':{
        mail:'[email protected]',
        phone:'13821647464',
        name:'小黑'
        } //接收人2
}

最后是心跳部分,其实c2d2和曹帅讨论过这种心跳机制,他认为Perpetu的死亡被反告知的方式似乎更合理一些,因为只要死亡的话,亲属肯定会知道,到时候拿着某个code来和服务端确认会更好一点,而且这样后台就可以做的很简单了;但我其实个人更喜欢心跳模式,但看样子两种模式都有人喜欢,我决定两个都做,但当前我打算在demo版本先实现心跳模式再说。

我琢磨了一段时间,觉得可以使用时间戳的方式来做,首先用户可以输入的是

{
    'heatbeatmail':heatbeatmail, //心跳邮箱
    'heatbeatrate':heatbeatrate, //心跳频率
    'heatbeatdelay':heatbeatdelay //心跳过期
}

而其实还需要一个最后更新的时间戳,来标注最后一次得知用户心跳的时间,然后所有的心跳频率与心跳过期时间均在这个时间戳上叠加即可,所以配合规则应该是这样的

{
    'heatbeatMail':heatbeatmail, //心跳邮箱
    'heatbeatRate':heatbeatrate, //心跳频率
    'heatbeatDelay':heatbeatdelay, //心跳过期
    'heatbeatUpdate':heatbeatUpdate, //心跳更新时间
    'heatbeatSync':heatbeatSync, //心跳待同步时间
    'heatbeatFinal':heatbeatFinal //心跳过期时间
}

其中应该满足如下规则:

  1. 心跳待同步时间=心跳更新时间+心跳频率
  2. 心跳过期时间=心跳待同步时间+心跳过期=心跳更新时间+心跳频率+心跳过期
  3. 心跳更新时间应该随着用户的返回消息而更新
  4. 心跳更新时间、心跳频率、心跳过期这三者任意值的更改,都应该连带更新心跳待同步时间和心跳过期时间
  5. 如果系统检测到当前时间≥心跳待同步时间,且≤心跳过期时间,则每天发送一次消息询问心跳

之前没做过mongodb的比较查询,搜了下发现这样做是可行的

MongoDB学习笔记(查询)

如果日期可以直接拿来当整数比较的话逻辑大概是这样:

find({"heatbeatSync":{"$lte":today},"heatbeatFinal":{"$gte":today}})

检索后,我觉得还应该设置一个参数,确定用户是否已经确实死亡,所以应该设置一个布尔参数进行判断,deathConfirm,还应该设置一个联系人的邮件是否已经投放,notesendConfirm,这样的话还应该有:

{
    'deathConfirm':'False', //死亡确认
    'notesendConfirm':'False' //留言发送确认
}

另外,这样的服务还应该可以停止,再加上一个用户的手机号码,所以总的一张基本完整的数据表应该是这样的:

{
    'id':id, //用户id
    'password':password, //密码
    'name':name, //用户名
    'phone':phone, //手机号码
    'note01':{
        'content':content, //遗书内容
        'link':['content01','content02'] //遗书关联
        } //遗书相关
    'note02':{
        'content':content, //遗书内容
        'link':['content03'] //遗书关联
        } //遗书相关
    'contact01':{
        mail:'[email protected]',
        phone:'13821647463',
        name:'小白'
        }, //接收人1
    'contact02':{
        mail:'[email protected]',
        phone:'13821647464',
        name:'小黑'
        }, //接收人2
    'contact03':{
        mail:'[email protected]',
        phone:'13821647465',
        name:'无常'
        }, //接收人3
    'heatbeatMail':heatbeatmail, //心跳邮箱
    'heatbeatRate':heatbeatrate, //心跳频率
    'heatbeatDelay':heatbeatdelay, //心跳过期
    'heatbeatUpdate':heatbeatUpdate, //心跳更新时间
    'heatbeatSync':heatbeatSync, //心跳待同步时间
    'heatbeatFinal':heatbeatFinal, //心跳过期时间
    'deathConfirm':'False', //死亡确认
    'notesendConfirm':'False', //留言发送确认
    'serviceState':'True' //服务状态
}

然后根据这张表重新调整一下用户的输入框内容,首先是wtf表单数据格式定义:

class DemoForm(Form):
    # 用户注册id名
    userID = TextField('用户名', validators = [DataRequired()])
    # 密码输入框
    password = PasswordField('密码',validators = [Length(min = 8, max = 40)])
    # 用户名输入框
    name = TextField('真实姓名', validators = [DataRequired()])
    # 用户手机输入框
    phone = TextField('手机', validators = [DataRequired()])
    # 遗书输入框
    content01 = TextAreaField('留言1', validators = [DataRequired()])
    content02 = TextAreaField('留言2', validators = [DataRequired()])
    # 接收人输入框
    contact01Name = TextField('接收人1姓名', validators = [DataRequired()])
    contact01Mail = TextField('接收人1邮箱', validators = [DataRequired()])
    contact01Phone = TextField('接收人1电话', validators = [DataRequired()])

    contact02Name = TextField('接收人2姓名', validators = [DataRequired()])
    contact02Mail = TextField('接收人2邮箱', validators = [DataRequired()])
    contact02Phone = TextField('接收人2电话', validators = [DataRequired()])

    contact03Name = TextField('接收人3姓名', validators = [DataRequired()])
    contact03Mail = TextField('接收人3邮箱', validators = [DataRequired()])
    contact03Phone = TextField('接收人3电话', validators = [DataRequired()])

    # 心跳邮箱
    heatbeatmail = TextField('心跳邮箱', validators = [Email()])
    # 心跳频率
    rate_choices = [('1', '每周'), ('2', '每月'), ('3', '每3个月')]
    heatbeatrate = SelectField('心跳频率',choices = rate_choices, default = '2')
    # 心跳延迟
    delay_choices = [('1', '1周后'), ('2', '1个月后'), ('3', '3个月后')]
    heatbeatdelay = SelectField('心跳延迟',choices = delay_choices, default = '2')

然后是web显示的表单页面:

    <form action="/" method="POST">
        {{ form.hidden_tag() }}
      <div class="form-group">
        {{ form.userID.label }} {{ form.userID(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.password.label }} {{ form.password(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.name.label }} {{ form.name(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.phone.label }} {{ form.phone(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.content01.label }} {{ form.content01(class="form-control note") }}
      </div>
      <div class="form-group">
        {{ form.content02.label }} {{ form.content02(class="form-control note") }}
      </div>
      <div class="form-group">
        {{ form.contact01Name.label }} {{ form.contact01Name(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.contact01Mail.label }} {{ form.contact01Mail(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.contact01Phone.label }} {{ form.contact01Phone(class="form-control") }}
      </div>

      <div class="form-group">
        {{ form.contact02Name.label }} {{ form.contact02Name(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.contact02Mail.label }} {{ form.contact02Mail(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.contact02Phone.label }} {{ form.contact02Phone(class="form-control") }}
      </div>

      <div class="form-group">
        {{ form.contact03Name.label }} {{ form.contact03Name(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.contact03Mail.label }} {{ form.contact03Mail(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.contact03Phone.label }} {{ form.contact03Phone(class="form-control") }}
      </div>

      <div class="form-group">
        {{ form.heatbeatmail.label }} {{ form.heatbeatmail(class="form-control") }}
      </div>
      <div class="form-group">
        {{ form.heatbeatrate.label }} {{ form.heatbeatrate(class="form-control") }}
      </div> 
      <div class="form-group">
        {{ form.heatbeatdelay.label }} {{ form.heatbeatdelay(class="form-control") }}
      </div>
      <button type="submit" class="btn btn-default">Submit</button>
    </form>

最后是处理post后的表单接收,和插入数据库前的数据细节整理:

# 如果接收到post提交
if request.method == 'POST':
    userID = request.form['userID']
    password = request.form['password']
    name = request.form['name']
    phone = request.form['phone']
    content01 = request.form['content01']
    content02 = request.form['content02']
    contact01Mail = request.form['contact01Mail']
    contact01Phone = request.form['contact01Phone']
    contact01Name = request.form['contact01Name']

    contact02Mail = request.form['contact02Mail']
    contact02Phone = request.form['contact02Phone']
    contact02Name = request.form['contact02Name']

    contact03Mail = request.form['contact03Mail']
    contact03Phone = request.form['contact03Phone']
    contact03Name = request.form['contact03Name']

    heatbeatmail = request.form['heatbeatmail']
    heatbeatrate = request.form['heatbeatrate']
    heatbeatdelay = request.form['heatbeatdelay']
    heatbeatUpdate = str(time.strftime("%Y-%m-%d %H:%M:%S"))
    heatbeatSync = str(time.strftime("%Y-%m-%d %H:%M:%S")) + 'heatbeatrate'
    heatbeatFinal = str(time.strftime("%Y-%m-%d %H:%M:%S")) + 'heatbeatrate' + 'heatbeatdelay'

    # 设置插入数据库的内容
    demo_data = {
    'userID':userID,
    'password':password,
    'name':name, 
    'phone':phone, 
    'note01':{
        'content':content01,
        'link':['content01','content02']
        },
    'note02':{
        'content':content02,
        'link':['content03']
        },
    'contact01':{
        'mail':contact01Mail,
        'phone':contact01Phone,
        'name':contact01Name
        },
    'contact02':{
        'mail':contact02Mail,
        'phone':contact02Phone,
        'name':contact02Name
        },
    'contact03':{
        'mail':contact03Mail,
        'phone':contact03Phone,
        'name':contact03Name
        },
    'heatbeatMail':heatbeatmail,
    'heatbeatRate':heatbeatrate,
    'heatbeatDelay':heatbeatdelay,
    'heatbeatUpdate':heatbeatUpdate,
    'heatbeatSync':heatbeatSync,
    'heatbeatFinal':heatbeatFinal,
    'deathConfirm':False,
    'notesendConfirm':False,
    'serviceState':True
    }

    # 插入数据
    collection.insert_one(demo_data)

测试一下,确认是可以输入成功的。

Demo(服务模块)

除了flask完成的web组件儿,我还需要两个函数服务,一个用于检查给到期同步的人发送心跳确认邮件,另一个检查心跳过期的人发送遗嘱邮件。

或者按功能区分,一个模块专负责检查数据库,然后把查到的整理成合适的json格式传递出去,另一个模块专门跑邮件服务,负责把接收到的json解析成邮件发送,这种划分其实更科学也更灵活,而且很容易扩展,但要用到至少3个container,所以demo场景不使用这种方式划分。

而且为了测试还应该设置一个按钮,当触发时发送邮件测试联通性。

检查数据库发心跳邮件的逻辑应该是:

  1. 检出所有应该发确认心跳的用户数据对象
  2. 检出这些对象中的邮箱、与用户名字段
  3. 向这些邮箱发送带用户名和restapi接收的链接

发过期邮件的逻辑很类似:

  1. 检出所有过期的用户数据对象
  2. 检出这些对象中的邮箱、用户名、手机号码、联系人联系方式等字段
  3. 给联系人发遗嘱链接
  4. 告知用户信息已传达

这两个函数都是每天运行一次,测试就是直接获取用户的这些对象,然后整理成邮件可触发函数

这里flask还需要配合实现两件事:

  1. 提供一个REST的post口服务来接收用户心跳回应,但并不需要提供web界面
  2. 提供一个遗嘱查看的界面

这里面最好做的是触发函数,先做个触发的测试

# 邮件服务测试
def service_test(name,usermail,note,contact,contactmail,heatbeatUpdate):
    content = '您好,这是一封网络遗嘱服务邮件,受到' + name + '的委托' + '代他发送给您的留言如下:' + '\n' + note


    from_addr = raw_input('enter mailaddr:')
    password = raw_input('enter password:')
    to_addr = contactmail
    smtp_server = 'smtp.163.com'

    msg = MIMEText('<html><body><h1>whenmgone网络遗书</h1>' +
        '<p>' + content + '</p>' +
        '</body></html>', 'html',  'utf-8')
    msg['From'] = _format_addr('whenmgone网络遗书服务 <%s>' % from_addr)
    msg['To'] = _format_addr(contact + ' <%s>' % to_addr)
    msg['Subject'] = Header('来自' + name +'先生的遗嘱', 'utf-8').encode()

    server = smtplib.SMTP(smtp_server, 25)
    server.set_debuglevel(1)
    server.login(from_addr, password)
    server.sendmail(from_addr, [to_addr], msg.as_string())
    server.quit()


name = '良朝'
usermail = 'liangchaob'
note = '快进到我的碗里来'
contact = '小冷'
contactmail = '[email protected]'
heatbeatUpdate = 12

service_test(name,usermail,note,contact,contactmail,heatbeatUpdate)

其实很简单,不过是把邮件发送给封装成一个函数,把变量传进去而已。

然后我要做的是想办法把人筛出来,就用之前的比较函数来做,由于我还没搞清楚怎么做日期计算,所以打算先用整数来代替: 首先我先要想办法插入整数的日期:

直接在主函数上改,先设置个1,8,15

    # heatbeatUpdate = str(time.strftime("%Y-%m-%d %H:%M:%S"))
    # heatbeatSync = str(time.strftime("%Y-%m-%d %H:%M:%S")) + 'heatbeatrate'
    # heatbeatFinal = str(time.strftime("%Y-%m-%d %H:%M:%S")) + 'heatbeatrate' + 'heatbeatdelay'
    heatbeatUpdate = 1
    heatbeatSync = 8
    heatbeatFinal = 15

再设置个4,14,24

    heatbeatUpdate = 4
    heatbeatSync = 14
    heatbeatFinal = 24

再来个10,25,35

    heatbeatUpdate = 10
    heatbeatSync = 25
    heatbeatFinal = 35

全部插入后,我使用比较来筛选该发同步邮件的人,我选择14这个数字,当今天是14时候,第一个和第二个都应该被返回

# coding: utf-8  
# 导入pymongo模块
import pymongo
# 设置数据库地址
client = pymongo.MongoClient('172.16.191.163', 27017)
# 设置数据库名
db = client['demodb']
# 设置表名
collection = db['demotable']

today = 14
# 筛选出应该发同步邮件的人
for i in collection.find({"heatbeatSync":{"$lte":today},"heatbeatFinal":{"$gte":today}}):
    print i

返回后如下:

$ python testdb.py
{u'notesendConfirm': False, u'heatbeatDelay': u'1', u'note02': {u'content': u'\u4f60\u5728\u54ea\u91cc', u'link': [u'content03']}, u'name': u'\u7528\u62371', u'phone': u'123', u'contact02': {u'mail': u'[email protected]', u'name': u'c2', u'phone': u'122'}, u'userID': u'id1', u'deathConfirm': False, u'heatbeatMail': u'[email protected]', u'serviceState': True, u'heatbeatFinal': 15, u'password': u'P@ssw0rd', u'note01': {u'content': u'\u597d\u4e45\u4e0d\u89c1', u'link': [u'content01', u'content02']}, u'heatbeatRate': u'1', u'heatbeatSync': 8, u'_id': ObjectId('566a7144e3465f0819850d48'), u'contact01': {u'mail': u'[email protected]', u'name': u'c1', u'phone': u'121'}, u'contact03': {u'mail': u'[email protected]', u'name': u'c3', u'phone': u'123'}, u'heatbeatUpdate': 1}
{u'notesendConfirm': False, u'heatbeatDelay': u'2', u'note02': {u'content': u'\u6ca1\u8def\u5c31\u6ca1\u8def', u'link': [u'content03']}, u'name': u'\u7528\u62372', u'phone': u'18681649691', u'contact02': {u'mail': u'[email protected]', u'name': u'\u8fd8\u662f\u826f\u671d', u'phone': u'123414144'}, u'userID': u'id2', u'deathConfirm': False, u'heatbeatMail': u'[email protected]', u'serviceState': True, u'heatbeatFinal': 24, u'password': u'P@ssw0rd', u'note01': {u'content': u'\u5c71\u91cd\u6c34\u590d\u7591\u65e0\u8def', u'link': [u'content01', u'content02']}, u'heatbeatRate': u'2', u'heatbeatSync': 14, u'_id': ObjectId('566a72c6e3465f081b03b990'), u'contact01': {u'mail': u'[email protected]', u'name': u'\u826f\u671d', u'phone': u'11231314'}, u'contact03': {u'mail': u'[email protected]', u'name': u'\u5fc5\u987b\u662f\u826f\u671d', u'phone': u'13441441'}, u'heatbeatUpdate': 4}

乱糟糟的,我改下查询条件将其中只查询用户id和用户名,筛出来:

collection.find({"heatbeatSync":{"$lte":today},"heatbeatFinal":{"$gte":today}},{'userID':1,'name':1})

得到结果精简了很多:

$ python testdb.py
{u'_id': ObjectId('566a7144e3465f0819850d48'), u'userID': u'id1', u'name': u'\u7528\u62371'}
{u'_id': ObjectId('566a72c6e3465f081b03b990'), u'userID': u'id2', u'name': u'\u7528\u62372'}

结果是准确的,然后照猫画虎,把最终遗书函数发出来

ftoday = 20
# 筛选出应该发遗嘱邮件的人
for i in collection.find({"heatbeatFinal":{"$lte":ftoday}},{'userID':1,'name':1}):
    print i

选择20,看能否把第一个筛出来:

$ python testdb.py
{u'_id': ObjectId('566a7144e3465f0819850d48'), u'userID': u'id1', u'name': u'\u7528\u62371'}

选择50,看能否把所有的筛出来:

$ python testdb.py
{u'_id': ObjectId('566a7144e3465f0819850d48'), u'userID': u'id1', u'name': u'\u7528\u62371'}
{u'_id': ObjectId('566a72c6e3465f081b03b990'), u'userID': u'id2', u'name': u'\u7528\u62372'}
{u'_id': ObjectId('566a7421e3465f081db88783'), u'userID': u'\x08id3', u'name': u'\u8001\u4e09'}

测试完成,发现都是ok的。

Demo(服务模块2)

然后我不应该拿这个整数瞎糊弄了,我应该换成日期计算器,它应该具备以下功能:

  1. 可以把日期转换成可计算的整数,好用数据库来筛对象
  2. 具备日期加减计算的功能,好设置日期

查到了以下资料:

Python timedelta

Python中时间的处理之——timedelta篇

python time模块详解

这个时候我发现,如果想要实现日期计算,和数据库内通过比较时间筛数据,那么这条时间记录就最好是个「时间戳」格式,这种情况下,比较就会非常简单,但是要注意最后想要呈现出来的时候都要先转化一下,而且测试后发现用于日期计算的timedalta是在datetime函数中的,这个要注意引入进来。

而且我尝试着help(datetime.timedelta())结果发现:

 |  Data descriptors defined here:
 |
 |  days
 |      Number of days.
 |
 |  microseconds
 |      Number of microseconds (>= 0 and less than 1 second).
 |
 |  seconds
 |      Number of seconds (>= 0 and less than 1 day).

这个只支持最粗到weeks和days,所以可能不好完成精确的月与年日期计算,好在这个服务上这个应该并不看重,先忽略掉。

然后还有一点,就是datetime.datetime.now()得到的是个时间元组,计算完成后还是需要把它额外转成时间戳才能入库,需要这两步:

>>> a=datetime.datetime.now()
>>> time.mktime(a.timetuple())
1449939085.0

首先获取当前时间时间戳是:

>>> a=datetime.datetime.now()

然后同步频率设置heatbeatrate,延迟heatbeatdelay

heatbeatrate=7
heatbeatdelay=7

获取下次同步时间heatbeatSync与过期时间

b = a + timedelta(days=heatbeatrate)
c = a + timedelta(days=heatbeatrate+heatbeatdelay)

最后都将其变成时间戳

heatbeatUpdate=time.mktime(a.timetuple())
heatbeatSync=time.mktime(b.timetuple())
heatbeatFinal=time.mktime(c.timetuple())
print a
print b
print c

测试一下,很顺利:

>>> a=datetime.now()
>>> heatbeatrate=7
>>> heatbeatdelay=7
>>> b = a + timedelta(days=heatbeatrate)
>>> c = a + timedelta(days=heatbeatrate+heatbeatdelay)
>>> b
datetime.datetime(2015, 12, 22, 0, 20, 40, 850559)
>>> c
datetime.datetime(2015, 12, 29, 0, 20, 40, 850559)
>>> heatbeatUpdate=time.mktime(a.timetuple())
>>> heatbeatSync=time.mktime(b.timetuple())
>>> heatbeatFinal=time.mktime(c.timetuple())
>>> print a
2015-12-15 00:20:40.850559
>>> print b
2015-12-22 00:20:40.850559
>>> print c
2015-12-29 00:20:40.850559
>>> print heatbeatUpdate
1450110040.0
>>> print heatbeatSync
1450714840.0
>>> print heatbeatFinal
1451319640.0