1. 程式人生 > >我的第一個python web開發框架(15)——公司介紹編輯功能

我的第一個python web開發框架(15)——公司介紹編輯功能

getcwd 轉義 導航菜單 unicode 存儲路徑 -c 序號 管理 bsp

  完成登錄以後,就會進入後臺管理系統的主界面,因為這個是小項目,所以導航菜單全部固化在HTML中,不能修改。一般後臺還會有一個歡迎頁或關鍵數據展示的主頁面,小項目也沒有多大的必要,所以登錄後直接進入公司介紹編輯頁面。

  首先我們來看一下公司介紹頁面內容

  技術分享

  看上去功能好像很簡單,其實我們要處理的東西還是挺多的。

  從頁面上看,我們需要有一個記錄讀取的接口,來獲取公司介紹的內容,並展示在頁面上。當然現在數據庫裏面沒有記錄存在,所以我們還需要向數據庫的信息表(infomation)中插入一條公司介紹的記錄,這樣好直接進行編輯(因為公司介紹不會有很多條記錄,一般定了後就不會再改變,所以只需要在數據庫的信息表裏插入一條就可以了)

  另外,從界面上看,我們還需要有一個上傳文件的接口,可以上傳圖片和文件;還需要一個更新公司介紹內容的接口。

  還有需要修改幾個地方,有上傳文件,肯定需要有下載的接口,所以需要增加一個下載的路由(python與其他語言不一樣的地方是,所有訪問都必須通過路由,所以上傳的或放在目錄中的文件需要統一定義一個接口來處理,不然用戶訪問不了,雖然有點麻煩,但這樣處理也安全很多,用戶上傳任何含有木馬或程序的文件,它也無法在服務器端執行);nginx配置文件也需要修改一下,增加下載路徑規則,這樣就可以直接通過nginx訪問下載路徑了。

  向數據庫中添加公司介紹記錄

  運行pgAdmin連上數據庫,然後按第4章的做法,打開sql查詢分析器,運行下面代碼添加一條數據庫記錄

INSERT INTO infomation(id, title)  VALUES (1, 公司介紹);

  添加公司介紹記錄讀取接口

 1 @get(/api/about/)
 2 def callback():
 3     """
 4     獲取指定記錄
 5     """
 6     sql = """select * from infomation where id = 1"""
 7     # 讀取記錄
 8     result = db_helper.read(sql)
 9     if result:
10         #
直接輸出json 11 return web_helper.return_msg(0, 成功, result[0]) 12 else: 13 return web_helper.return_msg(-1, "查詢失敗")

  因為公司介紹id添加後不會再改變,所以sql語句直接綁死id為1,另外,執行數據庫查詢以後,返回的是列表,所以返回記錄時要加上序號:result[0]

  啟動debug(對main.py點擊右鍵=》debug),將用戶登錄判斷那兩行註釋掉(不然直接訪問會返回-404,“您的登錄已失效,請重新登錄”提示),在瀏覽器輸入:http://127.0.0.1:9090/api/about/就可以看到返回結果(結果的中文字符是unicode編碼,需要用站長工具轉換一下才可以轉為下載效果)

{"msg": "成功", "data": {"content": "", "front_cover_img": "", "id": 1, "title": "公司介紹", "add_time": "2017-10-31 14:17:45"}, "state": 0}

  添加公司介紹內容修改接口

 1 @put(/api/about/)
 2 def callback():
 3     """
 4     修改記錄
 5     """
 6     front_cover_img = web_helper.get_form(front_cover_img, 圖片)
 7     content = web_helper.get_form(content, 內容, is_check_special_char=False)
 8     # 防sql註入攻擊處理
 9     content = string_helper.filter_str(content, "‘")
10     # 防xss攻擊處理
11     content = string_helper.clear_xss(content)
12 
13     # 更新記錄
14     sql = """update infomation set front_cover_img=%s, content=%s where id=1"""
15     vars = (front_cover_img, content,)
16     # 寫入數據庫
17     db_helper.write(sql, vars)
18 
19     # 直接輸出json
20     return web_helper.return_msg(0, 成功)

  因為公司介紹只需要一條記錄就夠了,前面使用手動方式向數據庫添加記錄,所以代碼中我們就不需要寫添加的方法

  修改記錄使用put方式接收:@put(‘/api/about/‘)

  從界面圖片中可以看到,有文章標題、首頁圖片和文章內容,因為標題不需要進行修改,所以我們修改接口只需要處理剩下兩項就可以了。

  因為提交的內容含有HTML代碼,所以使用web_helper.get_form提取值時,需要使用is_check_special_char參數,設置為不檢查特殊符號,不然會接收不了。另外接收到參數值以後,我們需要對它進行防sql註入和防xss處理。

  clear_xss()函數是string_helper包新增的清除xss攻擊標簽用的,它會過濾掉xss的攻擊代碼。詳細代碼如下:

def clear_xss(html):
    """
    清除xss攻擊標簽
    :param html: 要處理的html
    :return:
    """
    tags = [a, abbr, acronym, b, blockquote, code, em, i, li, ol, strong, ul]
    tags.extend(
        [div, p, hr, br, pre, code, span, h1, h2, h3, h4, h5, del, dl, img, sub, sup, u,
         table, thead, tr, th, td, tbody, dd, caption, blockquote, section])
    attributes = {*: [class, id], a: [href, title, target], img: [src, style, width, height]}
    return bleach.linkify(bleach.clean(html, tags=tags, attributes=attributes))

  clear_xss()函數中我們使用了bleach這個庫(需要安裝:pip install bleach),它是一個基於白名單、通過轉義或去除標簽和屬性的方式,來對HTML文本凈化的python庫。

  我們在string_helper_test.py這個測試單元中添加一個測試用例,來測試一下這個函數的使用效果

    def test_clear_xss(self):
        print(-----test_clear_xss------)
        print(string_helper.clear_xss(<script src="javascript:alert(1);">abc</script>))
        print(string_helper.clear_xss(<iframe src="javascript:alert(1);">abc</iframe>))
        print(string_helper.clear_xss(<div style="width:0;height:0;background:url(javascript:document.body.onload = function(){alert(/XSS/);};">div</div>))
        print(string_helper.clear_xss(<img src = "#"/**/onerror = alert(/XSS/)>))
        print(string_helper.clear_xss(<img src = j ava script:al er t(/XSS/)>))
        print(string_helper.clear_xss("""<img src = j
ava script :a ler t(/xss/)>"""))
        print(string_helper.clear_xss(<img src="javacript:alert(\‘abc\‘)"></img>))
        print(string_helper.clear_xss(<img src="https://www.baidu.com/img/baidu_jgylogo3.gif"></img>))
        print(string_helper.clear_xss(<p src="javascript:alert(1);">abc</p>))
        print(string_helper.clear_xss("""<input type="text" value="瑯琊榜" onclick="javascript:alert(‘handsome boy‘)">"""))
        print(string_helper.clear_xss(<p onclick="javascript:alert("handsome boy")>abc</p>))
        print(string_helper.clear_xss(<a href="javascript:alert(1);">abc</a>))
        print(string_helper.clear_xss(<a href="/api/">abc</a>))
        print(string_helper.clear_xss(<a href="http://www.baidu.com">abc</a>))
        print(string_helper.clear_xss(<marquee onstart="alert(/XSS/)">文字</marquee>))
        print(string_helper.clear_xss(<div style="" onmouseenter="alert(/XSS/)">文字</div>))
        print(string_helper.clear_xss(<li style = "TEST:e-xpression(alert(/XSS/))"></li>))
        print(string_helper.clear_xss(<input id = 1 type = "text" value="" <script>alert(/XSS/)</script>"/>))
        print(string_helper.clear_xss(<base href="http://www.labsecurity.org"/>))
        print(string_helper.clear_xss(<div id="x">alert%28document.cookie%29%3B</div>))
        print(string_helper.clear_xss(<limited_xss_point>eval(unescape(x.innerHTML));</limited_xss_point>))

  執行後輸出結果:

------ini------
-----test_clear_xss------
&lt;script src="javascript:alert(1);"&gt;abc&lt;/script&gt;
&lt;iframe src="javascript:alert(1);"&gt;abc&lt;/iframe&gt;
<div>div</div>
<img src="#">
<img src="j">
<img src="j">
<img>
<img src="https://www.baidu.com/img/baidu_jgylogo3.gif">
<p>abc</p>
&lt;input onclick="javascript:alert(‘handsome boy‘)" type="text" value="瑯琊榜"&gt;
<p>abc</p>
<a>abc</a>
<a href="/api/" rel="nofollow">abc</a>
<a href="http://www.baidu.com" rel="nofollow">abc</a>
&lt;marquee onstart="alert(/XSS/)"&gt;文字&lt;/marquee&gt;
<div>文字</div>
<li></li>
&lt;input &lt;script="" id="1" type="text" value=""&gt;alert(/XSS/)"/&gt;
&lt;base href="<a href="http://www.labsecurity.org" rel="nofollow">http://www.labsecurity.org</a>"&gt;
<div id="x">alert%28document.cookie%29%3B</div>
&lt;limited_xss_point&gt;eval(unescape(x.innerHTML));&lt;/limited_xss_point&gt;
------clear------

  可以看到,對於富文本編輯器提交的代碼,bleach基本滿足了我們的防範xss攻擊的處理需求

  添加上傳接口(PS:我們使用的文本編輯器是百度的ueditor,因為它沒有python的上傳處理代碼,所以我們需要動手編輯上傳接口,以及html上也要進行對應的修改)

#!/usr/bin/evn python
# coding=utf-8

import os
from bottle import post, request
from common import datetime_helper, random_helper, log_helper

@post(/api/files/)
def callback():
    """
    修改記錄
    """
    # 初始化輸出值
    result = {
        "state": "FAIL",
        "url": "",
        "title": "上傳失敗",
        "original": ""
    }
    # 獲取上傳文件
    try:
        # upfile為前端HTML上傳控件名稱
        upload = request.files.get(upfile)
        # 如果沒有讀取到上傳文件或上傳文件的方式不正確,則返回上傳失敗狀態
        if not upload:
            return result

        # 取出文件的名字和後綴
        name, ext = os.path.splitext(upload.filename)
        # 給上傳的文件重命名,默認上傳的是圖片
        if ext and ext != ‘‘:
            file_name = datetime_helper.to_number() + random_helper.get_string(5) + ext
        else:
            file_name = datetime_helper.to_number() + random_helper.get_string(5) + .jpg
        upload.filename = file_name

        # 設置文件存儲的相對路徑
        filepath = /upload/ + datetime_helper.to_number(%Y%m%d) + /
        # 組合成服務器端存儲絕對路徑
        upload_path = os.getcwd() + filepath
        # 如果目錄不存在,則創建目錄
        if not os.path.exists(upload_path):
            os.mkdir(upload_path)
        # 保存文件
        upload.save(upload_path + upload.filename, overwrite=True)

        # 設置輸出參數(返回相對路徑給客戶端)
        result[title] = result[original] = upload.filename
        result[url] = filepath + upload.filename
        result[state] = SUCCESS
    except Exception as e:
        log_helper.error(上傳失敗: + str(e.args))

    # 直接輸出json
    return result

  PS:這裏只做了上傳文件處理,沒有上傳成功以後存儲到數據庫中統一管理,如果前端反復上傳,會造成服務器存儲很多多余文件的問題,大家可以自己發揮想象與動手能力,看看怎麽解決這個問題。對於這個問題會在第二部分統一處理。

  添加上傳文件存儲文件夾:直接在項目的要目錄下創建upload文件夾

  技術分享

  修改main.py文件配置,並創建文件下載路由

  導入的bottle庫添加response, static_file這兩個包,response用於設置輸出文件類型為二進制數據傳輸格式,這樣設置後,上傳的各種類型文件都可以下載;static_file是使用安全的方式讀取文件並輸出到客戶端

from bottle import default_app, get, run, request, hook, route, response, static_file

  在第26行插入下面代碼,初始化上傳文件存儲路徑

# 定義upload為上傳文件存儲路徑
upload_path = os.path.join(program_path, upload)

  添加下載文件訪問路由,設置後只要放在upload目錄下的文件都可以直接通過瀏覽器下載

@get(/upload/<filepath:path>)
def upload_static(filepath):
    """設置靜態內容路由"""
    response.add_header(Content-Type, application/octet-stream)
    return static_file(filepath, root=upload_path)

  做完以上設置,上傳與更新就沒有問題了,上傳的圖片直接使用http://127.0.0.1:9090/upload/xxx.jpg方式就可以訪問了,如果想要使用81端口,也就是通過nginx訪問,那就需要再配置一下

  打開nginx配置文件 :E:\Service\nginx-1.11.5\conf\nginx.conf

  將location ~* ^/(index|api)/ 修改為 location ~* ^/(index|api|upload)/

  然後在windows任務管理器(鍵盤同時按Ctrl+Alt+Del鍵,點擊啟動任務管理器),找到nginx_service.exe,右鍵=》結束進程樹

  重新打開服務(控制面板=》所有控制面板項=》管理工具=》服務),啟動nginx_service服務

  前端頁面相關修改

  向/lib/ueditor/1.4.3/目錄中添加python文件夾,將添加config.json這個配置項

  修改/lib/ueditor/1.4.3/ueditor.config.js 配置項中 服務器統一請求接口路徑 為 /api/files/

  本文對應的源碼包裏有ueditor編輯器最新代碼(剛剛去百度下載的),去掉了多余的文件,大家可直接刪除lib目錄裏的ueditor這個文件夾,使用源碼包裏的替換上去就可以了

  前端頁面的javascript腳本添加了ueditor編輯器初始化、文件上傳和表單提交等功能,可直接替換about_edit.html文件,具體大家自己研究一下。

  最終效果:

  技術分享

    技術分享

  另外,聯系我們的功能與公司介紹差不多,在這裏留一下作業給大家自己嘗試做一個聯系我們編輯頁面出來,下一篇會給聯系我們編輯頁面源碼給大家

  本文對應的源碼下載

作者:AllEmpty
出處:http://www.cnblogs.com/EmptyFS/
有興趣的朋友可以加加python開發QQ群:669058475 ,大家一起探討。大家有問題的話可以在群裏發問,當然我平時工作也非常繁忙不一定會及時回復。

本文為AllEmpty原創,歡迎轉載,但未經同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

我的第一個python web開發框架(15)——公司介紹編輯功能