Remember小助手

禅心 · 悟道

前言

Mac上有一个自带的备忘录,感觉还挺好用的。然后也想自己动手,做个类似的Remember小工具来玩一下。

工具类型:胖服务端,瘦客户端的模式。大致的场景就是客户端只管把自己想让被提醒的事项发给服务器端,然后配合自己的本地扫描,对符合要求的memo进行弹框提醒。

最近对Redis比较着迷一点,被其优雅高效的设计所打动。虽然对于搜索方面支持的不太好,但是搜索的话使用专业的搜索服务就好了。我个人比较崇尚Unix工具系的宗旨:一个工具只专注于做一件事,这也是Redis目前所体现的。

服务器端

对于服务器端的设计的初衷,是一个“胖胖的”,做了大部分的工作的形式,但是做着做着,发现客户端做的工作其实也蛮多的。目前服务器端的任务是:

  • 接受用户注册
  • 对memo支持CRUD
  • 对请求进行“安全甄选”

目录结构

➜  server ll *.py
-rw-r--r--  1 changba164  staff   346B Oct 28 14:53 datastructor.py # 常见数据结构bean
-rw-r--r--  1 changba164  staff   2.6K Oct 28 18:22 redishelper.py  # redis操作相关的工具类
-rw-r--r--  1 changba164  staff   4.6K Oct 28 18:42 server.py   # 对客户端提供服务支持

代码比较简单,下面简单来看下具体的内容。

datastructor.py

#!/usr/bin python
# coding: utf8
# file: .py

import sys

reload(sys)
sys.setdefaultencoding('utf8')
import json

class ResponseCode(object):
    """
    服务响应码
    """
    def __init__(self, code, msg):
        self.code = code
        self.msg = msg

    def getcode(self):
        return json.dumps({"code":self.code, "msg":self.msg})

redishelper.py

#!/usr/bin python
# coding: utf8
# file: .py

import sys

reload(sys)
sys.setdefaultencoding('utf8')

import redis
import hashlib
import uuid
import time

class RedisHelper(object):
    """
    redis操作相关的工具类。
    """
    def __init__(self):
        self.rs = redis.Redis(host="localhost", port=6379, db=3, encoding="utf8", charset="utf8")
        # 相关key
        self.uids = "userids:set"
        self.rank = "unfinished:zset:"
        self.unfinished = "unfinished:hash:"
        self.finished = "finished:hash:"

    def check_request_valid(self, uid, securitycode):
        if uid is None:
            return False
        if securitycode == hashlib.md5(uid).hexdigest():
            return True
        return False

    def register(self, uid):
        if uid is None:
            return False
        self.rs.sadd(self.uids, uid)
        return True

    def check_user(self, uid):
        if uid is None:
            return False
        return True if self.rs.sismember(self.uids, uid) else False

    def add_memo(self, uid, memoid, memo=""):
        if uid is None:
            return False
        memoid = memoid
        self.rs.sadd(str(uid), memoid)
        self.rs.hset(self.unfinished+str(uid), memoid, memo)
        self.rs.zadd(self.rank+str(uid), memoid, int(memoid))


    def update_memo(self, uid, memoid, memo):
        if uid is None:
            return False
        if not self.rs.sismember(str(uid), memoid):
            return False
        self.rs.hset(self.unfinished+str(uid), memoid, memo)
        return True

    def delete_memo(self, uid, memoid):
        if uid is None:
            return False
        memo = self.rs.hget(self.unfinished+str(uid), memoid)
        self.rs.hset(self.finished+str(uid), memoid, memo)
        self.rs.zrem(self.rank+str(uid), memoid)
        return True

    def get_memo(self, uid, memoid):
        if uid is None:
            return None
        return self.rs.hget(self.unfinished+str(uid), memoid)

    def get_memos(self, uid, reverse=True):
        if uid is None:
            return None
        memoids = self.get_memoids(uid, reverse)
        print memoids
        memos = []
        for item in memoids:
            memos.append(self.rs.hget(self.unfinished+str(uid), item[0]))
        return memos

    def get_memoids(self, uid, reverse=True):
        if uid is None:
            return []
        if reverse == True:
            return [item[0] for item in self.rs.zrevrange(self.rank+str(uid), 0, -1, withscores=True)]
        else:
            return [item[0] for item in self.rs.zrange(self.rank+str(uid), 0, -1, withscores=True)]

