??關于云審批
云審批(cloud approve)
,一款專為小微企業打造,支持多租戶的在線審批神器。它簡化了申請和審批流程,讓您隨時隨地通過手機或電腦完成請款操作。員工一鍵提交申請,審批者即時響應,方便快捷。同時,云審批提供全面的數據記錄與分析,助力企業實現財務管理透明化、智能化,安全高效,讓企業的信息數字化管理變得簡單輕松!最后,重要的事情說三遍📢:開源、開源、所有代碼開源。
👉GITHUB開源地址 👈
👉飛書在線文檔👈
概述
釘釘免登
(此處專指自建H5微應用,官方文檔)是一種便捷的登錄機制,當用戶已在釘釘客戶端(包括PC端和移動端)完成登錄后,通過工作臺訪問我們的網站時,系統能夠自動識別并完成用戶身份驗證,無需重復輸入登錄信息。該功能廣泛應用于微信、飛書、抖音等主流平臺,為用戶提供無縫的跨平臺使用體驗。
流程詳解
數據表
登錄模塊設計到兩個表:賬號表/Account、員工表/Staff。
賬號/Account
此表為登錄到平臺的賬戶信息,支持傳統的賬密方式、釘釘免登等方式,并記錄與之關聯的員工ID
字段名 | 中文名 | 類型 | 必填 | 默認值 | 說明 |
---|---|---|---|---|---|
id | 編號 | Int | 是 | 唯一標識 | |
cid | 企業ID | Int | 是 | 關聯企業 | |
name | 賬號名稱 | String | 是 | ||
pwd | 密碼 | String | 加密 | ||
type | 類型 | String | 是 | 登錄類型 | |
sid | 員工ID | Int | 關聯員工 | ||
active | 是否生效 | Boolean | 是 | false | |
addOn | 錄入日期 | Int | 是 |
登錄類型:
- dingding=釘釘
- wechat=微信
- phone=手機號(未來支持手機驗證碼登錄)
- other=其它(傳統密碼登錄)
員工/Staff
字段名 | 中文名 | 類型 | 必填 | 默認值 | 說明 |
---|---|---|---|---|---|
id | 編號 | Int | 是 | 唯一標識 | |
cid | 企業ID | Int | 是 | 關聯企業 | |
name | 員工名稱 | String | 是 | ||
phone | 電話號碼 | String | |||
summary | 描述 | String |
免登流程
- 準備階段
- 企業管理員登錄
釘釘開發者后臺
,創建應用并配置網頁功能
- 獲取應用的
AppKey
與AppSecrect
- 企業管理員登錄
- 邏輯實現
- 新建釘釘登錄專用頁面(dingding.html)
- 在頁面中獲取兩個參數
cid(企業ID)
、corpId(釘釘內企業ID)
- 前端調用釘釘接口獲取授權碼/CODE
- 后端拿到上述 CODE 后通過
AppKey
與AppSecrect
獲取到用戶信息(包含唯一編號 unionid、姓名 name 等) - 構建唯一賬戶名:D_{unionid}_{name}
- 檢查強求賬戶名是否存在于 Account 表
- 如存在則判斷是否生效,若生效返回token,否則報錯
- 若不存在
- 自動創建
賬戶信息
- 檢索企業下
同名員工
,若不存在則自動創建并關聯到賬戶對象 - 若配置了賬戶自動生效,返回 token,否則前端提示賬戶未激活請聯系管理員
- 自動創建
- 部署上線
- 部署平臺獲取到登錄頁 URL(https://{域名}/dingding.html)
- 在釘釘后臺填入上述地址后發布應用版本
- 用戶在釘釘客戶端
工作臺
添加應用后即可訪問
新建 dingding.html
我們在前端項目代碼下新建對應頁面:
并在 rsbuild.config.mjs
中配置多頁面:
export default defineConfig({source:{entry:{index: './src/index.js',dingding: './src/pages/dingding/index.js'}}
})
至此,我們可以通過 http://{IP}/dingding.html
訪問到該頁面,作為釘釘免登的入口😄。
編寫登錄頁面邏輯
登錄頁主要組件 App.vue 代碼如下:
<template><div style="width: 80%; margin: 40px auto;"><div class="text-center" v-if="!errMsg"><n-spin :show="working"><template #description>釘釘客戶端登錄中,請稍候...</template></n-spin></div><n-alert v-else :type show-icon title="釘釘自動登錄失敗" :bordered="false">{{ errMsg }}</n-alert></div>
</template><script setup>import { NSpin, NAlert, useMessage, NMessageProvider } from 'naive-ui'import { requestAuth } from "./dingding"import { checkLocalToken, saveLocalToken } from "../login"const msg = useMessage()let cid = undefinedlet corpId = undefinedconst debug = import.meta.env.DEV;let working = ref(true)let errMsg = ref("")let type = ref("info")const onMsg = (msg, isError=true)=>{errMsg.value = msgtype.value = isError?"error":"info"}const tryToAutoLogin = ()=>{requestAuth(corpId).then(code=>{msg.info(`CODE=${code}`)fetch("/common/login-with-dingding",{method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ cid, code })}).then(response => response.json()).then(({ success, data, message }) => {if(success==true){msg.success(`登陸成功`)saveLocalToken(data)gotoIndex()}else{const messages = {E01 : "您的釘釘賬戶為首次登錄,請先聯系管理員完成激活",E02 : "您的釘釘賬戶關聯未激活,請聯系企業管理員",E03 : "找不到當前釘釘賬戶關聯的員工"}onMsg(messages[message]||message, !messages[message])}})}).catch(e=>onMsg(typeof(e)=='string'?e:e.message))}const gotoIndex = ()=> location.href = "/"onMounted(() => {let u = new URL(location.href)cid = u.searchParams.get('cid')corpId = u.searchParams.get('corpId')if(checkLocalToken())return gotoIndex()tryToAutoLogin()})
</script>
// dingding.js
import { runtime } from 'dingtalk-jsapi'export const requestAuth = async (corpId)=> {let { code } = await runtime.permission.requestAuthCode({corpId})return code
}//login.js
const NAME = import.meta.env.PUBLIC_HEADER_TOKEN
const CREATED = `${NAME}_CREATED`/*** 檢查本地 token 是否在有效期內* @param {Number} expired - token 有效期,默認12小時,單位毫秒* @returns {Boolean} true 時為 token 有效*/
export const checkLocalToken = (expired=12*60*60*1000)=>{let token = localStorage.getItem(NAME)if(!token) return falselet expire = localStorage.getItem(CREATED)||0if(Date.now() - expire>=expired)return falsereturn true
}export const saveLocalToken = token=>{localStorage.setItem(NAME, token)localStorage.setItem(CREATED, Date.now())
}
這里不得不吐槽下釘釘開發平臺的官網文檔,新舊版 API 文檔特別容易讓人混亂,引入dingtalk-jsapi
的話需要查看舊版文檔😔。
編寫后端與釘釘服務器的交互
const { get, post } = require('axios')
const { loadWithCidAndName } = require("./ConstantService")
const logger = require('../common/logger')/*** @typedef {Object} DDTokenResponse - 釘釘獲取token效應值* @property {String} access_token - token值* @property {Number} expires_in - 有效期(單位秒)* @property {Number} errcode - 錯誤代碼* @property {String} errmsg - 錯誤信息** @typedef {Object} DDUser - 釘釘用戶信息* @property {String} userid* @property {String} unionid - 唯一編號* @property {String} name - 用戶名稱** @typedef {Object} DDUserResponse - 釘釘用戶信息響應值* @property {DDUser} result* @property {String} request_id* @property {Number} errcode - 錯誤代碼* @property {String} errmsg - 錯誤信息*/const DING_HOST = "https://oapi.dingtalk.com"let localToken = {value: "",expire: 0
}const isTokenExpired = ()=> !localToken.value || localToken.expire<=Date.now()
const log = (msg, level='info')=> logger[level](`[釘釘] ${msg}`)exports.loginWithCode = async (cid, code)=>{if(isTokenExpired()){/**@type {CompanyConfig} */let cfg = await loadWithCidAndName(cid)if(!cfg || !(cfg.ddAppKey && cfg.ddAppSecret))throw `企業未配置釘釘登錄`let url = `${DING_HOST}/gettoken?appkey=${cfg.ddAppKey}&appsecret=${cfg.ddAppSecret}`/**@type {{data:DDTokenResponse}} */let { data } = await get(url)if(data.errcode != 0){log(`獲取企業 token 失敗:${data.errcode}|${data.errmsg}`, 'error')throw data.errmsg}localToken.value = data.access_tokenlocalToken.expire = Date.now() + data.expires_in*1000log(`更新 TOKEN 為 ${localToken.value}(EXPIRED=${data.expires_in})`)}let url = `${DING_HOST}/topapi/v2/user/getuserinfo?access_token=${localToken.value}`/**@type {{data:DDUserResponse}} */let { data } = await post(url, { code })if(data.errcode != 0){log(`[釘釘] 獲取用戶信息失敗:${data.errcode}|${data.errmsg}`, 'error')throw data.errmsg}global.isDebug && log(`獲取用戶信息 ${data.result.userid}/${data.result.name}`, 'debug')return data.result
}
部署及上線
創建釘釘H5微應用
- 登錄釘釘開發者后臺。
- 單擊應用開發 > 企業內部應用 > 釘釘應用 > 創建應用。
- 填寫應用信息。
配置項 | 是否必填 | 配置說明 |
---|---|---|
應用名稱 | 是 | 輸入應用名稱,應用名稱最小長度為 2 個字符。 |
應用描述 | 是 | 簡要描述應用提供的產品或服務,應用描述最小長度為 4 個字符。 |
應用圖標 | 否 | 上傳應用圖標,圖標要求 JPG/PNG 格式、240 px * 240 px 以上、1:1 、2 MB 以內的無圓角圖標。 |
- 單擊保存,進入應用詳情頁。
- 如果你需要開發 AI 應用、小程序、網頁應用、酷應用和機器人功能,你需要添加應用能力。
發布應用
創建應用后,需要發布才能看到噢
接著在釘釘客戶端
就能看到此應用啦🎉