接下來正式進入網站的功能開發。要完成後臺管理系統登陸功能,經過查看登陸頁面,咱們能夠了解到,咱們須要編寫驗證碼圖片獲取接口和登陸處理接口,而後在登陸頁面的HTML上編寫AJAX。javascript
在進行接口開發以前,還有一個重要的事情要處理,那就是對站點進行初始化,若是不進行初始化,那麼獨立文件編寫的接口將會找不到,要將異常錯誤寫入日誌文件也會找不到路徑,下面先上代碼。css
打開main.py文件,改成下面代碼(你們能夠比較一下和以前代碼有什麼不一樣)html
1 #!/usr/bin/evn python 2 # coding=utf-8 3 4 import bottle 5 import sys 6 import os 7 import logging 8 import urllib.parse 9 from bottle import default_app, get, run, request, hook 10 from beaker.middleware import SessionMiddleware 11 12 # 導入工具函數包 13 from common import web_helper, log_helper 14 # 導入api代碼模塊(初始化api文件夾裏的各個訪問路由,這一句不能刪除,刪除後將沒法訪問api文件夾裏的各個接口) 15 import api 16 17 ############################################# 18 # 初始化bottle框架相關參數 19 ############################################# 20 # 獲取當前main.py文件所在服務器的絕對路徑 21 program_path = os.path.split(os.path.realpath(__file__))[0] 22 # 將路徑添加到python環境變量中 23 sys.path.append(program_path) 24 # 讓提交數據最大改成2M(若是想上傳更多的文件,能夠在這裏進行修改) 25 bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024 * 2 26 27 ############################################# 28 # 初始化日誌相關參數 29 ############################################# 30 # 若是日誌目錄log文件夾不存在,則建立日誌目錄 31 if not os.path.exists('log'): 32 os.mkdir('log') 33 # 初始化日誌目錄路徑 34 log_path = os.path.join(program_path, 'log') 35 # 定義日誌輸出格式與路徑 36 logging.basicConfig(level=logging.INFO, 37 format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', 38 filename="%s/info.log" % log_path, 39 filemode='a') 40 41 # 設置session參數 42 session_opts = { 43 'session.type': 'file', 44 'session.cookie_expires': 3600, 45 'session.data_dir': '/tmp/sessions/simple', 46 'session.auto': True 47 } 48 49 50 @hook('before_request') 51 def validate(): 52 """使用勾子處理接口訪問事件""" 53 54 # 獲取當前訪問的Url路徑 55 path_info = request.environ.get("PATH_INFO") 56 # 過濾不用作任何操做的路由(即過濾不用進行判斷是否登陸和記錄日誌的url) 57 if path_info in ['/favicon.ico', '/', '/api/verify/']: 58 return 59 ### 記錄客戶端提交的參數 ### 60 # 獲取當前訪問url路徑與ip 61 request_log = 'url:' + path_info + ' ip:' + web_helper.get_ip() 62 try: 63 # 添加json方式提交的參數 64 if request.json: 65 request_log = request_log + ' params(json):' + urllib.parse.unquote(str(request.json)) 66 except: 67 pass 68 try: 69 # 添加GET方式提交的參數 70 if request.query_string: 71 request_log = request_log + ' params(get):' + urllib.parse.unquote(str(request.query_string)) 72 # 添加POST方式提交的參數 73 if request.method == 'POST': 74 request_log = request_log + ' params(post):' + urllib.parse.unquote(str(request.params.__dict__)) 75 # 存儲到日誌文件中 76 log_helper.info(request_log) 77 except: 78 pass 79 80 # 處理ajax提交的put、delete等請求轉換爲對應的請求路由(因爲AJAX不支持RESTful風格提交,因此須要在這裏處理一下,對提交方式進行轉換) 81 if request.method == 'POST' and request.POST.get('_method'): 82 request.environ['REQUEST_METHOD'] = request.POST.get('_method', '') 83 84 # 過濾不用進行登陸權限判斷的路由(登陸與退出登陸不用檢查是否已經登陸) 85 url_list = ["/api/login/", "/api/logout/"] 86 if path_info in url_list: 87 pass 88 else: 89 # 已經登陸成功的用戶session確定有值,沒有值的就是未登陸 90 session = web_helper.get_session() 91 # 獲取用戶id 92 manager_id = session.get('id', 0) 93 login_name = session.get('login_name', 0) 94 # 判斷用戶是否登陸 95 if not manager_id or not login_name: 96 web_helper.return_raise(web_helper.return_msg(-404, "您的登陸已失效,請從新登陸")) 97 98 99 100 # 函數主入口 101 if __name__ == '__main__': 102 app_argv = SessionMiddleware(default_app(), session_opts) 103 run(app=app_argv, host='0.0.0.0', port=9090, debug=True, reloader=True) 104 else: 105 # 使用uwsgi方式處理python訪問時,必需要添加這一句代碼,否則沒法訪問 106 application = SessionMiddleware(default_app(), session_opts)
main.py文件裏有詳細的註釋說明,因此不進行細說,在這裏講一講文件大致的思路。前端
由於咱們編寫的接口文件都放在api文件夾中,當web服務啓動後須要將api裏的接口文件自動裝載進來,讓咱們能夠經過url訪問裏面的接口,因此須要在main.py這個入口函數中,對api文件夾裏的接口文件進行導入,前面講解到咱們api文件夾裏有一個__init__.py文件,它會自動幫咱們導入當前文件夾裏的全部文件,因此咱們只須要在main.py中添加import api這一行代碼就能夠了。html5
另外,咱們須要告訴python服務當前程序所在的路徑,因此須要將當前文件所在的絕對路徑添加到python環境變量中(第21到23行)java
咱們要記錄異常信息到日誌,要記錄客戶端訪問的url與提交的請求參數,方便出錯時幫助咱們進行排查錯誤,因此要初始化日誌文件格式與存儲路徑(第30到39行)python
bottle框架有兩個好用的勾子處理函數(具體流程以下圖),客戶端訪問接口時,首先會從bottle web服務綁定的入口進入,而後調用before_request這個勾子函數(第50到97行),執行完裏面的代碼後再進入對應的接口函數裏,當接口函數運行完畢後,又會調用after_request這個勾子函數(咱們使用了nginx處理前端訪問服務不存在跨域問題,因此main.py就沒有添加這個勾子函數),運行完裏面的代碼後才返回最終結果給客戶端。因此咱們有不少事情能夠放在這兩個勾子函數中進行處理。before_request中咱們能夠運行初始化操做、記錄客戶端訪問的url與提交的請求參數操做、判斷用戶是否已經登陸等操做(若是沒有這個勾子函數,咱們要判斷用戶是否登陸,就必須在每一個接口文件中處理,這樣一方面代碼會很冗餘,出現大量重複的沒有必要的代碼,另外一方面也很容易出錯或遺漏掉,形成後端權限訪問漏洞。而after_request這個函數經過是用來處理輸出HTTP頭信息等內容,好比跨域處理等。jquery
第55到78行,會將客戶端訪問的url與各類方式提交的請求參數記錄到日誌。對於一些不想記錄到日誌的訪問,能夠添加到第57行。(以下圖)nginx
第90到96行,對登陸用戶訪問進行處理,若是未登陸的,則會返回-404狀態,客戶端的ajax接收到這個狀態後,自行處理跳轉到登陸頁面。web
驗證碼接口
咱們在api文件夾中建立verify.py文件
#!/usr/bin/python #coding: utf-8 from io import BytesIO from bottle import get, response from common import verify_helper, log_helper, web_helper @get('/api/verify/') def get_verify(): """生成驗證碼圖片""" try: # 獲取生成驗證碼圖片與驗證碼 code_img, verify_code = verify_helper.create_verify_code() # 將字符串轉化成大寫保存到session中 s = web_helper.get_session() s['verify_code'] = verify_code.upper() s.save() # 輸出圖片流 buffer = BytesIO() code_img.save(buffer, "jpeg") code_img.close() response.set_header('Content-Type', 'image/jpg') return buffer.getvalue() except Exception as e: log_helper.error(str(e.args))
code_img, verify_code = verify_helper.create_verify_code() :運行verify_helper.create_verify_code() ,會返回圖片流和驗證碼,python語言執行函數後,能夠直接返回字符串、數值、元組、字典、列表等各類類型的值,返回元組類型值時,就可使用這樣的方式進行接收。(verify_helper須要導入PIL包,在python3中已更改成pillow包了,因此咱們須要執行pip進行安裝:pip install pillow)
log_helper.error(str(e.args)) 這是咱們前面工具函數包時所講到的錯誤記錄函數,當生成驗證碼出現異常時,它會將異常信息記錄到日誌文件中,並將異常發送到咱們指定的郵箱。
添加完這個文件後,咱們就能夠運行一下main.py,而後在瀏覽器中輸入http://127.0.0.1:9090/api/verify/或http://127.0.0.1:81/api/verify/,就能夠看到生成的驗證碼了(若是使用81端口沒法訪問,請參考個人第一個python web開發框架(7)——本地部署前端訪問服務器 章節進行處理)
登陸接口
咱們在api文件夾中建立login.py文件
1 #!/usr/bin/evn python 2 # coding=utf-8 3 4 from bottle import put 5 from common import web_helper, encrypt_helper, db_helper 6 7 8 @put('/api/login/') 9 def post_login(): 10 """用戶登錄驗證""" 11 ############################################################## 12 # 獲取並驗證客戶端提交的參數 13 ############################################################## 14 username = web_helper.get_form('username', '賬號') 15 password = web_helper.get_form('password', '密碼') 16 verify = web_helper.get_form('verify', '驗證碼') 17 ip = web_helper.get_ip() 18 19 ############################################################## 20 # 從session中讀取驗證碼信息 21 ############################################################## 22 s = web_helper.get_session() 23 verify_code = s.get('verify_code') 24 # 刪除session中的驗證碼(驗證碼每提交一次就失效) 25 if 'verify_code' in s: 26 del s['verify_code'] 27 s.save() 28 # 判斷用戶提交的驗證碼和存儲在session中的驗證碼是否相同 29 if verify.upper() != verify_code: 30 return web_helper.return_msg(-1, '驗證碼錯誤') 31 32 ############################################################## 33 ### 獲取登陸用戶記錄,並進行登陸驗證 ### 34 ############################################################## 35 sql = """select * from manager where login_name='%s'""" % (username,) 36 # 從數據庫中讀取用戶信息 37 manager_result = db_helper.read(sql) 38 # 判斷用戶記錄是否存在 39 if not manager_result: 40 return web_helper.return_msg(-1, '帳戶不存在') 41 42 ############################################################## 43 ### 驗證用戶登陸密碼與狀態 ### 44 ############################################################## 45 # 對客戶端提交上來的驗證進行md5加密將轉爲大寫(爲了密碼的保密性,這裏進行雙重md5加密,加密時從第一次加密後的密串中提取一段字符串出來進行再次加密,提取的串你們能夠自由設定) 46 # pwd = encrypt_helper.md5(encrypt_helper.md5(password)[1:30]).upper() 47 # 對客戶端提交上來的驗證進行md5加密將轉爲大寫(只加密一次) 48 pwd = encrypt_helper.md5(password).upper() 49 # 檢查登陸密碼輸入是否正確 50 if pwd != manager_result[0].get('login_password', ''): 51 return web_helper.return_msg(-1, '密碼錯誤') 52 # 檢查該帳號雖否禁用了 53 if manager_result[0].get('is_enable', 0) == 0: 54 return web_helper.return_msg(-1, '帳號已被禁用') 55 56 ############################################################## 57 ### 把用戶信息保存到session中 ### 58 ############################################################## 59 manager_id = manager_result[0].get('id', 0) 60 s['id'] = manager_id 61 s['login_name'] = username 62 s.save() 63 64 ############################################################## 65 ### 更新用戶信息到數據庫 ### 66 ############################################################## 67 # 更新當前管理員最後登陸時間、Ip與登陸次數(字段說明,請看數據字典) 68 sql = """update manager set last_login_time=%s, last_login_ip=%s, login_count=login_count+1 where id=%s""" 69 # 組合更新值 70 vars = ('now()', ip, manager_id,) 71 # 寫入數據庫 72 db_helper.write(sql, vars) 73 74 return web_helper.return_msg(0, '登陸成功')
在編寫登陸接口前,咱們首先要了解登陸接口處理的流程是怎麼樣的
login.py後臺登陸處理接口代碼能夠看到,路由咱們使用的是@put('/api/login/'),RESTful風格中,post是用於新增記錄,put是用於修改或改變服務器數據,登陸我理解它確定不是新增,它是改變用戶登陸的狀態,因此這裏使用put方式接收
登陸接口的代碼有詳細的註釋,還有上面的流程圖,因此就再也不深刻解說,你們本身看代碼,若有不明白的,文章後面留言。
前端登陸html頁面(login.html)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="renderer" content="webkit|ie-comp|ie-stand"> 6 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 7 <meta name="viewport" 8 content="width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/> 9 <meta http-equiv="Cache-Control" content="no-siteapp"/> 10 <!--[if lt IE 9]> 11 <script type="text/javascript" src="lib/html5shiv.js"></script> 12 <script type="text/javascript" src="lib/respond.min.js"></script> 13 <![endif]--> 14 <link href="static/h-ui/css/H-ui.min.css" rel="stylesheet" type="text/css"/> 15 <link href="static/h-ui.admin/css/H-ui.login.css" rel="stylesheet" type="text/css"/> 16 <link href="static/h-ui.admin/css/style.css" rel="stylesheet" type="text/css"/> 17 <link href="lib/Hui-iconfont/1.0.8/iconfont.css" rel="stylesheet" type="text/css"/> 18 <!--[if IE 6]> 19 <script type="text/javascript" src="lib/DD_belatedPNG_0.0.8a-min.js"></script> 20 <script>DD_belatedPNG.fix('*');</script> 21 <![endif]--> 22 <title>後臺登陸 - H-ui.admin v3.1</title> 23 <meta name="keywords" content="H-ui.admin v3.1,H-ui網站後臺模版,後臺模版下載,後臺管理系統模版,HTML後臺模版下載"> 24 <meta name="description" content="H-ui.admin v3.1,是一款由國人開發的輕量級扁平化網站後臺模板,徹底免費開源的網站後臺管理系統模版,適合中小型CMS後臺系統。"> 25 </head> 26 <body> 27 <input type="hidden" id="TenantId" name="TenantId" value=""/> 28 <div class="header"></div> 29 <div class="loginWraper"> 30 <div id="loginform" class="loginBox"> 31 <form class="form form-horizontal"> 32 <div class="row cl"> 33 <label class="form-label col-xs-3"><i class="Hui-iconfont"></i></label> 34 <div class="formControls col-xs-8"> 35 <input id="username" name="username" type="text" placeholder="帳號" class="input-text size-L"> 36 </div> 37 </div> 38 <div class="row cl"> 39 <label class="form-label col-xs-3"><i class="Hui-iconfont"></i></label> 40 <div class="formControls col-xs-8"> 41 <input id="password" name="password" type="password" placeholder="密碼" class="input-text size-L"> 42 </div> 43 </div> 44 <div class="row cl"> 45 <div class="formControls col-xs-8 col-xs-offset-3"> 46 <input id="verify" name="verify" class="input-text size-L" type="text" value="" 47 style="width:150px;"> 48 <img style="width: 100px;height: 40px;padding: 0px;vertical-align:middle" id="verifycode" 49 src="/api/verify/" onclick="get_verify()"> <a href="javascript:;" onclick="get_verify()">看不清,換一張</a></div> 50 </div> 51 <div class="row cl"> 52 <div> 53 <h5 class="formControls col-xs-8 col-xs-offset-3"><span id="msg" style="color:#F00"></span></h5> 54 </div> 55 </div> 56 <div class="row cl"> 57 <div class="col-xs-8 col-xs-offset-3"> 58 <input type="button" class="btn btn-success size-L" onclick="submit1()" 59 value=" 登 錄 "> 60 </div> 61 </div> 62 </form> 63 </div> 64 </div> 65 <div class="footer">Copyright 你的公司名稱 by H-ui.admin v3.1</div> 66 <script type="text/javascript" src="lib/jquery/1.9.1/jquery.min.js"></script> 67 <script type="text/javascript" src="static/h-ui/js/H-ui.min.js"></script> 68 <script> 69 function submit1() { 70 if ($("#username").val().trim().length == '') { 71 $("#msg").html('').append('請輸入用戶名'); 72 } 73 else if ($("#password").val().trim().length == '') { 74 $("#msg").html('').append('請輸入登陸密碼'); 75 } 76 else if ($("#verify").val().trim().length != 4) { 77 $("#msg").html('').append('請輸入4位圖形驗證碼'); 78 } else { 79 username = $("#username").val(); 80 password = $("#password").val(); 81 verify = $("#verify").val(); 82 $.ajax({ 83 type: 'POST', 84 url: "/api/login/", 85 data: {'_method': 'put', 'username': username, 'password': password, 'verify': verify}, 86 dataType: 'json', 87 success: function (data) { 88 if(data && data.state>-1){ 89 $(location).prop('href', 'main.html'); 90 } 91 else{ 92 $("#msg").html('').append(data.msg); 93 get_verify(); 94 } 95 }, 96 error: function(data){ 97 if (data){ 98 alert(data.msg); 99 } 100 get_verify(); 101 } 102 }); 103 } 104 } 105 106 function get_verify() { 107 $("#verifycode").attr("src", "/api/verify/?" + 100 * Math.random()); 108 } 109 110 </script> 111 </body> 112 </html>
對前面下載的login.html頁面進行了微調,添加了請求的AJAX代碼。
因爲火狐和谷歌運行AJAX不支持PUT、DELETE等提交方式,因此AJAX提交時type類型仍是POST方式,在提交參數項裏面,須要增長 _method 這個參數,值爲put。(因爲本系列使用的是RESTful風格,因此雖然有點麻煩,但不影響咱們的使用)
html和js我也不進行詳細說明,你們本身看代碼吧,若是你們都要求須要對js寫註釋的,我到時再添加註釋進去。
相關頁面功能都完成了,接下來就是進行運行調試
在瀏覽器中輸入:http://127.0.0.1:81/login.html 而後輸入帳號:admin,密碼:123456,還有驗證碼
點擊登陸,能正常跳轉到http://127.0.0.1:81/main.html 頁面,就表示登陸接口能正常使用了。
你們想要熟悉登陸接口代碼的運行,最好使用debug運行跟蹤一下,看看每一行代碼是怎麼運行的,就清楚了。固然若是想要加深理解,最佳方式是照着代碼手打一次,每完成幾行就debug運行一下,看看執行效果。
版權聲明:本文原創發表於 博客園,做者爲 AllEmpty 本文歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然視爲侵權。
python開發QQ羣:669058475(本羣已滿)、733466321(能夠加2羣) 做者博客:http://www.cnblogs.com/EmptyFS/