import cv2错误: ImportError: libXrender.so.1: cannot open shared object file: No such file or directory

环境: centos,python3.6
错误: import cv2 出错
已安装: pip install opencv-python==3.4.3.18

解决参考: https://www.centos.org/forums/viewtopic.php?t=49456

yum install libXrender.x86_64

支付宝签名配置验证

公私钥生成: https://docs.open.alipay.com/291/106130

进入ssh后

OpenSSL> genrsa -out app_private_key.pem   2048  #生成私钥
OpenSSL> pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out app_private_key_pkcs8.pem #Java开发者需要将私钥转换成PKCS8格式
OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem #生成公钥
OpenSSL> exit #退出OpenSSL程序

配置后验证公钥

  • rsa

app_private_key = open('/home/wukt/project/python_planet/pem/app_private_key.pem', 'r') # 开始计算签名 key = RSA.importKey(app_private_key.read()) signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA.new("a=123".encode())) sign = encodebytes(signature).decode("utf8").replace("\n", "") print(sign)
  • rsa2

app_private_key = open('/home/wukt/project/python_planet/pem/app_private_key.pem', 'r') # 开始计算签名 key = RSA.importKey(app_private_key.read()) signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA256.new("a=123".encode())) sign = encodebytes(signature).decode("utf8").replace("\n", "") print(sign)

mysql 忘记密码重置密码和修改密码

Linux下MySQL重置密码

Linux下重置密码的操作与Windows下类似。

停止MySQL服务

[[email protected] ~]# /etc/init.d/mysql stop

以安全模式启动MySQL

[[email protected] ~]# /usr/local/mysql/bin/mysqld_safe –skip-grant-tables &

登录MySQL

[[email protected] ~]# mysql -u root -p

mysql> use mysql;
mysql> update user set password=password(“123456″) where user=”root”;
mysql> flush privileges;

启动MySQL服务

[[email protected] ~]# /etc/init.d/mysql start
注意了,MySQL5.7之后,重置root密码SQL(感谢刘韦声回复):

update mysql.user set authentication_string=password('123456') where user='root';

创建用户

CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
GRANT ALL ON myapp.* TO 'username'@'localhost';
FLUSH PRIVILEGES

supervisor进程管理

以supervisor管理celery进程为例

一 安装supervisor

二 配置

三 启动和关闭

四 打开web监听


安装supervisor

python3 无法直接使用pypi源安装, 使用

pip3 install git+https://github.com/Supervisor/supervisor

配置

  • 生成默认的配置文件
echo_supervisord_conf > /etc/supervisord.conf
  • 添加自定义配置

在生成的conf文件最后添加

[program:tiis_is_name]
command=/home/wukt/.virtualenvs/p3.6/bin/celery worker -l INFO -c 1 -A flaskrun.celery --beat --loglevel debug --logfile celery_beat.log
stdout_logfile=celeryd.log
stderr_logfile=celeryd.log
autostart=true
autorestart=true
startsecs=1
stopwaitsecs=600

启动和关闭

启动

supervisord -c supervisord.conf

关闭supervisord需要通过supervisor的控制器:

supervisorctl -c supervisord.conf shutdown

重启supervisord也是通过supervisor的控制器:

supervisorctl -c supervisord.conf reload

打开web监听

在生成的conf文件删除指定的注释即可:

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket
;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
# ...


一些配置文件的含义, 来源 https://www.jianshu.com/p/805977544d7f:

[program:usercenter]
directory = /home/leon/projects/usercenter       ; 程序的启动目录
command = gunicorn -c gunicorn.py wsgi:app         ; 启动命令,可以看出与手动在命令行启动的命令是一样的
process_name=%(program_name)s       ; process_name expr (default %(program_name)s)
numprocs=1           ; number of processes copies to start (def 1)
autostart = true     ; 在 supervisord 启动的时候也自动启动
startsecs = 1        ; 启动 1 秒后没有异常退出,就当作已经正常启动了
autorestart = true   ; 程序异常退出后自动重启
startretries = 3     ; 启动失败自动重试次数,默认是 3
user = leon          ; 用哪个用户启动
redirect_stderr = true          ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 20MB  ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 10     ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /data/logs/usercenter_stdout.log
;这一配置项的作用是:如果supervisord管理的进程px又产生了若干子进程,使用supervisorctl停止px进程,停止信号会传播给px产生的所有子进程,确保子进程也一起停止。这一配置项对希望停止所有进程的需求是非常有用的。
stopasgroup=true

; 可以通过 environment 来添加需要的环境变量,一种常见的用法是修改PYTHONPATH ;process environment additions      
; environment=PYTHONPATH=$PYTHONPATH:/path/to/somewhere

主要参考:

vim + tmux窗格平滑切换

再也不用担心无法一边打开vim一边运行终端命令了

