Dataset基于SQLAlchemy的便利工具
數據集使得數據庫中的數據讀取和寫入數據就像閱讀和編寫JSON文件一樣簡單。
Dataset對于操作JSON、CSV文件、NoSQL非常好用。
- import dataset
連接MySQL數據庫:
- db = dataset.connect('mysql://username:password@10.10.10.10/ctf?charset=utf8')
用戶名:username,密碼:password,數據庫地址(地址+端口):10.10.10.10,database名: ctf
連接SQLite數據庫:
- db = dataset.connect('sqlite:///ctf.db')
連接PostgreSQL數據庫:
- db = dataset.connect('postgresql://scott:tiger@localhost:5432/mydatabase')
一定要注意指定字符編碼
- table = db['city'] #(選擇city表)
- user = table('name') # 找出表中'name'列屬性所有數據
- res = db.query('select name from table limit 10') # 如果不需要查看全部數據的話***用limit,因為全部數據的載入非常非常耗時間
- for x in res:
- print x['name'] # 選name字段的數據
- table.insert(dict(name='John Doe', age=37))
- table.insert(dict(name='Jane Doe', age=34, gender='female'))
- john = table.find_one(name='John Doe')
在數據庫中查找是否有同時滿足多個條件的數據:table.find_one(屬性1=屬性值1, 屬性2=屬性值2, …)
注:find_one速度很慢
插入數據
dataset會根據輸入自動創建表和字段名
- table = db['user']
- # 或者table = db.get_table('user')
- table.insert(dict(name='John Doe', age=46, country='China'))
- table.insert(dict(name='Jane Doe', age=37, country='France', gender='female'))
- # 主鍵id自動生成
更新數據
- table.update(dict(name='John Doe', age=47), ['name'])
- # 第二個參數相當于sql update語句中的where,用來過濾出需要更新的記錄
事務操作
事務操作可以簡單的使用上下文管理器來實現,出現異常,將會回滾
- with dataset.connect() as tx:
- tx['user'].insert(dict(name='John Doe', age=46, country='China'))
- # 相當于:
- db = dataset.connect()
- db.begin()
- try:
- db['user'].insert(dict(name='John Doe', age=46, country='China'))
- db.commit()
- except:
- db.rollback()
- # 也可以嵌套使用:
- db = dataset.connect()
- with db as tx1:
- tx1['user'].insert(dict(name='John Doe', age=46, country='China'))
- with db as tx2:
- tx2['user'].insert(dict(name='Jane Doe', age=37, country='France', gender='female'))
從表獲取數據
- users = db['user'].all()
- for user in db['user']:
- # print(user['age'])
- # chinese_users = user.find(country='China')
- john = user.find_one(name='John Doe')
獲取非重復數據
- db['user'].distinct('country')
刪除記錄
- table.delete(place='Berlin')
執行SQL語句
- result = db.query('SELECT country, COUNT(*) c FROM user GROUP BY country')
- for row in result:
- print(row['country'], row['c'])
導出數據
- result = db['users'].all()
- dataset.freeze(result, format='json', filename='users.json')
JSON
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式,非常易于人閱讀和編寫。
- import json
json.dumps 將 Python 對象編碼成 JSON 字符串
json.loads 將已編碼的 JSON 字符串解碼為 Python 對象
MySQL數據庫:
分類表-categories,包括類別web,reversing,crypto(加解密),mic等
題目表-tasks,包括題目id,題目名,flag,分值,文件&地址,題目等級,題目詳細描述
flag表-flag,包括題目id,用戶id,得分,時間戳
用戶表-users,包括用戶id,用戶名,密碼
題目分類表-cat_task,包括題目id,題目類別id
flag表中每條數據由于是有題目ID task_id和用戶ID user_id來共同確認的,所以采用復合主鍵:primary key (task_id,user_id)
python裝飾器
Decorator通過返回包裝對象實現間接調用,以此插入額外邏輯
https://www.zhihu.com/question/26930016
wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了
- from functools import wraps
- def logged(func):
- @wraps(func)
- def with_logging(*args,**kwargs):
- print func.__name__ + "was called"
- return func(*args,**kwargs)
- return with_logging
- @logged
- def f(x):
- """does some math"""
- return x + x * x
- print f.__name__ # prints 'f'
- print f.__doc__ # prints 'does some math'
web框架采用flask
- from flask import Flask
引入Flask類,Flask類實現了一個WSGI(Web Server Gateway Interface)應用
- app = Flask(__name__)
app是Flask的實例,它接收包或者模塊的名字作為參數,但一般都是傳遞__name__
- @app.route('/')
- def hello_world():
- return 'Hello World!'
使用app.route裝飾器會將URL和執行的視圖函數的關系保存到app.url_map屬性上。處理URL和視圖函數的關系的程序就是路由,這里的視圖函數就是hello_world
- if __name__ == '__main__':
- app.run(host='0.0.0.0',port=9000)
使用這個判斷可以保證當其他文件引用這個文件的時候(例如from hello import app)不會執行這個判斷內的代碼,也就是不會執行app.run函數。
執行app.run就可以啟動服務了。默認Flask只監聽虛擬機的本地127.0.0.1這個地址,端口為5000。而我們對虛擬機做的端口轉發端口是9000,所以需要制定host和port參數,0.0.0.0表示監聽所有地址,這樣就可以在本機訪問了。
服務器啟動后,會調用werkzeug.serving.run_simple進入輪詢,默認使用單進程單線程的werkzeug.serving.BaseWSGIServer處理請求,實際上還是使用標準庫BaseHTTPServer.HTTPServer,通過select.select做0.5秒的while TRUE的事件輪詢。當我們訪問http://127.0.0.1:9000/,通過app.url_map找到注冊的/這個URL模式,就找到了對應的hello_world函數執行,返回hello world!,狀態碼為200。如果訪問一個不存在的路徑,如訪問http://127.0.0.1:9000/a,Flask找不到對應的模式,就會向瀏覽器返回Not Found,狀態碼為404
flask中jsonify的作用
jsonify的作用實際上就是將我們傳入的json形式數據序列化成為json字符串,作為響應的body,并且設置響應的Content-Type為application/json,構造出響應返回至客戶端
效果等于json.dumps
jsonify的Content-Type字段值為application/json
json.dumps的Content-Type字段值為text/html
修改flask中靜態文件夾
修改的flask默認的static文件夾只需要在創建Flask實例的時候,把static_folder和static_url_path參數設置為空字符串即可。
app = Flask(__name__, static_folder=”, static_url_path=”)
訪問的時候用url_for函數,res文件夾和static文件夾同一級:
url_for(‘static’, filename=’res/favicon.ico’)
werkzeug
werkzeug是一個WSGI工具包,可以作為一個Web框架的底層庫。它封裝好了很多Web框架的東西,例如 Request,Response等等。Flask框架就是一Werkzeug 為基礎開發的
generate_password_hash(password)
將用戶輸入的明文密碼加密成密文進行存儲
密碼加鹽哈希函數。用來將明文密碼加密,返回加密后的密文,用來進行用戶注冊
函數定義:
werkzeug.security.generate_password_hash(password, method='pbkdf2:sha1', salt_length=8)
密文格式:method$salt$hash
password: 明文密碼
method: 哈希的方式(需要是hashlib庫支持的),格式為
pbpdf2:<method>[:iterations]。參數說明:
method:哈希的方式,一般為SHA1,
iterations:(可選參數)迭代次數,默認為1000。
slat_length: 鹽值的長度,默認為8
check_password_hash(hash,password)
驗證經過generate_password_hash哈希的密碼,將明文和密文進行比較,查看是否一致,用來驗證用戶登錄
函數定義:
werkzeug.security.check_password_hash(pwhash, password)
pwhash: generate_password_hash生成的哈希字符串
password: 需要驗證的明文密碼
flask中的session
- rom flask import session
- user = db['users'].find_one(username=username)
- session['user_id'] = user['id']
由于使用了session,所以需要設置一個secret_key用來做一些模塊的hash
Flask Web Development 中的內容:
SECRET_KEY配置變量是通用密鑰,可在Flask和多個第三方擴展中使用。如其名所示,加密的強度取決于變量值的機密度。不同的程序要使用不同的密鑰,而且要保證其他人不知道你所用的字符串。
SECRET_KEY的作用主要是提供一個值做各種HASH, 是在其加密過程中作為算法的一個參數(salt或其他)。所以這個值的復雜度也就影響到了數據傳輸和存儲時的復雜度。
flask 變量規則
要給URL添加變量部分,你可以把這些特殊的字段標記為<variable_name>, 這個部分將會作為命名參數傳遞到你的函數。規則可以用<converter:variable_name>指定一個可選的轉換器
- @route('/hello/<name>')
- def index(name):
- return '<b>Hello {{name}}</b>!'
數據庫查詢
對dataset的數據查詢,使用冒號來為變量傳參。
select f.task_id from flags f where f.user_id = :user_id”’,user_id=session[‘user_id’])
模板渲染
使用render_template方法來渲染模板。將模板名和你想作為關鍵字的參數傳入模板的變量
MySQL
IFNULL(expr1,expr2)
如果expr1不是NULL,IFNULL()返回expr1,否則它返回expr2。
IFNULL()返回一個數字或字符串值,取決于它被使用的上下文環境。
max函數是用來找出記錄集中***值的記錄
- 對于left join,不管on后面跟什么條件,左表的數據全部查出來,因此要想過濾需把條件放到where后面
- 對于inner join,滿足on后面的條件表的數據才能查出,可以起到過濾作用。也可以把條件放到where后面
在使用left jion時,on和where條件的區別如下:
- on條件是在生成臨時表時使用的條件,它不管on中的條件是否為真,都會返回左邊表中的記錄。
- where條件是在臨時表生成好后,再對臨時表進行過濾的條件。這時已經沒有left join的含義(必須返回左邊表的記錄)了,條件不為真的就全部過濾掉。
order by的用法
使用order by,一般是用來,依照查詢結果的某一列(或多列)屬性,進行排序(升序:ASC;降序:DESC;默認為升序)。
當排序列含空值時:
ASC:排序列為空值的元組***顯示。
DESC:排序列為空值的元組***顯示。
可以把null值看做無窮大
select * from s order by sno desc, sage asc
group by的用法
group by按照查詢結果集中的某一列(或多列),進行分組,值相等的為一組
1、細化集函數(count,sum,avg,max,min)的作用對象:
未對查詢結果分組,集函數將作用于整個查詢結果。
對查詢結果分組后,集函數將分別作用于每個組。
SELECT cno,count(sno) from sc group by cno
2、GROUP BY子句的作用對象是查詢的中間結果表
分組方法:按指定的一列或多列值分組,值相等的為一組。
使用GROUP BY子句后,SELECT子句的列名列表中只能出現分組屬性(比如:sno)和集函數(比如:count())
select sno,count(cno) from sc group by sno
3、多個列屬性進行分組
select cno,grade,count(cno) from sc group by cno,grade
4、使用HAVING短語篩選最終輸出結果
只有滿足HAVING短語指定條件的組才輸出。
HAVING短語與WHERE子句的區別:作用對象不同。
1、WHERE子句作用于基表或視圖,從中選擇滿足條件的元組。
2、HAVING短語作用于組,從中選擇滿足條件的組
select sno from sc group by sno having count(cno)>3
select sno,count(cno) from sc where grade>60 group by sno having count(cno)>3
MySQL的左連接、右連接、等值連接
1.左連接(left join )
- select m.columnname……,n.* columnname…..
- from left_table m left join right_table n on m.columnname_join=n.columnname_join and n.columnname=xxx
- where m.columnname=xxx…..
ON是連接條件,用于把2表中等值的記錄連接在一起,但是不影響記錄集的數量。若是表left_table中的某記錄,無法在表right_table找到對應的記錄,則此記錄依然顯示在記錄集中,只是表right_table需要在查詢顯示的列的值用NULL替代;
ON連接條件中表n.columnname=xxx用于控制right_table表是否有符合要求的列值還是用NULL替換的方式顯示在查詢列中,不影響記錄集的數量;
WHERE字句控制記錄是否符合查詢要求,不符合則過濾掉
2.右連接(right join)
- select m.columnname……,n.* columnname…..
- from left_table m right join right_table n on m. columnname_join=n. columnname_join and m. columnname=xxx
- where n.columnname=xxx…..
3.等值連接
- select m.columnname……,n.* columnname…..
- from left_table m [inner] join right_table n on m. columnname_join=n. columnname_join
- where m.columnname=xxx….. and n.columnname=xxx….
或者
- select m.columnname……,n.* columnname…..
- from left_table m , right_table n
- where m. columnname_join=n. columnname_join and
- m.columnname=xxx….. and n.columnname=xxx….
ON是連接條件,不再與左連接或右連接的功效一樣,除了作為2表記錄匹配的條件外,還會起到過濾記錄的作用,若left_table中記錄無法在right_table中找到對應的記錄,則會被過濾掉;
WHERE字句,不管是涉及表left_table、表right_table上的限制條件,還是涉及2表連接的條件,都會對記錄集起到過濾作用,把不符合要求的記錄刷選掉;
jinja2獲取循環索引
jinja2獲取循環{% for i in n %}的索引使用loop.index
- {% for i in names %}
- <tr>
- <td>{{ loop.index }}</td> //當前是第x條
- <td>{{ i.name }}</td>
- </tr>
- {% endfor %}
flask 重定向和錯誤
可以用redirect()函數把用戶重定向到其它地方。放棄請求并返回錯誤代碼,用abort()函數。
- from flask import abort, redirect, url_for
- @app.route('/')
- def index():
- return redirect(url_for('login'))
- @app.route('/login')
- def login():
- abort(401)
- this_is_never_executed()
默認情況下,錯誤代碼會顯示一個黑白的錯誤頁面。如果你要定制錯誤頁面,可以使用errorhandler()
裝飾器:
- from flask import render_template
- @app.errorhandler(404)
- def page_not_found(error):
- return render_template('page_not_found.html'), 404
注意 render_template()調用之后的 404 。這告訴Flask,該頁的錯誤代碼是404 ,即沒有找到。默認為200,也就是一切正常。
flask CSRF防護機制
- @app.before_request
- def csrf_protect():
- if request.method == "POST":
- token = session.pop('_csrf_token', None)
- if not token or token != request.form.get('_csrf_token'):
- abort(403)
- def some_random_string():
- return hashlib.sha256(os.urandom(16).hexdigest())
- def generate_csrf_token():
- if '_csrf_token' not in session:
- session['_csrf_token'] = some_random_string()
- return session['_csrf_token']
在flask的全局變量里面注冊 上面那個生成隨機token的函數
app.jinja_env.globals[‘csrf_token’] = generate_csrf_token
在網頁的模板是這么引入的
- <form method=post action="">
- <input name=_csrf_token type=hidden value="{{ csrf_token() }}">
flask上下文處理器
Flask 上下文處理器自動向模板的上下文中插入新變量。上下文處理器在模板渲染之前運行,并且可以在模板上下文中插入新值。上下文處理器是一個返回字典的函數,這個字典的鍵值最終將傳入應用中所有模板的上下文:
- @app.context_processor
- def inject_user():
- return dict(user=g.user)
上面的上下文處理器使得模板可以使用一個名為user值為g.user的變量。不過這個例子不是很有意思,因為g在模板中本來就是可用的,但它解釋了上下文處理器是如何工作的。
變量不僅限于值,上下文處理器也可以使某個函數在模板中可用(由于Python允許傳遞函數):
- @app.context_processor
- def utility_processor():
- def format_price(amount, currency=u'€'):
- return u'{0:.2f}{1}.format(amount, currency)
- return dict(format_price=format_price)
上面的上下文處理器使得format_price函數在所有模板中可用:
{{ format_price(0.33) }}
日志記錄
handler = logging.FileHandler(‘flask.log’, encoding=’UTF-8′)
1、請求之前設置requestId并記錄日志
每個URL請求之前,定義requestId并綁定到g
- @app.before_request
- def before_request():
- g.requestId = gen_requestId()
- logger.info("Start Once Access, and this requestId is %s" % g.requestId)
2、請求之后添加響應頭與記錄日志
每次返回數據中,帶上響應頭,包含API版本和本次請求的requestId,以及允許所有域跨域訪問API, 記錄訪問日志
- @app.after_request
- def add_header(response):
- response.headers["X-SaintIC-Media-Type"] = "saintic.v1"
- response.headers["X-SaintIC-Request-Id"] = g.requestId
- response.headers["Access-Control-Allow-Origin"] = "*"
- logger.info(json.dumps({
- "AccessLog": {
- "status_code": response.status_code,
- "method": request.method,
- "ip": request.headers.get('X-Real-Ip', request.remote_addr),
- "url": request.url,
- "referer": request.headers.get('Referer'),
- "agent": request.headers.get("User-Agent"),
- "requestId": str(g.requestId),
- }
- }
- ))
- return response
basicConfig方法可以滿足你在絕大多數場景下的使用需求,但是basicConfig有一個很大的缺點。調用basicConfig其實是給root logger添加了一個handler(FileHandler ),這樣當你的程序和別的使用了 logging的第三方模塊一起工作時,會影響第三方模塊的logger行為。這是由logger的繼承特性決定的
- logging.basicConfig(level=logging.DEBUG,
- format='%(asctime)s %(levelname)s %(message)s',
- datefmt='%a, %d %b %Y %H:%M:%S',
- filename='logs/pro.log',
- filemode='w')
- logging.debug('dddddddddd')
MySQL字符編碼
除了設置數據庫的之外,由于dataset默認創建數據庫和表的字符集不是utf8,所以需要自己設置,否則會中文亂碼,所以需要修改表的字符集
- my.cnf
- [client]
- default-character-set=utf8
- [mysqld]
- character-set-server=utf8
- collation-server=utf8_general_ci
- default-storage-engine=INNODB
表的字符集
- show create table tasks;
- alter table tasks convert to character set utf8;