一.钱包接口文档
1、基础配置
1.1 所有第三方请求钱包接口(请求Header都需要加上下面字段)
钱包和商场之间调用只存在在服务端间调用不能直接客户端跟对方的服务端交互例如商场的客户端只能跟商场交互,
钱包提供公钥一定规则加密 为了验证这个是商城来的请求的安全性(7、 系统级别的安全校验)具体说明
① timestamp时间戳
②source表示商城的来源的字段例如source=2 支持后面对接其他系统以及统计等业务(类似2、注册模等场景需要区分是来自哪个第三方系统注册过来)
③sign签名 时间戳+来源+指定字符串 用公钥加密生成
枚举
注册来源
- 1:钱包,
- 2:商城
物品类型:
- 1:币
- 2:积分
用户角色
- 0:管理员
- 2:用户
- 3:码主
- 4:商户
- 5:代理商
双方接口请求加密方式
为了接口安全性,接口请求需要采用加密的方式:简单考虑采用 RSA 加密方式
加密方式如下,采用公钥,密钥的形式。双方对固定字符串加密(商城对钱包的加密串:MALL,钱包对商城的请求为:WALLET)
package com.allensea.rcoin.auth;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
public class RSACrypt {
/**
* 生成RAS公钥与私钥字符串,直接返回
*
* @return
*/
public static HashMap<String, String> getKeys() {
HashMap<String, String> map = new HashMap<>();
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到公钥字符串
String publicKey = base64ToStr(keyPair.getPublic().getEncoded());
// 得到私钥字符串
String privateKey = base64ToStr(keyPair.getPrivate().getEncoded());
map.put("publicKey", publicKey);
map.put("privateKey", privateKey);
return map;
}
/**
* 根据公钥字符串加载公钥
*
* @param publicKeyStr 公钥字符串
* @return
* @throws Exception
*/
public static RSAPublicKey loadPublicKey(String publicKeyStr) throws Exception {
try {
byte[] buffer = javax.xml.bind.DatatypeConverter.parseBase64Binary(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法", e);
} catch (InvalidKeySpecException e) {
throw new Exception("公钥非法", e);
} catch (NullPointerException e) {
throw new Exception("公钥数据为空", e);
}
}
/**
* 根据私钥字符串加载私钥
*
* @param privateKeyStr 私钥字符串
* @return
* @throws Exception
*/
public static RSAPrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
try {
byte[] buffer = javax.xml.bind.DatatypeConverter.parseBase64Binary(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法", e);
} catch (InvalidKeySpecException e) {
throw new Exception("私钥非法", e);
} catch (NullPointerException e) {
throw new Exception("私钥数据为空", e);
}
}
/**
* 公钥加密
*
* @param publicKey 公钥
* @param plainTextData 明文数据
* @return
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt(RSAPublicKey publicKey, byte[] plainTextData) throws Exception {
if (publicKey == null) {
throw new Exception("加密公钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(plainTextData);
return base64ToStr(output);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此加密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("加密公钥非法,请检查");
} catch (IllegalBlockSizeException e) {
throw new Exception("明文长度非法");
} catch (BadPaddingException e) {
throw new Exception("明文数据已损坏");
}
}
/**
* 私钥解密
*
* @param privateKey 私钥
* @param cipherData 密文数据
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(RSAPrivateKey privateKey, byte[] cipherData) throws Exception {
if (privateKey == null) {
throw new Exception("解密私钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] output = cipher.doFinal(cipherData);
return new String(output);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此解密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("解密私钥非法,请检查");
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
throw new Exception("密文长度非法");
} catch (BadPaddingException e) {
e.printStackTrace();
throw new Exception("密文数据已损坏");
}
}
public static String base64ToStr(byte[] b) {
return javax.xml.bind.DatatypeConverter.printBase64Binary(b);
}
public static byte[] strToBase64(String str) {
return javax.xml.bind.DatatypeConverter.parseBase64Binary(str);
}
public static void main(String[] args) throws Exception {
//初始化阶段,初始化后生成秘钥对
//公钥发送给消息发送方用于加密传输数据;私钥严格保存于消息接收方,收到加密的消息之后进行解密
HashMap<String, String> map = RSACrypt.getKeys();
String privateKeyStr = map.get("privateKey");
String publicKeyStr = map.get("publicKey");
System.out.println("初始化私钥为:" + privateKeyStr);
System.out.println("初始化共钥为:" + publicKeyStr);
//消息发送方
String originData = "token";
System.out.println("信息原文:" + originData);
String encryptData = RSACrypt.encrypt(RSACrypt.loadPublicKey(publicKeyStr), originData.getBytes());
System.out.println("加密后:" + encryptData);
//消息接收方
String decryptData = RSACrypt.decrypt(RSACrypt.loadPrivateKey(privateKeyStr), RSACrypt.strToBase64(encryptData));
System.out.println("解密后:" + decryptData);
}
}
2、注册模块提供注册接口
2.1 请求说明
请求方式:POST
请求URL :/wallet/sign
2.2 请求参数
字段 | 类型 | 是否必须 | 描述 |
---|---|---|---|
nick | String | 是 | 用户昵称 |
mobile | String | 是 | 手机号 |
password | String | 是 | 登录加密后的密码 |
firstInviterMobile | String | 否 | 一级邀请人手机号 |
secondInviterMobile | String | 否 | 二级邀请人手机号 |
2.3 返回结果
请求成功格式
{
"code": 0,
"data": null
}
请求失败格式
{
"code": 4001,
"errorMsg": "token错误"
}
2.4 注册需要注意的地方
两个系统在注册的时候是否强依赖?
如何保证注册时候的推荐人用户在各自系统里一定存在?
方案一:强依赖,双方注册是,一定要求对方成功,再保存数据
方案二:补偿措施,如果推荐人未查询到,在各自系统补偿注册用户信息。
因为双方系统可能会跟其他系统再对接过于强依赖不适合
选择方案二 各自系统注册的时候调用第三方系统同步注册信息的时候采用异步方式不需要强依赖 第三方系统自己去做补偿例如商城注册的时候要把注册信息同步给钱包 但钱包发现一级邀请人手机号在钱包系统不存在 这时候钱包需要去商城查下然后注册下一级邀请人手机号
保证双方用户实时是同步的( 5、根据手机号获取用户信息)这个接口可以去查到信息然后把邀请人也给注册掉
3、密码加密
用户在登录的时候,检测数据库用户的注册来源,如果是对方系统的时候请求对方系统获取加密后的密码跟注册时候存的密码是否是一样
3.1 请求说明
请求方式:POST
请求URL :/wallet/decrypt
3.2 请求参数
字段 | 类型 | 是否必须 | 描述 |
---|---|---|---|
password | String | 是 | 用户密码 |
3.3 返回结果
{
"code": 0,
"data":'password'(加密后的密码)
}
4、根据token获取用户信息
商城和钱包系统要互相跳区分出是对方系统的token时候要去查对方
系统的用户信息来对应到自己的系统的哪个用户
4.1 请求说明
请求方式:POST
请求URL :/wallet/get_user_info_by_token
4.2 请求参数
字段 | 类型 | 是否必须 | 描述 |
---|---|---|---|
walletToken | String | 是 | 用户token |
4.3 返回结果
字段 | 类型 | 描述 |
---|---|---|
nick | String | 用户昵称 |
mobile | String | 手机号码 |
password | String | 用户密码 |
userId | String | 用户ID |
5、根据手机号获取用户信息
目前是为了注册的时候怕推荐人用户双方不同步需要去对方系统查出来然后重新注册一下
5.1 请求说明
请求方式:POST
请求URL :/wallet/get_user_info_by_mobile
5.2 请求参数
字段 | 类型 | 是否必须 | 描述 |
---|---|---|---|
mobile | String | 是 | 用户手机号 |
5.3 返回结果
字段 | 类型 | 描述 |
---|---|---|
nick | String | 用户昵称 |
mobile | String | 手机号码 |
password | String | 用户密码 |
userId | String | 用户ID |
role | String | 角色 |
6、钱包cookie
钱包系统用户登入后不再写入cookie,商城系统同理,避免系统之间跳转串cookie的问题,用户登入之后返回给客户端,客户端自己维护登入用户令牌不从cookie里面拿优先从请求参数拿
7、 系统级别的安全校验
钱包系统调用商城系统或者商城系统调用钱包系统,系统级别调用不需要用户级别的用户令牌校验,但需要系统级别RSA公私钥加密互相用对方
8、 获取用户币的数量接口
例如商场的资产页面可以显示
8.1 请求说明
请求方式:POST
请求URL :/wallet/get_property_by_mobile
8.2 请求参数
字段 | 类型 | 是否必须 | 描述 |
---|---|---|---|
mobile | String | 是 | 用户手机号 |
8.3 返回结果
字段 | 类型 | 描述 |
---|---|---|
balance | String | 币的资产总余额 |
9、积分兑换币
扣除用户积分再为用户添加响应的币
①要判断配置开关是否打开 关闭直接提示不开兑换
②看下钱包币池里面的币是否够
③商城要等先锁住积分等钱包系统返回给用户增加币成功后
才能真正扣钱包没返回钱那部分积分不准操作了 返回失败则回退用户
2.1 请求说明
请求方式:POST
请求URL :/wallet/coin_to_integral
2.2 请求参数
字段 | 类型 | 是否必须 | 描述 |
---|---|---|---|
mobile | String | 是 | 手机号 |
incrCoin | String | 是 | 需要增加的币(小数点后8位) |
2.3 返回结果
请求成功格式
{
"code": 0,
"data": {
"balance": "币的总余额 "
}
}
请求失败格式
{
"code": 4001,
"errorMsg": "token错误"
}
二.商城需要支持接口
①注册用户(用户昵称,用户手机号,用户密码,一级推荐人手机号,二级推荐人手机号)。
②通过手机号码查询用户信息
③商城用户密码加密的接口
④通过商城的用户令牌查询用户信息(商城系统跳到钱包系统时候,钱包系统需要通过token知道当前是哪个用户,钱包用户令牌带前缀给wallet,商城用户令牌带前缀mall)
⑤给某一个用户增加积分的接口(同步返回用户最新的总积分)
⑥获取某一个用户积分接口(同步返回用户最新的总积分 )
⑦币兑换积分的一个接口(先锁住币等积分返回兑换成功后再真在扣除币);需要判断兑换开关是否打开(商城可以关闭兑换) 如果关闭返回具体错误码;同步返回当前用户最新的总积分
⑧用户用积分买收款码分两个接口(生成交易流水返回流水ID 并锁住用户需要扣除的积分变成不可用状态;买码成功或者失败流程 我们通过流水ID回调你们处理结果)
网友评论