當知道要上傳的視頻資料從20條變成100條時,我就明白,絕對不能再人工處理了。他們老是想固然的認爲,錄入一條數據須要1分鐘,那錄入20條數據就是20分鐘,錄入100條數據,不就是100分鐘嗎?我有時候,真的很想問問他們,沒有考慮過人是會犯錯的嗎?數據越多,出錯的可能就越大;可是數據自己,又是不容許出現紕漏的。那拿什麼去保證數據的正確性?刷臉?可能嗎?node
大多數時候,相似的爭論,最終幾乎老是會以他們的一句「我不懂技術,大家看着辦吧」結束。因此,也懶得去作口舌之爭。我盡力儘快作;可是你承不認可事情自己的複雜度,並不會影響事情自己的複雜度。ios
回到問題自己,究竟如何處理新到來的100條數據以及之後更多的數據,確實是一個必須想辦法完全解決下的問題。git
此處適當象徵性的描述下我拿到的數據。如下討論,單以 10 條數據爲例。github
內容假定是:typescript
1.【smart-transform】取自 Atom 的 babeljs&coffeescript&typescript 智能轉 es5 庫 2.【YFMemoryLeakDetector】人人都能理解的 iOS 內存泄露檢測工具類 3.【玩轉樹莓派】使用 sinopia 搭建私有 npm 服務器 4.【小技巧解決大問題】使用 frp 突破阿里雲主機無彈性公網 IP 不能用做 Web 服務器的限制 5.【樹莓派自動化應用實例】整點提醒本身休息五分鐘 6. 藉助 frp 隨時隨地訪問本身的樹莓派 7.【LuaJIT版】從零開始在 macOS 上配置 Lua 開發環境 8.【最新版】從零開始在 macOS 上配置 Lua 開發環境 9. 關於混合應用開發的將來的一些思考 10.記錄我發現的第一個關於 Google 的 Bug
是的,內容中還有各類中文標點。他們有至關一部分人不理解攻城獅爲何喜歡用英文標點,甚至還有人以此爲由說咱們小學標點符號沒學好。懶得解釋那麼多,可是既然給出來了,做爲純文本,也不用管這麼多,照單全收就好了。符號習慣問題自己,也是一個無傷大雅的問題。macos
微軟的 Luis 語義分析服務,勉強算是和人工智能沾點邊吧,感興趣的請自行了解下。從客戶端角度來講,你給它一個文本字符串,他們分析出來和這個字符串匹配度最高的某個預錄入的答案的惟一標記。每一個惟一標記 ID,被稱做一個 intent。每次請求,最多隻有一個匹配度最高的 intent。npm
感受已經有的 word 問題,咱們的後端小夥伴,送來了另外一個 word 文檔:json
1. smart_transform 2. memory_leakDetector 3. sinopia_npm 4. frp_ip 5. tip_rest 6. frp_anywhere 7. luajit_macos 8. lua_macos 9. app_future 10. google_bug
又是非結構化的數據。顯而易見,咱們可愛的後端同窗,只是簡單完成了錄入,本身沒有作必要的單元測試。這是在等着我去發現問題啊。好久好久之前,我老是幻想着,全部的攻城獅,必然都是各類自動化測試用例,就像樹上寫的各類敏捷,各類快速迭代。事實上,我見到的許多所謂的敏捷式開發,最終其實只是把成本後置,各類技術債。出來混,真的早晚是要換的。100個問題,逐一去驗證,真的是很耗費時間的,並且最終有問題的,數量也不會太多。也就說說,若是手動去作,頗有可能尋找問題的時間,要遠遠大於發現問題的時間。因此,自動化批量測試,是顯而易見的。根據不一樣的場景和須要,快速構建基本夠用的批量自動化測試工具鏈,應該成爲每一個攻城獅的必修課。後端
我依然是象徵性的描述下,結構相似於:瀏覽器
/videos/樹莓派/【smart-transform】取自 Atom 的 babeljs&coffeescript&typescript 智能轉 es5 庫.mp4 /videos/樹莓派/【YFMemoryLeakDetector】人人都能理解的 iOS 內存泄露檢測工具類.mp4 /videos/樹莓派/【玩轉樹莓派】使用 sinopia 搭建私有 npm 服務器.mp4 /videos/樹莓派/【小技巧解決大問題】使用 frp 突破阿里雲主機無彈性公網 IP 不能用做 Web 服務器的限制.mp4 /videos/frp/【樹莓派自動化應用實例】整點提醒本身休息五分鐘.mp4 /videos/frp/藉助 frp 隨時隨地訪問本身的樹莓派.mp4 /videos/Lua/【LuaJIT版】從零開始在 macOS 上配置 Lua 開發環境.mp4 /videos/Lua/【最新版】從零開始在 macOS 上配置 Lua 開發環境.mp4 /videos/Lua/關於混合應用開發的將來的一些思考.mp4 /videos/Lua/記錄我發現的第一個關於 Google 的 Bug.mp4
顯而易見,應該使用 intent 做爲數據的惟一 id。爲了便於處理,索性寫成了一個 JS 模塊。之因此不直接用 JSON,是由於模塊比 JSON 文件,更靈活性,後期擴展方便,若是有的話。
這一步是必須手動作的,或者說老是須要有一我的手動去作的。爲了效率,團隊內老是須要有一我的必需要充當這個角色。
大體處理下,第一版結構 intent_info.js 大概相似這樣:
module.exports = { /* 樹莓派 */ "smart_transform":"【smart-transform】取自 Atom 的 babeljs&coffeescript&typescript 智能轉 es5 庫", "memory_leakDetector":"【YFMemoryLeakDetector】人人都能理解的 iOS 內存泄露檢測工具類", "sinopia_npm":"【玩轉樹莓派】使用 sinopia 搭建私有 npm 服務器", "frp_ip":"【小技巧解決大問題】使用 frp 突破阿里雲主機無彈性公網 IP 不能用做 Web 服務器的限制", /* frp */ "tip_rest":"【樹莓派自動化應用實例】整點提醒本身休息五分鐘", "frp_anywhere":"藉助 frp 隨時隨地訪問本身的樹莓派", /* Lua */ "luajit_macos":"【LuaJIT版】從零開始在 macOS 上配置 Lua 開發環境", "lua_macos":"【最新版】從零開始在 macOS 上配置 Lua 開發環境", "app_future":"關於混合應用開發的將來的一些思考", "google_bug":"記錄我發現的第一個關於 Google 的 Bug", }
排序,是須要增長一個新的字段 order。不過,我就直接上面的相似 JSON 的結構來排序的。由於排序是由另一我的作,懂技術,操做很簡單些。
通過對方排序後,intent_info.js,可能變成了這樣:
module.exports = { /* 樹莓派 */ "smart_transform":"【smart-transform】取自 Atom 的 babeljs&coffeescript&typescript 智能轉 es5 庫", "memory_leakDetector":"【YFMemoryLeakDetector】人人都能理解的 iOS 內存泄露檢測工具類", "sinopia_npm":"【玩轉樹莓派】使用 sinopia 搭建私有 npm 服務器", "frp_ip":"【小技巧解決大問題】使用 frp 突破阿里雲主機無彈性公網 IP 不能用做 Web 服務器的限制", /* Lua */ "luajit_macos":"【LuaJIT版】從零開始在 macOS 上配置 Lua 開發環境", "lua_macos":"【最新版】從零開始在 macOS 上配置 Lua 開發環境", "app_future":"關於混合應用開發的將來的一些思考", "google_bug":"記錄我發現的第一個關於 Google 的 Bug", /* frp */ "tip_rest":"【樹莓派自動化應用實例】整點提醒本身休息五分鐘", "frp_anywhere":"藉助 frp 隨時隨地訪問本身的樹莓派", }
在上面的優先顯示。在真正生成 order 字段時,是藉助 Node 一個不太可靠的特性: 字典遍歷時,會基於key的書寫順序來遍歷。這一點,在 Node 和 Android 瀏覽器上都是成立的,在 safari 上,無效。通常開發時,不該依賴於這一點,不過目前,我只是須要一個夠用的東西。Node 的這個特性,在短期內,應該是不會有改變的。
沒過幾天,果真又加了新需求,說是視頻太多了,太雜亂,想給每一個視頻加個分類,而後能夠按分類查看視頻。
好,那我給你加個分類:
module.exports = { /* 樹莓派 */ "樹莓派":"_category", "smart_transform":"【smart-transform】取自 Atom 的 babeljs&coffeescript&typescript 智能轉 es5 庫", "memory_leakDetector":"【YFMemoryLeakDetector】人人都能理解的 iOS 內存泄露檢測工具類", "sinopia_npm":"【玩轉樹莓派】使用 sinopia 搭建私有 npm 服務器", "frp_ip":"【小技巧解決大問題】使用 frp 突破阿里雲主機無彈性公網 IP 不能用做 Web 服務器的限制", /* Lua */ "Lua":"_category", "luajit_macos":"【LuaJIT版】從零開始在 macOS 上配置 Lua 開發環境", "lua_macos":"【最新版】從零開始在 macOS 上配置 Lua 開發環境", "app_future":"關於混合應用開發的將來的一些思考", "google_bug":"記錄我發現的第一個關於 Google 的 Bug", /* frp */ "frp":"_category", "tip_rest":"【樹莓派自動化應用實例】整點提醒本身休息五分鐘", "frp_anywhere":"藉助 frp 隨時隨地訪問本身的樹莓派", }
新加了幾個值爲 **_category** 的字段。當檢測到值爲 **_category** 時,就自動斷定爲是一個分類。我這種處理方式,免不了引來一陣唏噓。可是,許多時候,你選擇的技術策略,都必須根據項目所處的狀態和各類條件,去綜合權衡。我只有幾十分鐘時間去從新規劃和整理100條數據。可能真的無法想太多。需求老是變化的,不知道明天又會變成什麼樣,可能再進一步,就變成」過分設計「了。另外,項目自己, intent 自己約定了本身特有命名規律,是能夠安全認爲 intent 和 分類必定不會重複的。
在讀取 intent_info.js 中的足夠可信的結構化數據後,我會動態創建問題和視頻的關聯。這個過程當中,可能須要適當修改問題和視頻的標題。爲了不遺漏,一個標題,若是沒有對應的視頻或對應多個視頻,就直接crash。有些霸道,但總比後期一個一個比對排查,省太多事了。結合問題和視頻標題的特色,我專門封裝了一個方法:
/* 獲取某個標題對應的本地路徑. 爲了不未知錯誤,若是找不到或找到多個,就直接 crash. @return 本地視頻的相對路徑. */ function localVideoPath(title) { let path = require("path") let fs = require ('fs-plus') let fse = require('fs-extra') let os = require("os") let {execSync} = require("child_process") let videoDir = path.resolve(__dirname,"./videos") let videos = fs.listTreeSync(videoDir) .filter(item=>{ return [".mov",".mp4"].includes(path.extname(item)) }) .map(item=>{ return path.relative(__dirname,item) }) /* 一個標題,能且只能對應一個視頻,不然就拋出異常. */ let localVideoPath = null for (let item of videos) { if (item.includes(title)) { if (localVideoPath) { const tip = `致命異常: ${title} 對應的視頻重複: ${localVideoPath} ${item}` throw new Error(tip) } localVideoPath = item } } if (!localVideoPath) { const tip = `致命異常!這個標題居然沒有對應的視頻:\n${title}` throw new Error(tip) } return localVideoPath }
完整的自動化處理成結構數據的邏輯以下,都集中在 make_data.js 中。
/* 生成帶有排序等信息的文件. */ /* 支持自動生成數據. */ makeDataWithOrder() function makeDataWithOrder() { const fs = require('fs-extra') const path = require('path') const intentInfo = require("./intent_info.js") let intentInfoNew = [] let index = 1 /* 在node中遍歷時,key的順序是和原始key的順序對應的. 這個特性,並不老是有效,好比在 ios 瀏覽器中. 目前,僅僅是夠用. */ let category = "" for (let intent in intentInfo) { if (intentInfo[intent] == "_category") { /* 說明是一個分類標記. */ category = intent continue } let title = intentInfo[intent] const local_path = localVideoPath(title) intentInfoNew.push({ "type":"video", "content":"", "intent": intent, "title": title, "order": index, "local_video_path": local_path, "ext": path.extname(local_path), "category":category, }) ++ index } localVideoLoseCheck(intentInfoNew) const dataPath = path.resolve(__dirname, "./data.json") fs.writeJsonSync(dataPath, intentInfoNew) console.log(`恭喜!數據已寫入 ${dataPath}`) } /* 確保視頻總數與intent總數是對應的,防止有視頻遺漏. 有視頻沒有對應問題時,會直接拋出異常. */ function localVideoLoseCheck(intents) { /* 先把視頻信息處理成 key-value. */ let path = require("path") let fs = require ('fs-plus') let fse = require('fs-extra') let os = require("os") let {execSync} = require("child_process") let videoDir = path.resolve(__dirname,"./videos") let videoDict = fs.listTreeSync(videoDir) .filter(item=>{ return [".mov",".mp4"].includes(path.extname(item)) }) .map(item=>{ return path.relative(__dirname,item) }) .reduce((sum,item,idx)=>{ sum[item] = false return sum },{}) for (let item of intents) { videoDict[item.local_video_path] = true } /* 尋找缺失的. */ let loses = [] for (let item in videoDict) { if (!videoDict[item]) { loses.push(item) } } if (loses.length) { const tip = `一下 ${loses.length} 個視頻沒有對應的問題: ${JSON.stringify(loses)}` throw new Error(tip) } } /* 獲取某個標題對應的本地路徑. 爲了不未知錯誤,若是找不到或找到多個,就直接 crash. @return 本地視頻的相對路徑. */ function localVideoPath(title) { let path = require("path") let fs = require ('fs-plus') let fse = require('fs-extra') let os = require("os") let {execSync} = require("child_process") let videoDir = path.resolve(__dirname,"./videos") let videos = fs.listTreeSync(videoDir) .filter(item=>{ return [".mov",".mp4"].includes(path.extname(item)) }) .map(item=>{ return path.relative(__dirname,item) }) /* 一個標題,能且只能對應一個視頻,不然就拋出異常. */ let localVideoPath = null for (let item of videos) { if (item.includes(title)) { if (localVideoPath) { const tip = `致命異常: ${title} 對應的視頻重複: ${localVideoPath} ${item}` throw new Error(tip) } localVideoPath = item } } if (!localVideoPath) { const tip = `致命異常!這個標題居然沒有對應的視頻:\n${title}` throw new Error(tip) } return localVideoPath }
咱們在項目目錄執行
node ./make_data.js
就能夠獲得咱們想要的結構化的數據:
[ { "type": "video", "content": "", "intent": "smart_transform", "title": "【smart-transform】取自 Atom 的 babeljs:coffeescript:typescript 智能轉 es5 庫", "order": 1, "local_video_path": "videos/樹莓派/【smart-transform】取自 Atom 的 babeljs:coffeescript:typescript 智能轉 es5 庫.mp4", "ext": ".mp4", "category": "樹莓派" }, { "type": "video", "content": "", "intent": "memory_leakDetector", "title": "【YFMemoryLeakDetector】人人都能理解的 iOS 內存泄露檢測工具類", "order": 2, "local_video_path": "videos/樹莓派/【YFMemoryLeakDetector】人人都能理解的 iOS 內存泄露檢測工具類.mp4", "ext": ".mp4", "category": "樹莓派" }, { "type": "video", "content": "", "intent": "sinopia_npm", "title": "【玩轉樹莓派】使用 sinopia 搭建私有 npm 服務器", "order": 3, "local_video_path": "videos/樹莓派/【玩轉樹莓派】使用 sinopia 搭建私有 npm 服務器.mp4", "ext": ".mp4", "category": "樹莓派" }, { "type": "video", "content": "", "intent": "frp_ip", "title": "【小技巧解決大問題】使用 frp 突破阿里雲主機無彈性公網 IP 不能用做 Web 服務器的限制", "order": 4, "local_video_path": "videos/樹莓派/【小技巧解決大問題】使用 frp 突破阿里雲主機無彈性公網 IP 不能用做 Web 服務器的限制.mp4", "ext": ".mp4", "category": "樹莓派" }, { "type": "video", "content": "", "intent": "luajit_macos", "title": "【LuaJIT版】從零開始在 macOS 上配置 Lua 開發環境", "order": 5, "local_video_path": "videos/Lua/【LuaJIT版】從零開始在 macOS 上配置 Lua 開發環境.mp4", "ext": ".mp4", "category": "Lua" }, { "type": "video", "content": "", "intent": "lua_macos", "title": "【最新版】從零開始在 macOS 上配置 Lua 開發環境", "order": 6, "local_video_path": "videos/Lua/【最新版】從零開始在 macOS 上配置 Lua 開發環境.mp4", "ext": ".mp4", "category": "Lua" }, { "type": "video", "content": "", "intent": "app_future", "title": "關於混合應用開發的將來的一些思考", "order": 7, "local_video_path": "videos/Lua/關於混合應用開發的將來的一些思考.mp4", "ext": ".mp4", "category": "Lua" }, { "type": "video", "content": "", "intent": "google_bug", "title": "記錄我發現的第一個關於 Google 的 Bug", "order": 8, "local_video_path": "videos/Lua/記錄我發現的第一個關於 Google 的 Bug.mp4", "ext": ".mp4", "category": "Lua" }, { "type": "video", "content": "", "intent": "tip_rest", "title": "【樹莓派自動化應用實例】整點提醒本身休息五分鐘", "order": 9, "local_video_path": "videos/frp/【樹莓派自動化應用實例】整點提醒本身休息五分鐘.mp4", "ext": ".mp4", "category": "frp" }, { "type": "video", "content": "", "intent": "frp_anywhere", "title": "藉助 frp 隨時隨地訪問本身的樹莓派", "order": 10, "local_video_path": "videos/frp/藉助 frp 隨時隨地訪問本身的樹莓派.mp4", "ext": ".mp4", "category": "frp" } ]
個人博客即將同步至騰訊雲+社區,邀請你們一同入駐。