推荐~/.vimrc和~/.tmux.conf配置, 以实现vim和tmux窗格的平滑切换

  • vimrc添加并安装
Plugin 'christoomey/vim-tmux-navigator'
  • /.tmux.conf 添加:
# Smart pane switching with awareness of Vim splits.
# See: https://github.com/christoomey/vim-tmux-navigator
is_vim="ps -o state= -o comm= -t '#{pane_tty}' \
    | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
bind-key -n C-h if-shell "$is_vim" "send-keys C-h"  "select-pane -L"
bind-key -n C-j if-shell "$is_vim" "send-keys C-j"  "select-pane -D"
bind-key -n C-k if-shell "$is_vim" "send-keys C-k"  "select-pane -U"
bind-key -n C-l if-shell "$is_vim" "send-keys C-l"  "select-pane -R"
bind-key -n C-\ if-shell "$is_vim" "send-keys C-\\" "select-pane -l"
bind-key -T copy-mode-vi C-h select-pane -L
bind-key -T copy-mode-vi C-j select-pane -D
bind-key -T copy-mode-vi C-k select-pane -U
bind-key -T copy-mode-vi C-l select-pane -R
bind-key -T copy-mode-vi C-\ select-pane -l

窗格却换:

<ctrl-h> => Left
<ctrl-j> => Down
<ctrl-k> => Up
<ctrl-l> => Right
<ctrl-\> => Previous split

知乎中截的图:

我自己的图:

vim+tmux更多配置 更多配置https://github.com/christoomey/vim-tmux-navigator

python多线程高级锁condition

多线程编程中如果使用Condition对象代替lock, 能够实现在某个事件触发后才处理数据, condition中含有的方法:
– wait:线程挂起,收到notify通知后继续运行
– notify:通知其他线程, 解除其它线程的wai状态
– notifyAll(): 通知所有线程
– acquire和release: 获得锁和解除锁, 与lock类似,
– enter和exit使得对象支持上下文操作:

    def __enter__(self):
        return self._lock.__enter__()

    def __exit__(self, *args):
        return self._lock.__exit__(*args)

代码:

import threading
from threading import Condition
# condition


class XiaoAi(threading.Thread):
    def __init__(self, cond):
        self.cond = cond
        super().__init__(name="xiaoai")

    def run(self):
        self.cond.acquire()
        self.cond.wait()
        print('{}:ennn. '.format(self.name))
        self.cond.notify()
        self.cond.wait()

        print('{}:好嗒. '.format(self.name))
        self.cond.release()

class TianMao(threading.Thread):
    def __init__(self, cond):
        super().__init__(name="tiaomao")
        self.cond = cond

    def run(self):
        self.cond.acquire()
        print('{}:hello ~ xiaoai. '.format(self.name))
        self.cond.notify()
        self.cond.wait()

        print('{}:我们来念一首诗吧! . '.format(self.name))
        self.cond.notify()
        self.cond.release()

if __name__ == '__main__':
    condition = Condition()
    xiaoai = XiaoAi(condition)
    tianmao = TianMao(condition)
    # 启动顺序很重要
    xiaoai.start()
    tianmao.start()

打印结果:

tiaomao:hello ~ xiaoai. 
xiaoai:ennn. 
tiaomao:我们来念一首诗吧! . 
xiaoai:好嗒

总结:

这个比较鸡肋

上下文管理器类和上下文管理器装饰器

一. 什么是上下文管理器

二. 自定义一个上下文管理器类:

三. 使用contextmanager

四. 一个例子, sqlalchemy: 数据库的自动提交和回滚


一. 什么是上下文管理器

上下文管理器是在Python2.5之后加入的功能,可以在方便的需要的时候比较精确地分配和释放资源, with便是上下文管理器的最广泛的应用, 比如:

with open("test/test.txt","w") as f:
  f.write("hello")

这上会比使用try:...finally:f.close方便的多.

二. 自定义一个上下文管理器类:

class MyResource:
    # __enter__ 返回的对象会被with语句中as后的变量接受
    def __enter__(self):
        print('connect to resource')
        return self

    def __exit__(self, exc_type, exc_value, tb):
        print('close resource conection')

    def query(self):
        print('query data')

类中有两个特殊的魔术方法:
__enter__: with语句中的代码块执行前, 会执行__enter__, 返回的值将赋值给with句中as后的变量.
__exit__: with语句中的代码块执行结束或出错, 会执行_exit__

比如以下代码:

with Myresource() as r:
    r.query()

的打印结果为:

connect to resource
query data
close resource conection

那么有没有一个简化定义的方法呢, python提供了一个装饰器contextmanager

三. 使用contextmanager

from contextlib import contextmanager
class MyResource:
    def query(self):
        print('query data')

