基於小程序·雲開發構建高考查分小程序丨實戰


2019高考報名人數達到了 1031 萬的新高,作為一名三年前參考高考的准程序猿,趕在高考前,加班加點從零開始做了一款高考查分小程序,算是一名老學長送給學弟學妹們的高考禮。上線僅 1 個月,用戶數就突破了 1k,關於小程序的介紹就不多說了,可以去搜【歷年高考分數線查詢】體驗,今天主要談談技術原理和實現細節。

數據來源

小程序后台共收錄近 30w 條數據,包含 2008-2017 年所有重點高校的各個批次的文理分科錄取分數線以及 2008-2018 所有采用新課標一卷、新課標二卷、新課標三卷以及部分自主命題省份的從提前批到高職專科批的錄取分數線,勉強稱得上內容翔實。

所有數據均采集自各大院校和各高考相關網站,由於數據量巨大,為提高速度,使用了 concurrent.futures (需要 Python3.5+) 模塊里的 ThreadPoolExecutor 來構建線程池來並發執行多任務。

數據庫采用的是 PgSQL,一款號稱世界上最強大的開源數據庫產品,所有數據均存在新建的 gaokao 數據庫中,其下有兩個表,university(院校的錄取分)和 province(省份的批次線)

university 表說明

字段 解釋
name 院校名稱
stu_loc 生源地
stu_wl 文理科
pc 錄取批次
year 年份
score 錄取平均分

province 表說明

字段 解釋
year 年份
stu_loc 考生所在地
stu_wl 文理科
pc 批次
control 本批次最低控制線

30w 的數據量,多個站點,並發爬取,數據沖突是不可避免地,在執行插入之前,首先過濾掉殘缺不全的數據,比如在插入 university 表時某條數據缺少 pc 字段,那么這條記錄就應該被舍棄,最嚴重的是數據重復,我采用的解決辦法是:先查詢待插入的數據是否已經存在, university 表的主碼是(name,stu,stu_wl,pc,year),因為現實約束一個院校只能在一個年份在一個類別一個批次只能有一個錄取平均分,如果不存在,才執行最后的插入,並 commit 提交事務。

后台搭建

在 30w 條數據拿到后,我打算后台采用 Flask+PgSQL 的模式實現,甚至在后台在阿里雲服務器部署好,小程序端在開發者工具聯調通過之后,小程序上線遇到到一個大麻煩,因為小程序要求線上運行不能通過 ip 地址訪問后台,必須通過備案的域名訪問,域名購買一個倒不麻煩,只是域名備案比較耗時間,需要一周多時間,而當時距離高考也就不到 5 天,在手足無措之時,無意間看到小程序雲開發,關於小程序雲開發,官網的介紹是:

開發者可以使用雲開發開發微信小程序、小游戲,無需搭建服務器,即可使用雲端能力。

雲開發為開發者提供完整的原生雲端支持和微信服務支持,弱化后端和運維概念,無需搭建服務器,使用平台提供的 API 進行核心業務開發,即可實現快速上線和迭代,同時這一能力,同開發者已經使用的雲服務相互兼容,並不互斥。

也就是說,只要把數據導入小程序自帶的后台,就能通過小程序平台的 API 訪問到這些數據,以前了解過第三方的 LeanCloud雲 和 Bomb 雲,沒想到小程序現在集成了這些功能,不得不佩服一下騰訊。

也就是,接下來的后台的工作是主要是導入數據,查詢小程序后台可知,后台支持導入 json 或者 csv 格式的數據。於是我就寫了個腳本,把數據從本地數據庫導出到 json 文件中:

import psycopg2
import json

# 連接 pgsql 數據庫,為保證隱私,密碼已隱藏
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 格式的縮進
# ensure_ascii 控制中文的正常顯示
with open("province.json", 'w', encoding="utf-8") as f:
	f.write(json.dumps(result, indent=2, ensure_ascii=False))

這里還有有個坑需要說明一下,小程序后台要求的 json 格式和我們平常意義上的 json 格式還有點區別,首先,json 的所有內容不能被 [ 和 ] 包括起來,而且每個被 {} 所包括得數據項之間不能有逗號。

選用 notepad++ 打開原來的 json 文件,使用替換功能就能解決,把 [ 和 ] 替換成空格,把 },替換成 } 即可。

修改之后,在小程序后台通過導入該 json 文件,后台搭建就基本完成了。

小程序端編寫

關於小程序端的編寫,我主要談談兩點經驗,第一是頁面的編寫,比如下面這個界面。

最開始想實現這樣的效果,完全沒有思路,最后在從自定義模態彈窗那得到了思路,一開始地區院校這個下拉框對應的布局是隱藏的,在 wxml 文件中通過 hidden=true 控制,一點擊 地區/院校 下拉框,就把 hidden 置為 false,如果開始有其他下拉框對應的布局的 hidden 屬性是 false 的話,同時要把這些布局的 hidden 屬性置為 true 來隱藏其他布局,當然,這里的 true or false 需要在 js 里通過 setData() 動態修改,把修改后的數據從數據層渲染到視圖層。

第二是關於小程序雲開發的原生 Bug,查詢后台時一次只能最多查詢到 20 條數據,要實現一次得到所有匹配的結果,需要解決兩個問題,第一個問題很自然而然就能想到,第一次查到 20 條數據后,第二次跳過前 20 條再取 20 條,第三次跳過前 40 條再取 20 條,以此類推;還有一個更為致命的問題,查詢后台的 API 獲取結果的回調函數的 異步 的,也就是說,為了保證獲得完整數據,第二次查詢需要寫在第一次查詢的回調里,第三次查詢需要寫在第二次查詢的回調里,而且你還不能顯式地知道要查詢多少次,需要寫多少層這樣的嵌套,以及煩人的同名變量覆蓋問題,這就是所謂的 異步地獄。為了解決這個問題,需要我們編寫代碼把這個異步方法轉成同步的,具體做法是:

先在所要添加功能的js頁面中導入 runtime.js 文件,同時把runtime.js文件放入相應文件夾

const regeneratorRuntime = require("../runtime");

runtime.js 下載地址:https://github.com/inspurer/CampusPunchcard/blob/master/runtime.js

同時模仿下例代碼完成業務邏輯:

// 查詢可能較慢,最好加入加載動畫​
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 的數組
		//初次循環獲取雲端數據庫的分次數的promise數組
		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()
		  //二次循環根據​獲取的promise數組的數據長度獲取全部數據push到newResult數組中
		  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()

以上就是我本次開發的一些心得體會,歡迎批評指正。

課程完整源碼

https://github.com/TencentCloudBase/Good-practice-tutorial-recommended

雲開發(CloudBase)是一款雲端一體化的產品方案 ,采用 serverless 架構,免環境搭建等運維事務 ,支持一雲多端,助力快速構建小程序、Web應用、移動應用。

技術文檔:https://www.cloudbase.net/

聯系我們

更多雲開發使用技巧及 Serverless 行業動態,掃碼關注我們~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM