钉钉开发之浙政钉

标签: 无 分类: 未分类 创建时间:2022-06-02 05:19:00 更新时间:2025-01-17 10:39:24

1.前言

浙政钉,是浙江省弄的一个专有钉钉,但是很多的客户还是要使用浙政钉的,我觉得开发和测试真是一个巨大的麻烦,比如前端测试,后端测试,谁用谁知道,真是操蛋的玩意。

在钉钉的 “杭州市‘浙政钉2.0’应用迁移” 交流群里,有一个文件叫做 “浙政钉应用上钉对接文档-IRS(杭州).docx” 文件。

参考文章:
1.浙政钉h5微应用开发vue
2.浙政钉2.0应用接入流程(专有钉钉)_摩羯座de杰杰陆的博客-程序员秘密_浙政钉对接 这里还有专有钉钉的注册,但是我现在已经注册不了了。
3.专有钉钉 浙政钉 前端 对接流程(小程序) 这里是小程序的浙正钉对接的流程,里面非常的详细了,从项目创建、开发工具搭建还有免登录

2.专有钉钉

在文章中,需要创建一个专有钉钉账号,这个专有钉钉账号的创建又是一个问题,是通过邀请注册机制进行的,我去哪里找谁注册呢?入住开放平台,当前专有钉钉平台仅支持邀请制入驻:

  • 客户合作伙伴:专有钉钉客户合作的开发者,由专有钉钉客户邀请入驻,请联系客户侧对接人获取入驻方式。
  • 平台合作伙伴:专有钉钉平台合作的开发者,由平台邀请入驻,目前仅开放定向入驻。

(1)专有钉钉入住
登陆专有钉钉开放平台 注册页面,进行ISV的申请和入住。

这里我还遇到了一个问题,就是我们公司的账号不知道是谁申请的,也找不到人了,但是就是注册不了,只能请群里的大佬来处理了,让他把账号改掉。

(2)创建账号
审核通过后,会有一个管理员。管理员登录专有钉钉 可以查看后台管理。

(3)项目开发

3.应用上架

根据群内文章,应用上架需要在IRS上申请。IRS分为两部分,一部分是业主工作台,一部分是开发商工作台。

  • 业主工作台
    政务外网打开业主工作台,创建治理侧应用发布,治理侧负责将应用发布至浙政钉APP。创建治理侧发布后,开发商首次登录开发商工作台时,系统通过手机号码自动将治理侧应用同步至开发商工作台的治理侧名称列表中,应用状态为部署准备中(应用发布类型为H5或小程序+PC,代码托管)。

文档中只讲了关于应用发布的方法,没有讲如何创建应用。也就是说,进行应用发布的前提就是在工作台上已经有了一个应用,至于如何创建的这个应用,我猜测是通过应用编目的方式进行的。通过应用注册功能,进行应用创建,然后一步步的创建一个应用,创建完成之后。

  • 开发商工作台
    业主侧创建了应用之后,开发商就可以在开发商工作台查看了。这里我遇到了一个问题就是,开始的时候是看不见的,但是后来又看见了,我估计是在业主工作台点击了发布应用的关系,这个待定。

(1) 业主登陆 “业主工作台”,创建应用,填写基本信息

(2) 开发商登陆 “开发商工作台”,进行应用管理,上传代码,提交代码部署
在开发商工作台上,有治理测名称列表,找到相关的应用,进行应用部署发布

(3) 业主在工作台上进行项目验收,准备相关材料填写相关内容,进行上架。“浙政钉”应用上架需要提交的材料清单,里面包括了很多的内容:“浙政钉“ xxxx应用联系人-模板、“浙政钉”xxxx应用上架自查-模板、“浙政钉”xxxx应用系统部署文档-模板、“浙政钉”xxxx应用系统缺省页设计展示-模版、“浙政钉”xxxx应用系统应急预案-模版、”浙政钉“ xxxx应用安全报告、扩大使用范围的模板、情况说明模板指引、系统压测报告说明

(4) 如果需要升级 “版本是需提交发布的版本,请先联系业主发起版本升级,再进行代码上传。”

