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&timestamp=%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))
}