@contextmanager
def make_myresource():
    print('start to connect')
    yield MyResource()
    print('end connect')
    pass

被装饰器装饰的函数分为三部分:
with语句中的代码块执行前执行函数中yield之前代码
yield返回的内容复制给as之后的变量
with代码块执行完毕后执行函数中yield之后的代码

比如下方代码:

with make_myresource() as r:
     r.query()

的结果为:


start to connect query data end connect

四. 一个例子, sqlalchemy: 数据库的自动提交和回滚

在编程中如果频繁的修改数据库, 一味的使用类似try:... except..: rollback() raise e其实是不太好的.

比如某一段的代码的是这样的:

   try:
        gift = Gift()
        gift.isbn = isbn
        ... 
        db.session.add(gift)
        db.session.commit()
    except Exception as e:
        db.session.rollback()
        raise e

为了达到使用with语句的目的, 我们可以重写db所属的类:

from flask_sqlalchemy import SQLAlchemy as _SQLALchemy
class SQLAlchemy(_SQLALchemy):
    @contextmanager
    def auto_commit(self):
        try:
            yield
            self.session.commit()
        except Exception as e:
            db.session.rollback()
            raise e

这时候, 在执行数据的修改的时候便可以:

 with db.auto_commit():
        gift = Gift()
        gift.isbn = isbndb.session.add(gift)
        db.session.add(gift)

with db.auto_commit():
    user = User()
    user.set_attrs(form.data)
    db.session.add(user)

更好的对象转字典

一. 方便但不完美的__dict__

对象转字典用到的方法为__dict__. 比如对象对象a的属性a.name='wk', a.age=18, 那么如果直接将使用a.__dict__获得对应的字典的值为: {name: 'wk', aget:18}, 很方便, 但是也存在一些限制. 其不完美之处在于:

比如:

class A(object):
    name = 'wukt'
    age = 18

    def __init__(self):
        self.gender = 'male'
a = A()
print(a.__dict__)

此时的打印结果是:
{gender: 'male'}
但是类变量name和age无法一同转换.

二. 使用dict

使用dict的方式如下, 如果直接使用会报错.

a = A()
dict(a)
  1. 使用dict之时, 将自动调用类中的keys方法, keys中定义了字典的键, 调用keys方法后, 程序将依照字典取值的方式尝试获得这些键对应的值.
  2. 当使用如字典的取值方式时: 比如a['name'], 将会调用类中的__getitem__方法, __getitem__方法决定了这个值是多少.

因此只需要在一例中添加两个方法就可以使对象可以通过dict转字典:

def keys(self):
    return ('name', 'age' )

def __getitem__(self, item):
    return getattr(self, item)

通过这种方式, 既可以支持类变量的转换, 又可以自定义需要转换的字段.

完整代码:

class A(object):
    name = 'wukt'
    age = 18

    def __init__(self):
        self.gender = 'male'

    def keys(self):
        '''当对实例化对象使用dict(obj)的时候, 会调用这个方法,这里定义了字典的键, 其对应的值将以obj['name']的形式取,
        但是对象是不可以以这种方式取值的, 为了支持这种取值, 可以为类增加一个方法'''
        return ('name', 'age', 'gender')

    def __getitem__(self, item):
        '''内置方法, 当使用obj['name']的形式的时候, 将调用这个方法, 这里返回的结果就是值'''
        return getattr(self, item)

a = A()
r = dict(a)
print(r)

flask: 內置HttpException的rest风格改进

在api的设计中, 无论异常还是正常数据均需要服务器以json的格式返回, 为了对异常的统一管理, 同时为了后续更加方便的返回和验证数据, 我们自定义异常返回类.
设计异常数据的返回格式为:

{
    "error_code": 999,
    "msg": "sorry, we make a mistake",
    "request": "POST /v1/client/register"
}

异常值分别代表:

999  未知错误
1006  客户端错误
1007  服务器错误
1000 参数错误
...

其中json中的request包含了请求的方法和路径.


一. 定义一个最基本的类

二. 参数错误和客户端错误类

三. 所有的异常


一. 定义一个最基本的类

为了使代码简洁, 首先定义一个最基本的类, 供其它类继承, 这个自定义的APIException继承HTTPException.
1. 为了返回特定的body信息, 需要重写get_body;
2. 为了指定返回类型, 需要重写get_headers.
3. 为了接收自定义的参数, 重写了__init__;
4. 同时定义了类变量作为几个默认参数.(code500和error_code:999 均表示未知错误, error_code表示自定义异常code)

from flask import request, json
from werkzeug.exceptions import HTTPException