(5) 代码更新
如果需要代码更新,前端工程需要提交到开发商工作台上,在压缩包中需要能执行 npm run build 命令,并生成 build 目录,build 目录里面就是打包后到代码。进入到 业主工作台,然后找到 “工作台” -> “我的资源” -> “应用发布” -> “治理侧发布”,找到相应的应用,在操作里面选择 “重新部署”,然后登陆开发商工作台,进行代码部署就可以了。

4.获取access_token

使用官方提供的sdk进行access_token的获取,首先在pom.xml中添加依赖

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
<dependency>
<groupId>dingding</groupId>
<artifactId>dingding</artifactId>
<scope>system</scope>
<version>1.2.0</version>
<systemPath>${project.basedir}/lib/zwdd-sdk-java-1.2.0.jar</systemPath>
</dependency>

<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>

<!-- Apache Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

<!--这个很重要-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>

(2) 编写java代码,获取tokan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping("gettoken")
public AjaxResult gettoken(){
try {
// 调用接口
ExecutableClient executableClient =ExecutableClient.getInstance();
executableClient.setAccessKey(appkey);
executableClient.setSecretKey(appsecret);
executableClient.setDomainName("openplatform.dg-work.cn");
executableClient.setProtocal("https");
executableClient.init();

String api ="/gettoken.json";
PostClient postClient = executableClient.newPostClient(api);
//Set the parameters
//Call API
String apiResult = postClient.post();
System.out.println(apiResult);
}catch (Exception e){
logger.error("gettoken",e);
}
return AjaxResult.error();
}
参考文章:
1.专有钉钉门户后端接口
2.获取access_token 这是专有钉钉的获取token的接口

问题

(1) Could not initialize class com.alibaba.xxpt.gateway.shared.client.http.ExecutableClient
我使用的是 zwdd-sdk-java-1.2.0.jar 这个开发包,使用了测试用例进行token获取,结果报错了,还有就是错误:java.lang.NoClassDefFoundError: org/apache/http/client/ResponseHandler,也是同样的问题

【解决】
主要就是没有配置 httpclient 这个依赖,并且版本版本要是最新的,我这里旧版本不行。

参考文章:
1.Springboot+浙政钉 扫码免登 集成 这里是整个的开发流程,还有一些示例代码。
2.SDK 下载
3.java.lang.NoSuchMethodError: org.apache.http.impl.conn.CPool.setValidateAfterInactivity(I)V #84 这里的httpclient的版本是4.5.3
4.java.lang.NoClassDefFoundError: org/apache/http/client/HttpClient 这里的httpclient版本是4.3.4,结果报了另外的错误,方法不存在。

5.免密登录

(1)在浙政钉中打开应用,根据手机和pc端不同,分别会在url后面携带一个免登授权码。pc端为 auth_code,手机端为 code。
(2)通过免登授权码,构造请求 “/rpc/oauth2/dingtalk_app_user.json”,获取用户信息,和自己项目中的用户体系进行比较,从而获取是否可以进行免登操作。

6.扫码登陆

扫码登录和免密登录内容不一样,根据群里沟通的结果,如果需要进行扫码登录,还需要在irs上申请组件。和专有钉钉不太一样,需要先走通专有钉钉的扫码登录。
(1)打开irs首页

(2)搜索 “扫码登录”,申请扫码登录组件

(3) 根据应用信息填写相关内容
申请之后,相关的 appid 和 appkey,浙政钉的相关工作人员,会把信息发送到申请人的手机号上,开发人员需要去业主单位那里去拿。

应用标志 client_id 会在 appid 的基础上增加一个 _dingoa 后缀。

(4)接入钉钉扫码登录
通过两种方式接入钉钉扫码登录:

  • 方式一:独立页面
    使用专有钉钉提供的扫码登录页面,根据相关参数构造以下页面,URL中的client_id和redirect_uri两个参数的值填入第三方web系统的应用标识和回调地址。专有钉钉用户扫码登录并确认后,会302到你指定的redirect_uri,并向url参数中追加临时授权码code(此code非authcode)及state两个参数。获取到code之后,就可以通过code获取用户身份信息了。

    1
    https://login-pro.ding.zj.gov.cn/oauth2/auth.htm?response_type=code&client_id=应用标识&redirect_uri=回调地址&scope=get_user_info&authType=QRCODE
  • 方式二:内嵌iframe
    在页面中通过iframe嵌入页面,参数和第一种一样,就是多了一个 embedMode=true 的参数。

    1
    https://login-pro.ding.zj.gov.cn/oauth2/auth.htm?response_type=code&client_id=应用标识&redirect_uri=回调地址&scope=get_user_info&authType=QRCODE&embedMode=true

扫码成功后,需要在页面中监听扫码结果

1
2
3
4
5
6
7
<script type="application/javascript">
window.addEventListener('message', function(event) {
// 这里的event.data 就是登录成功的信息
// 数据格式:{ "code": "aaaa", "state": "bbbb" }
alert(JSON.stringify(event.data));
});
</script>

(6) 配置ip白名单
调用接口gettoken获取token的时候,还需要配置ip地址白名单。我没有找到配置的地方,只能通过群里的运维人员进行添加和修改了。

(5) 获取用户信息

  • 通过 /gettoken.json 接口,根据appkey,和appscret获取调用的凭证。

  • 通过服务端的 /rpc/oauth2/getuserinfo_bycode.json ,传入获取到的临时授权码,可以获取用户信息,这里没有手机号。我发现我找了很多的地方,还是没有找到直接获取用户手机号的方法。根据群里的消息说明:“经过大数据局评估,处于安全考虑,目前暂时无法提供获取人员信息返回手机号的功能。”

参考文章:
1.浙政钉(专有钉钉)扫码登录对接
2.扫码登录 这是专有钉钉的两种扫码登陆方式,一种是独立页面,一种是内嵌的iframe页面。
3.浙政钉-专有钉扫码登录

7.应用埋点

浙政钉的应用埋点,主要分为稳定性分析和流量分析,如果是单页应用,就需要在每一个页面上都填入相应的内容。

(1)稳定性代码
在首页 head 中增加如下代码

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
<!--应用埋点-->
<script src='https://wpkgate-emas.ding.zj.gov.cn/static/wpk-jssdk.1.0.2/wpkReporter.js' crossorigin='true'></script>
<script>
try{
const config={
bid:'Civil_air_defense__zzdpro',
signkey:'1234567890abcdef',
gateway:'https://wpkgate-emas.ding.zj.gov.cn'
};
const wpk=new wpkReporter(config);
wpk.installAll();
window._wpk=wpk;
}catch(err){
console.error('WpkReporterinitfail',err);
}
</script>
<script>
(function (w, d, s, q, i) {
var key=(new Date()).getTime();
w[q] = w[q] || [];
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s);
j.async = true;
j.id = "beacon-aplus";
j.src = "https://alidt.alicdn.com/alilog/mlog/aplus_cloud.js?_="+key;
f.parentNode.insertBefore(j, f);
})(window, document, "script", "aplus_queue");

aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['aplus-rhost-v', 'alog-api.ding.zj.gov.cn']
});
aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['aplus-rhost-g', 'alog-api.ding.zj.gov.cn']
});

var u = navigator.userAgent
var isAndroid = u.indexOf('Android') > -1
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)

aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['appId', isAndroid ? '28302650' : isIOS ? '28328447' : '47130293']
});
</script>

注意
这里有一个地方需要注意,原先的我的 bid 写错误了:Civil_air_defense__zzdpro,正确的 bid 应该是:Civil_air_defense_zzdpro,所以导致了稳定性监控没有接入成功

(2)流量分析代码
加入了流量分析之后,需要通过执行pv操作上报流量代码。

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
//接收3个参数:page_id,page_name,page_url
//流量分析埋点
export function queue(metaId, metaName, metaPath) {
if(!aplus_queue){
return
}
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["aplus-rhost-v", "alog-api.ding.zj.gov.cn"],
});
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["aplus-rhost-g", "alog-api.ding.zj.gov.cn"],
});

