最近在和幾個夥伴嘗試搭建一個新聞推薦系統, 算是一個推薦演演算法的實戰專案, 裡面涉及到了前後端互動, 該專案裡面,使用了Flask作為後臺框架, 為了理清楚整個系統的互動,所以就快速參考著資料學習了下flask, 主要還是參考夥伴們寫的flask簡介和基礎的內容, 加上了一些其他理解和實驗輔助, 整理一篇心得文章, 算是flask初步入門。
對於一個演演算法工程師來講,後端這塊雖然不必詳細弄清楚原理,但學習一些開發相關知識還是有好處的,因為在實際工作中經常會偵錯線上的程式碼呼叫策略或者模型,我們至少得弄明白,我們的資料流, 模型流到底是怎麼走的, 整個系統從輸入到最終輸出是怎麼執行的,這樣才能運籌帷幄,從一個更高的角度去看待問題。
好吧,有點扯遠了, 本篇文章主要介紹flask,這裡依然是從使用的角度整理(因為我對原理也不清楚哈哈), 先不管那麼多,會用就行, flask簡單的來講, 就是一個後端框架,基於python語言編寫,比較容易上手,或者說不用懂太多細節就能寫程式碼, 把前端傳過來的請求,通過編寫一些函數進行處理,然後返回給前端。 這裡為了更好的理解,我會用一個非常簡單的例子貫穿整個流程。 當然,如果想看稍微高大上點的程式碼,也可以去我們寫的fun-rec專案,看新聞推薦系統的程式碼, 那個是vue-flask互動配合的,更加高階些。
所以,最簡單的整個流程就是, 我們在前端頁面上輸入資訊,傳送請求給後端(flask), flask根據我們傳過來的請求,去找到相應的函數去處理我們的請求(路由), 然後函數處理的結果封裝起來返回給前端展示。 這次,主要是看看請求傳過來之後,後端這個怎麼找函數處理以及返回回去。
主要內容:
Ok, let’s go!
這個不用多整理, flask在python裡面也是一個包的形式存在,所以我們如果事先安裝好了anaconda, 建立了虛擬環境,那麼就直接可以
pip install flask
然後輸入下面程式碼測試下:
from flask import Flask
app = Flask(__name__)
@app.route('/') # 這個根目錄,就是127.0.0.1:5000進入的位置
def hello_world():
return "hello world"
if __name__ = '__main__':
app.run()
如果正常的話,介面會顯示hello world。其實,這就簡單的走了一遍小流程(輸入網址,根據路由到了hello_word函數,返回結果給前端)。
Flask將(name)作為引數,即Flask在當前模組執行,route()函數是一個裝飾器,將請求的url對映到對應的函數上。上述程式碼將’/'與hello_world()函數進行繫結,因此在請求localhost:5000時,網頁顯示 Hello World 結果。
這裡有幾個關鍵點: 導包, 建立app(Flask(__name__)
),路由匹配(@app.route()
)以及啟動(app.run()
)。 幾乎在寫每個後端處理之前,這幾個先寫上再說。
程式的啟動是用過Flask類的run()
方法在本地啟動伺服器應用程式
app.run(host, port, debug, options)
# 允許伺服器被公開存取
app.run(debug=True, host='0.0.0.0', port=3000, threaded=True)
# 只能被自己的機子存取
# app.run(debug=True, host='127.0.0.1', port=10086, threaded=True)
這幾個引數的描述如下:
安裝這塊比較簡單,先這樣。
web介面輸入一個網址,點選回車, 其實是存取的web伺服器,然後伺服器把結果返回到前端。 這個過程中有個匹配url的過程, 就是flask路由。
Flask中,路由是指使用者請求的URL與檢視函數之間的對映。Flask通過利用路由表將URL對映到對應的檢視函數,根據檢視函數的執行結果返回給WSGI伺服器。
路由表的內容是由開發者進行填充, 主要有以下兩個方式:
route裝飾器: 使用Flask應用範例的route裝飾器,將一個URL規則繫結到一個檢視函數上
# 通過裝飾器的方式, Flask框架會將URL規則繫結到test()函數上, 這個很好用
@app.route('/test') # 瀏覽器存取的時候,輸入的url是127.0.0.1/test
def test():
return 'this is response of test function'
add_url_rule()
:該方法直接會在路由表中註冊對映關係。其實route裝飾器內部也是通過呼叫add_url_rule()
方法實現的路由註冊
def test():
return 'this is response of test function.'
app.add_url_rule('/test',view_func=test)
我習慣看第一種方式, 感覺比較帥。
預設情況下, Flask的路由支援HTTP的GET請求, 如果需要試圖函數支援HTTP的其他方法, 可以通過methods關鍵字引數進行設定。 關鍵字引數methods的型別為list, 可以同時指定多種HTTP方法。
# 接收post和get請求, 如果不指定的話,就是get請求, 此時如果提交post請求是捕捉不到的
@app.route('/user', methods = ['POST', 'GET'])
def get_users():
if request.method == 'GET':
return ... # 返回使用者列表
else:
return ... # 建立新使用者
這裡先說下這兩種請求的區別:
這樣就完成了最基本的功能, 當然,你說, 這個URL(/user)是寫死的目前,如果我不確定怎麼辦呢? 比如, 我可能這個使用者是某某, 而不同的某某,可能有不同的執行操作,這時候,就可以使用動態URL。
動態URL用於當需要將同一類URL對映到同一個檢視函數處理,比如,使用同一個檢視函數 來顯示不同使用者的個人資訊。那麼可以將URL中的可變部分使用一對小括號<>
宣告為變數, 併為檢視函數宣告同名的引數:
@app.route('/user/<uname>') # <>提取引數用的
def get_userInfo(uname):
return '%s\'s Informations' % uname
# 輸入網址127.0.0.1/user/wuzhongqiang wuzhongqiang's Informations
# 輸入網址127.0.0.1/user/zhangsan zhangsan's Informations
除了上述方式來設定引數,還可以在URL引數前新增轉換器來轉換引數型別: 前端本身預設是傳入過來的是字串格式,如果感覺本身傳入的引數不應該是字串格式的,那就可以在URL引數前新增轉換器轉換引數型別
@app.route('/user/<int:uname>') # <int: name> int 是一個轉換器
def get_userInfo(uname):
return '%s\'s Informations' % uname
使用該方法時,請求的引數必須是屬於int型別,否則將會出現404錯誤。目前支援的引數型別轉換器有int, float, string, path。後兩者的區別是path裡面可以有\
。
為了滿足一個檢視函數可以解決多個問題,因此每個檢視函數可以設定多個路由規則
@app.route('/user')
@app.route('/user/<uname>')
@app.route('/user/<int:uname>')
def get_userInfo(uname=None):
if uname:
return '%s\'s Informations' % uname
else:
return 'this is all informations of users'
當然,這裡也可以自定義一些規則,去進行輸入方面的一些限制, 這時候,就需要繼承BaseConverter類,然後寫自己的規則了。 這裡只是舉個簡單的例子:
from flask import Flask
from werkzeug.routing import BaseConverter
app = Flask(__name__)
class RegexConverter(BaseConverter):
"""自定義轉化器類"""
def __init__(self, url_map, regex):
super(RegexConverter, self).__init__(url_map)
self.regex = regex
def to_python(self, value: str):
return value
# 將自定義的轉換器類新增到flask應用中
app.url_map.converters['re'] = RegexConverter
@app.route('/index/<re("1\d{5}"):value>')
def index(value):
return "hello, world"
app.run()
# 只有如輸入 127.0.0.1/index/123456 1開頭,後面5位整數的才能匹配到
這個裡面的匹配URL就可以使用正則的形式,匹配非常特殊的那種了。不過,一般用不到這麼複雜的。
在很多時候,在一個實用的檢視中需要指向其他檢視的連線,為了防止路徑出現問題,我們可以讓Flask框架幫我們計算連結URL。簡單地給url_for()
函數傳入一個存取點,它返回將是一個可靠的URL地址
@app.route('/')
def hello():
return 'Hello world!'
@app.route('/test')
def test_url_for():
print(url_for('hello')) # 跳轉到了hello函數下面執行
對於一個完整的HTTP請求,包括了來自使用者端的請求物件(Request), 伺服器端的響應物件(Respose)和對談物件(Session)等。 在Flask框架中,當然也具有這些物件, 這些物件不僅可以在請求函數中使用, 同時也可以在模板中使用。
Flask包中, 可以直接引入request物件, 其中包含Form,args,Cookies,files等屬性。
以一個登陸的例子看看如何搭配屬性
from flask import request, session, make_response
@app.route('/login', methods=['POST', 'GET'])
def logion():
if request.method == 'POST':
if request.form['username'] == 'admin':
session['username'] = request.form['username']
response = make_response('Admin login successfully!')
response.set_cookie('login_time', time.strftime('%Y-%m-%d' %H:%M:%S'))
return 'Admin login successfully!'
elif request.method == 'GET':
if request.args.get("username") == 'admin':
session['username'] = request.form['username']
return 'Admin login successfully!'
else:
return 'No such user!'
app.secret_key = '123456'
可以根據method屬性判斷當前請求的型別,通過form屬性可以獲取表單資訊,並通過session來儲存使用者登陸資訊。當然這裡的session,可以換成字典,然後把資訊儲存到資料庫裡面去。
由於現在前後端互動會採用json的資料格式進行傳輸, 因此當前端請求的資料是json型別的時候, 可以使用get_data()
方法來獲取。
from flask import Flask, jsonify, request
@app.route('/login', methods=["POST"])
def login():
request_str = request.get_data()
request_dict = json.loads(request_str)
# 然後,就可以對request_dict進行處理了,相當於從後端拿到了前端的資料
如果函數試圖想向前端返回資料, 必須是Response物件,主要有下面幾種返回資料的格式:
試圖函數return多個值
@app.route("/user_one")
def user_one():
return "userInfo.html", "200 Ok", {"name": "zhangsan"; "age":"20"}
當return多個值的時候,第一個是字串,也是網頁的內容;"200 Ok"
表示狀態碼及解析;{「name」: 「zhangsan」; 「age」:「20」} 表示請求頭。其中前面兩個值是必須要的並且順序不能改變,請求頭不是必須要的,這樣Flask會自動將返回的值轉換成一個相應物件。如果返回一個字串,則Response將該字串作為主體,狀態碼為200,然後返回該Response物件。
使用Response建立
可以通過直接建立Response物件,設定其引數。
from flask import Response
@app.route("/user_one")
def user_one():
response = Response("user_one")
response.status_code = 200
response.status = "200 ok"
response.data = {"name": "zhangsan"; "age":"20"}
return response
由於現在前後端互動往往採用的是json的資料格式,因此可以將資料通過 jsonify 函數將其轉化成json格式,再通過response物件傳送給前端。
# 這個jsonify可以直接向前端返回json資料
from flask import Flask, make_response, jsonify
@app.route('/hot_list', methods=["GET"])
def hot_list():
if request.method == "GET":
user_id = request.args.get('user_id')
page_id = request.args.get('page_id')
if user_id is None or page_id is None:
return make_response(jsonify({"code": 2000, "msg": "user_id or page_id is none!"}), 200)
try:
rec_news_list = recsys_server.get_rec_list(user_id, page_id)
if len(rec_news_list) == 0:
return jsonify({"code": 500, "msg": "rec_list data is empty."})
return jsonify({"code": 200, "msg": "request rec_list success.", "data": rec_news_list, "user_id": user_id}) # 後面的資料就能返回到前端去
except Exception as e:
print(str(e))
return jsonify({"code": 500, "msg": "redis fail."})
前端拿到這個data,和user_id,就可以通過變數的方式進行使用了。這種方式用的多一些。
當然,這些東西直接這麼寫,可能會很抽象,後面一個小例子一串就瞭然, 這裡可以先有個印象。
當一個請求過來後可能還需要請求另一個檢視函數才能達到目的, 就可以呼叫redirect(location, code=302, Response=None)
函數指定重定向頁面。
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route("/demo")
def demo():
url = url_for("demo2") # 路由反轉,根據檢視函數名獲取路由地址
return redirect(url) # 相當於到了/demo2這個頁面,顯式this is demo2 page
@app.route("/demo2")
def demo2():
return "this is demo2 page"
@app.route("/")
def index():
# 使用方法:redirect(location, code=302, Response=None)
return redirect("/demo", 301)
url_for函數我理解是能根據給定的url對映到對應的函數,比如給定demo2, 就對映到了demo2()
, 但具體執行, 應該是redirect()
函數起作用。
當請求或伺服器出現錯誤的時候, 我們希望遇到特定錯誤程式碼走不通的處理錯誤邏輯, 可以使用errorhandler()
裝飾器
from flask import render_template # 渲染頁面
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
當遇到404錯誤時,會呼叫page_not_found()
函數,返回元組資料,第一個元素是」page_not_found.html」的模板頁,第二個元素代表錯誤程式碼,返回值會自動轉成 response 物件。 如果這個地方想在網頁裡面放張圖片的話,一定要放到static目錄裡面才行, 存取的是靜態檔案目錄, 這個static名字不能改。
這個東西主要是為了後面處理異常,如果滿足什麼條件,就進行什麼樣的處理:
from flask import abort
@app.route('/index', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html')
if request.method == 'POST':
name = request.form.get('name')
password = request.form.get('password')
if name == 'zhangsan' and password == '123':
return 'login success'
else:
abort(404)
return None
上面整理了那麼一大推, 這裡想通過一個例子串一下, 否則總會有一股朦朧之感, 由於我不是很懂前端, 這裡就簡單參考程式碼寫一個前端頁面, 不用很複雜,就構建一個輸入使用者名稱和密碼的對話方塊,然後點選提交,看看與後端的互動效果。
前端頁面的程式碼如下:
這個佈局方式也是蠻重要的, 就是先建立一個templates目錄,這個目錄可以認為是有一個模板目錄,預設定義了一些前後端互動的程式碼格式。這個模板是jinjia2(右擊目錄,mark directory as設定), 然後在該目錄下建立一個HTML介面。
然後在上一級目錄,建立一個form表單檔案,把這個HTML渲染出來:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/index')
def index():
return render_template('index.html') # 渲染當前的html頁面
if __name__ == '__main__':
app.run()
# 輸入127.0.0.1:5000/index 就會出來寫的那個html頁面了,然後輸入密碼,提交,就會得到一個get請求
此時,就能把前端的html頁面顯示出來。
當然,比較陋, 但演示足夠。下面看看如何互動。
這裡前端,從上面的兩個框裡輸入使用者名稱和密碼,然後點選提交給後端。 後端接收過來, 把使用者和密碼封裝起來, 給到另一個前端頁面, 然後另一個前端頁面就能用這個資料了。
首先, 需要先修改上面前端頁面資料, 把提交請求的方式改為POST,非常簡單, 只需要修改這裡。
然後在總目錄下建立了request物件.py檔案,在這裡面寫接收資料的邏輯
from flask import Flask, render_template
from flask import request
app = Flask(__name__)
@app.route('/index', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
# 渲染index頁面
return render_template('index.html')
elif request.method == 'POST':
# 獲取資料
data = {}
data['name'] = request.args.get('name') # 後面這個name和前端的name保持一致
data['passwd'] = request.args.get('password')
# 返回到前端去
return render_template('index2.html', data=data)
return render_template('index.html')
app.run()
其實也非常簡單,輸入網址的時候,顯式的就是index.html頁面,這個頁面就是讓使用者輸入使用者名稱密碼,然後提交即可,此時由於修改了index的提交方式是post請求,所以後端這塊捕捉到,拿到傳過來的資料, 給到index2.html, 此時index2.html就可以直接拿到data使用或者用來展示。
index2.html頁面此時就能使用data資料了。
框裡的這兩個,就是index.html傳給後端,然後後端傳過來的資料, 可以直接在index2.html中顯示。 當然,這裡的{{變數名}}
的這種定義格式,就是模板事先定義好的,如果不是jinjia2模板,可能不能使用。所謂模板,就是事先定義了一些前後端互動的規則。
上面就是一個前後端互動的小例子啦, 其實flask框架用起來還是比較容易上手的。
這裡主要是介紹這兩天用到的兩個工具,SQLAlchemy和Postman。
這是一個功能強大的python ORM工具包, 也就是提供了API去運算元據庫裡面的表的相關操作,而不是編寫原始的SQL語句,非常方便。安裝
# 安裝
pip install SQLalchemy
下面建立連線,也就是連線到我們的mysql資料庫:
from sqlalchemy import create_engine
def mysql_db(host='127.0.0.1', dbname='3306'):
engine = create_engine("mysql+pymysql://root:123456@{}:49168/{}?charset=utf8".formate(host, dbname))
print(engine) # Engine(mysql+pymysql://root:***@127.0.0.1:49168/3306?charset=utf8)
通過create_engine函數已經建立了Engine,在Engine內部實際上會建立一個Pool(連線池)和Dialect(方言),並且可以發現此時Engine並不會建立連線,只會等到執行到具體的語句時才會連線到資料庫。上述程式碼預設本地已經存在並開啟mysql服務。
create_engine("mysql://user:password@hostname/dbname?charset=utf8",
echo=True,
pool_size=8,
pool_recycle=60*30)
第一個引數是和框架表明連線資料庫所需的資訊,「資料庫+資料庫連線框架://使用者名稱:密碼@IP地址:埠號/資料庫名稱?連線引數」;echo是設定當前ORM語句是否轉化為SQL列印;pool_size是用來設定連線池大小,預設值為5;pool_recycle設定連線失效的時間,超過時間連線池會自動斷開。
用於SQLAlchemy是物件關係對映,在運算元據庫表時是通過操作物件實現的, 每一條記錄其實是一個物件,所以需要先建立一個資料庫表類說明欄位資訊。
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'UserInfo' # 表名
# 資料庫欄位的型別
index = Column(Integer(), primary_key=True)
user_id = Column(Integer(), unique=True)
username = Column(String(30))
passwd = Column(String(500))
def __init__(self, index, user_id, username, passwd):
self.index = index
self.user_id = user_id
self.username = username
self.paswd = passwd
這個可以當做是固定套路格式, 通過declarative_base()
函數,可以將python類和資料庫表進行關聯對映,並通過 _tablename_
屬性將資料庫模型類和表進行管理。其中Column()
表示資料表中的列,Integer()
和String()
表示資料庫的資料型別。
建立完連線, 需要藉助sqlarchemy中的session來建立程式與資料庫之間的對談,此時通過sessionmarker()
函數建立。
def mysql_db(host='127.0.0.1', dbname='test'):
engine = create("mysql+pymysql://root:123456@{}:49168/{}?charset=utf8mb4".format(host,dbname))
session = sessionmaker(bind=engine)
Base.metadata.create_all(engine)
return engine, session()
這樣,相當於正式與mysql建立了連線時候的對談。 session常用的方法:
下面演示下資料庫裡面的增刪改查。
增加資料
# 增加一個使用者
engine, session = mysql_db()
user = User("100", "zhangsan", "11111")
session.add(user)
session.commit()
# 也可以通過addall批次提交
engine, session = mysql_db()
user1 = User("101","lisi","11111")
user2 = User("102","wangwu","22222")
session.add_all([user1,user2])
session.commit()
不用我們事先建立資料庫和表, 呼叫程式的時候,會自動建立好。 把使用者的資訊封裝成一個物件,然後採用add的方式就可以新增進去了。 當然session.add()
不會直接提交到資料庫,當執行了commit()
之後才會提交。 這時候,就不用什麼insert table_name values
…。
注意,如果是python的datetime格式資料,是無法直接存到mysql時間格式裡面的, 必須強轉成字串才能存進去, 這也是實踐中遇到的一個坑。
查詢資料
engine, session = mysql_db()
users = session.query(User).filter_by(passwd='11111').all()
for item in users:
print(item.username,item.passwd)
通過上面程式碼就可以存取資料庫, 類似於select操作了, 其中query()返回一個query物件,在這裡指明查哪個大類(這裡面對映一個資料表),告訴去哪個表裡查。 當然此時並沒有真正去查詢, 只有等到具體執行函數count(), first(), all()
等採取資料庫查詢。 當然,還可以使用filter()
方法指定查詢條件,類似where。 這裡其實有兩種寫法:
filter_by(passwd=''11111")
過濾filter(User.passwd="11111")
過濾 , 這一種需要帶上類名修改資料
session.query(User).filter_by(username="zhangsan").update({'passwd': "123456"})
update()
函數進行修改。
刪除資料
session.query(User).filter(User.username == "zhangsan-test").delete()
session.commit()
當然這個東西由於也是剛接觸,並不是太會用。 這個東西是有個介面測試工具, 是為了驗證後端開發的介面是否可用。
因為真正開發大專案,前後端是分離開發的, 並且此時前端可能沒有完全搭建好,所以介面測試的時候,postman,就相當於一個使用者端, 可以模擬使用者發起各類的HTTP請求, 將請求資料傳送給伺服器端, 來獲取對應的響應結果。 這樣就能測試出後端的函數邏輯是否正確。
我這裡是偶然接觸到,因為學習上面新聞推薦系統的時候,我這邊後端的每個py檔案都執行通過了,此時想基於介面傳資料看看效果,結果就是和前端的vue框架連不起來。 上面我自己寫HTML檔案好好的, 一旦用上vue框架,再去存取網址總是報錯或者被拒絕啥的。
所以,這裡就想看看到底是後端給的網址和介面不對,還是前端vue的問題,那麼怎麼測試呢? 意哥就告訴了我這個工具,用他來模擬前端,給後端發請求,看看後端能返回結果不。
當然具體的下載和使用, 我給出兩篇參考檔案postman教學, postman教學大全, 這玩意也是個軟體,所以直接Windows下載安裝即可。
開啟之後, 我們新建一個collections,其實就是目錄,然後在這裡面新建一個request請求,就可以測試了。
我這裡給出我這邊的測試例子, 我當時想通過postman測試下,能不能存取到後端。測試的後端函數是這個:
@app.route('/recsys/register', methods=["POST"])
def register():
"""使用者註冊
"""
request_str = request.get_data()
request_dict = json.loads(request_str)
print(request_dict)
user = RegisterUser()
user.username = request_dict["username"]
user.passwd = request_dict["passwd"]
# 查詢當前使用者名稱是否已經被用過了
result = UserAction().user_is_exist(user, "register")
if result != 0:
return jsonify({"code": 500, "mgs": "this username is exists"})
#user.userid = snowflake.client.get_guid() # 雪花演演算法
user.userid = 20211971672
user.age = request_dict["age"]
user.gender = request_dict["gender"]
user.city = request_dict["city"]
print("hello world")
# 新增註冊使用者
save_res = UserAction().save_user(user)
if not save_res:
return jsonify({"code": 500, "mgs": "register fail."})
return jsonify({"code": 200, "msg": "register success."})
使用者註冊函數, 這是一個post請求格式的,然後需要傳入使用者的相關引數,給到後端,後端把這個存到使用者登入檔裡面去。然後返回成功資訊。
其實邏輯很簡單,首先, 建立post請求格式在postman的操作, 首先請求格式改成POST,然後headers這裡需要設定json格式。
然後, 在body裡面傳入請求引數,也就是使用者的註冊資訊, 這裡是一個字典的形式
這樣,點選右上角send即可傳送了。根據下面後端返回的資訊,說明後端這塊是可以被存取的,沒有什麼問題。如果想傳送get請求,以及傳引數,還可以這樣:
那,這就確定了, vue框架的設定有問題。
這裡主要是記錄下解決上面這個問題的方法, 因為我這邊遇到了vue服務開啟完了之後, 輸入網址並沒有到相應的介面中去,而是報錯。 這個問題還是困擾我一段時間的,一開始以為是後端那邊的網址不能存取, 但用了postman之後,發現後端這邊沒問題。
於是我覺得是我vue那邊設定有問題,因為我對vue內部一竅不通, 並且我夥伴們之前都測試好了,不可能是程式碼方面的問題。
於是乎,開始排查路徑問題: 這篇文章啟發
這個沒有問題。下面這裡也需要改:
這樣操作完了,然後在瀏覽器輸入
這樣一頓操作之後,就搞定了上面的問題。202.199.6.190
是我實驗室伺服器的地址。
當然我後端是這樣:
總結起來, 就是需要修改前端的main.js裡面的網址,然後修改package.json裡面的主機地址。 然後存取的時候是從前端running的地址進行存取。
當然,開啟前端的過程中還遇到一個奇葩的報錯問題:
這個問題我也不知道是啥原因, vue涉及到盲區, 但下面這行程式碼卻能無腦搞定,挺神奇:
# 命令列輸入
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
到這裡就差不多了,花了兩三天的探索,終於把整個系統換了我實驗室伺服器跑出來,然後整理這篇文章記錄下, 實測能執行, 感興趣的夥伴可以玩玩啦 😉
專案地址: https://github.com/datawhalechina/fun-rec/tree/master/codes/news_recsys