server.py

#!/usr/bin python
# coding: utf8
# file: .py

import sys

reload(sys)
sys.setdefaultencoding('utf8')
from flask import Flask, request
import redishelper
import datastructor
import logging
import json

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s:%(levelname)s %(message)s',
    datafmt="%Y-%m-%d %H:%i:%s",
    filename="/Users/changba164/guo/tools/remeber/server/memo-server.log",
    filemode="a"
)

app = Flask(__name__)
helper = redishelper.RedisHelper()


@app.route("/", methods=['GET', 'POST'])
def home():
    return "It works!"

@app.route("/register", methods=["POST"])
def register():
    uid = request.form["uid"]
    helper.register(uid)
    res = datastructor.ResponseCode(1000, "uid={}".format(uid))
    return res.getcode()

@app.route("/add", methods=["POST"])
def add():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    memoid = request.form['memoid']
    memo = request.form['memo']

    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "该用户还未注册哟!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    logging.info("{}: {}".format(uid, securitycode))
    if isvalid == False:
        return datastructor.ResponseCode(1002, "身份信息不合法!").getcode()
    helper.add_memo(uid, memoid, memo)
    return datastructor.ResponseCode(1000, "memo已经保存啦!").getcode()

@app.route("/update", methods=["POST"])
def update():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    memoid = request.form['memoid']
    memo = request.form['memo']

    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "该用户还未注册哟!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "身份信息不合法!").getcode()
    helper.update_memo(uid, memoid, memo)
    return datastructor.ResponseCode(1000, "memo已经更新啦!").getcode()

@app.route("/delete", methods=["POST"])
def deletememo():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    memoid = request.form['memoid']

    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "该用户还未注册哟!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "身份信息不合法!").getcode()
    helper.delete_memo(uid, memoid)
    return datastructor.ResponseCode(1000, "memo已经删除/完成啦!").getcode()

@app.route("/detail", methods=["POST"])
def detail():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    memoid = request.form['memoid']

    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "该用户还未注册哟!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "身份信息不合法!").getcode()
    detail = helper.get_memo(uid, memoid)
    return datastructor.ResponseCode(1000, detail).getcode()

@app.route("/lists", methods=['POST'])
def lists():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    reverse = request.form['reverse']
    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "该用户还未注册哟!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "身份信息不合法!").getcode()
    memos = helper.get_memos(uid, reverse)
    res = datastructor.ResponseCode(1000, json.dumps((memos))).getcode()
    return res

@app.route("/recent", methods=['POST'])
def recentids():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "该用户还未注册哟!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "身份信息不合法!").getcode()
    memoid = helper.get_memoids(uid, False)[0]
    result = {
        "memoid": memoid,
        "memo": helper.get_memo(uid, memoid)
    }
    res = datastructor.ResponseCode(1000, json.dumps((result))).getcode()
    return res


if __name__ == '__main__':
    app.run(host='localhost', port=9999, debug=True)

“瘦客户端”

客户端所要做的无非是:新增memo,获得最需要处理的memo,并进行弹窗提示等。目前客户端的任务还不算完成,也就简单实现了下。

➜  client ls
testutils.py utils.py
➜  client

utils.py

#!/usr/bin python
# coding: utf8
# file: .py

import sys

reload(sys)
sys.setdefaultencoding('utf8')

import requests
import json
import hashlib
import time
import datetime
import tkMessageBox
from Tkinter import *

def get_security_code(uid):
    return hashlib.md5(uid).hexdigest()

def time_to_stamp(seed):
    """
    将白话点的时间转换为时间戳,格式为: 00:00:00:00 天:小时:分钟:秒
    """
    curtime = int(time.time())
    d, h, m, s = (int(item) for item in str(seed).split(":"))
    targettime = curtime + (d*86400)+ (h*3600)+(m*60)+s
    return targettime

