获取用户信息
可以通过该流程获取用户的 手机号、头像、昵称 等信息
简要流程
开发者主要操作步骤:
- 开发者小程序通过
isHostLogin判断用户是否登录,未登录时使用appLogin唤起登录页面并完成登录(逻辑请参考 getUserId, 本章节略) - 开发者通过
getUserData获取授权令牌 - 开发者通过后台效用小程序开放平台接口
/api/developer/user/info换取用户信息
流程图如下所示:

详细步骤
1. 开发者小程序通过 isHostLogin 判断用户是否登录,未登录时使用 appLogin 唤起登录页面并完成登录(逻辑请参考 登录流程 getUserId, 本章节略)
2. 开发者通过 getUserData 获取授权令牌
相关接口如下:
getUserData(OBJECT)
OBJECT参数说明:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| authFields | String | 是 | 需要授权的字段code, 目前一次只能授权一个字段, 可用字段见下方 |
| success | Function | 是 | 成功处理时的回调函数 |
| fail | Function | 是 | 处理失败时的回调函数 |
用户信息字段:
| 字段名称 | 字段 code |
|---|---|
| albumUrl | 头像 |
| nickName | 昵称 |
| mobile | 手机号 |
| carNo | 车牌号 |
| frameNo | 车架号 |
| realName | 姓名 |
| idNo | 身份证 |
| passport | 护照 |
| residencePermit | 留居证 |
| sex | 性别 |
| 邮箱 | |
| address | 收货信息 |
success返回参数说明:
回参数据结构为 {"data":{"userInfoCode":"xxxxx"}}, 需要取 data 数据, data 数据结构如下
| 参数 | 类型 | 说明 |
|---|---|---|
| userInfoCode | String | 授权令牌 |
fail返回参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| errCode | String | 异常编码 |
| msg | String | 异常信息 |
调用示例
PAMINA.call('getUserData', {authFields: "mobile"}, (res) => {
// 用户同意授权, 此处会返回 mobile 的授权查询令牌
console.log(res);
}, (e) => {
// 用户拒绝授权
console.log(e);
})
3. 开发者通过后台效用小程序开放平台接口/api/developer/user/info换取用户信息
这是一个 HTTPS 接口,开发者通过后台服务器 使用 授权令牌 从小程序开放平台换取指定的用户信息字段。
为了保证数据安全,开发者需要使用 RSA 密钥进行身份认证,RSA 密钥可以从小程序开放平台如下入口获取: 开发中心 -> 你的小程序 -> 基础信息 -> 小程序密钥
在完成上线全部流程前只能通过测试环境接口方式联调,完成上线流程后需再切换为生产环境接口地址。其中调用测试接口联调时需要提供手机号和设备号给产险开发添加测试数据
令牌接口地址
在完成业务接口调用前需要调用访问令牌接口获取access_token,获取到的acess_token在调用业务接口是放到url后面。
测试地址
POST https://test-api.pingan.com.cn:20443/oauth/oauth2/access_token
生产地址
POST https://api.pingan.com.cn/oauth/oauth2/access_token
请求参数
具体入参值可找产险开发老师获取。
| 传值方式 | 参数值 | |
|---|---|---|
| client_id | POST 请求 | 应用ID 例如:P_PA002_ELIS_UWS(在“我的应用”列表中) |
| grant_type | POST 请求 | 授权类型 固定值:client_credentials |
| client_secret | POST 请求 | 应用密钥 例如:znD5x4d1(创建应用时返回的应用密码) |
响应参数
| access_token | oauth系统对获取access_token有调用限制,请将获取的token值放到本地缓存;注意token是有有效期的,当token失效时需要做相应处理。 |
|---|---|
| expires_in | 有效期 例如 :60 (单位:分钟);0表示永久有效;expires_in只是有效期参考,不以这个为唯一更新token的标准。注意调用接口返回13002和13012的错误码必须处理。 |
| openid | openid |
具体指引链接:https://api.pingan.com.cn/dev/index.do?bAPhdWDKZYpPM00
业务接口地址
测试地址
POST https://test-api.pingan.com.cn:20443/open/mina-store/api/stg/developer/user/info?access_token=C89B2C0D6A4D4262B17CD7DC1EC05E11&request_id=123451
生产地址
POST https://api.pingan.com.cn/open/mina-store/api/developer/user/info?access_token=C89B2C0D6A4D4262B17CD7DC1EC05E11&request_id=123451
Headers
| 参数名称 | 参数值 | 是否必须 | 备注 |
|---|---|---|---|
| Content-Type | application/json | 是 | |
| X-MINA-MINI-APP-ID | 是 | 小程序ID | |
| X-MINA-TIMESTAMP | 是 | 请求时间(毫秒级) | |
| X-MINA-SIGN | 是 | 请求签名, 签名有效时间 60 秒, 采用 RSA 算法, 签名结果使用 Base64 进行编码;签名格式: RSA(requestBodyString + X-MINA-MINI-APP-ID + X-MINA-TIMESTAMP) |
请求参数
| 名称 | 类型 | 是否必须 | 默认值 | 备注 |
|---|---|---|---|---|
| miniAppId | string | 是 | 小程序ID,可从端能力getSystemInfo获取 | |
| deviceId | string | 是 | 设备ID,获取途径同miniAppId | |
| hostAppId | string | 是 | 宿主渠道,如PA00300000000_01_HCZ,获取途径同miniAppId | |
| userInfoCode | string | 是 | 一次性授权令牌,有效时间 60 秒,可从getUserData获取 |
响应参数
| 名称 | 类型 | 是否必须 | 默认值 | 备注 |
|---|---|---|---|---|
| responseCode | string | 是 | 默认0为成功 | |
| responseMsg | string | 是 | 提示信息 | |
| data | object | 是 | 业务数据 | |
| └─ encrypted | string | 是 | 加密后的用户信息。 本字段为AES(AES/GCM/NoPadding)加密的字符串,采用 base64 编码; AES 密钥使用 userInfoCode 生成, 见下方示例。解密后后为json格式 |
调用示例
axios(
{
method: "post",
url: `https://api.pingan.com.cn/open/mina-store/api/developer/user/info`,
data: '', // 入参
headers: {} // 请求头
}
).then((res) => {
console.log("msgSend---sucess", res);
this.msgSendResult = JSON.stringify(res);
})
完整代码示例
示例代码(前端 vue):
export default {
name: "getUserData",
data() {
return {
authFields: "mobile",
getUserDataResult: null,
}
},
methods: {
getUserData() {
PAMINA.call('getUserData', {authFields: this.authFields}, (res) => {
console.log(res);
this.getUserDataResult = res;
if (res.data === undefined && res.errMsg === 'getUserData:ok') {
this.getUserData();
}
}, (e) => {
console.log(e)
this.getUserDataResult = JSON.stringify(e)
this.$toast(e.msg)
})
},
getDeveloperUserInfo(code) {
if (this.getDeviceIDResult === '') {
this.$toast('请先获取设备ID');
return;
}
if (this.getUserDataResult.data === undefined) {
this.$toast('请先获取授权令牌');
return;
}
// code = 'a8c48ca5bc9e4a19b037947f00602222';
if (code === undefined) {
code = this.getUserDataResult.data.userInfoCode;
}
var now_timestamp = new Date().getTime();
var reqBody = JSON.stringify({
"miniAppId": "cxec128d79635042d7", // 小程序ID,可从端能力getSystemInfo获取
"deviceId": '', // 设备id, 可从端能力getSystemInfo获取
"hostAppId": "PA00300000000_01_HCZ", // 宿主渠道, 可从端能力getSystemInfo获取
"userInfoCode": code.replace(/\s+/g, ""), // 一次性授权令牌
});
console.log('getDeveloperUserInfo---reqBody:', reqBody);
let bodyJson = JSON.parse(reqBody);
let appId = bodyJson.miniAppId;
var signstr = reqBody + appId + now_timestamp;
console.log("signString : " + signstr);
let key ="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2UkXBU/RVl...."; // 秘钥
console.log("getDeveloperUserInfo---privateKey : " + key)
let sign = this.signRsa(key,signstr);
const header = {
"Content-Type": 'application/json',
"X-MINA-TIMESTAMP": now_timestamp,
"X-MINA-SIGN": sign,
"X-MINA-MINI-APP-ID": appId
};
console.log('getDeveloperUserInfo---header:', header);
axios(
{
method: "post",
url: `https://api.pingan.com.cn/open/mina-store/api/developer/user/info`,
data: bodyJson,
headers: header
}
).then((res) => {
console.log("getDeveloperUserInfo---sucess", res);
this.getDeveloperUserInfoResult = JSON.stringify(res);
})
.catch((e) => {
console.log("getDeveloperUserInfo---fail", e);
this.getDeveloperUserInfoResult = JSON.stringify(e);
});
},
signRsa(encrypt_key, clearText){
console.info('cleartext: ' + clearText);
//注意此处上下的BEGIN PRIVATE KEY不要删除,框架自带的
const private_key = '-----BEGIN PRIVATE KEY-----\n'
+ encrypt_key
+ '\n-----END PRIVATE KEY-----';
// !!!请自行下载Forge加密js包引入
var privateKey = forge.pki.privateKeyFromPem(private_key);
// const md = forge.md.sha1.create();//这里的sha1对应的java的SHA1WithRSA
const md = forge.md.sha256.create();
md.update(clearText,"utf8");
let sig = privateKey.sign(md);
let erg = forge.util.encode64(sig);
console.log("Signature is: "+erg);
return erg;
},
}
}
示例代码(后端 java):加密
基础方法
/**
* 初始化安全提供者
*/
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 解密方法
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密数据
*/
public static String decrypt(
byte[] data, byte[] key
) throws IllegalBlockSizeException, BadPaddingException,
NoSuchPaddingException, NoSuchAlgorithmException,
UnsupportedEncodingException, InvalidAlgorithmParameterException, InvalidKeyException {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// 初始化,设置解密模式
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(key));
// 执行操作
return new String(cipher.doFinal(data), "UTF-8");
}
/**
* 根据String 生成 密钥
* @param code 字符串
* @param keySize 密钥位数, 例如 128/192/256
* @return 二进制密钥
*/
public static byte[] getKey(String code, int keySize) {
int keyByteSize = keySize / 8;
byte[] aesBytes = new byte[keyByteSize];
System.arraycopy(code.getBytes(), 0, aesBytes, 0, keyByteSize);
SecretKeySpec aes = new SecretKeySpec(aesBytes, "AES");
return aes.getEncoded();
}
解密用户数据( 对应 response.data.encrypted 字段)
import org.apache.commons.codec.binary.Base64;
public static void main(String[] args) {
// 1. 根据 userInfoCode 生成 AES 密钥
byte[] aesKeyBytes = getKey(code, 128);
// 2. 解密用户信息(import org.apache.commons.codec.binary.Base64;)
String decryptedUserInfoBytes = decrypt(Base64.decodeBase64(encryptedUserInfo), aesKeyBytes);
}