支付宝签名配置验证

公私钥生成: 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

主要参考:

Alembic隐藏数据库配置信息

Alembic数据库迁移使用参考: https://blog.csdn.net/deerlux/article/details/50181997


此迁移工具的详细配置信息在alembic.ini文件中, 其中包括

sqlalchemy.url = driver://user:[email protected]/dbname

为了防止不小心将密码信息提交, 可将此行删除,并修改alembic/env.py

import sys

sys.path.append(os.path.realpath('.'))
from planet.common.base_model import DB_PARAMS

from planet import models
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.


config = context.config
config.set_main_option('sqlalchemy.url', DB_PARAMS)  # 关键行

target_metadata = None

target_metadata = models.Base.metadata

uwsgi+flask

uwsgi:

uwsgi.ini at project directory

[uwsgi]
socket = 127.0.0.1:5000
chdir = /home/wukt/project/ziru/zr
wsgi-file = run.py
callable = app
processes = 4
threads = 2
stats = 127.0.0.1:9191

nginx:

a.com.conf or nginx.conf

server {
      listen 80; 
      server_name a.com;
      access_log /data/wwwlogs/a.com_nginx.log combined;
      index index.html index.htm index.php;
      root /data/wwwroot/a.com;
      include /usr/local/nginx/conf/rewrite/none.conf;
      #error_page 404 /404.html;
      #error_page 502 /502.html;
    location / { 
        include /usr/local/nginx/conf/uwsgi_params;
        uwsgi_pass 127.0.0.1:5000;  # 指向uwsgi 所应用的内部地址,所有请求将转发给uwsgi 处理
    }
}
~      

run

uwsgi –ini uwsgi.ini


my sogou input is crashed!

windows server部署flask项目

部署环境
– windows server
– python3.6
– nginx

使用tornado作为flask的启动服务器

安装依赖

安装tornado

pip install tornado

可能会需要的包

pip install pycurl

创建启动文件

在flask项目根目录下创建tornado_server.py


import sys from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from flaskrun import app if len(sys.argv) == 2:   port = sys.argv[1] else:   port = 5000 http_server = HTTPServer(WSGIContainer(app)) http_server.listen(port) IOLoop.instance().start()

其中文件中app指的是flask项目的核心对象, 视情况修改, port为端口.

启动flask项目:

python tornado_server.py

配置nginx反向代理

为nginx添加一条服务器配置:

server {
  listen 443 default ssl; # 或者使用80端口
  # 这里填写你自己的域名(或者ip)
  server_name mydomain.com  www.mydomain.com;
    # 如果需要配置证书可以在这里配置
  location / {
      proxy_pass http://127.0.0.1:5000;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_pass_header Set-Cookie;
 }
 }

注意, 反向代理的端口, 和 tornado_server文件中使用的端口一致.
最后, 重启nginx服务器即可(可到任务管理器里结束任务再启动)

LocalStack–线程隔离的栈结构

  Werkzeug 通过自定义 werkzeug.local.Local 类实现线程隔离的栈结构, 封装了push, pop, 和top方法.可以将对象推入、弹出,也可以快速拿到栈顶对象. 同样具有线程隔离的作用. 并没有直接使用threading.Local .

  1. LocalStack作为栈结构的特性

栈是一种先进后出的基本数据结构.

from werkzeug.local import LocalStack
s = LocalStack()
s.push(1)
print(s.top)
print(s.top)  # 获取栈顶元素
print(s.pop())  # 弹出栈顶元素
print(s.top)  # 弹出的栈顶元素会删除

s.push(1)
s.push(2)
print(s.top) 
print(s.top)
print(s.pop())
print(s.pop())

  1. 作为线程隔离的特性

线程隔离的作用是: 使当前对象可以正确的使用自己创建的对象, 而不会使用和破坏其他进程的对象.

my_stack = LocalStack()
my_stack.push(2)
print('in main thread after push , value is ', my_stack.top)  

def my_work():
    print('in new thread before, value is ', my_stack.top)
    my_stack.push(3)
    print('after new thread after push, value is ', my_stack.top)
new_thread = threading.Thread(target=my_work, name='my_work_thread')
new_thread.start()
time.sleep(1)
print('finally, in new thread , value is', my_stack.top)

打印结果为:

in main thread after push , value is  2
in new thread before, value is  None
after new thread after push, value is  3
finally, in new thread , value is 2

源代码

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)
    ...
     def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

__slots__限制了Local类只可以有两个属性:__storage__和__ident_func__。从构造函,__storage__是一个字典,而__ident_func__是一个函数,用来识别当前线程或协程.

wtforms的调用验证方式优化

通常, 在使用flask验证器的过程中视图会有类似下面的语句:

data = request.json
form = ClientForm(data=data)
if form.validate():
    #...
else:
    #...
  • 重写的目的:
  1. 无需每次在视图中使用data=reqeust.data, form = XXForm(data=data)获取和传入请求数据.
  2. 无需在视图中使用ifelse判断, 请求数据有问题时候, 直接向客户端返回异常.
  • 分析:

为了省去视图函数向验证器每次传数据的过程, 重写构造器的时候可以直接使用request.data获取;
为了直接返回异常, 需要重写validate, 此处我们不重写, 直接定义一个类似的方法, 去调用validate.


from flask import request from wtforms import Form # 这是之前定义的参数错误异常处理类 from app.libs.error_code import ParameterException class BaseForm(Form): def __init__(self): data = request.json super(BaseForm, self).__init__(data=data) def validate_for_api(self): valid = super(BaseForm, self).validate() if not valid: # 这里如果出错将直接跑出异常, 视图中将免除了判断并使用ifelse的麻烦 raise ParameterException(msg=self.errors) return self

这时候, 继承这个类的验证表单如果验证参数有问题, 将会’自动向客户端返回一个异常json数据, 而无需在后续使用raise, 因为在验证器已经将错误raise出去了.

在form定义的表单可以直接继承自定义的基础表单, 不在继承wtfforms的原始表单, 如:

class ClientForm(BaseForm):
    #...
    secret = StringField(validators=[...])  

之后在视图中如果需要验证变方便的多, 如: 

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

视图中不在需要使用if判断参数是否合法, 也不需要将request数据当做参数传到form对象中, 后续将大大减少代码量.

jsonify的改进

一. json和jsonify

二. 改进的jsonify

三. 使用


一. json和jsonify

json.dumps需要指定返回类型:

    t = {
      #...
    }
    return Response(json.dumps(t), mimetype='application/json')

jsonify默认将返回的类型指定成了application/json :

    t = {
      #...
    }
    return jsonify(t), 200

但是在使用默认情况下, jsonify对于很多类型的数据是无法序列化的(当然json.dumps也不行), 比如时间类型的字典, model的实例对象.

二. 改进的jsonify

如果jsonify不知道如何序列化指定的对象, 程序便会调用JSONEncodedefualt方法:

# 源码部分
class JSONEncoder(_json.JSONEncoder):
    def default(self, o):
        # ...
        if isinstance(o, datetime):
            return http_date(o.utctimetuple())
        if isinstance(o, date):
            return http_date(o.timetuple())
        if isinstance(o, uuid.UUID):
            return str(o)
        if hasattr(o, '__html__'):
            return text_type(o.__html__())
        return _json.JSONEncoder.default(self, o)
    #...

其中参数o便是需要序列化的对象.
因此可以重新定义这个default方法, 使jsonify支持对sqlalchemy实例和datetime类型数据的序列化:

在合适的位置引入并重写:

from flask.json import JSONEncoder as _JSONEncoder
class JSONEncoder(_JSONEncoder):
    """重写对象序列化, 当默认jsonify无法序列化对象的时候将调用这里的default"""
    def default(self, o):
        if hasattr(o, 'keys') and hasattr(o, '__getitem__'):
            return dict(o)
        if isinstance(o, date):
            # 也可以序列化时间类型的对象
            return o.strftime('%Y-%m-%d')
        raise ServerError()
  • : dict(o)用到了对象转字典, 其中ServerError也是自定义的异常.

重写后必须指定flask的json_encoder:

from flask import Flask as _Flask
class Flask(_Flask):
    json_encoder = JSONEncoder

实例化flask对象的时候, 不要使用原版的Flask类, 而是使用刚刚改进后的class Flask(_Flask):., 直接app = Flask(__name__)即可.

三. 使用

改进后的jsonify已经支持对时间和model对象的序列化

from flask import jsonify

#...
    books = Book.query.all()
    return jsonify(books)

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()