class ApiException(HTTPException):
    code = 500
    msg = 'sorry, we make a mistake'
    error_code = 999

    def __init__(self, code=None, msg=None, error_code=None, header=None):
        if code:
            self.code = code
        if msg:
            self.msg = msg
        if error_code:
            self.error_code = error_code
        super(ApiException, self).__init__(msg, None)

    def get_body(self, environ=None):
        body = dict(
            msg=self.msg,
            error_code=self.error_code,
            # 形如request="POST v1/client/register"
            request=request.method + ' ' + self.get_url_no_param()
        )
        text = json.dumps(body)
        return text

    def get_headers(self, environ=None):
        return [('Content-Type', 'application/json')]

    @staticmethod
    def get_url_no_param():
        full_url = str(request.full_path)
        main_path = full_url.split('?')
        return main_path[0]

二. 参数错误和客户端错误类

这两个类继承ApiException, 只是重写了三个默认的类变量.


class ClientTypeError(ApiException): code = 400 msg = 'client is invalid' error_code = 1006 # 参数错误 class ParameterException(ApiException): code = 400 msg = 'invalid parameter' error_code = 1000

一切定义完毕!
那么该怎么如何抛出异常呢?

# 抛出参数异常
raise ParameterException(msg=self.errors)

当然也可以只捕捉异常.

三. 所有的异常

至此我们无法将一些特殊的异常返回为json格式, 比如客户端提交的数据不符合规范等 在程序中, 我们会对所有的已知异常跑出一个ApiException的异常.但是无法对未知的异常琢一处理, 因此我们可以使用AOP的思想, 在程序的出口统一处理, 直接在入口函数中编写一个函数

# 为了捕捉所有的异常, 我们需要绑定异常的基类 Exception, Flask>1.0
@app.errorhandler(Exception)
def framework_error(e):
    # ApiExcetion
    # HttpException
    # Exception
    if isinstance(e, ApiException):
        return e
    if isinstance(e, HTTPException):
        code = e.code
        msg = e.description
        error_code = 1007
        return ApiException(code=code, msg=msg, error_code=error_code)
    else:
        if not app.config['DEBUG']:
            return ApiException()
        raise e

此时便可以捕捉所有异常了.

当然在一些正常的返回中也可以使用这个ApiException. 比如:

class Success(ApiException):
    code = 200
    msg = 'success'
    error_code = 0

@api.route('/register', methods=['POST'])
def create_client():
    #...
    return Success()

JWT vs Session

一. session

1.1 session对于服务器的开销

在传统的用户登录认证中,都是采用session方式。用户登录成功,服务端会保证一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大

1.2 session对于服务扩展性的限制

用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

1.3 session+cookie认证方式存在的风险

CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

二. jwt(json web token)

jwt的认证方式只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。

2.1 jwt生成的流程

  • 客户端使用用户名和密码请求服务器
  • 服务器验证后返回一个jwt
  • 客户端存储jwt, 每次请求都会携带这个jwt
  • 服务器验证jwt, 并返回数据

2.2 jwt的构成:

jwt有三部分构成: header, payload, signature

2.2.1 header(头部)

jwt头部中承载了两个信息, 完整的头部类似与:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

typ: 类型
alg: 加密算法, 通常为HMAC SHA256

2.2.2 payload(载荷)

载荷就是存放有效信息的地方, 这些有效信息, 包含三个部分:
– 标准中注册的声明
– 公共的声明
– 私有的声明

标准中注册的声明 (建议但不强制使用):
  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明:

公共的声明可以任意添加信息, 但是不可以包括敏跟信息, 因为可以被解密.
私有的声明:

私有的声明:

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

payload的样式形如:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

2.2.3 signature(签名)

签名由base64加密后的header和加密后的payload以’.’链接, 然后顺纯色header中声明的加密方式进行加盐secret后加密.

2.3 如何使用jwt:

客户端根据接口文档, 一般讲jwt加到请求头内, 比如Authorization中或者自定义字段x-access-token等方式.后台程序取出对应部分的信息进行验证即可.

生成过程举例:

if check_password_hash(user.password, auth.password):
        payload = {
            'public_id': user.public_id,
            'exp': datetime.now() + timedelta(minutes=10)
        }
        token = jwt.encode(payload, app.config['SECRET_KEY'])
        return jsonify({'token': token.decode('utf8')})
    return make_response(
                'Could not verify', 401,
                {'WWW-Authencate': 'Basic realm = "Login Required!"'})

验证装饰器举例

客户端请求时将jwt放置在请求头的x-access-token字段中:


def token_required(func): # decoration @wraps(func) def decorated(*args, **kwargs): token = None if 'x-access-token' in request.headers: token = request.headers['x-access-token'] if not token: return jsonify({'message': 'Token is missing!'}), 401 try: data = jwt.decode(token, app.config['SECRET_KEY']) corrent_user = User.query.filter_by(public_id=data['public_id']).first() except: return jsonify({'message': 'Token is invalid!'}) return func(corrent_user, *args, **kwargs) return decorated