1、首先在釘釘開放平臺,配置移動接入應用的登錄,域名,獲取appid 和 appSecret,(後臺服務需要)
2、我在前端的登錄頁面放置了釘釘的掃碼登錄的二維碼,
login.vue代碼
<template>
<div class="login-container">
<div id="login_container" class="dingding_scan" ></div>
</div>
</template>
<script>
import { ddlogin } from "@/api/dashboard";
import { getQueryVariable } from "@/utils";
export default {
name: "Login",
data() {
return {}
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true
}
},
created() {
},
mounted() {
this.ddLogin();
},
destroyed() {
},
methods: {
ddLogin() {
let code = getQueryVariable("code"); //查看當前Url中有沒有state的這個參數,如果有這個參數證明掃碼登錄成功重定向地址已經調轉完成
if (code) {
let state = getQueryVariable("state");
let temp = {
code: code,
state: state
};
//釘釘跳轉的url後會攜帶code,state,要傳給後臺,即調用後臺api
//因爲系統需要,我在狀態裏面調用並報錯了,你們可以自行寫自己的接口調用就行
this.$store.dispatch("user/ddlogin", temp).then(() => {
//調用後,會獲取當前系統的權限信息和登錄用戶的信息
// 因爲我是跳轉到當前頁面,所以,當前頁面直接回帶codehe state,
//我調用api後,就不需要這些參數了,就刪除了了url後攜帶的參數,直接跳轉首頁
var url = window.location.href; //獲取當前頁面的url
if (url.indexOf("?") != -1) {
//判斷是否存在參數
url = url.replace(/(\?|#)[^'"]*/, ""); //去除參數
window.history.pushState({}, 0, url);
}
//然後跳轉首頁
this.$router.replace({ path: "/" });
});
} else {
//默認二維碼的顯示
let appid='dingoasvam8tvfkuezexjo'
let strurl= window.location.protocol+"//"+window.location.host+'/'
let url = encodeURIComponent(strurl);
console.log('window.location.host 跳轉',url)
let goto = encodeURIComponent(
"https://oapi.dingtalk.com/connect/qrconnect?appid="+appid +"&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=" +
url
);
var obj = DDLogin({
id: "login_container",
goto: goto,
style: "border:none;background-color:#FFFFFF;",
width: "365",
height: "400"
});
var hanndleMessage = function(event) {
var origin = event.origin;
// console.log("origin", event.origin);
//判斷是否來自ddLogin掃碼事件。
if (origin == "https://login.dingtalk.com") {
var loginTmpCode = event.data; //拿到loginTmpCode後就可以在這裏構造跳轉鏈接進行跳轉了
// console.log("loginTmpCode", loginTmpCode);
if (loginTmpCode) {
window.location.href =
"https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid="+appid+"&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=" +
url +
"&loginTmpCode=" +
loginTmpCode;
//拿到用戶唯一標示然後在進行當前頁面的調轉,注意這裏跳轉以後還是會在當前頁面只不過Url上帶了參數了這時候咱們去看上面的條件
}
}
};
if (typeof window.addEventListener != "undefined") {
window.addEventListener("message", hanndleMessage, false);
} else if (typeof window.attachEvent != "undefined") {
window.attachEvent("onmessage", hanndleMessage);
}
}
},
}
};
</script>
<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.dingding_scan{
margin-top: 10%;
float: right;
width: 500px;
height: 500px;
}
}
</style>
2、utils文件裏面跑出的方法:getQueryVariable
//判斷當前url是否攜帶參數
export function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
go 配置文件dingding.yaml
AppKey: dingoasxxxxxxxxxxxuezexjo
AppSecret: kdLP8FbJY2xxxxxxxxxxxxxxx5gRUyU_7gHL432hpuL6Qul2jF
DDServerAddress: https://oapi.dingtalk.com/sns/getuserinfo_bycode //釘釘後臺用code請求當前掃碼人的信息鏈接
讀取配置文件
package dd
import (
"io/ioutil"
log "github.com/Sirupsen/logrus"
"gopkg.in/yaml.v2"
)
var DdConf DDConf
type DDConf struct {
AppKey string `yaml:"AppKey"`
AppSecret string `yaml:"AppSecret"`
DDServerAddress string `yaml:"DDServerAddress"`
}
func init() {
yamlFile, err := ioutil.ReadFile("./conf/dingding.yaml")
if err != nil {
log.Fatal("init conf/dingding.yaml發生錯誤:", err)
}
err = yaml.Unmarshal(yamlFile, &DdConf)
if err != nil {
log.Fatal("init conf/dingding.yaml發生錯誤:", err)
}
return
}
go 後臺服務api
package user
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
dd "server/common/dd"
jwt "server/common/jwt"
models "server/models"
table_user "server/models/table/user"
"time"
log "github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
)
type DdLogin struct {
}
type dDLoginReq struct {
TmpAuthCode string `json:"tmp_auth_code"` //臨時授權碼
}
type RetDDUserInfo struct {
Nick string `json:"nick"` //釘釘暱稱
Unionid string `json:"unionid"` //Unionid
DingId string `json:"dingId"` //DingId
Openid string `json:","openid"` //Openid
MainOrgAuthHighLevel bool `json:"main_org_auth_high_level"` //MainOrgAuthHighLevel
}
type DDResp struct {
Errcode int64 `json:"errcode"` //錯誤碼
Errmsg string `json:"errmsg"` //錯誤信息
UserInfo RetDDUserInfo `json:"user_info"` //用戶信息
}
type RetddModule struct {
Id int64 `json:"id"`
Name string `json:"name"` //模塊名字
Op int64 `json:"op"` //模塊權限定義值
}
type dDLoginResp struct {
StatusCode int `json:"status_code"` //狀態碼
StatusMsg string `json:"status_msg"` //狀態信息
UserId int64 `json:"user_id"` //用戶id
Name string `json:"name"` //用戶名稱
RoleId int64 `json:"role_id"` //用戶所屬角色id
RoleName string `json:"role_name"` //用戶所屬角色名稱
Phone string `json:"phone"`
Email string `json:"email"`
Avatar string `json:"avatar"`
JobNumber string `json:"job_number"`
Token string `json:"token"` //登錄token
Modules []RetddModule `json:"modules"` //模塊定義
}
// @Summary 釘釘登錄接口
// @Description 無
// @Tags user
// @Accept json
// @Produce json
// @Param 請求體 body user.dDLoginReq true "請求體"
// @Success 200 {object} user.dDLoginResp "返回體"
// @Router /algorithm_platform_api/v1/user/dd_login [post]
func (this *DdLogin) DdLogin(c *gin.Context) {
var resp dDLoginResp
str_code, _ := c.GetQuery("code")
timestamp := time.Now().UnixNano() / 1e6
strTimeStamp := fmt.Sprintf("%d", timestamp)
appKey := dd.DdConf.AppKey // 讀取配置文件 appid
appSecret := dd.DdConf.AppSecret
signature := ComputeHmacSha256(strTimeStamp, appSecret) //簽名
signature = url.QueryEscape(signature)
//post請求提交json數據
var ddreq dDLoginReq
ddreq.TmpAuthCode = str_code
ba, _ := json.Marshal(ddreq)
targetUrl := fmt.Sprintf("%s?accessKey=%s×tamp=%d&signature=%s", dd.DdConf.DDServerAddress, appKey, timestamp, signature)
tr := &http.Transport{
////把從服務器傳過來的非葉子證書,添加到中間證書的池中,使用設置的根證書和中間證書對葉子證書進行驗證。
// TLSClientConfig: &tls.Config{RootCAs: pool},
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //InsecureSkipVerify用來控制客戶端是否證書和服務器主機名。如果設置爲true,//
//則不會校驗證書以及證書中的主機名和服務器主機名是否一致。
}
client := &http.Client{Transport: tr}
resp_dingding, err := client.Post(targetUrl, "application/json", bytes.NewBuffer([]byte(ba)))
if err != nil {
log.Error(err)
resp.StatusCode = 1001
resp.StatusMsg = "請求參數錯誤"
c.JSON(http.StatusOK, resp)
return
}
defer resp_dingding.Body.Close()
body, err := ioutil.ReadAll(resp_dingding.Body)
if err != nil {
log.Error(err)
resp.StatusCode = 1001
resp.StatusMsg = "請求參數錯誤"
c.JSON(http.StatusOK, resp)
return
}
log.Debug("到這裏 釘釘登錄=user信息==========================:", string(body))
log.Debug("下面是我平臺系統的邏輯 ==========================:")
var ddResp DDResp
//解析json結構體
json.Unmarshal([]byte(body), &ddResp)
//先查找釘釘用戶表,用Unionid查找,找到返回信息,找不到插入信息
diongdingUser := new(table_user.DdUser)
log.Debug("ddResp=先查找釘釘用戶表,用Unionid查找,找到返回信息,找不到插入信息==================================:")
has, err := models.UserDb.Where("`unionid` = ?", ddResp.UserInfo.Unionid).Get(diongdingUser)
if err != nil {
log.Error("查詢DdUser表發生錯誤:", err.Error())
resp.StatusCode = 1003
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
//之前存在系統中按協議返回,不存在則新加user表和dd_user
if has {
user := new(table_user.User)
//has, err := models.UserDb.Id(diongdingUser.UserId).Get(user)
has, err := models.UserDb.Where("`id` = ?", diongdingUser.UserId).Get(user)
if err != nil {
log.Error("查詢user表發生錯誤:", err.Error())
resp.StatusCode = 1004
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
if !has {
log.Error("user表中沒發現id:", diongdingUser.UserId)
resp.StatusCode = 1005
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
//是否是禁止登錄用戶
if user.State == PROHIBIT_LOGIN {
log.Warn("系統禁止登錄用戶:", diongdingUser.Name)
resp.StatusCode = 1006
resp.StatusMsg = "系統禁止登錄用戶"
c.JSON(http.StatusOK, resp)
return
}
role := new(table_user.Role)
//has, err = models.UserDb.Id(user.RoleId).Get(role)
has, err = models.UserDb.Where("`id` = ?", user.RoleId).Get(role)
if err != nil {
log.Error(err)
resp.StatusCode = 1007
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
if !has {
log.Warn(fmt.Sprintf("role表中沒有id:%d", user.RoleId))
resp.StatusCode = 1008
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
permissions := make([]table_user.Permission, 0)
err = models.UserDb.Where("`role_id` = ?", user.RoleId).Find(&permissions)
if err != nil {
log.Error(err)
resp.StatusCode = 1009
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
modules := make([]RetddModule, 0)
for i := 0; i < len(permissions); i++ {
module_id := permissions[i].ModuleId
module := new(table_user.Module)
//has, err = models.UserDb.Id(module_id).Get(module)
has, err = models.UserDb.Where("`id` = ?", module_id).Get(module)
if err != nil {
log.Error(err)
resp.StatusCode = 1010
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
if !has {
log.Warn(fmt.Sprintf("moudle表中沒有發現id:%d", module_id))
resp.StatusCode = 1011
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
var retModule RetddModule
retModule.Id = module.Id
retModule.Name = module.Name
retModule.Op = permissions[i].CrudOperation
modules = append(modules, retModule)
}
token := jwt.GenToken(user.Id)
resp.StatusCode = 1000
resp.StatusMsg = "ok"
resp.UserId = user.Id
resp.Name = ddResp.UserInfo.Nick
//從釘釘後臺獲取的信息
// resp.Phone = userInfo.Tel
// resp.Email = userInfo.Email
// resp.Avatar = userInfo.Avatar
// resp.JobNumber = userInfo.JobNumber
resp.RoleId = role.Id
resp.RoleName = role.Name
resp.Token = token
resp.Modules = modules
c.JSON(http.StatusOK, resp)
return
} else {
//這裏看是否要按名稱綁定,現在不添加綁定邏輯,只有添加新的
//先拿到默認角色
role := new(table_user.Role)
has, err = models.UserDb.Where("`level` = ?", DEFAULT_USER_ROLE_LEVEL).Get(role)
if err != nil {
log.Error(err)
resp.StatusCode = 1012
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
if !has {
log.Warn(fmt.Sprintf("role表中沒有level:%d", DEFAULT_USER_ROLE_LEVEL))
resp.StatusCode = 1013
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
//先生成user表記錄
//這塊最好的做法是能過事務來做,但golang xorm沒有發現沒有Flush方法,沒法獲取到先提交的用戶id
//不存在系統中增加到系統中,生成默認角色
var user table_user.User
user.Name = ddResp.UserInfo.Nick
// user.Unionid = ddResp.UserInfo.Unionid
// user.Email = userInfo.Email
// user.Phone = userInfo.Tel
user.RoleId = role.Id
//開始事務時這裏不能用models.DB.Insert得用session.Insert
_, err = models.UserDb.Insert(&user)
if err != nil {
log.Error(err)
resp.StatusCode = 1014
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
//再生成dd_user表記錄
var diongdingUser table_user.DdUser
//這個id能自動映射
diongdingUser.Unionid = ddResp.UserInfo.Unionid
diongdingUser.UserId = user.Id
diongdingUser.Name = ddResp.UserInfo.Nick
diongdingUser.Openid = ddResp.UserInfo.Openid
// diongdingUser.Email = userInfo.Email
// diongdingUser.Phone = userInfo.Tel
_, err = models.UserDb.Insert(&diongdingUser)
if err != nil {
log.Error("增加dd用戶表記錄發生錯誤:", err)
//要刪除之前添加的用戶
_, err = models.UserDb.Where("id = ?", user.Id).Delete(new(table_user.User))
if err != nil {
log.Error("增加dd用戶表記錄失敗,在刪除對應的user表中記錄時發生錯誤:", err)
}
resp.StatusCode = 1015
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
//user role都有了,下面邏輯和上面一樣
permissions := make([]table_user.Permission, 0)
err = models.UserDb.Where("`role_id` = ?", user.RoleId).Find(&permissions)
if err != nil {
log.Error(err)
resp.StatusCode = 1009
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
modules := make([]RetddModule, 0)
for i := 0; i < len(permissions); i++ {
module_id := permissions[i].ModuleId
module := new(table_user.Module)
has, err = models.UserDb.Id(module_id).Get(module)
if err != nil {
log.Error(err)
resp.StatusCode = 1010
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
if !has {
log.Warn(fmt.Sprintf("moudle表中沒有發現id:%d", module_id))
resp.StatusCode = 1011
resp.StatusMsg = "系統內部錯誤"
c.JSON(http.StatusOK, resp)
return
}
var retModule RetddModule
retModule.Id = module.Id
retModule.Name = module.Name
retModule.Op = permissions[i].CrudOperation
modules = append(modules, retModule)
}
token := jwt.GenToken(user.Id)
resp.StatusCode = 1000
resp.StatusMsg = "ok"
resp.UserId = user.Id
resp.Name = user.Name
resp.RoleId = role.Id
resp.RoleName = role.Name
//從釘釘後臺獲取的信息
// resp.Phone = userInfo.Tel
// resp.Email = userInfo.Email
// resp.Avatar = userInfo.Avatar
// resp.JobNumber = userInfo.JobNumber
resp.Token = token
resp.Modules = modules
c.JSON(http.StatusOK, resp)
return
}
}
//釘釘簽名
func ComputeHmacSha256(message string, secret string) string {
key := []byte(secret)
h := hmac.New(sha256.New, key)
h.Write([]byte(message))
sha := h.Sum(nil)
return base64.StdEncoding.EncodeToString([]byte(sha))
}