在上一節「偷個懶,公號摳腚早報80%自動化——3.Flask速成大法」中,快速地把 Flask的基本語法擼了一遍,本節直接開衝,用Flask來寫下摳腚男孩的後臺。css
PS:筆者沒有真正參加過先後端開發,此文都是現學現賣,你能夠理解成小白文章, 有錯的地方,還望海涵,批評或建議歡迎在評論區留言,謝謝~html
先捋下整個業務流程吧前端
(PS:下述部分能夠直接跳過,不過我仍是建議看看概念性的東西)python
抽象出業務邏輯,把相同的東西先放一塊兒,而後經過思惟導圖的形式表現出來。mysql
「業務邏輯」和「功能模塊」呈現的內容結合,一個model對應多個業務邏輯。 模塊的劃分依據:功能與業務的關係,功能和功能間不能有關係,功能儘量實現一對多。 筆者的項目過於簡單,圖跟業務邏輯思惟導圖差不了多少,直接略過。nginx
找出功能——業務邏輯思惟導圖中的對應關係,功能模塊按照人和事來劃分。 事不能理解爲用戶的行爲,「事」就是單純的事,不是用戶行爲,「人」就是用戶, 「事」就是指事物,「事件」是人和事之間的關係。不能主動發出請求的都歸屬於事
。 你去星巴克喝咖啡 = 事件 = 人和事之間的關係。事是事物,不是事件,我給你發短信, 你接收短信,這是兩個事件。sql
- 我是人,短信是事物,我發短信是事件
- 你是人,短信是事物,你收短信是事件
若是商家能主動發起請求,那就是人,即一個東西具有主動性,它就是人。 (這裏的人之間沒有啥關聯,因此沒有線~)數據庫
只考慮功能模塊,設計接口去解決問題,注意耦合把控,過高不能拆分,過低失去化模塊意義。編程
目前所需的API就上面這些,後面按需擴展便可。json
先是項目的結構,直接使用上一節說的簡單通用的結構:
結構簡述:
定義三種類型的數據:源新聞,篩選新聞、早報、字段大同小異,代碼以下:
# models\news.py
from app import db
__all__ = ['OriginNews', 'ChooseNews', 'MorningNews']
class OriginNews(db.Model):
__tablename__ = 'news_origin'
__table_args__ = {"useexisting": True}
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.Text)
url = db.Column(db.Text)
create_time = db.Column(db.Text)
def to_dict(self):
return {"id": self.id, "title": self.title, "url": self.url, "create_time": self.create_time}
class ChooseNews(db.Model):
__tablename__ = 'news_choose'
__table_args__ = {"useexisting": True}
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.Text)
url = db.Column(db.Text)
create_time = db.Column(db.Text)
def to_dict(self):
return {"id": self.id, "title": self.title, "url": self.url, "create_time": self.create_time}
class MorningNews(db.Model):
__tablename__ = 'news_morning'
__table_args__ = {"useexisting": True}
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.Text)
url = db.Column(db.Text)
create_time = db.Column(db.Text)
add_time = db.Column(db.Text)
def to_dict(self):
return {"id": self.id, "title": self.title, "url": self.url, "create_time": self.create_time,
"add_time": self.add_time}
複製代碼
添加sqlalchemy相關的配置,以下:
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:Jay12345@127.0.0.1:3306/news'
SQLALCHEMY_TRACK_MODIFICATIONS = True
複製代碼
__init__.py
文件在這裏完成Flask,SQLAlchemy對象的實例化,以及相關數據庫的建立:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
from app.models.news import *
db.create_all()
複製代碼
從業務邏輯思惟導圖那裏就知道,不僅是作早報,還有表情包,沙雕圖等,爲了 便於後面方便擴展,利用藍圖來分離模塊。直接在views目錄下建立一個news.py。
# 建立藍圖
ns = Blueprint('news', __name__)
# flask實例註冊藍圖
app.register_blueprint(ns, url_prefix='/news')
複製代碼
視圖文件建立,如今大部分的接口返回的數據都是Json字符串,若是每次返回數據都要 咱們自行去拼接字符串,顯得過於繁瑣,能夠包裝下jsonify,把字典類型的數據直接 轉換成Json字符串返回。代碼以下:
class JsonResponse(Response):
@classmethod
def force_type(cls, response, environ=None):
if isinstance(response, dict):
response = jsonify(response)
return super(JsonResponse, cls).force_type(response, environ)
app.response_class = JsonResponse
複製代碼
約定下返回的Json數據格式:
{
"code":"200",
"msg":"請求成功",
"data":[]
}
複製代碼
準備工做作的差很少了,接着開始着手編寫API接口,分幾類進行編寫,先是和數據庫增刪改查有關的:
# 查詢新聞,判斷是否傳入nid來判斷是單條查詢仍是多條,
# kind爲表序號:1來源表,2篩選表,3早報表
@ns.route("/show", methods=['GET'])
def news_show():
req_args = request.args
if 'kind' in req_args:
kind = int(req_args['kind'])
# 若是有nid參數,說明是查詢單條,不然是查詢所有
if 'nid' in req_args:
nid = request.args['nid']
if kind == 1:
news = OriginNews.query.filter_by(id=nid).first()
elif kind == 2:
news = ChooseNews.query.filter_by(id=nid).first()
elif kind == 3:
news = ChooseNews.query.filter_by(id=nid).first()
else:
return make_response({'code': '200', 'msg': '無效的kind參數', 'data': []})
return make_response({'code': '200', 'msg': '請求成功', 'data': news.to_dict()})
else:
if 'count' in req_args and 'page' in req_args:
count = int(req_args['count'])
page = int(req_args['page'])
news_list = []
if kind == 1:
for n in OriginNews.query.filter().offset(page * count).limit(count):
news_list.append(n.to_dict())
elif kind == 2:
for n in ChooseNews.query.filter().offset(page * count).limit(count):
news_list.append(n.to_dict())
elif kind == 3:
for n in MorningNews.query.filter().offset(page * count).limit(count):
news_list.append(n.to_dict())
else:
return make_response({'code': '200', 'msg': '無效的kind參數', 'data': []})
return make_response({'code': '200', 'msg': '請求成功', 'data': news_list})
else:
return make_response({'code': '200', 'msg': '缺乏count或page參數', 'data': []})
else:
return make_response({'code': '200', 'msg': '缺乏kind參數', 'data': []}
# 查詢新聞條數
# kind爲表序號:1來源表,2篩選表,3早報表
@ns.route("/<int:kind>/count", methods=['GET'])
def news_count(kind):
if kind == 1:
count = OriginNews.query.filter().count()
elif kind == 2:
count = ChooseNews.query.filter().count()
elif kind == 3:
count = MorningNews.query.filter().count()
else:
return make_response({'code': '201', 'msg': '錯誤的參數類型', 'data': []})
resp = make_response({'code': '200', 'msg': '請求成功', 'data': {'count': count}})
return resp
# 刪除某條新聞,傳入參數nid表明新聞id
# kind爲表序號:1來源表,2篩選表,3早報表
@ns.route("/destroy", methods=['DELETE'])
def news_delete():
req_args = request.form
if 'kind' in req_args:
kind = int(req_args['kind'])
if 'nid' in req_args:
nid = int(req_args['nid'])
if kind == 1:
news = OriginNews.query.filter_by(id=nid).first()
db.session.delete(news)
db.session.commit()
elif kind == 2:
db.session.delete(ChooseNews.query.filter_by(id=nid).first())
db.session.commit()
elif kind == 3:
db.session.delete(MorningNews.query.filter_by(id=nid).first())
db.session.commit()
else:
return make_response({'code': '200', 'msg': '無效的kind參數', 'data': []})
return make_response({'code': '200', 'msg': '請求成功', 'data': []})
else:
return make_response({'code': '200', 'msg': '缺乏mid 參數', 'data': []})
else:
return make_response({'code': '200', 'msg': '缺乏kind參數', 'data': []})
# 更新篩選池裏的新聞(有的更新,沒的插入)
@ns.route("/update", methods=['POST'])
def add_news():
req_args = request.form
if 'news' in req_args:
news_dict = json.loads(req_args['news'])
news = ChooseNews.query.filter_by(id=news_dict['nid']).first()
# 沒有數據是插入,有數據是修改
if news is None:
news = ChooseNews()
news.id = news_dict['nid']
news.title = news_dict['title']
news.url = news_dict['url']
news.create_time = news_dict['create_time']
db.session.add(news)
db.session.commit()
return make_response({'code': '200', 'msg': '插入成功', 'data': []})
else:
news.id = news_dict['nid']
news.title = news_dict['title']
news.url = news_dict['url']
news.create_time = news_dict['create_time']
db.session.commit()
return make_response({'code': '200', 'msg': '更新成功', 'data': []})
else:
return make_response({'code': '200', 'msg': '缺乏news參數', 'data': []})
# 把篩選池的新聞插入到日報池中(限制15條)
@ns.route("/insert_morning", methods=['POST'])
def add_morning_news():
for n in ChooseNews.query.filter().limit(15):
n_dict = n.to_dict()
morning_news = MorningNews()
morning_news.id = n_dict.get('id')
morning_news.title = n_dict.get('title')
morning_news.url = n_dict.get('url')
morning_news.create_time = n_dict.get('create_time')
morning_news.add_time = time.strftime("%Y%m%d")
db.session.add(morning_news)
db.session.commit()
return make_response({'code': '200', 'msg': '請求成功', 'data': []})
複製代碼
須要經過命令行來啓動爬蟲,爬蟲的執行比較耗時,而Flask的服務默認是同步的。 只有爬蟲執行完畢纔會響應客戶端,顯然是很是不合理的。這裏用線程池來實現 最簡單的異步操做,請求後直接響應,後臺去執行爬蟲。
executor = ThreadPoolExecutor(max_workers=2)
# 執行新聞爬蟲
def spider():
os.system("python PenpaiSpider.py")
os.system("python WeiboSpider.py")
print("爬蟲執行完畢...")
# 執行爬取新聞的爬蟲
@ns.route("/spider", methods=['GET'])
def run_spider():
os.system("python DBHelper.py")
executor.submit(spider)
return make_response({'code': '200', 'msg': '請求成功', 'data': []})
複製代碼
就是簡單的字符串拼接:
# 生成複製模板文本
@ns.route("/show_copy_model", methods=['GET'])
def show_copy_model():
req_args = request.args
if 'date' in req_args:
date = req_args['date']
text_model = "『摳腚早報速讀』| 第%s期\n\n要聞速讀\n\n" % date[2:]
news = MorningNews.query.filter_by(add_time=date).all()
for i in range(len(news)):
text_model += str(i + 1)
text_model += "、%s。\n\n" % news[i].title
return make_response({'code': '200', 'msg': '請求成功', 'data': text_model[:-1]})
else:
return make_response({'code': '200', 'msg': '缺乏date參數', 'data': []})
複製代碼
就是微信公號編寫文章時的內容,利用flask內置的jinja2模板來動態生成。這裏有一點要注意: render_template()函數雖然返回的是html,可是請求接口後瀏覽器顯示的是HTML代碼而非HTML 頁面,並且還有亂碼。這裏須要在響應頭中把「content-type」設置爲「text/html; charset=utf-8」。 可是問題來了,筆者對於前端一竅不通(從我寫的新聞列表頁就知道了...)一個最簡單的作法就是打開 瀏覽器的開發者工具,複製下網頁源碼,調整下代碼以及排版,找出天天新聞對應的代碼,利用 循環來構造,抽取後的部分html代碼以下:
{% for i in news_list %}
<p style="max-width: 100%; min-height: 1em;"><br></p>
<p style="max-width: 100%; min-height: 1em;">{{loop.index}}、{{i.title}}。</p>
{% endfor %}
複製代碼
接着在視圖函數中爲模板傳入新聞信息,動態生成頁面:
# 生成微信複製模板
@ns.route("/create_wc_model", methods=['GET'])
def show_wc_model():
req_args = request.args
if 'date' in req_args:
date = req_args['date']
news = MorningNews.query.filter_by(add_time=date).all()
resp = make_response(render_template('news.html', news_list=news))
resp.headers['content-type'] = 'text/html; charset=utf-8'
return resp
else:
return make_response({'code': '200', 'msg': '缺乏date參數', 'data': []})
複製代碼
和上面那個同樣玩法,定義模板news_list.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>『摳腚早報速讀』| 第{{news_list[0].add_time[2:]}}期</title>
</head>
<body>
{% for news in news_list %}
<div style="height: 48px"><a href="{{news.url}}" target="_blank" style="color:black;text-decoration:none;">{{loop.index}}、{{news.title}}</a></div>
{% endfor %}
</div>
</body>
</html>
複製代碼
一樣在乎圖中傳入新聞信息:
# 生成新聞列表頁
@ns.route("/create_news_list", methods=['GET'])
def show_news_list():
req_args = request.args
if 'date' in req_args:
date = req_args['date']
news = MorningNews.query.filter_by(add_time=date).all()
resp = make_response(render_template('news_list.html', news_list=news))
resp.headers['content-type'] = 'text/html; charset=utf-8'
return resp
else:
return make_response({'code': '200', 'msg': '缺乏date參數', 'data': []})
複製代碼
對應常見的404和500錯誤,直接返回很差,這裏簡單的處理下。
@app.errorhandler(404)
def error_404(e):
return make_response({'code': '404', 'msg': '404錯誤', 'data': []})
@app.errorhandler(500)
def error_404(e):
return make_response({'code': '500', 'msg': '500錯誤', 'data': []})
複製代碼
行吧,API接口編寫完畢,接着用PostMan模擬下請求:
增刪改查的結果就不演示了,只展現早報復制文本,公號編輯,以及新聞詳情列表頁接口的請求結果,依次以下:
行吧,接着把項目部署到服務器上,怎麼部署在上一節《偷個懶,公號摳腚早報80%自動化——3.Flask速成大法》 已經講解過了,把代碼傳服務器上,安裝配置nginx和uwsgi,配置完後,便可經過服務器公網ip進行訪問。 固然你能夠坐下域名解析,指向服務器,直接經過域名訪問。(貌似我的域名備案變把之前嚴格了,前不久在騰訊雲 備案一個域名,寫的CoderPig的編程技術小站,客服說不能出現編程字眼,還有什麼商業性的都不行~)
行吧,關於摳腚男孩的簡陋後臺,基本雛形就完成了,有些粗糙,又不是不能用。
( 順帶以此圖,緬懷沒有下個系統版本更新的堅果Pro 2S)後續根據需求,以及本身掌握更多 新的姿式後再來一點點優化把。
下一節就是本系列的最後一節的了,手撕一個APP來調這些接口,敬請期待~
參考文獻:
Tips:公號目前只是堅持發早報,在慢慢完善,有點心虛,只敢貼個小圖,想看早報的能夠關注下~