var u = navigator.userAgent;
var isAndroid = u.indexOf("Android") > -1;
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);

aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: [
"appId",
isAndroid ? "28302650" : isIOS ? "28328447" : "47130293",
],
});

aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["_hold", "BLOCK"],
});
//基础埋点
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["aplus-waiting", "MAN"],
});
aplus_queue.push({
action: "aplus.sendPV",
arguments: [
{
is_auto: false,
},
{
//当前应用信息
sapp_id: '26318',
sapp_name: 'Civil_air_defense',
page_id: metaId, // '页面ID,与page 参数配合使用,保证唯一性',
page_name: metaName, //'页面中文名称'
page_url: metaPath, // 页面路由
},
],
});
// 用户信息埋点
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["_user_id", userInfo.accountId||"34268160"],
});
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["_user_nick", userInfo.nickNameCn||"人防工程"],
});
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["_hold", "START"],
});
}

在单页应用中,每一个页面中都要执行 queue 函数

1
2
3
4
onMounted(() => {
// 埋点
queue("login","用户登陆","pages/login/index")
});

测试,查询埋点情况

另外一个版本,主要不同点就是这个域名不同,后来咨询过了,这个域名已经废弃了,不用了。

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
<!--稳定性分析-->
<script src='https://wpk-gate.zjzwfw.gov.cn/static/wpk-jssdk.1.0.2/wpkReporter.js' crossorigin='true'></script>
<script>
try {
const config = {
bid: 'Civil_air_defense_zzdpro',
signkey: '1234567890abcdef',
gateway: 'https://wpk-gate.zjzwfw.gov.cn'
};
const wpk = new wpkReporter(config);
wpk.installAll();
window._wpk = wpk;
} catch (err) {
console.error('WpkReporter init fail', err);
}
</script>
<!--通用流量分析-->
<script>
(function(w, d, s, q, i) {
w[q] = w[q] || [];
var f = d.getElementsByTagName(s)[0],j = d.createElement(s);
j.async = true;
j.id = 'beacon-aplus';
j.src = 'https://alidt.alicdn.com/alilog/mlog/aplus_cloud.js';
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'aplus_queue');

aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['aplus-rhost-v', 'alog.zjzwfw.gov.cn']
});
aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['aplus-rhost-g', 'alog.zjzwfw.gov.cn']
});

var u = navigator.userAgent
var isAndroid = u.indexOf('Android') > -1
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)

aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['appId', isAndroid ? '28302650' : isIOS ? '28328447' : '47130293']
});
</script>

通用页面函数

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
//接收3个参数:page_id,page_name,page_url
//流量分析埋点
export function queue(metaId, metaName, metaPath) {
if(!aplus_queue){
return
}
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["_hold", "BLOCK"],
});
//基础埋点
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["aplus-waiting", "MAN"],
});
aplus_queue.push({
action: "aplus.sendPV",
arguments: [
{
is_auto: false,
},
{
//当前应用信息
sapp_id: '26318',
sapp_name: 'Civil_air_defense',
page_id: metaId, // '页面ID,与page 参数配合使用,保证唯一性',
page_name: metaName, //'页面中文名称'
page_url: metaPath, // 页面路由
},
],
});
// 用户信息埋点
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["_user_id", userInfo.accountId||"34268160"],
});
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["_user_nick", userInfo.nickNameCn||"人防工程"],
});
aplus_queue.push({
action: "aplus.setMetaInfo",
arguments: ["_hold", "START"],
});
}
参考文章:
【1】.浙政钉-H5&小程序应用采集开发手册 这里有相关参数的说明,基本上能用,但是有些东西就是说的不是很清楚,说的很乱,我刚开始的时候,就是被误导了,比如切换域名这个东西。
【2】.解决Refused to execute script from ‘http://127.0.0.1:8004/login‘ because its MIME type (‘text/html‘) i
【3】.Js文件函数中调用另一个Js文件函数的方法
【4】.浙政钉2.0-应用监控(埋点)完整分析 及 成功实现埋点
小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。