前言
最近用 python tordado 框架, 整了一個模板頁面, 用於接入與發布數據的展示,
tornado 簡單易用, bootstrap 比較流行, 用起來也省事, 配合起來做些小案例非常迅速.
技術儲備
-
python
基礎知識, 面向對象封裝,繼承
-
數據庫
mysql
-
框架
tornado, sqlalchemy (ORM), template
-
開發工具
pycharm, chrome
功能開發
一. 需求分析
-
每頁展示 5 條統計數據, 按日期倒序排列
沒有統計數據時, 要有文字提示: "暫沒有對接統計信息"
-
支持按日期進行查詢
查詢時不顯示分頁情況
-
支持分頁查詢, 提示目前所在頁碼, 可跳轉到任意一頁
二. 頁面原型
個人前端水平有限, 弄這樣一個頁面從無到有估計得花半天時間, 所以就找了 bootstrap 的模板, 里邊有很多現成的組件可以直接使用, 然后改下布局, 調下樣式, 做一個簡單的頁面足夠.
bootstrap : https://v3.bootcss.com/components/
三. 搭建 tornado 框架
熟悉 python 的同學, 直接 easy_install 或者 pip 安裝最新的 tornado, sqlalchemy, pymysql 等模塊
bootstrap.js 直接到官網下載就行, 當然還需要 bootstrap 依賴的 JQuery.js
app.py
配置靜態資源路徑:
"static_path": os.path.join(os.path.dirname(__file__), "statics"),
配置模板路徑:
"template_path": os.path.join(os.path.dirname(__file__), 'templates'),
配置運行模式:
# 通過腳本傳參的方式指定
define("debug", default=1, help="debug mode: 1 to open, 2 to test env, other to production")
"debug": bool(options.debug),
開發, 調試模式, 開啟單進程:
application.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
生產模式, 開啟多進程:
application.bind(options.port)
application.start(3) # 開啟 3 個進程
tornado.ioloop.IOLoop.instance().start()
四. 連接 mysql
獲取了DBSession 類, 在需要使用的地方, 創建對象[ session = DBSession() ]即可,
如果數據庫操作不是特別平凡, 會話回收的時間周期可以設置的長一點, 例如: pool_recycle = 60
# coding: utf-8
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from settings.setting import MYSQL_SERVER, MYSQL_DRIVER, MYSQL_USERNAME, MYSQL_PASSWORD, DB_NAME, DB_CHARSET
# MYSQL_USERNAME = os.getenv('MYSQL_USER') or settings.MYSQL_USERNAME
# MYSQL_PASSWORD =
# 數據庫
engine = create_engine("mysql+{driver}://{username}:{password}@{server}/{database}?charset={charset}"
.format(driver=MYSQL_DRIVER,
username=MYSQL_USERNAME,
password=MYSQL_PASSWORD,
server=MYSQL_SERVER,
database=DB_NAME,
charset=DB_CHARSET),
pool_size=20,
max_overflow=100,
pool_recycle=1,
echo=False)
engine.execute("SET NAMES {charset};".format(charset=DB_CHARSET))
MapBase = declarative_base(bind=engine)
DBSession = sessionmaker(bind=engine)
五. 處理類
基礎類, 定義了一些 API 規范和常用的工具方法
先封裝了基礎類 GlobalBaseHandler , 數據庫會話和獲取參數的方法封裝
# 全局基類方法
class GlobalBaseHandler(BaseHandler):
@property
def session(self):
if hasattr(self, "_session"):
return self._session
self._session = DBSession()
return self._session
# 關閉數據庫會話
def on_finish(self):
if hasattr(self, "_session"):
self._session.close()
def prepare(self):
pass
查詢, 分頁業務處理類
需要注意的是: (小於第1頁情況); (大於總頁數的情況, 但總頁數為 0 的情況).
class IllegalStats(GlobalBaseHandler):
def get(self):
"""
獲取接入,發布統計數據
:return:
"""
date = self.get_argument("date")
current_page = self.get_argument("current_page") # 當前頁
if date:
return self.query_by_date(date)
elif current_page:
return self.query_paginate(current_page)
# 默認返回第一頁的數據
else:
return self.query_paginate()
def query_paginate(self, current_page=None):
if current_page is None:
current_page = 1
else:
current_page = int(current_page)
#
page_size = 5 # 分頁條數
# 總記錄數, 總頁數
total_count = IllegalAccessStats.query_count(self.session)
if total_count % page_size == 0:
total_page = int(total_count / page_size)
else:
total_page = int(total_count / page_size) + 1
# 小於第一頁
if current_page <= 0:
current_page = 1
# 大於最后一頁
if total_page > 0:
if current_page > total_page:
current_page = total_page
else:
current_page = 1
# 當前頁的數據
stats_list = IllegalAccessStats.query_paginate(self.session, current_page=current_page, page_size=page_size)
return self.render("statistics.html", stats_list=stats_list, date="", total_count=total_count, total_page=total_page, current_page=current_page)
def query_by_date(self, date):
stats_list = IllegalAccessStats.query_by_date(self.session, date=date)
#
return self.render("statistics.html", stats_list=stats_list, date=date, total_count=None, total_page=None, current_page=None)
六. 模板渲染
template, 就是在 html 里邊寫 python 代碼, if 判斷, for range 語句和python語法一模一樣, 沒有太大難度, 慢慢調試就出來了, 模板渲染報錯提示非常詳細, 很好調試,
感覺寫起來賊惡心, 又是標簽, 又是邏輯處理.
statistics.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- viewport視口:網頁可以根據設置的寬度自動進行適配,在瀏覽器的內部虛擬一個容器,容器的寬度與設備的寬度相同。
width: 默認寬度與設備的寬度相同
initial-scale: 初始的縮放比,為1:1 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>接入&發布統計信息</title>
<!-- Bootstrap -->
<link href="{{static_url('css/bootstrap.min.css')}}" rel="stylesheet">
<script src="{{static_url('js/jquery-2.1.0.min.js')}}"></script>
<script src="{{static_url('js/bootstrap.min.js')}}"></script>
<style type="text/css">
th, td {
text-align: center;
height: 50px;
vertical-align:middle;
}
</style>
</head>
<body>
<div class="container">
<h2 style="text-align: center">接入 & 發布統計信息</h2>
<br>
<br>
<div style="float: left;">
<form class="form-inline" action="/illegal/stats" method="get">
<div class="form-group">
<label for="exampleInputName1">日期</label>
<input type="text" name="date" value="{{ date }}" class="form-control" id="exampleInputName1" placeholder="yyyy-mm-dd" >
</div>
<button type="submit" class="btn btn-default" style="margin: 5px;">查詢</button>
</form>
</div>
<br>
<br>
<br>
<br>
<br>
<table border="1" class="table table-bordered table-hover">
<div>
<a class="btn btn-primary" href="/illegal/stats?" style="float: left; margin: 3px;">接入</a>
<a class="btn btn-primary" href="/illegal/stats?" style="position: relative; left: 872px; margin: 3px">發布</a>
</div>
<tr class="access">
<th style="background: #f8efc0; vertical-align:middle">日期</th>
<th style="background: #4cae4c; vertical-align:middle">違法接收</th>
<th style="background: #4cae4c; vertical-align:middle">入庫成功</th>
<th style="vertical-align: middle">讀取成功</th>
<th style="background: #d9534f; vertical-align:middle">讀取失敗</th>
<th style="vertical-align: middle">下載成功</th>
<th style="background: #d9534f; vertical-align:middle">下載失敗</th>
<th style="vertical-align: middle">寫入成功</th>
<th style="background: #d9534f; vertical-align:middle">寫入失敗</th>
<th style="background: #4cae4c; vertical-align:middle">審核接收</th>
<th style="background: #4cae4c; vertical-align:middle">發布成功</th>
</tr>
{% if len(stats_list) > 0 %}
{% for stats in stats_list %}
<tr>
<td style="vertical-align: middle">{{ stats["date"] }}</td>
<td style="vertical-align: middle">{{ stats["access_received_total"] }}</td>
<td style="vertical-align: middle">{{ stats["access_inserted_total"] }}</td>
<td style="vertical-align: middle">{{ stats["read_success_total"] }}</td>
<td style="vertical-align: middle">{{ stats["read_false_total"] }}</td>
<td style="vertical-align: middle">{{ stats["download_success_total"] }}</td>
<td style="vertical-align: middle">{{ stats["download_false_total"] }}</td>
<td style="vertical-align: middle">{{ stats["write_success_total"] }}</td>
<td style="vertical-align: middle">{{ stats["write_false_total"] }}</td>
<td style="vertical-align: middle">{{ stats["publish_received_total"] }}</td>
<td style="vertical-align: middle">{{ stats["publish_send_total"] }}</td>
</tr>
{% end %}
{% else %}
<tr>
<td colspan="11" style="vertical-align: middle">暫沒有對接統計信息</td>
</tr>
{% end %}
</table>
<!-- 查詢的時候不顯示分頁內容-->
{% if total_count is not None or total_page is not None %}
<div>
<nav aria-label="Page navigation">
<ul class="pagination">
{% if current_page == 1 %}
<li class="disabled">
{% end %}
{% if current_page != 1 %}
<li>
{% end %}
<a href="/illegal/stats?current_page={{current_page - 1}}" aria-label="Previous">
<span aria-hidden="false">«</span>
</a>
</li>
{% for page in range(1, total_page + 1) %}
{% if current_page == page %}
<li class="active"><a href="/illegal/stats?current_page={{ page }}">{{ page }}</a></li>
{% end %}
{% if current_page != page %}
<li><a href="/illegal/stats?current_page={{ page }}">{{ page }}</a></li>
{% end %}
{% end %}
{% if current_page == total_page or current_page == 1 %}
<li class="disabled">
{% end %}
{% if current_page != total_page and current_page != 1 %}
<li>
{% end %}
<a href="/illegal/stats?current_page={{current_page + 1}}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
<span style="font-size: 25px;margin-left: 5px;">
共{{ total_count }}條記錄,共{{ total_page }}頁
</span>
</ul>
</nav>
</div>
{% end %}
</div>
</body>
</html>
需要注意的地方
-
模板渲染的時候, 需要使用
static_url()
函數獲取靜態資源( statics ) 的路徑, 不然模板會找不到 css, js 文件, 不能正常加載頁面.<link href="{{static_url('css/bootstrap.min.css')}}" rel="stylesheet">
<script src="{{static_url('js/jquery-2.1.0.min.js')}}"></script>
<script src="{{static_url('js/bootstrap.min.js')}}"></script>
-
給當前頁添加激活狀態
項目改進
xxx
最后給出web項目地址: https://github.com/kaichenkai/TornadoWebApp