sanic官方中文教程https://sanicframework.org/zh/guide/
教程不涉及模板等内容,所以用前后端分离架构,后端只返回数据,不渲染模板
架构如下
使用两个服务器软件,sanic自带的软件和nginx
sanic服务器软件,运行在5000端口,只接收本机请求,只代理后端
nginx运行在80端口,接收外部请求,同时代理前端和后端,将指定请求转发到后端所在的5000端口,改动部分如下,其他都是默认配置,没有改动
server { listen 80; server_name localhost; access_log logs/host.access.log main; #前端,页面 location / { #访问根路径,打开前端主页,不进行转发 root C:/Users/zx/Documents/pyProject/web/my_app; #设置前端文件的主目录 index index.html index.htm; } #后端,接口 location /api/{ #访问路径带api时,进行转发,如http://127.0.0.1/api/login, 会被转发给后端 proxy_pass http://127.0.0.1:5000/; #转发给本机5000端口 proxy_set_header Host $host; }
为便于展示所有文件放在同一个目录里
api #存放目录
----server.py #主程序
----auth.py #指定页面需要登录才能执行操作
----login.py #用户登录验证,jwt认证
----query.py #自定义文件,使用蓝图注册到主程序
前端文件
----index.html #登录页
----query.html #功能查询页
import jwt #注意,安装命令是pip install pyjwt,而不是pip install jwt from sanic import text from sanic import Blueprint #导入蓝图类 login = Blueprint("login") #实例化蓝图,login是蓝图名称,引号里的内容随便填, @login.post("/login") async def do_login(request): #sanic的函数都要带request参数,用于获取前端传来的数据 user = request.form.get('user') #获取表单中id为user的值 passwd = request.form.get('passwd') if user== 'inflow' and passwd == 'inflow': token = jwt.encode({}, request.app.config.SECRET) return text(token) #验证通过返回token return text('fail')from sanic import Blueprint login = Blueprint("login",url_prefix=‘/abc’) #创建一个蓝图实例,url__prefix参数可选,用于指定路径 如以上代码指定该参数,访问login的路径就变成了/abc/login,不添加时为/login 该文件用于验证用户登录,验证成功返回一个token,在受保护的路由上,只有验证成功才能访问数据
from sanic import Sanic from login import login #从login.py文件导入创建的login蓝图对象 from query import query #0.配置 app = Sanic(__name__) app.config.SECRET = "KEEP_IT_SECRET_KEEP_IT_SAFE" #设置秘钥,用于jwt认证,config配置项一般放在一个本地文件中,从文件读取 app.config.SERVER_NAME = "/api" #设置顶层路径,所有的路由路径都在api下面,比如/api/login #1.蓝图注册 app.blueprint(login) #注册login蓝图,路径为http://127.0.0.1/api/login app.blueprint(query) #3.执行 if __name__ =='__main__': app.run(port=5000, debug=True, access_log=False, workers=4) #1.端口运行在5000,2.开启debug模式,有修改时自动加载,不用重启程序,3.关闭访问日志,提高速度,日志可在nginx中设置。4.开启4个线程 #命令行执行方式,进入server.py 所在目录sanic server.app --host=0.0.0.0 --port=5000 --workers=4 --debug=True --access_log=False #和flask一样,host设置为0000时,从其他计算机也能访问,设置为127,只有本机能访问,因为我们使用nginx做为代理,所以可以用127
from functools import wraps import jwt from sanic import text #完全照搬官方教程,一字未改 def check_token(request): if not request.token: return False try: jwt.decode( request.token, request.app.config.SECRET, algorithms=["HS256"] ) except jwt.exceptions.InvalidTokenError: return False else: return True def protected(wrapped): def decorator(f): @wraps(f) async def decorated_function(request, *args, **kwargs): is_authenticated = check_token(request) if is_authenticated: response = await f(request, *args, **kwargs) return response else: return text("You are unauthorized.", 401) return decorated_function return decorator(wrapped)
#这个文件照搬的官方脚本,没做任何改动,就不说了
用法:需要保护的路由,导入auth模块的protected函数
from auth import protected @protected #在函数上加一个装饰器就可以了,示例见1.4from sanic import Blueprint, json from auth import protected #导入protected import tools, add_user_right # 0.数据定义 query = Blueprint('query') # 一.相关函数 @query.post("/query") @protected #添加了protected,只有1.1 login.py文件通过登录验证才会执行下面的函数,否则返回login.py中定义的401 async def query_data(request): name = request.form.get('name') data = {} if request.form.get('radio1') == 'user_info': data = tools.find_sysuser(name) return json({"h": data}) #返回json类型的数据
1.5index.html 用户登录表单
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>登录</title> <link rel="stylesheet" type="text/css" href="../css/style.css"> <link href="../css/bootstrap.min.css" rel="stylesheet"> <link href="../css/main.css" rel="stylesheet"> </head> <body> <link rel="stylesheet" type="text/css" href="css/style.css"> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="/">首页</a> <a class="navbar-brand" href="/templates/query.html">数据查询</a> <a class="navbar-brand" href="/templates/export.html">数据导出</a> </div> </div> </div> <br> <form action="/api/login" method="post" class="form" role="form"> <div class="form-group required"><label class="form-control-label" for="user">账号</label> <input class="form-control" id="user" name="user" required type="text" value=""> </div> <div class="form-group required"><label class="form-control-label" for="passwd">密码</label> <input class="form-control" id="passwd" name="passwd" required type="password" value=""> </div> <input class="btn btn-primary btn-md" id="submit" name="submit" type="submit" value="登录"> </form> <!-- 表单 --> </body>
action中定义了表单的提交路径/api/login .点击提交后访问http://127.0.0.1/api/lgoin , 路径含有api会转发到后端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>查询</title> <link rel="stylesheet" type="text/css" href="../css/style.css"> <link href="../css/bootstrap.min.css" rel="stylesheet"> <link href="../css/main.css" rel="stylesheet"> <style> body { font-size: 14px; } label { display: inline-block; width: 8em; margin-left: 0.3em; margin-right: 0.3em; } input { margin-top: 0.3em; margin-bottom: 0.3em; } .tipmsg { font-size: 14px; color: #f00; } </style> </head> <body> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="/">首页</a> <a class="navbar-brand" href="/templates/query.html">数据查询</a> <a class="navbar-brand" href="/templates/export.html">数据导出</a> </div> </div> </div> <div class="container"> <br /> <div> </div> <form id="query_form" name="query_form" method="post" class="form" role="form"> <div class="form-group row"> <label class="col-form-label col-lg-2" for="name">姓名</label> <div class=" col-lg-10"> <input class="form-control" id="name" name="name" type="text" value=""> </div> </div> <div class="form-group row"> <label class="col-form-label col-lg-2" for="phone">手机</label> <div class=" col-lg-10"> <input class="form-control" id="phone" name="phone" type="text" value=""> </div> </div> <div class="form-group row"> <label class="col-form-label col-lg-2" for="role">角色</label> <div class=" col-lg-10"> <input class="form-control" id="role" name="role" type="text" value=""> </div> </div> <div class="form-group row"> <label class="col-form-label col-lg-2" for="depart">部门</label> <div class=" col-lg-10"> <input class="form-control" id="depart" name="depart" type="text" value=""> </div> </div> <div> 用户信息查询<input type="radio" name="radio1" value="user_info" /> 角色部门查询<input type="radio" name="radio1" value="role_depart" /> 测试<input type="radio" name="radio1" value="test" /> 添加角色<input type="radio" name="radio1" value="role_add" /> 修改角色<input type="radio" name="radio1" value="role_mod" /> 删除角色<input type="radio" name="radio1" value="role_del" /> 添加用户<input type="radio" name="radio1" value="user_add" /> </div> <div class="form-group row"> <div class="offset-lg-2col-lg-10"> <input class="btn btn-primary btn-md" id="query_button" name="query_button" type="button" value="查询" onclick="user_query()"> </div> </div> </form> <!-- 数据 --> <div id="con"></div> </div> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script type=text/javascript> function user_query(){ $.ajax({ url:'/api/query', type:'POST', data: $('#query_form').serialize(), success:function(data){ show_data(data.h); } }); }; function show_data(data){ for(var i=0;i<data.length;i++){ var addDivDom = document.createElement('div'); // 创建div标签 var bodyDom = document.body; bodyDom.insertBefore(addDivDom, bodyDom.lastChild); // 将addDivDom添加到body中的最后子节点中。 addDivDom.innerHTML = i + "." + data[i]; //输出 //addDivDom.id = 'id'; // div标签添加id // addDivDom.style.color = '#fff'; // 书写style样式 //addDivDom.classList.add('classname'); // 引入css文件中的某个class样式 //$('#con').html(data[i]); //$('#con').after(data[i]); } } </script> </body> </html>
表单提交使用ajax异步加载,提交路径/api/query , 该路由被protectd保护,登录验证通过才能执行查询