?
?
?
明天就是一年一度的高考了,今年的高考報名人數(shù)達(dá)到了 1031w?的新高,作為一名三年前參考高考的準(zhǔn)程序猿,趕在高考前,加班加點從零開始做了一款高考查分小程序,算是一名老學(xué)長送給學(xué)弟學(xué)妹們的高考禮。關(guān)于小程序的介紹,可以參考我上一篇文章,今天主要談?wù)劶夹g(shù)原理和實現(xiàn)細(xì)節(jié)。
?
數(shù)據(jù)來源
?
小程序后臺共收錄近 30w 條數(shù)據(jù),包含 2008-2017 年所有重點高校的各個批次的文理分科錄取分?jǐn)?shù)線以及 2008-2018 所有采用新課標(biāo)一卷、新課標(biāo)二卷、新課標(biāo)三卷以及部分自主命題省份的從提前批到高職??婆匿浫》?jǐn)?shù)線,勉強(qiáng)稱得上內(nèi)容翔實。
所有數(shù)據(jù)均采集自各大院校和各高考相關(guān)網(wǎng)站,由于數(shù)據(jù)量巨大,為提高速度,使用了 concurrent.futures (需要 Python3.5+) 模塊里的 ThreadPoolExecutor 來構(gòu)建線程池來并發(fā)執(zhí)行多任務(wù)。
數(shù)據(jù)庫采用的是 PgSQL,一款號稱世界上最強(qiáng)大的開源數(shù)據(jù)庫產(chǎn)品,所有數(shù)據(jù)均存在新建的 gaokao 數(shù)據(jù)庫中,其下有兩個表,university (院校的錄取分)和 province (省份的批次線)
?
university 表說明
?
字段 | 解釋 |
---|---|
name | 院校名稱 |
stu_loc | 生源地 |
stu_wl | 文理科 |
pc | 錄取批次 |
year | 年份 |
score | 錄取平均分 |
?
province 表說明
?
字段 | 解釋 |
---|---|
year | 年份 |
stu_loc | 考生所在地 |
stu_wl | 文理科 |
pc | 批次 |
control | 本批次最低控制線 |
?
30w 的數(shù)據(jù)量,多個站點,并發(fā)爬取,數(shù)據(jù)沖突是不可避免地,在執(zhí)行插入之前,首先過濾掉殘缺不全的數(shù)據(jù),比如在插入 university 表時某條數(shù)據(jù)缺少 pc 字段,那么這條記錄就應(yīng)該被舍棄,最嚴(yán)重的是數(shù)據(jù)重復(fù),我采用的解決辦法是:先查詢待插入的數(shù)據(jù)是否已經(jīng)存在, university 表的主碼是(name,stu,stu_wl,pc,year),因為現(xiàn)實約束一個院校只能在一個年份在一個類別一個批次只能有一個錄取平均分,如果不存在,才執(zhí)行最后的插入,并 commit 提交事務(wù)。
?
后臺搭建
?
在 30w ?條數(shù)據(jù)拿到后,我打算后臺采用 Flask+PgSQL 的模式實現(xiàn),甚至在后臺在阿里云服務(wù)器部署好,小程序端在開發(fā)者工具聯(lián)調(diào)通過之后,小程序上線遇到到一個大麻煩,因為小程序要求線上運行不能通過 ip 地址訪問后臺,必須通過備案的域名訪問,域名購買一個倒不麻煩,只是域名備案比較耗時間,需要一周多時間,而當(dāng)時距離高考也就不到 5 天,在手足無措之時,無意間看到小程序云開發(fā),關(guān)于小程序云開發(fā),官網(wǎng)的介紹是:
?
開發(fā)者可以使用云開發(fā)開發(fā)微信小程序、小游戲,無需搭建服務(wù)器,即可使用云端能力。
云開發(fā)為開發(fā)者提供完整的原生云端支持和微信服務(wù)支持,弱化后端和運維概念,無需搭建服務(wù)器,使用平臺提供的 API 進(jìn)行核心業(yè)務(wù)開發(fā),即可實現(xiàn)快速上線和迭代,同時這一能力,同開發(fā)者已經(jīng)使用的云服務(wù)相互兼容,并不互斥。
?
也就是說,只要把數(shù)據(jù)導(dǎo)入小程序自帶的后臺,就能通過小程序平臺的 API 訪問到這些數(shù)據(jù),以前了解過第三方的 LeanCloud 云 和 Bomb 云,沒想到小程序現(xiàn)在集成了這些功能,不得不佩服一下騰訊。
也就是,接下來的后臺的工作是主要是導(dǎo)入數(shù)據(jù),查詢小程序后臺可知,后臺支持導(dǎo)入 json 或者 csv 格式的數(shù)據(jù)。于是我就寫了個腳本,把數(shù)據(jù)從本地數(shù)據(jù)庫導(dǎo)出到 json 文件中:
?
import?psycopg2
import?json
#?連接?pgsql?數(shù)據(jù)庫,為保證隱私,密碼已隱藏
conn?=?psycopg2.connect(database="gaokao",?user="postgres",?password="*******",?host="127.0.0.1",?port="5432")
cur?=?conn.cursor()
cur.execute('select?stu_loc,year,stu_wl,pc,control?from?province')
result?=?[]
query_res?=?cur.fetchall()
for?i?in?query_res:
????item?=?{}
????item['stu_loc']?=?i[0]
????item['year']?=?i[1]
????item['wl']?=?i[2]
????item['pc']?=?i[3]
????item['score']?=?i[4]
????result.append(item)
#?indent=2?控制?json?格式的縮進(jìn)
#?ensure_ascii?控制中文的正常顯示
with?open("province.json",?'w',?encoding="utf-8")?as?f:
????f.write(json.dumps(result,?indent=2,?ensure_ascii=False))
?
這里還有個坑需要說明一下,小程序后臺要求的 json 格式和我們平常意義上的 json 格式還有點區(qū)別,首先,json 的所有內(nèi)容不能被 [ 和 ] 包括起來,而且每個被 {} 所包括得數(shù)據(jù)項之間不能有逗號。
?
?
選用 notepad++ 打開原來的 json 文件,使用替換功能就能解決,把 [ 和 ] 替換成空格,把 },替換成 } 即可。
修改之后,在小程序后臺通過導(dǎo)入該 json 文件,后臺搭建就基本完成了。
?
小程序端編寫
?
關(guān)于小程序端的編寫,我主要談?wù)剝牲c經(jīng)驗,第一是頁面的編寫,比如下面這個界面。
?
?
3最開始想實現(xiàn)這樣的效果,完全沒有思路,最后在從自定義模態(tài)彈窗那得到了思路,一開始地區(qū)院校這個下拉框?qū)?yīng)的布局是隱藏的,在 wxml 文件中通過 hidden=true 控制,一點擊 地區(qū)/院校 下拉框,就把 hidden 置為 false,如果開始有其他下拉框?qū)?yīng)的布局的 hidden 屬性是 false 的話,同時要把這些布局的 hidden 屬性置為 true 來隱藏其他布局,當(dāng)然,這里的 true or false 需要在 js 文件里通過 setData() 動態(tài)修改,把修改后的數(shù)據(jù)從數(shù)據(jù)層渲染到視圖層。
?
第二是關(guān)于小程序云開發(fā)的原生 Bug,查詢后臺時一次只能最多只能查詢到 20 條數(shù)據(jù),要實現(xiàn)一次得到所有匹配的結(jié)果,需要解決兩個問題,第一個問題很自然而然就能想到,第一次查到 20 條數(shù)據(jù)后,第二次跳過前 20 條再取 20 條,第三次跳過前 40 條再取 20 條,以此類推;還有一個更為致命的問題,查詢后臺的 API 獲取結(jié)果的回調(diào)函數(shù)的 異步 的,也就是說,為了保證獲得完整數(shù)據(jù),第二次查詢需要寫在第一次查詢的回調(diào)里,第三次查詢需要寫在第二次查詢的回調(diào)里,而且你還不能顯式地知道要查詢多少次,需要寫多少層這樣的嵌套,以及煩人的同名變量覆蓋問題,這就是所謂的 異步地獄。為了解決這個問題,需要我們編寫代碼把這個異步方法轉(zhuǎn)成同步的,具體做法是:
?
先在所要添加功能的js頁面中導(dǎo)入 runtime.js 文件,同時把runtime.js文件放入相應(yīng)文件夾
?
const?regeneratorRuntime?=?require("../runtime");
?
runtime.js 下載地址:https://github.com/inspurer/CampusPunchcard/blob/master/runtime.js
?
同時模仿下例代碼完成業(yè)務(wù)邏輯:
?
//?查詢可能較慢,最好加入加載動畫
wx.showLoading({
??????????title:?'加載中',
????????})
????????const?countResult?=?await?db.collection('province').where({
??????????stu_loc:?name,
??????????pc:?pici,
????????}).count()
????????const?total?=?countResult.total
????????//計算需分幾次取
????????const?batchTimes?=?Math.ceil(total?/?MAX_LIMIT)
????????//?承載所有讀操作的?promise?的數(shù)組
????????//初次循環(huán)獲取云端數(shù)據(jù)庫的分次數(shù)的promise數(shù)組
????????for?(let?i?=?0;?i?<?batchTimes;?i++)?{
??????????const?promise?=?await?db.collection('province').where({
????????????stu_loc:?name,
????????????pc:?pici,
??????????}).skip(i?*?MAX_LIMIT).limit(MAX_LIMIT).get()
??????????//二次循環(huán)根據(jù)獲取的promise數(shù)組的數(shù)據(jù)長度獲取全部數(shù)據(jù)push到newResult數(shù)組中
??????????for?(let?j?=?0;?j?<?promise.data.length;?j++)?{
????????????var?item?=?{};
????????????item.code?=?i?*?MAX_LIMIT?+?j;
????????????item.name?=?promise.data[j].stu_loc;
????????????item.year?=?promise.data[j].year;
????????????item.wl?=?promise.data[j].wl;
????????????item.pc?=?promise.data[j].pc;
????????????item.score?=?promise.data[j].score;
????????????console.table(promise.data)
????????????newResult.push(item)
??????????}
????????}
????????if?(newResult.length?!=?0)?{
??????????that.setData({
????????????hasdataFlag:?true,
????????????resultData:?newResult
??????????})
????????}?else?{
??????????that.setData({
????????????hasdataFlag:?false,
????????????resultData:?newResult
??????????})
????????}
????????//?隱藏加載動畫
????????wx.hideLoading()
?
以上就是我本次開發(fā)的一些心得體會,歡迎批評指正。
?
點擊可快速查詢往年高考分?jǐn)?shù)線
?
?
?
本文摘自 :https://blog.51cto.com/u