vue 前端顯示圖片加token_手摸手,帶你用vue擼后臺 系列二(登錄權限篇)

8611e777-5d12-eb11-8da9-e4434bdf6706.png

完整項目地址:vue-element-admin

https://github.com/PanJiaChen/vue-element-admin

前言

拖更有點嚴重,過了半個月才寫了第二篇教程。無奈自己是一個業務猿,每天被我司的產品虐的死去活來,之前又病了一下休息了幾天,大家見諒。

進入正題,做后臺項目區別于做其它的項目,權限驗證與安全性是非常重要的,可以說是一個后臺項目一開始就必須考慮和搭建的基礎核心功能。我們所要做到的是:不同的權限對應著不同的路由,同時側邊欄也需根據不同的權限,異步生成。這里先簡單說一下,我實現登錄和權限驗證的思路。

  • 登錄:當用戶填寫完賬號和密碼后向服務端驗證是否正確,驗證通過之后,服務端會返回一個token,拿到token之后(我會將這個token存貯到cookie中,保證刷新頁面后能記住用戶登錄狀態),前端會根據token再去拉取一個 user_info 的接口來獲取用戶的詳細信息(如用戶權限,用戶名等等信息)。
  • 權限驗證:通過token獲取用戶對應的 role,動態根據用戶的 role 算出其對應有權限的路由,通過 router.addRoutes 動態掛載這些路由。

上述所有的數據和操作都是通過vuex全局管理控制的。(補充說明:刷新頁面后 vuex的內容也會丟失,所以需要重復上述的那些操作)接下來,我們一起手摸手一步一步實現這個系統。

登錄篇

首先我們不管什么權限,來實現最基礎的登錄功能。

隨便找一個空白頁面擼上兩個input的框,一個是登錄賬號,一個是登錄密碼。再放置一個登錄按鈕。我們將登錄按鈕上綁上click事件,點擊登錄之后向服務端提交賬號和密碼進行驗證。 這就是一個最簡單的登錄頁面。如果你覺得還要寫的更加完美點,你可以在向服務端提交之前對賬號和密碼做一次簡單的校驗。詳細代碼

8911e777-5d12-eb11-8da9-e4434bdf6706.png

click事件觸發登錄操作:

