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


  完成登錄以后,就會進入后台管理系統的主界面,因為這個是小項目,所以導航菜單全部固化在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 本文歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則視為侵權。

python開發QQ群:669058475(本群已滿)、733466321(可以加2群)    作者博客:http://www.cnblogs.com/EmptyFS/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM