目录
微信绑定登录
抖音小程序开发
顶象无感验证
企业微信代开发
微信绑定登录
1.实现微信绑定有两种方式
- 微信服务号(需用户关注服务号)
- 微信开放平台- OAuth2.0(以下采用这种方式)
2.微信开放平台OAuth2.0授权登录材料
- 开发者账号(通过资质认证)
- 账号下有一个已审核通过的网站应用
- 申请微信登录功能且通过审核
3.用户使用微信登录流程:
- 请求微信OAuth2.0授权登录:
打开以下连接(将微信登录按钮设置成跳转到以下网址,然后后端返回微信生成的二维码)1
2
3
4
5
6
7
8
9https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
# 参数说明:
# appid:应用唯一标识(前面认证网页应用中获得)
# redirect_uri:重定向地址,需要进行UrlEncode(前面认证网页应用中获得)
# response_type:填code
# scope:应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
# 返回:
# 二维码 - 用户扫码授权后跳转到重定向地址并附参数code
- 重定向的地址如下,获得code后去申请获得openid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
# 参数说明:
# appid:应用唯一标识,在微信开放平台提交应用审核通过后获得
# secret:应用密钥AppSecret,在微信开放平台提交应用审核通过后获得
# code:填写第一步获取的code参数
# grant_type:填authorization_code
# 返回:
# {
# "access_token":"ACCESS_TOKEN",
# "expires_in":7200,
# "refresh_token":"REFRESH_TOKEN",
# "openid":"OPENID",
# "scope":"SCOPE",
# "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
# } - 使用获得的open_id在数据库中找用户资料
- 若找到则以该用户身份生成token登录
- 若找不到则提示未绑定
4.整体实现有两种方案:
- 方案一:
- 前端:显示二维码(这里可以内嵌在网页里)
- 后端:用重定向跳转的路由接受code,用code去申请获得openid,然后与数据库中对照
- 方案二:
- 后端负责所有工作:显示二维码(这里只能显示一个页面来单独展示二维码),用重定向跳转的路由接受code,用code去申请获得openid,然后与数据库中对照
抖音小程序开发
1.小程序项目中单个页面会依赖不同类型的文件
- .json 后缀的 JSON 配置文件
- .ttml 后缀的 TTML 模板文件
- .ttss 后缀的 TTSS 样式文件
- .js 后缀的 JS 脚本文件
2.小程序的目录结构
1 | . |
3.一个简体流程就是
- 根目录
- app.js和app.ttss都可以空着不写
- app.json里改一下navigationBarTitleText用来显示标题栏名称
- project.config.json里面填一下appid和projectname就行
- 在pages文件夹里写
- index.ttml文件里写网页结构,语法和html相似,但关键字不一样
- index.js文件,用来写函数
- index.ttss文件用来写index网页的样式
4.个位计算器的demo
- 实现功能:两个个位数字的+-*/运算,有两个按钮计算、随机,点击随机会自动生成不同数字和运算符,点击计算会根据当前运算式显示结果
- index.ttml
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<view class="intro">Welcome to Sc-test</view>
<view class="content">
<!--显示块-->
<view class="content_up_1">
<picker class="calculation" value="{{index_1}}" range="{{array}}" data-value="1" bindchange="bindPickerChange" bindcancel="bindPickerCancel">
<view class="picker">
{{array[index_1]}}
</view>
</picker>
<picker class="calculation" value="{{cal_num}}" range="{{cal_array}}" data-value="1" bindchange="bindPickerChange" bindcancel="bindPickerCancel">
<view class="picker">
{{cal_array[cal_num]}}
</view>
</picker>
<picker class="calculation" value="{{index_2}}" range="{{array}}" data-value="1" bindchange="bindPickerChange" bindcancel="bindPickerCancel">
<view class="picker">
{{array[index_2]}}
</view>
</picker>
</view>
<!--
<view>
<button type="primary" bindtap="func1">页面主操作 Normal</button>
<button type="primary" loading="true">页面主操作 Loading</button>
<button type="primary" disabled="true">页面主操作 Disabled</button>
</view>
-->
<view class="content_up_1">
<view class="calculation_submit" bindtap="bindSubmit">计算</view>
<view class="calculation_random" bindtap="bindRandom">随机</view>
<!-- <view class="calculation_clear" bindtap="bindClear">清空</view> -->
</view>
<view class="content_down">
<view class="text">{{result}}</view>
</view> - index.js
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
41const app = getApp()
var that
Page({
data: {
array: [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
],
cal_array: [
'+', '-', '*', '/',
],
index_1: 2,
index_2: 0,
cal_num: 0,
result: '',
},
onLoad: function () {
console.log('Welcome to Mini Code')
},
bindSubmit: function(e){
var temple
if(this.data.cal_array[this.data.cal_num] == '+') temple = this.data.array[this.data.index_1] + this.data.array[this.data.index_2]
if(this.data.cal_array[this.data.cal_num] == '-') temple = this.data.array[this.data.index_1] - this.data.array[this.data.index_2]
if(this.data.cal_array[this.data.cal_num] == '*') temple = this.data.array[this.data.index_1] * this.data.array[this.data.index_2]
if(this.data.cal_array[this.data.cal_num] == '/') temple = this.data.array[this.data.index_1] / this.data.array[this.data.index_2]
this.setData({
result: temple,
})
},
bindRandom: function (e) {
this.setData({
index_1: Math.floor(Math.random() * 10),//下标,0到9的随机整数
index_2: Math.floor(Math.random() * 10),//下标,0到9的随机整数
cal_num: Math.floor(Math.random() * 4),//下标,0到2的随机整数
result: '',
})
},
}) - index.ttss
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.intro {
margin: 30px;
text-align: center;
}
.my-button {
border-radius: 20px;
}
.my-button:after {
border-color: #f00;
border-radius: 40px; /* 需要设置为按钮圆角的两倍 */
}
.my-button-2 {
border: 1px solid;
}
.my-button-2:after {
display: none;
}
.content {
display: flex;
flex-direction: column; /*orientation 排版 column:垂直 row:水平*/
}
.content_up{
width: max-content;
height: 300rpx;
}
.content_up_1{
margin-top: 20rpx;
width: max-content;
display: flex;
flex-direction: row; /*orientation 排版 column:垂直 row:水平*/
align-items: center;
justify-content: center;
text-align: center;
}
.picker{
height: 80rpx;
color:#585858;
text-align: center;
justify-content: center;
align-items: center;
display: flex;
}
.calculation_random{
width: 155rpx;
height: 80rpx;
border-radius:8rpx;
margin-left: 22rpx;
background: rgba(83,217,105,1);
color:#ffffff;
text-align: center;
justify-content: center;
align-items: center;
display: flex;
}
.calculation_submit{
width: 155rpx;
height: 80rpx;
border-radius:8rpx;
margin-left: 22rpx;
background: rgb(3, 78, 16);
color:#ffffff;
text-align: center;
justify-content: center;
align-items: center;
display: flex;
}
.calculation{
width: 158rpx;
height: 80rpx;
border-radius:8rpx;
margin-left: 22rpx;
border:1rpx solid rgba(209,209,209,1);
background: #F9F9F9;
}
.text{
height: 80rpx;
color:#585858;
text-align: center;
justify-content: flex-start;
align-items: center ;
margin-left: 20rpx;
display: flex;
}
5.抖音小程序与后端交互
- 前端js部分(写在某个函数里面的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const task = tt.request({
url: '127.0.0.1:8080/index/bar', // 目标服务器url
dataType: 'string',
success: (res) => {
this.setData({
result: res.data,
})
},
fail: (res) => {
console.log('get失败')
this.setData({
err_inf: res.errMsg,
})
},
}); - 后端部分
1
2
3
4
5
6
7
8
9
10func main() {
router := gin.Default()
router.GET("/index/bar", response)
router.Run()
}
func response(c *gin.Context) {
str := "123456789"
c.String(200, str)
fmt.Println(str)
}
顶象无感验证
1.使用无感验证之前需要在顶象控制台新建一个实例
- 输入:自己页面运行的域名
- 输出:生成独有的appid、appsecret和接入域名(需要在后端和前端手动配置)
2.两种模式,在顶象自己的控制台上切换
- 用户通过人机识别后,开启无感验证,利用地理位置等信息记忆该用户,后续不需要再进行人机识别,用户每次滑动人机验证成功后就会得到一个token
- 不使用无感验证,每次使用都需要进行人机识别
3.前端三种表现形式
- 嵌入式,直接在网页上展示完全验证界面
1
2
3
4
5
6
7
8var demo_1 = _dx.Captcha(document.getElementById('demo-embed'), {
appId: appId,
style: 'embed',
width: 300,
success: function(token) {
window.console && console.log('success, token:', token)
}
}) - 向上浮现的内联式,一个滚动条,用户拉取时才在其上方浮现验证界面
1
2
3
4
5var demo_2 = _dx.Captcha(document.getElementById('demo-inline-up'), {
appId: appId,
style: 'inline',
language: 'en' // 表示滚动条内的语言为英语
}) - 向下浮现的内联式
1
2
3
4
5var demo_3 = _dx.Captcha(document.getElementById('demo-inline-down'), {
appId: appId,
style: 'inline',
inlineFloatPosition: 'down'
}) - 弹出式,一个按钮,点击后弹出验证界面
1
2
3
4
5
6
7
8var demo_4 = _dx.Captcha(document.getElementById('demo-popup'), {
appId: appId,
style: 'popup'
})
document.getElementById('btn-popup').onclick = function() {
demo_4.show()
}
4.Go写的后端(gin框架)
1 | package main |
5.流程
- 前端进行人机验证:
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<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=8">
<link rel="shortcut icon" href="https://dingxiang-inc.com/favicon.ico">
<title>{{.title}}</title>
<script src="https://cdn.dingxiang-inc.com/fe/common/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdn.dingxiang-inc.com/fe/common/beautify/1.6.14/beautify.js"></script>
</head>
<body>
<script src="https://cdn.dingxiang-inc.com/ctu-group/captcha-ui/index.js"></script>
<div id="c1"></div>
<div class="ln">
<h2>人机验证</h2>
<div class="block">
<div class="eg">
<div id="demo-embed"></div>
</div>
<script>
var demo_1 = _dx.Captcha(document.getElementById('demo-embed'), {
appId: 'fb6d598f6f2911728e76ba2e60ca9ae0',
apiServer: 'cap.dingxiang-inc.com',
style: 'embed',
width: 300,
success: function (token) {
$.ajax({
type:"GET",
url:"http://127.0.0.1:8080/index/bar",
dataType:"text",
data: { token: token}
});
//window.console && console.log('success, token:', token)
window.location.href="http://127.0.0.1:8080/index/done"
}
})
</script>
</div>
</div>
<script>
function getCfg (key) {
try {
return localStorage.getItem(key)
} catch (e) {
return undefined
}
}
function setCfg (key, val) {
try {
localStorage.setItem(key, val)
} catch (e) {
}
}
var WG_KEY = 'dx-captcha-demo-wg'
var g_wg = getCfg(WG_KEY) !== '0'
function updateStateInfo (v) {
if (!v) {
_dx.Captcha._clearVID()
}
}
$(document).ready(function () {
$('pre').each(function () {
var pre = $(this)
var s = pre.parent().find('script').html()
pre.html(js_beautify(s, {indent_size: 2}))
})
if (g_wg) {
$("#switchOn").prop('checked', true)
} else {
$("#switchOff").prop('checked', true)
}
updateStateInfo(g_wg)
})
function updateCheckStatus(obj) {
var val = !!parseInt(obj.value)
g_wg = val
setCfg(WG_KEY, val ? 1 : 0)
updateStateInfo(val)
}
function resetCaptcha (idx) {
// _dx.Captcha._clearVID()
if (!g_wg) {
_dx.Captcha.getByIdx(idx).reload()
}
}
(function () {
var i
for (i = 1; i <= 4; i++) {
(function (i2) {
var cpt = _dx.Captcha.getByIdx(i2)
if (!cpt) {
setTimeout(arguments.callee, 100, i2)
return
}
cpt.on('verifySuccess', function () {
setTimeout(function () {resetCaptcha(i2)}, 3000)
if (!g_wg) {
_dx.Captcha._clearVID()
}
})
})(i)
}
})()
</script>
<script src="https://cdn.dingxiang-inc.com/ctu-group/captcha-ui/index.js"></script>
</body>
</html> - 后端根据token和客户端ip调顶象写好的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
58package main
import (
"Captcha_Project/captcha-client"
"fmt"
"github.com/gin-gonic/gin"
)
func main(){
router := gin.Default()
router.LoadHTMLGlob("tmp/*")
router.GET("/index", show_index)
router.GET("/index/done", show_done)
router.GET("/index/bar", response)
router.Run()
}
func show_index(c *gin.Context){
fmt.Println("show_index")
c.HTML(200, "index.html", gin.H{"title": "测试"})
}
func show_done(c *gin.Context){
fmt.Println("show_done")
c.HTML(200, "done.html", gin.H{"title": "done"})
}
func response(c *gin.Context){
fmt.Println("response")
token := c.Query("token")
fmt.Println(token)
Run_verify(token)
}
func Run_verify(token string) {
/*
参数token在前端完成验证后可以获取到,随业务请求发送到后台,有效期为两分钟
参数ip(string)可选,提交业务参数的客户端ip
*/
fmt.Println("Run_verify")
appId := "fb6d598f6f2911728e76ba2e60ca9ae0" //和前端验证码的appId保持一致,在顶象的应用配置里有
appSecret := "cfc89571ec179ef809fe7f29bf1c7be3" //appSecret为秘钥,在顶象的应用配置里有
captchaClient := captcha_client.NewCaptchaClient(appId, appSecret) //构建一个用户对象
captchaClient.SetTimeout(2000) //设置超时时间,单位毫秒
captchaClient.SetCaptchaUrl("https://xx.dingxiang-inc.com/api/tokenVerify") //指定服务器地址,saas可在控制台,应用管理页面最上方获取
captchaResponse := captchaClient.VerifyToken(token) //token在前端完成验证后可以获取到,随业务请求发送到后台,token有效期为两分钟
//captchaResponse := captchaClient.VerifyTokenAndIP(token, ip)
//针对一些token冒用的情况,除了判断token的合法性还会校验提交业务参数的客户端ip和验证码颁发token的客户端ip是否一致
//fmt.Println(captchaResponse.CaptchaStatus) //确保验证状态是SERVER_SUCCESS,SDK中有容错机制,在网络出现异常的情况会返回通过
//fmt.Println(captchaResponse.Ip) //验证码服务采集到的客户端ip
if captchaResponse.Result { //为真表示验证通过
//此处为验证通过下一步的代码
fmt.Println("done!")
} else { //为假表示验证失败
//此处为阻断该次请求或者进行下一次验证的代码
fmt.Println("fail!")
}
} - token验证成功跳转到下一页面(一般是登录成功页面)
企业微信代开发
1.自建应用的流程:
- 获取access_token:
1
(get)https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
- 企业微信向接受者发送信息:
1
(post)https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN
2.参数介绍
- corpid:每个企业都拥有唯一的corpid,获取此信息可在管理后台“我的企业”-“企业信息”下查看“企业ID”(需要有管理员权限)
- userid:每个成员都有唯一的userid,即所谓“帐号”。在管理后台->“通讯录”->点进某个成员的详情页,可以看到。
- external_userid:企业外部联系人的id,可能是微信用户,也可能是企业微信用户。需要开发者(尤其是第三方开发者)注意的是,对于同一个外部联系人,不同调用方(企业/第三方服务商)获取到的external_userid是不同的。
- agentid:每个应用都有唯一的agentid。在管理后台->“应用与小程序”->“应用”,点进某个应用,即可看到agentid。
- secret:secret是企业应用里面用于保障数据安全的“钥匙”,每一个应用都有一个独立的访问密钥,为了保证数据的安全,secret务必不能泄漏。
3.用户登录授权
- 自建应用
1
2
3
4
5
6
7
8https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
# appid企业的CorpID
# redirect_uri是授权后重定向的回调链接地址,请使用urlencode对链接进行处理
# response_type是返回类型,此时固定为:code
# scope是应用授权作用域。企业自建应用固定填写:snsapi_base
# state是重定向后会带上state参数,企业可以填写a-zA-Z0-9的参数值,长度不可超过128个字节,非必须
# #wechat_redirect是终端使用此参数判断是否需要带上身份信息 - 代开发应用
1
2
3
4
5
6
7
8
9https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&agentid=AGENTID&state=STATE#wechat_redirect
# appid是第三方应用id(即ww或wx开头的suite_id)。注意与企业的网页授权登录不同
# redirect_uri是授权后重定向的回调链接地址,请使用urlencode对链接进行处理 ,注意域名需要设置为第三方应用的可信域名
# response_type是返回类型,此时固定为:code
# scope是应用授权作用域。snsapi_base:静默授权,可获取成员的基础信息(UserId与DeviceId);snsapi_userinfo:静默授权,可获取成员的详细信息,但不包含头像、二维码等敏感信息;snsapi_privateinfo:手动授权,可获取成员的详细信息,包含头像、二维码等敏感信息。
# agentid是企业应用的id。当scope是snsapi_userinfo或snsapi_privateinfo时,该参数必填注意redirect_uri的域名必须与该应用的可信域名一致。
# state是重定向后会带上state参数,企业可以填写a-zA-Z0-9的参数值,长度不可超过128个字节
# #wechat_redirect是固定内容
4.流程
- 企业用户扫码授权,会向这个地址发起一个Post请求
1
2
3
4
5
6
7
8
9
10https://127.0.0.1/suite/receive?msg_signature=3a7b08bb8e6dbce3c9671d6fdb69d15066227608×tamp=1403610513&nonce=380320359
# 包含的信息体是
<xml>
<SuiteId><![CDATA[ww4asffe9xxx4c0f4c]]></SuiteId>
<AuthCode><![CDATA[AUTHCODE]]></AuthCode>
<InfoType><![CDATA[create_auth]]></InfoType>
<TimeStamp>1403610513</TimeStamp>
<State><![CDATA[123]]></State>
</xml> - 用得到的AuthCode去向以下网址发起Post请求
1
2
3
4
5https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code?suite_access_token=SUITE_ACCESS_TOKEN
{
"auth_code": "xx"
}- 会返回以下信息
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# permanent_code就是自建应用用的secret
{
"errcode":0,
"errmsg":"ok",
"access_token": "xxxxxx",
"expires_in": 7200,
"permanent_code": "xxxx",
"dealer_corp_info":
{
"corpid": "xxxx",
"corp_name": "name"
},
"auth_corp_info":
{
"corpid": "xxxx",
"corp_name": "name",
"corp_type": "verified",
"corp_square_logo_url": "yyyyy",
"corp_user_max": 50,
"corp_full_name":"full_name",
"verified_end_time":1431775834,
"subject_type": 1,
"corp_wxqrcode": "zzzzz",
"corp_scale": "1-50人",
"corp_industry": "IT服务",
"corp_sub_industry": "计算机软件/硬件/信息服务"
},
"auth_info":
{
"agent" :
[
{
"agentid":1,
"name":"NAME",
"round_logo_url":"xxxxxx",
"square_logo_url":"yyyyyy",
"appid":1,
"auth_mode":1,
"is_customized_app":false,
"privilege":
{
"level":1,
"allow_party":[1,2,3],
"allow_user":["zhansan","lisi"],
"allow_tag":[1,2,3],
"extra_party":[4,5,6],
"extra_user":["wangwu"],
"extra_tag":[4,5,6]
},
"shared_from":
{
"corpid":"wwyyyyy",
"share_type": 1
}
},
{
"agentid":2,
"name":"NAME2",
"round_logo_url":"xxxxxx",
"square_logo_url":"yyyyyy",
"appid":5,
"shared_from":
{
"corpid":"wwyyyyy",
"share_type": 0
}
}
]
},
"auth_user_info":
{
"userid":"aa",
"open_userid":"xxxxxx",
"name":"xxx",
"avatar":"http://xxx"
},
"register_code_info":
{
"register_code":"1111",
"template_id":"tpl111",
"state":"state001"
},
"state":"state001"
}
- 会返回以下信息