JWT##
一種可以把token保存在cookie中的前端會話解決方案。相對于傳統(tǒng)的session,它有效的減輕了后端的存儲和隨之而來的增刪改查的壓力。但相應(yīng)的,由于是保存在前端cookie中,處于對其安全性的考量,加密/解密的過程會帶來一定的服務(wù)器壓力。
其構(gòu)成分為三部分:
header
{ typ:"jwt", alg:"HS256" }
可以看出,頭部用來保存兩個(gè)信息:token的類型和其加密的算法。
claims(payload)
{
iss:"temp.com",
sub:"uid123",
exp:"123456789",
iat:"123456789",
jtd:"hiy5td656fifs85yd"
}
jwt的實(shí)體,保存了這個(gè)jwt的發(fā)布者(iss),其實(shí)就是這個(gè)站點(diǎn)標(biāo)識;操作的對象,也稱之為這個(gè)jwt的主題(sub),我在操作的時(shí)候保存的是用戶id,不過鑒于其json的數(shù)據(jù)存儲形式,我覺得直接保存用戶對象也何嘗不可(以后可以嘗試一下)。既然描述的是主題,那么意味著這其中也應(yīng)當(dāng)填寫操作的動詞(究竟干了什么);這個(gè)jwt的創(chuàng)建時(shí)間(iat)和過期時(shí)間(exp),這個(gè)沒什么好說的,但是后端的過期邏輯得注意一點(diǎn),不然時(shí)間間隔太久,這一塊的測試工作可能會遺漏(我就是,寫了兩塊登陸狀態(tài)的判斷邏輯,結(jié)果其中一塊有bug,直接導(dǎo)致流程來回跳轉(zhuǎn)不停,一邊提示未登錄,一邊提示已登陸);由于我是使用的jwt模塊,所以未給出jtd(jwt的id)這個(gè)參數(shù),可能模塊幫助我生成了(官網(wǎng)上我已經(jīng)找不到這個(gè)字段的說明了)。
signature
const sig = [
encode(header),
encode(claims)
].join(".");
const signature = identifiedAlg(sig + secret);
有效性驗(yàn)證塊,一般拿前面兩塊的encode字段配合服務(wù)器給出的secret的標(biāo)識同時(shí)加密所得,主要用于jwt的惡意篡改。
組合
三塊內(nèi)容各自按照指定算法加密之后,用"."字符連接起來作為jwt發(fā)送給客戶端。在客戶端中,jwt會已httponly的狀態(tài)保存在cookie中(httponly:無法通過js訪問和修改),每次向指定站點(diǎn)發(fā)送請求的時(shí)候(一般也是這個(gè)jwt的發(fā)布者),這個(gè)jwt都會寫在請求頭中發(fā)送給服務(wù)器做數(shù)據(jù)驗(yàn)證。
dataTable##
一個(gè)基于jquery的表格拓展工具,支持多種動態(tài)表格功能的使用和開發(fā),高度自定義選項(xiàng)。不過需要引用自己指定的jquery(用前端框架的話),因而會對其他前端工具有覆蓋,建議獨(dú)立出去,然后再以模塊的形式引入。
使用過程很簡單,首先需要在頁面定義一個(gè)table,并提供一種方式訪問:
<table id="dataTable" class="display">
<thead>
<tr>
<td>id</td>
<td>名字</td>
<td>單位</td>
</tr>
</thead>
</table>
之后調(diào)用dataTable為jquery添加的接口dataTable/DataTable即可,當(dāng)然,如果使用的是前端框架,則需要保證在生成頁面之后調(diào)用接口。
$("#dataTable").DataTable();
dataTable提供了兩個(gè)版本的接口(dataTable和DataTable),兩者的不同之處在于其返回值,dataTable返回的是$("#dataTable"),而DataTable返回的是表的API,可以用來做一些特殊的修改,之后我們會用到。
這樣操作完之后,應(yīng)該就能在頁面上顯示一個(gè)0參數(shù)的dataTable表格了。
表格的作用是用來呈現(xiàn)數(shù)據(jù),dataTable允許我們直接把數(shù)據(jù)填寫進(jìn)表格中,或者定義成js數(shù)組,但這在實(shí)際生產(chǎn)過程中是不太現(xiàn)實(shí)的。絕大多數(shù)的數(shù)據(jù)都會以ajax請求返回值的形式發(fā)送給dataTable,因此,我們需要在調(diào)用接口的時(shí)候給出指定的參數(shù)
$("#dataTable").DataTable({
ajax: "/json/data.txt"
});
這樣,我們就可以獲取到本地服務(wù)器靜態(tài)目錄json下的data.txt文件了。這里除了填寫url之外,我們還可以填寫完整的ajax參數(shù)表,即
$("#dataTable").DataTable({
ajax: {
url: "/json/data.txt",
data: {
from: 0,
to: -1,
}
}
});
dataTable的ajax和jquery的基本一樣,但是有個(gè)參數(shù)需要特別注意,即dataSrc,這個(gè)參數(shù)的意義在于指定這個(gè)返回值的key,比如一般的json返回為:{data: [...]},那么這時(shí)候就需要指定dataSrc為"data"了。當(dāng)然如果不指定,dataSrc的默認(rèn)值就是"data";如果沒有鍵值,則指定空字符串;如果返回的是XML格式,也可以將dataSrc定義為函數(shù)function(json) { ... }以便進(jìn)行轉(zhuǎn)化。
在ajax中也可以定義success用于接受傳過來的結(jié)果,不過這貌似會替換dataTable自身的處理函數(shù),所以可以用success檢測返回值正不正確,但檢測完畢之后應(yīng)當(dāng)將處理權(quán)交由dataTable。
數(shù)據(jù)收到后第二步就是應(yīng)該做項(xiàng)綁定了,傳統(tǒng)的ejs方式是做一個(gè)循環(huán),根據(jù)數(shù)據(jù)的多少生成多少tr。dataTable提供了列綁定的功能,可以處理簡單的數(shù)據(jù)填充
// 假設(shè)返回的數(shù)據(jù)是
// { data: [ { id: 123, name: "張三", company: [ { name: "煎餅果子學(xué)院" } ] } ] }
$("#dataTable").DataTable({
ajax: "/json/data.txt",
columns: [
{data: "id"},
{data: "name"},
{data: "company.0.name"}
]
});
這里,數(shù)據(jù)是按照列數(shù)組的順序依次綁定的,我們這邊還演示了嵌套對象和嵌套數(shù)組的處理方法。
數(shù)據(jù)如果都是這樣依次填入的,那我們的工作會很輕松。但實(shí)際需求往往會復(fù)雜的多:比如想在每行表格的最后一個(gè)表格顯示一組操作按鈕、比如在第一列設(shè)定自增長的行ID、比如某列數(shù)據(jù)是結(jié)果中幾個(gè)參數(shù)共同計(jì)算的結(jié)果。面對那樣的情況,簡單的列指定是不夠的,因此dataTable提供了createdRow回調(diào)函數(shù)來讓我們在數(shù)據(jù)填充完之后,對每行數(shù)據(jù)進(jìn)行進(jìn)一步的修改
$("#dataTable").DataTable({
ajax: "/json/data.txt",
columns: [
{data: "id"},
{data: "name"},
{data: "company.0.name"}
],
createdRow: (row, data, index) => {
const td0 = $("td", row).eq(0);
td0.html(index);
const td4 = $("td", row).eq(4);
td4.html("");
td4.append("<button>操作</button>");
const td3 = $("td", row).eq(3);
const staffNum = data.company[0].managers.length + data.company[0].workers.length;
td3.html(String(staffNum));
}
});
隨著jquery的介入,表格的排版可以變得異常靈活。
行的修改我們用createdRow,表其他部分的修改怎么辦呢?比如修改表頭。這里我們使用initComplete屬性(在初始化完成之后),下面這個(gè)例子就是為表頭添加下拉列表:
initComplete: (settings, json) => {
// 還記得之前說過的dataTable()和DataTable()的區(qū)別嗎?這里的話如果之前
// 初始化時(shí)用的是DataTable,則其返回值就是api
const api = $("#dataTable").dataTable().api();
api.columns().indexes().flatten().each((i) => {
// 用于指定列的表頭生成下拉列表,如果不加這個(gè)判斷,則所有的th都會包含過濾用的下拉列表
if (i !== 2) { return; }
const column = api.column(i);
// 這里就是jquery表現(xiàn)的時(shí)候了,我們往這個(gè)列的列頭中append一個(gè)select
const select = $("<select><option value=''></option></select>")
.appendTo($(column.header()))
.on("change", (event) => {
// 如果下拉表發(fā)生了變化,則把選中的項(xiàng)的value作為查詢的key組裝成正則表達(dá)式
// 交給列查詢之后重繪
const vlu = $.fn.dataTable.util.escapeRegex($(event.target).val());
column.search(vlu ? "^" + vlu + "$" : "", true, false).draw();
});
// 對該列的數(shù)據(jù)做group篩選掉重復(fù)的,然后排序之后插入select
column.data().unique().sort().each((d, j) => {
select.append("<option value='" + d + "'>" + d + "</option>");
});
});
}
這個(gè)例子相對復(fù)雜,但通過它,可以有很多拓展的方向。initComplete函數(shù)意味著在表繪制完成之后可以進(jìn)行很多操作(雖然之后用jquery選擇器也可以,但從整體的角度來看,在這里面寫比較合理);下拉列表可以對列進(jìn)行操作,那么意味著我們也可以設(shè)置其他空間來對列里的項(xiàng)進(jìn)行整體操作。
至此,對表,我們可以做到隨意修改了。但同時(shí)會發(fā)現(xiàn),除了表,dataTable會幫我們生成一些其他組件:每頁顯示的項(xiàng)數(shù)、查找框、信息、分頁。這些東西也可以通過設(shè)置初始化參數(shù)來操作,我們可以分配給dataTable一個(gè)dom屬性,該屬性是一個(gè)字符串,dataTable的設(shè)計(jì)者開發(fā)了一組簡易的組件標(biāo)識,然后配合類似于xml的結(jié)構(gòu)組裝起來,便可以對表以及表四周的這一套組件進(jìn)行定位了
// <> - 代表一個(gè)div,可以指定類和id,id的指定和jquery一樣:#id,不同的是類,
// 類單獨(dú)存在的時(shí)候,類不需要在類名前加【.】
// l - Length changing 每頁顯示多少條數(shù)據(jù)選項(xiàng)
// f - Filtering input 搜索框
// t - The Table 表格
// i - Information 表格信息
// p - Pagination 分頁按鈕
// r - pRocessing 加載等待顯示信息
// 最近寫的dataTable模板用的dom(左上角指定id的div是用來存放新建按鈕的)
dom: "<'row'<'#btnCreateBtn.col-md-1'><lf>><'row'rt><'row'p>"
可以選擇dataTable是否生成某些組件,也可以綁定監(jiān)聽組件的觸發(fā)事件
// 是否生成組件的選項(xiàng)(設(shè)置true/false)
// paging:分頁
// ordering:表頭排序
// info:表信息
// searching:查找
// lengthChange:頁長
// 組件事件(用on來監(jiān)聽,表相關(guān)的需要追加dt命名空間)
// page:翻頁事件
// length:選擇單頁顯示的項(xiàng)數(shù)
// search:查找框變化事件
// processing:請求過程中間事件
table.on("search.dt", (e, settings) => {
console.log(table.search());
});