def stamp_to_time(stamp):
    return datetime.datetime.utcfromtimestamp(float(stamp))

def make_dialog(timestr, msg):
    tkMessageBox.showinfo(title=timestr, message=msg)
    # root = Tk()
    # root.title(timestr)
    # frame = Frame(root)
    # Label(frame, text=msg).pack()
    # frame.pack(side=TOP)
    # root.mainloop()

class UserService(object):
    """
    注册用户
    """
    def __init__(self):
        pass

    @staticmethod
    def register(uid):
        if uid is None:
            return False
        url = "http://localhost:9999/register"
        response = requests.post(url, data={"uid": uid})
        if response.status_code == 200 and response.json()['code'] == 1000:
            return True
        return False




class MemoHelper(object):
    """
    客户端memo工具类
    """
    def __init__(self):
        self.url = "http://localhost:9999/{}"

    def post(self, uid, memoid, memo):
        posturl = self.url.format("add")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "memoid": memoid,
            "memo": memo
        }
        response = requests.post(posturl, data=payload)
        return response.text
    def getmemo(self, uid, memoid):
        url = self.url.format("detail")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "memoid": memoid
        }
        response = requests.post(url, data=payload)
        return response.text
    def getmemos(self, uid, reverse=True):
        url = self.url.format("lists")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "reverse": reverse
        }
        response = requests.post(url, data=payload)
        return response.text
    def getrecent(self, uid):
        url = self.url.format("recent")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid)
        }
        response = requests.post(url, data=payload)
        return response.text
    def updatememo(self, uid, memoid, memo):
        url = self.url.format("update")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "memoid": memoid,
            "memo": memo
        }
        response = requests.post(url, data=payload)
        return response.text
    def deletememo(self, uid, memoid):
        url = self.url.format("delete")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "memoid": memoid
        }
        response = requests.post(url, data=payload)
        return response.text


class Emitter(object):
    """
    检测到到期任务,则弹出一个提醒框!
    """
    def __init__(self, uid):
        self.uid = str(uid)
        self.memohelper = MemoHelper()

    def emit_in(self, timestr, memo):
        timestamp = time_to_stamp(timestr)
        self.memohelper.post(self.uid, timestamp, memo)



    def emit_out(self):
        # 如果时间符合要求就emit出来, 找出距离时间最远的memo。
        data = self.memohelper.getrecent(self.uid)
        data = json.loads(data)
        data = data['msg']
        data = json.loads(data)
        targettime = stamp_to_time(data['memoid'])
        memo = data['memo']
        make_dialog(targettime, memo)


if __name__ == '__main__':

    emitter = Emitter(201393260)
    emitter.emit_out()

总结

总体来说,这个小工具没有达到我的预期效果,大致有如下几点:

  • 扫描服务可以说没怎么做。
  • “安全”这里简单就是限制客户端请求方式为POST,简单的使用securitycode做了下判断(如果被抓包分析的话,那也没什么用,比较好的做法就是模仿Flask使用PIN码,来进一步提高安全性)。
  • 客户端弹框效果不是很理想,而且对于发布一个POST而言,还没做对应的GUI支持,目前也就在命令行里面使用类似:

emitin xxxxxxx a:b:c:d
大致的意思是: 记录 在a天b小时c分钟d秒 后要做的xxxxxxxx事项。

有兴趣的可以在目前的代码基础上进行优化,这里权当抛砖引玉吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,266评论 25 707
  • #幸福是需要修出来的~每天进步1%~幸福实修08班~05~袁群芽# 20170620(1/99) 【幸福三朵玫瑰】...
    乐Ma666阅读 196评论 1 0
  • 昨晚八点,我参加的“配音活动”有了结果。我过了初选,现在进入决定性的阶段。听说只要进了前八就可以给参赛者投去的文章...
    会疼这思念阅读 238评论 2 1
  • 在这里 人人相食 是一身一菩提,一池一世界 时常没人知道你会死在何处 更没人关心 你的死因 在这里 你越勤恳 就越...
    连月东风阅读 174评论 0 0