我的第一個python web開發框架(14)——後臺管理系統登錄功能

来源:http://www.cnblogs.com/EmptyFS/archive/2017/10/31/7726030.html
-Advertisement-
Play Games

接下來正式進入網站的功能開發。要完成後臺管理系統登錄功能,通過查看登錄頁面,我們可以瞭解到,我們需要編寫驗證碼圖片獲取介面和登錄處理介面,然後在登錄頁面的HTML上編寫AJAX。 在進行介面開發之前,還有一個重要的事情要處理,那就是對站點進行初始化,如果不進行初始化,那麼獨立文件編寫的介面將會找不到 ...


  接下來正式進入網站的功能開發。要完成後臺管理系統登錄功能,通過查看登錄頁面,我們可以瞭解到,我們需要編寫驗證碼圖片獲取介面和登錄處理介面,然後在登錄頁面的HTML上編寫AJAX。

  在進行介面開發之前,還有一個重要的事情要處理,那就是對站點進行初始化,如果不進行初始化,那麼獨立文件編寫的介面將會找不到,要將異常錯誤寫入日誌文件也會找不到路徑,下麵先上代碼。

  打開main.py文件,改為下麵代碼(大家可以比較一下和之前代碼有什麼不同)

  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)
View Code

  main.py文件里有詳細的註釋說明,所以不進行細說,在這裡講一講文件大體的思路。

  因為我們編寫的介面文件都放在api文件夾中,當web服務啟動後需要將api里的介面文件自動裝載進來,讓我們可以通過url訪問裡面的介面,所以需要在main.py這個入口函數中,對api文件夾里的介面文件進行導入,前面講解到我們api文件夾里有一個__init__.py文件,它會自動幫我們導入當前文件夾里的所有文件,所以我們只需要在main.py中添加import api這一行代碼就可以了。

  另外,我們需要告訴python服務當前程式所在的路徑,所以需要將當前文件所在的絕對路徑添加到python環境變數中(第21到23行)

  我們要記錄異常信息到日誌,要記錄客戶端訪問的url與提交的請求參數,方便出錯時幫助我們進行排查錯誤,所以要初始化日誌文件格式與存儲路徑(第30到39行)

  bottle框架有兩個好用的勾子處理函數(具體流程如下圖),客戶端訪問介面時,首先會從bottle web服務綁定的入口進入,然後調用before_request這個勾子函數(第50到97行),執行完裡面的代碼後再進入對應的介面函數里,當介面函數運行完畢後,又會調用after_request這個勾子函數(我們使用了nginx處理前端訪問服務不存在跨域問題,所以main.py就沒有添加這個勾子函數),運行完裡面的代碼後才返回最終結果給客戶端。所以我們有很多事情可以放在這兩個勾子函數中進行處理。before_request中我們可以運行初始化操作、記錄客戶端訪問的url與提交的請求參數操作、判斷用戶是否已經登錄等操作(如果沒有這個勾子函數,我們要判斷用戶是否登錄,就必須在每個介面文件中處理,這樣一方面代碼會很冗餘,出現大量重覆的沒有必要的代碼,另一方面也很容易出錯或遺漏掉,造成後端許可權訪問漏洞。而after_request這個函數通過是用來處理輸出HTTP頭信息等內容,比如跨域處理等。

  

  第55到78行,會將客戶端訪問的url與各種方式提交的請求參數記錄到日誌。對於一些不想記錄到日誌的訪問,可以添加到第57行。(如下圖)

  

  第90到96行,對登錄用戶訪問進行處理,如果未登錄的,則會返回-404狀態,客戶端的ajax接收到這個狀態後,自行處理跳轉到登錄頁面。

 

 

  驗證碼介面

  我們在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語言執行函數後,可以直接返回字元串、數值、元組、字典、列表等各種類型的值,返回元組類型值時,就可以使用這樣的方式進行接收。

  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, '登錄成功')
View Code

  在編寫登錄介面前,我們首先要瞭解登錄介面處理的流程是怎麼樣的

  

  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">&#xe60d;</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">&#xe60e;</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="&nbsp;登&nbsp;&nbsp;&nbsp;&nbsp;錄&nbsp;">
 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
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...