this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {  this.$router.push({ path: '/' }); //登錄成功之后重定向到首頁}).catch(err => {  this.$message.error(err); //登錄失敗提示錯誤});復制代碼

action:

LoginByUsername({ commit }, userInfo) {  const username = userInfo.username.trim()  return new Promise((resolve, reject) => {    loginByUsername(username, userInfo.password).then(response => {      const data = response.data      Cookies.set('Token', response.data.token) //登錄成功后將token存儲在cookie之中      commit('SET_TOKEN', data.token)      resolve()    }).catch(error => {      reject(error)    });  });}復制代碼

登錄成功后,服務端會返回一個 token(該token的是一個能唯一標示用戶身份的一個key),之后我們將token存儲在本地cookie之中,這樣下次打開頁面或者刷新頁面的時候能記住用戶的登錄狀態,不用再去登錄頁面重新登錄了。

ps:為了保證安全性,我司現在后臺所有token有效期(Expires/Max-Age)都是Session,就是當瀏覽器關閉了就丟失了。重新打開瀏覽器都需要重新登錄驗證,后端也會在每周固定一個時間點重新刷新token,讓后臺用戶全部重新登錄一次,確保后臺用戶不會因為電腦遺失或者其它原因被人隨意使用賬號。

獲取用戶信息

用戶登錄成功之后,我們會在全局鉤子router.beforeEach中攔截路由,判斷是否已獲得token,在獲得token之后我們就要去獲取用戶的基本信息了

//router.beforeEachif (store.getters.roles.length === 0) { // 判斷當前用戶是否已拉取完user_info信息  store.dispatch('GetInfo').then(res => { // 拉取user_info    const roles = res.data.role;    next();//resolve 鉤子  })復制代碼

就如前面所說的,我只在本地存儲了一個用戶的token,并沒有存儲別的用戶信息(如用戶權限,用戶名,用戶頭像等)。有些人會問為什么不把一些其它的用戶信息也存一下?主要出于如下的考慮:

假設我把用戶權限和用戶名也存在了本地,但我這時候用另一臺電腦登錄修改了自己的用戶名,之后再用這臺存有之前用戶信息的電腦登錄,它默認會去讀取本地 cookie 中的名字,并不會去拉去新的用戶信息。

所以現在的策略是:頁面會先從 cookie 中查看是否存有 token,沒有,就走一遍上一部分的流程重新登錄,如果有token,就會把這個 token 返給后端去拉取user_info,保證用戶信息是最新的。 當然如果是做了單點登錄得功能的話,用戶信息存儲在本地也是可以的。當你一臺電腦登錄時,另一臺會被提下線,所以總會重新登錄獲取最新的內容。

而且從代碼層面我建議還是把 login和get_user_info兩件事分開比較好,在這個后端全面微服務的年代,后端同學也想寫優雅的代碼~


權限篇

先說一說我權限控制的主體思路,前端會有一份路由表,它表示了每一個路由可訪問的權限。當用戶登錄之后,通過 token 獲取用戶的 role ,動態根據用戶的 role 算出其對應有權限的路由,再通過router.addRoutes動態掛載路由。但這些控制都只是頁面級的,說白了前端再怎么做權限控制都不是絕對安全的,后端的權限驗證是逃不掉的。

我司現在就是前端來控制頁面級的權限,不同權限的用戶顯示不同的側邊欄和限制其所能進入的頁面(也做了少許按鈕級別的權限控制),后端則會驗證每一個涉及請求的操作,驗證其是否有該操作的權限,每一個后臺的請求不管是 get 還是 post 都會讓前端在請求 header里面攜帶用戶的 token,后端會根據該 token 來驗證用戶是否有權限執行該操作。若沒有權限則拋出一個對應的狀態碼,前端檢測到該狀態碼,做出相對應的操作。

權限 前端or后端 來控制?

有很多人表示他們公司的路由表是于后端根據用戶的權限動態生成的,我司不采取這種方式的原因如下:

  • 項目不斷的迭代你會異常痛苦,前端新開發一個頁面還要讓后端配一下路由和權限,讓我們想了曾經前后端不分離,被后端支配的那段恐怖時間了。
  • 其次,就拿我司的業務來說,雖然后端的確也是有權限驗證的,但它的驗證其實是針對業務來劃分的,比如超級編輯可以發布文章,而實習編輯只能編輯文章不能發布,但對于前端來說不管是超級編輯還是實習編輯都是有權限進入文章編輯頁面的。所以前端和后端權限的劃分是不太一致。
  • 還有一點是就vue2.2.0之前異步掛載路由是很麻煩的一件事!不過好在官方也出了新的api,雖然本意是來解決ssr的痛點的。。。

addRoutes

在之前通過后端動態返回前端路由一直很難做的,因為vue-router必須是要vue在實例化之前就掛載上去的,不太方便動態改變。不過好在vue2.2.0以后新增了router.addRoutes

Dynamically add more routes to the router. The argument must be an Array using the same route config format with the routes constructor option.

有了這個我們就可相對方便的做權限控制了。(樓主之前在權限控制也走了不少歪路,可以在項目的commit記錄中看到,重構了很多次,最早沒用addRoute整個權限控制代碼里都是各種if/else的邏輯判斷,代碼相當的耦合和復雜)


具體實現

  1. 創建vue實例的時候將vue-router掛載,但這個時候vue-router掛載一些登錄或者不用權限的公用的頁面。
  2. 當用戶登錄后,獲取用role,將role和路由表每個頁面的需要的權限作比較,生成最終用戶可訪問的路由表。
  3. 調用router.addRoutes(store.getters.addRouters)添加用戶可訪問的路由。
  4. 使用vuex管理路由表,根據vuex中可訪問的路由渲染側邊欄組件。

router.js

首先我們實現router.js路由表,這里就拿前端控制路由來舉例(后端存儲的也差不多,稍微改造一下就好了)

// router.jsimport Vue from 'vue';import Router from 'vue-router';import Login from '../views/login/';const dashboard = resolve => require(['../views/dashboard/index'], resolve);//使用了vue-routerd的[Lazy Loading Routes](https://router.vuejs.org/en/advanced/lazy-loading.html)//所有權限通用路由表 //如首頁和登錄頁和一些不用權限的公用頁面export const constantRouterMap = [  { path: '/login', component: Login },  {    path: '/',    component: Layout,    redirect: '/dashboard',    name: '首頁',    children: [{ path: 'dashboard', component: dashboard }]  },]//實例化vue的時候只掛載constantRouterexport default new Router({  routes: constantRouterMap});//異步掛載的路由//動態需要根據權限加載的路由表 export const asyncRouterMap = [  {    path: '/permission',    component: Layout,    name: '權限測試',    meta: { role: ['admin','super_editor'] }, //頁面需要的權限    children: [    {       path: 'index',      component: Permission,      name: '權限測試頁',      meta: { role: ['admin','super_editor'] }  //頁面需要的權限    }]  },  { path: '*', redirect: '/404', hidden: true }];復制代碼

這里我們根據 vue-router官方推薦 的方法通過meta標簽來標示改頁面能訪問的權限有哪些。如meta: { role: ['admin','super_editor'] }表示該頁面只有admin和超級編輯才能有資格進入。

注意事項:這里有一個需要非常注意的地方就是 404 頁面一定要最后加載,如果放在constantRouterMap一同聲明了404,后面的所以頁面都會被攔截到404,詳細的問題見addRoutes when you've got a wildcard route for 404s does not work

main.js

關鍵的main.js

// main.jsrouter.beforeEach((to, from, next) => {  if (store.getters.token) { // 判斷是否有token    if (to.path === '/login') {      next({ path: '/' });    } else {      if (store.getters.roles.length === 0) { // 判斷當前用戶是否已拉取完user_info信息        store.dispatch('GetInfo').then(res => { // 拉取info          const roles = res.data.role;          store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可訪問的路由表            router.addRoutes(store.getters.addRouters) // 動態添加可訪問路由表            next({ ...to, replace: true }) // hack方法 確保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record          })        }).catch(err => {          console.log(err);        });      } else {        next() //當有用戶權限的時候,說明所有可訪問路由已生成 如訪問沒權限的全面會自動進入404頁面      }    }  } else {    if (whiteList.indexOf(to.path) !== -1) { // 在免登錄白名單,直接進入      next();    } else {      next('/login'); // 否則全部重定向到登錄頁    }  }});復制代碼

這里的router.beforeEach也結合了上一章講的一些登錄邏輯代碼。

8d11e777-5d12-eb11-8da9-e4434bdf6706.png

上面一張圖就是在使用addRoutes方法之前的權限判斷,非常的繁瑣,因為我是把所有的路由都掛在了上去,所有我要各種判斷當前的用戶是否有權限進入該頁面,各種if/else的嵌套,維護起來相當的困難。但現在有了addRoutes之后就非常的方便,我只掛載了用戶有權限進入的頁面,沒權限,路由自動幫我跳轉的404,省去了不少的判斷。

這里還有一個小hack的地方,就是router.addRoutes之后的next()可能會失效,因為可能next()的時候路由并沒有完全add完成,好在查閱文檔發現

next('/') or next({ path: '/' }): redirect to a different location. The current navigation will be aborted and a new one will be started.

這樣我們就可以簡單的通過next(to)巧妙的避開之前的那個問題了。這行代碼重新進入router.beforeEach這個鉤子,這時候再通過next()來釋放鉤子,就能確保所有的路由都已經掛在完成了。

store/permission.js

就來就講一講 GenerateRoutes Action

// store/permission.jsimport { asyncRouterMap, constantRouterMap } from 'src/router';function hasPermission(roles, route) {  if (route.meta && route.meta.role) {    return roles.some(role => route.meta.role.indexOf(role) >= 0)  } else {    return true  }}const permission = {  state: {    routers: constantRouterMap,    addRouters: []  },  mutations: {    SET_ROUTERS: (state, routers) => {      state.addRouters = routers;      state.routers = constantRouterMap.concat(routers);    }  },  actions: {    GenerateRoutes({ commit }, data) {      return new Promise(resolve => {        const { roles } = data;        const accessedRouters = asyncRouterMap.filter(v => {          if (roles.indexOf('admin') >= 0) return true;          if (hasPermission(roles, v)) {            if (v.children && v.children.length > 0) {              v.children = v.children.filter(child => {                if (hasPermission(roles, child)) {                  return child                }                return false;              });              return v            } else {              return v            }          }          return false;        });        commit('SET_ROUTERS', accessedRouters);        resolve();      })    }  }};export default permission;復制代碼

這里的代碼說白了就是干了一件事,通過用戶的權限和之前在router.js里面asyncRouterMap的每一個頁面所需要的權限做匹配,最后返回一個該用戶能夠訪問路由有哪些。


側邊欄

最后一個涉及到權限的地方就是側邊欄,不過在前面的基礎上已經很方便就能實現動態顯示側邊欄了。這里側邊欄基于element-ui的NavMenu來實現的。 代碼有點多不貼詳細的代碼了,有興趣的可以直接去github上看地址,或者直接看關于側邊欄的文檔。

說白了就是遍歷之前算出來的permission_routers,通過vuex拿到之后動態v-for渲染而已。不過這里因為有一些業務需求所以加了很多判斷 比如我們在定義路由的時候會加很多參數

/*** hidden: true                   if `hidden:true` will not show in the sidebar(default is false)* redirect: noredirect           if `redirect:noredirect` will no redirct in the breadcrumb* name:'router-name'             the name is used by  (must set!!!)* meta : {   role: ['admin','editor']     will control the page role (you can set multiple roles)   title: 'title'               the name show in submenu and breadcrumb (recommend set)   icon: 'svg-name'             the icon show in the sidebar,   noCache: true                if fasle ,the page will no be cached(default is false) }**/復制代碼

這里僅供參考,而且本項目為了支持無限嵌套路由,所有側邊欄這塊使用了遞歸組件。如需要請大家自行改造,來打造滿足自己業務需求的側邊欄。

側邊欄高亮問題:很多人在群里問為什么自己的側邊欄不能跟著自己的路由高亮,其實很簡單,element-ui官方已經給了default-active所以我們只要

:default-active="$route.path" 將default-active一直指向當前路由就可以了,就是這么簡單


按鈕級別權限控制

有很多人一直在問關于按鈕級別粒度的權限控制怎么做。我司現在是這樣的,真正需要按鈕級別控制的地方不是很多,現在是通過獲取到用戶的role之后,在前端用v-if手動判斷來區分不同權限對應的按鈕的。理由前面也說了,我司顆粒度的權限判斷是交給后端來做的,每個操作后端都會進行權限判斷。而且我覺得其實前端真正需要按鈕級別判斷的地方不是很多,如果一個頁面有很多種不同權限的按鈕,我覺得更多的應該是考慮產品層面是否設計合理。當然你強行說我想做按鈕級別的權限控制,你也可以參照路由層面的做法,搞一個操作權限表。。。但個人覺得有點多此一舉。或者將它封裝成一個指令都是可以的。


axios攔截器

這里再說一說 axios 吧。雖然在上一篇系列文章中簡單介紹過,不過這里還是要在嘮叨一下。如上文所說,我司服務端對每一個請求都會驗證權限,所以這里我們針對業務封裝了一下請求。首先我們通過request攔截器在每個請求頭里面塞入token,好讓后端對請求進行權限驗證。并創建一個respone攔截器,當服務端返回特殊的狀態碼,我們統一做處理,如沒權限或者token失效等操作。

import axios from 'axios'import { Message } from 'element-ui'import store from '@/store'import { getToken } from '@/utils/auth'// 創建axios實例const service = axios.create({  baseURL: process.env.BASE_API, // api的base_url  timeout: 5000 // 請求超時時間})// request攔截器service.interceptors.request.use(config => {  // Do something before request is sent  if (store.getters.token) {    config.headers['X-Token'] = getToken() // 讓每個請求攜帶token--['X-Token']為自定義key 請根據實際情況自行修改  }  return config}, error => {  // Do something with request error  console.log(error) // for debug  Promise.reject(error)})// respone攔截器service.interceptors.response.use(  response => response,  /**  * 下面的注釋為通過response自定義code來標示請求狀態,當code返回如下情況為權限有問題,登出并返回到登錄頁  * 如通過xmlhttprequest 狀態碼標識 邏輯可寫在下面error中  */  //  const res = response.data;  //     if (res.code !== 20000) {  //       Message({  //         message: res.message,  //         type: 'error',  //         duration: 5 * 1000  //       });  //       // 50008:非法的token; 50012:其他客戶端登錄了;  50014:Token 過期了;  //       if (res.code === 50008 || res.code === 50012 || res.code === 50014) {  //         MessageBox.confirm('你已被登出,可以取消繼續留在該頁面,或者重新登錄', '確定登出', {  //           confirmButtonText: '重新登錄',  //           cancelButtonText: '取消',  //           type: 'warning'  //         }).then(() => {  //           store.dispatch('FedLogOut').then(() => {  //             location.reload();// 為了重新實例化vue-router對象 避免bug  //           });  //         })  //       }  //       return Promise.reject('error');  //     } else {  //       return response.data;  //     }  error => {    console.log('err' + error)// for debug    Message({      message: error.message,      type: 'error',      duration: 5 * 1000    })    return Promise.reject(error)  })export default service復制代碼

兩步驗證

8f11e777-5d12-eb11-8da9-e4434bdf6706.png
9311e777-5d12-eb11-8da9-e4434bdf6706.png

文章一開始也說了,后臺的安全性是很重要的,簡簡單單的一個賬號+密碼的方式是很難保證安全性的。所以我司的后臺項目都是用了兩步驗證的方式,之前我們也嘗試過使用基于 google-authenticator 或者youbikey這樣的方式但難度和操作成本都比較大。后來還是準備借助騰訊爸爸,這年代誰不用微信。。。安全性騰訊爸爸也幫我做好了保障。 樓主建議兩步驗證要支持多個渠道不要只微信或者QQ,前段時間QQ第三方登錄就出了bug,官方兩三天才修好的,害我背了鍋/(ㄒoㄒ)/~~ 。

這里的兩部驗證有點名不副實,其實就是賬號密碼驗證過之后還需要一個綁定的第三方平臺登錄驗證而已。 寫起來也很簡單,在原有登錄得邏輯上改造一下就好。

this.$store.dispatch('LoginByEmail', this.loginForm).then(() => {  //this.$router.push({ path: '/' });  //不重定向到首頁  this.showDialog = true //彈出選擇第三方平臺的dialog}).catch(err => {  this.$message.error(err); //登錄失敗提示錯誤});復制代碼

登錄成功之后不直接跳到首頁而是讓用戶兩步登錄,選擇登錄得平臺。 接下來就是所有第三方登錄一樣的地方通過 OAuth2.0 授權。這個各大平臺大同小異,大家自行查閱文檔,不展開了,就說一個微信授權比較坑的地方。注意你連參數的順序都不能換,不然會驗證不通過。具體代碼,同時我也封裝了openWindow方法大家自行看吧。 當第三方授權成功之后都會跳到一個你之前有一個傳入redirect——uri的頁面

9511e777-5d12-eb11-8da9-e4434bdf6706.png

如微信還必須是你授權賬號的一級域名。所以你授權的域名是vue-element-admin.com,你就必須重定向到vue-element-admin.com/xxx/下面,所以你需要寫一個重定向的服務,如vue-element-admin.com/auth/redirect?a.com 跳到該頁面時會再次重定向給a.com。

所以我們后臺也需要開一個authredirect頁面:代碼。他的作用是第三方登錄成功之后會默認跳到授權的頁面,授權的頁面會再次重定向回我們的后臺,由于是spa,改變路由的體驗不好,我們通過window.opener.location.href的方式改變hash,在login.js里面再監聽hash的變化。當hash變化時,獲取之前第三方登錄成功返回的code與第一步賬號密碼登錄之后返回的uid一同發送給服務端驗證是否正確,如果正確,這時候就是真正的登錄成功。

 created() {     window.addEventListener('hashchange', this.afterQRScan);   },   destroyed() {     window.removeEventListener('hashchange', this.afterQRScan);   },   afterQRScan() {     const hash = window.location.hash.slice(1);     const hashObj = getQueryObject(hash);     const originUrl = window.location.origin;     history.replaceState({}, '', originUrl);     const codeMap = {       wechat: 'code',       tencent: 'code'     };     const codeName = hashObj[codeMap[this.auth_type]];     this.$store.dispatch('LoginByThirdparty', codeName).then(() => {       this.$router.push({         path: '/'       });     });   }復制代碼

到這里涉及登錄權限的東西也差不多講完了,這里樓主只是給了大家一個實現的思路(都是樓主不斷摸索的血淚史),每個公司實現的方案都有些出入,請謹慎選擇適合自己業務形態的解決方案。如果有什么想法或者建議歡迎去本項目下留言,一同討論。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/397181.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/397181.shtml
英文地址,請注明出處:http://en.pswp.cn/news/397181.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

注釋工具_蘋果已購丨Notability丨功能強大而簡單易用的筆記及PDF注釋工具

點擊上方“天澤黑科技”右上角“...”點選“設為星標”點擊加星★ 貼近你心 ?今天給大家購買效率類排行第3名的 Notability !大家在桌面 App store 登陸我的賬號,搜索下載即可!榮獲 iPad、iPhone 和 Mac 的 Apple「編」愛新 App 殊榮&#x…

第四章 大網高級 ? NSSA

STUB、完全stub、NSSA、完全nssa實驗要求:1、配置IP地址2、配置OSPF多區域3、配置 stub 末梢區域4、配置完全stub末梢區域5、配置 nssa 非純末梢區域6、配置完全nssa非純末梢區域7、配置兩種協議相互注入重分發8、實現全網互通一、配置OSPF多區域二、配置rip v2三、…

[AlwaysOn Availability Groups] 健康模型 Part 2 ——擴展

[AlwaysOn Availability Groups] 健康模型 Part 2 ——擴展 健康模型擴展 第一部分已經介紹了AlwayOn健康模型的概述。現在是創建一個自己的PBM策略,然后設置為制定的歸類。創建這些策略,創建之后修改一下配置,dashboard就會自動評估這些策略…

665. Non-decreasing Array - LeetCode

Question 665. Non-decreasing Array Solution 題目大意: 思路:當前判斷2的時候可以將當前元素2變為4,也可以將上一個元素4變為2,再判斷兩變化后是否滿足要求。 Java實現: public boolean checkPossibility(int[] nums…

如何制作印章_如何用Photoshop制作個性印章/文字圖片

帶印章和文字的圖片,不僅可以作為個人的標簽,更能直接表達照片的意境,讓片子與眾不同。那么,怎樣才能給照片加印章和文字呢?或許方法有很多,甚至有多款App也可以直接做效果。但想要做出精細的效果&#xff…

麒麟810處理器_麒麟810性能實測:對比驍龍845驍龍730,誰更強?

隨著榮耀9X、Nova5i Pro一眾新機發布,采用7nm工藝制程的全新麒麟810進入了我們的視野。以手機處理器性能劃分產品定位向來是最為直接的方法,在搭載麒麟810的榮耀9X將價格下探到1399元后,這枚網友口中“拳打845,腳踢730”的中端神u…

打造全鍵盤操作的PDF閱讀器

其實我只想要一個非常簡單的PDF閱讀器,不要很花哨的功能,只要能夠: 速度夠快,不要翻一頁等半天;全鍵盤操作,不想在鼠標和鍵盤之間來回倒騰;可以改變背景色,深夜的白光好刺眼&#xf…

python篩選含變量的特定行_Python SQL從特定變量字段中選擇行

我想從基于隨機變量的行中獲取一個特定的值。下面是一個示例表,PID列是一個“自動遞增主鍵整數”,其他兩列是文本示例表PID NAME PHONE--- ---- -----1 bill 999-99992 joe 888-8888我想把一個隨機變量扔到桌子上randomVariable raw_input(Enter someth…

mysql 導出dmp文件_一文帶你了解MySQL主從復制(Master-Slave)

1.復制概述Mysql內建的復制功能是構建大型,高性能應用程序的基礎。將Mysql的數據分布到多個系統上去,這種分布的機制,是通過將Mysql的某一臺主機的數據復制到其它主機(slaves)上,并重新執行一遍來實現的。復制過程中一個服務器充當…

iOS開發學無止境 - NSFileManager文件操作的十個小功能

(配圖的小故事還記得嘛) NSFileManager是一個單列類,也是一個文件管理器。可以通過NSFileManager創建文件夾、創建文件、寫文件、讀文件內容等等基本功能。 下面將介紹NSFileManager文件操作的十個小功能。我們在Documents里面進行舉例&#…

smokeping自動檢測系統

如何的使用smokeping來監控idc機房的網絡質量情況,從監控圖上的延時與丟包能分辨出你機房的網絡是否穩定,是否為多線,是否為BGP機房,到各城市的3個運行商網絡各是什么情況,如果出現問題,如果有針對的解決。…

ElasticSearch聚合分析

聚合用于分析查詢結果集的統計指標,我們以觀看日志分析為例,介紹各種常用的ElasticSearch聚合操作。 目錄: 查詢用戶觀看視頻數和觀看時長聚合分頁器查詢視頻uv 單個視頻uv批量查詢視頻uvHaving查詢 根據 count 進行過濾根據其它指標進行過濾…

bg感_【0328】BG推文 | 5本我在逃生游戲里養娃娃+歲月繾綣已無你+關于我比女主蘇這回事+消失的白月光又回來了等...

大家多多支持原文!以下內容多為網絡搜集,非商業用途。版權歸原作者所有,侵聯!BG文《我在逃生游戲里養娃娃》作者:鶴舫閑人《歲月繾綣已無你》作者:酒爺《關于我比女主蘇這回事》作者:歡何極《消…

android 屏幕最小寬度_AndroidTV屏幕適配-smallestWidth(最小寬度) 限定符

背景前幾天接到一個需求,把項目上的原來的2k屏幕適配到4k屏幕。我采用的是smallestWidth最小寬度限定符進行適配的我們項目的。1,smallestWidth 限定符適配原理系統都是根據限定符去尋找對應的 dimens.xml 文件。例如在最小寬度為 720dp 的設備上&#x…

mysql 組合索引

MySQL單列索引是我們使用MySQL數據庫中經常會見到的,MySQL單列索引和組合索引的區別可能有很多人還不是十分的了解,下面就為您分析兩者的主要區別,供您參考學習。 為了形象地對比兩者,再建一個表: CREATE TABLE myInde…

cmake使用總結(轉)---工程主目錄CMakeList文件編寫

在linux 下進行開發很多人選擇編寫makefile 文件進行項目環境搭建,而makefile 文件依賴關系復雜,工作量很大,搞的人頭很大。采用自動化的項目構建工具cmake 可以將程序員從復雜的makefile 文件中解脫出來。cmake 根據內置的規則和語法來自動生…

微信開發者工具 wxmi修改模版顏色_十款高效好用的在線網頁工具,提升你的辦公效率...

大家好, 我是阿毛,今天給大家推薦高效辦公的10個在線網頁工具,可以不用下載安裝很多app,也不用在電腦上裝很多軟件。在線制作精彩視頻操作非常簡單,選擇模板,上傳照片然后點擊制作等待完成就可以了&#xf…

三星ml1660拆機圖解_三星s6拆機圖解介紹

三星s6拆機圖解介紹三星s6怎么拆機?不管你是手機維修者還是狂熱的手機玩家,相信對您手中的三星s6內部構造和組裝步驟應該都是非常有興趣的吧?今天綠茶通過Fixit發布的三星s6拆機教程來和大家一起分享一下三星s6拆機步驟,從三星s6的內部構造一起來了解一…

Ajax請求利用jsonp實現跨域

跨域: js有一個同源限制,簡單說來源不一樣的話就無法相互間交互.那么怎么算來源不一樣呢, 舉個例子:瀏覽器訪問-->服務器A--->得到頁面A---頁面A中的js腳本只能訪問服務器A的資源(相同域名和端口,此外域名與對應的ip也算不同源,要么都域名,要么都ip). 以上就是js的跨域問…

[轉]使用Navicat for Oracle工具連接oracle的

使用Navicat for Oracle工具連接oracle的 這是一款oracle的客戶端的圖形化管理和開發工具,對于許多的數據庫都有支持。之前用過 Navicat for sqlserver,感覺很好用,所以下載了Oracle版的用。上網查看了一下這個工具可以用于任何版本 8i 或以上的 Oracle …