1、首先在钉钉开放平台,配置移动接入应用的登录,域名,获取appid 和 appSecret,(后台服务需要)
2、我在前端的登录页面放置了钉钉的扫码登录的二维码,
login.vue代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | <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
1 2 3 4 5 6 7 8 9 10 11 | //判断当前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
1 2 3 | AppKey: dingoasxxxxxxxxxxxuezexjo AppSecret: kdLP8FbJY2xxxxxxxxxxxxxxx5gRUyU_7gHL432hpuL6Qul2jF DDServerAddress: https://oapi.dingtalk.com/sns/getuserinfo_bycode //钉钉后台用code请求当前扫码人的信息链接 |
读取配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | 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)) } |