第三方支付推广方案APP方案开发要多少钱

他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)40被浏览5,949分享邀请回答2411 条评论分享收藏感谢收起0添加评论分享收藏感谢收起写回答Android app 第三方支付宝支付接入
支付宝的接入相对比较简单,看看支付宝官网的文档基本都能搞定,但是切记一点让你们的后台也要搞清楚支付宝的流程,不然对接起来是一件很蛋疼的事情。
开发前一定要支付宝官方文档
强烈建议签名等处理在后台处理,我这个是测试是在自己本地写的,不要吐槽
想获取支付宝合作商户ID,及支付宝公钥请点击支付宝链接,生成密钥及PKCS8转码工具在文档中
添加android.permission.INTERNET权限和android.permission.ACCESS_NETWORK_STATE权限
要导入支付宝的包
特别注意事项(坑点)
下载支付宝的官网之后生成你所需要的密钥及PKCS8转码一定要小心
MainActivity中调起支付
Pay pay = new Pay(AffirmOrderActivity.this, paymoney);
public class Pay {
/**以下四项这些数值机密填入自己申请的就好**/
// 商户PID
public static final String PARTNER = &&;
// 商户收款账号
public static final String SELLER = &&;
// 商户私钥,pkcs8格式
public static final String RSA_PRIVATE = &&;
// 支付宝公钥
public static final String RSA_PUBLIC = &&;
private String mgoods,mprice,
private String mOutTradeNo;
Pay(Activity activity, String goods,String goodsmore,String price,String icon,int type, String outTradeNo) {
pay(price);
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case SDK_PAY_FLAG: {
PayResult payResult = new PayResult((String) msg.obj);
* 同步返回的结果必须放置到服务端进行验证(验证的规则请看https://doc.open.alipay.com/doc2/
* detail.htm?spm=0.0.0.0.xdvAU6&treeId=59&articleId=103665&
* docType=1) 建议商户依赖异步通知
String resultInfo = payResult.getResult();// 同步返回需要验证的信息
String resultStatus = payResult.getResultStatus();
// 判断resultStatus 为&9000&则代表支付成功,具体状态码代表含义可参考接口文档
if (TextUtils.equals(resultStatus, &9000&)) {
Toast.makeText(context, &支付成功&, Toast.LENGTH_SHORT).show();
// 判断resultStatus 为非&9000&则代表可能支付失败
// &8000&代表支付结果因为支付渠道原因或者原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态)
if (TextUtils.equals(resultStatus, &8000&)) {
Toast.makeText(context, &支付结果确认中&, Toast.LENGTH_SHORT).show();
// 其他值就可以判断为支付失败,包括用户主动取消支付,或者系统返回的错误
Toast.makeText(context, &支付失败&, Toast.LENGTH_SHORT).show();
* call alipay sdk pay. 调用SDK支付
public void pay(String goods,String goodsmore,String price) {
if (TextUtils.isEmpty(PARTNER) || TextUtils.isEmpty(RSA_PRIVATE) || TextUtils.isEmpty(SELLER)) {
new AlertDialog.Builder(context).setTitle(&警告&).setMessage(&需要配置PARTNER | RSA_PRIVATE| SELLER&)
.setPositiveButton(&确定&, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialoginterface, int i) {
}).show();
String orderInfo = getOrderInfo(goods, goodsmore, price);
String sign = sign(orderInfo);
* 仅需对sign 做URL编码
sign = URLEncoder.encode(sign, &UTF-8&);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
* 完整的符合支付宝参数规范的订单信息
final String payInfo = orderInfo + &&sign=\&& + sign + &\&&& + getSignType();
Log.i(&lff&, &orderInfo == & +orderInfo);
Log.i(&lff&, &sign == & +sign);
Runnable payRunnable = new Runnable() {
public void run() {
// 构造PayTask 对象
PayTask alipay = new PayTask(context);
// 调用支付接口,获取支付结果
String result = alipay.pay(payInfo, true);
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
mHandler.sendMessage(msg);
// 必须异步调用
Thread payThread = new Thread(payRunnable);
payThread.start();
* get the sdk version. 获取SDK版本号
public void getSDKVersion() {
PayTask payTask = new PayTask(context);
String version = payTask.getVersion();
Toast.makeText(context, version, Toast.LENGTH_SHORT).show();
* create the order info. 创建订单信息
private String getOrderInfo(String subject, String body, String price) {
// 签约合作者身份ID
String orderInfo = &partner=& + &\&& + PARTNER + &\&&;
// 签约卖家支付宝账号
orderInfo += &&seller_id=& + &\&& + SELLER + &\&&;
// 商户网站唯一订单号
orderInfo += &&out_trade_no=& + &\&& + getOutTradeNo() + &\&&;
// 商品名称
orderInfo += &&subject=& + &\&& + subject + &\&&;
// 商品详情
orderInfo += &&body=& + &\&& + body + &\&&;
// 商品金额
orderInfo += &&total_fee=& + &\&& + &0.01& + &\&&;
// 服务器异步通知页面路径
orderInfo += &&ify_url=& + &\&& + &http://notify.msp.hk/notify.htm& + &\&&;
orderInfo += &&ify_url=& + &\&& + &http://112.74.129.252/new/zfbCallback& + &\&&;
// 服务接口名称, 固定值
orderInfo += &&service=\&mobile.securitypay.pay\&&;
// 支付类型, 固定值
orderInfo += &&payment_type=\&1\&&;
// 参数编码, 固定值
orderInfo += &&_input_charset=\&utf-8\&&;
// 设置未付款交易的超时时间
// 默认30分钟,一旦超时,该笔交易就会自动被关闭。
// 取值范围:1m~15d。
// m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。
// 该参数数值不接受小数点,如1.5h,可转换为90m。
orderInfo += &&it_b_pay=\&30m\&&;
// extern_token为经过快登授权获取到的alipay_open_id,带上此参数用户将使用授权的账户进行支付
// orderInfo += &&extern_token=& + &\&& + extern_token + &\&&;
// 支付宝处理完请求后,当前页面跳转到商户指定页面的路径,可空
orderInfo += &&return_url=\&m.alipay.com\&&;
// 调用银行卡支付,需配置此参数,参与签名, 固定值 (需要签约《无线银行卡快捷支付》才能使用)
// orderInfo += &&paymethod=\&expressGateway\&&;
return orderI
* get the out_trade_no for an order. 生成商户订单号,该值在商户端应保持唯一(可自定义格式规范)
private String getOutTradeNo() {
SimpleDateFormat format = new SimpleDateFormat(&MMddHHmmss&, Locale.getDefault());
Date date = new Date();
String key = format.format(date);
Random r = new Random();
key = key + r.nextInt();
key = key.substring(0, 15);
return mOutTradeNo;
* sign the order info. 对订单信息进行签名
* @param content
待签名订单信息
private String sign(String content) {
return SignUtils.sign(content, RSA_PRIVATE);
* get the sign type we use. 获取签名方式
private String getSignType() {
return &sign_type=\&RSA\&&;
其它的工具类,原封不动拷贝过去就好
package com.easyhomework.teacher.
public final class Base64 {
private static final int BASELENGTH = 128;
private static final int LOOKUPLENGTH = 64;
private static final int TWENTYFOURBITGROUP = 24;
private static final int EIGHTBIT = 8;
private static final int SIXTEENBIT = 16;
private static final int FOURBYTE = 4;
private static final int SIGN = -128;
private static char PAD = '=';
private static byte[] base64Alphabet = new byte[BASELENGTH];
private static char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH];
for (int i = 0; i & BASELENGTH; ++i) {
base64Alphabet[i] = -1;
for (int i = 'Z'; i &= 'A'; i--) {
base64Alphabet[i] = (byte) (i - 'A');
for (int i = 'z'; i &= 'a'; i--) {
base64Alphabet[i] = (byte) (i - 'a' + 26);
for (int i = '9'; i &= '0'; i--) {
base64Alphabet[i] = (byte) (i - '0' + 52);
base64Alphabet['+'] = 62;
base64Alphabet['/'] = 63;
for (int i = 0; i &= 25; i++) {
lookUpBase64Alphabet[i] = (char) ('A' + i);
for (int i = 26, j = 0; i &= 51; i++, j++) {
lookUpBase64Alphabet[i] = (char) ('a' + j);
for (int i = 52, j = 0; i &= 61; i++, j++) {
lookUpBase64Alphabet[i] = (char) ('0' + j);
lookUpBase64Alphabet[62] = (char) '+';
lookUpBase64Alphabet[63] = (char) '/';
private static boolean isWhiteSpace(char octect) {
return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
private static boolean isPad(char octect) {
return (octect == PAD);
private static boolean isData(char octect) {
return (octect & BASELENGTH && base64Alphabet[octect] != -1);
* Encodes hex octects into Base64
* @param binaryData
Array containing binaryData
* @return Encoded Base64 array
public static String encode(byte[] binaryData) {
if (binaryData == null) {
int lengthDataBits = binaryData.length * EIGHTBIT;
if (lengthDataBits == 0) {
return &&;
int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1
char encodedData[] =
encodedData = new char[numberQuartet * 4];
byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
int encodedIndex = 0;
int dataIndex = 0;
for (int i = 0; i & numberT i++) {
b1 = binaryData[dataIndex++];
b2 = binaryData[dataIndex++];
b3 = binaryData[dataIndex++];
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 && 2)
: (byte) ((b1) && 2 ^ 0xc0);
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 && 4)
: (byte) ((b2) && 4 ^ 0xf0);
byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 && 6)
: (byte) ((b3) && 6 ^ 0xfc);
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k && 4)];
encodedData[encodedIndex++] = lookUpBase64Alphabet[(l && 2) | val3];
encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
// form integral number of 6-bit groups
if (fewerThan24bits == EIGHTBIT) {
b1 = binaryData[dataIndex];
k = (byte) (b1 & 0x03);
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 && 2)
: (byte) ((b1) && 2 ^ 0xc0);
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex++] = lookUpBase64Alphabet[k && 4];
encodedData[encodedIndex++] = PAD;
encodedData[encodedIndex++] = PAD;
} else if (fewerThan24bits == SIXTEENBIT) {
b1 = binaryData[dataIndex];
b2 = binaryData[dataIndex + 1];
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 && 2)
: (byte) ((b1) && 2 ^ 0xc0);
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 && 4)
: (byte) ((b2) && 4 ^ 0xf0);
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k && 4)];
encodedData[encodedIndex++] = lookUpBase64Alphabet[l && 2];
encodedData[encodedIndex++] = PAD;
return new String(encodedData);
* Decodes Base64 data into octects
* @param encoded
string containing Base64 data
* @return Array containind decoded data.
public static byte[] decode(String encoded) {
if (encoded == null) {
char[] base64Data = encoded.toCharArray();
// remove white spaces
int len = removeWhiteSpace(base64Data);
if (len % FOURBYTE != 0) {
// should be pisible by four
int numberQuadruple = (len / FOURBYTE);
if (numberQuadruple == 0) {
return new byte[0];
byte decodedData[] =
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
int i = 0;
int encodedIndex = 0;
int dataIndex = 0;
decodedData = new byte[(numberQuadruple) * 3];
for (; i & numberQuadruple - 1; i++) {
if (!isData((d1 = base64Data[dataIndex++]))
|| !isData((d2 = base64Data[dataIndex++]))
|| !isData((d3 = base64Data[dataIndex++]))
|| !isData((d4 = base64Data[dataIndex++]))) {
}// if found &no data& just return null
b1 = base64Alphabet[d1];
b2 = base64Alphabet[d2];
b3 = base64Alphabet[d3];
b4 = base64Alphabet[d4];
decodedData[encodedIndex++] = (byte) (b1 && 2 | b2 && 4);
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) && 4) | ((b3 && 2) & 0xf));
decodedData[encodedIndex++] = (byte) (b3 && 6 | b4);
if (!isData((d1 = base64Data[dataIndex++]))
|| !isData((d2 = base64Data[dataIndex++]))) {
// if found &no data& just return null
b1 = base64Alphabet[d1];
b2 = base64Alphabet[d2];
d3 = base64Data[dataIndex++];
d4 = base64Data[dataIndex++];
if (!isData((d3)) || !isData((d4))) {// Check if they are PAD characters
if (isPad(d3) && isPad(d4)) {
if ((b2 & 0xf) != 0)// last 4 bits should be zero
byte[] tmp = new byte[i * 3 + 1];
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
tmp[encodedIndex] = (byte) (b1 && 2 | b2 && 4);
} else if (!isPad(d3) && isPad(d4)) {
b3 = base64Alphabet[d3];
if ((b3 & 0x3) != 0)// last 2 bits should be zero
byte[] tmp = new byte[i * 3 + 2];
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
tmp[encodedIndex++] = (byte) (b1 && 2 | b2 && 4);
tmp[encodedIndex] = (byte) (((b2 & 0xf) && 4) | ((b3 && 2) & 0xf));
} else { // No PAD e.g 3cQl
b3 = base64Alphabet[d3];
b4 = base64Alphabet[d4];
decodedData[encodedIndex++] = (byte) (b1 && 2 | b2 && 4);
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) && 4) | ((b3 && 2) & 0xf));
decodedData[encodedIndex++] = (byte) (b3 && 6 | b4);
return decodedD
* remove WhiteSpace from MIME containing encoded Base64 data.
* @param data
the byte array of base64 data (with WS)
* @return the new length
private static int removeWhiteSpace(char[] data) {
if (data == null) {
// count characters that's not whitespace
int newSize = 0;
int len = data.
for (int i = 0; i & i++) {
if (!isWhiteSpace(data[i])) {
data[newSize++] = data[i];
return newS
支付宝支付一共五部
生成秘钥私钥等(一般是后台处理的)
支付宝包复制带项目中
调用支付(上述MainActivity和pay类)
添加工具类做一个客户端多少钱,关于第三方支付的
买一个app多少钱,要求有第三方支付的
买一个app价格,要求有第三方支付
全部答案(共1个回答)
这样的大规模软件的话 也就几十万 如果是hello wold的话 找对人了不要钱 基本上 几百快的软件 非常少
尼玛前面在玩J3朋友用微信M我说魔兽更新了于是乎去官网看了看天赋改了然后就去更新了特意删了点东西留了30G更新魔兽这尼玛才优化到5%45G了有木有这是瘦身呢还是...
1、现在用的WOW语音包是中文的,要改回英文的就是在WOW安装文件夹里找到DATA文件夹,里面有SPEECH。MPQ和SPEECH2。MPQ两个文件,删除SPE...
哦,你那机子还可以的!不过你首先要去买张盛大的碟!把他拷进去!但你自己要找到怎么样去运行程序的!不过你开了4F后,自己机子一般是不能玩的!不然的话会很卡的!最好...
各地小时工最低工资标准不一。。。你可以百度一下,你所在地的小时工最低工资标准
合同没有最少签多久的限制,但是最晚15天结算一次工资。。。
答: 12315一般都是需要证据的,也就是口头的都和没说一样,有什么承诺一定要有书面的证据,记着以后为了自己的利益不要相信别人说的话,而要让他写下来,否则12315也...
答: 什么问题啊?
很难的问题
大家还关注
Copyright &
Corporation, All Rights Reserved
确定举报此问题
举报原因(必选):
广告或垃圾信息
激进时政或意识形态话题
不雅词句或人身攻击
侵犯他人隐私
其它违法和不良信息
报告,这不是个问题
报告原因(必选):
这不是个问题
这个问题分类似乎错了
这个不是我熟悉的地区围绕消息推送时产生的服务不稳定性,消息丢失、延迟,接入复杂性,统计缺失等问题,提供了一整套平台级的高可用消息推送解决方案编者按:本文来自微信公众号“InfoQ”(ID:infoqchina),作者李晓清、董泽光;36氪经授权发布。消息推送作为移动 APP 运营中的一项关键技术,已经被越来越广泛的运用。本文追溯了推送技术的发展历史,剖析了其核心原理,并对推送服务的关键技术进行深入剖析,围绕消息推送时产生的服务不稳定性,消息丢失、延迟,接入复杂性,统计缺失等问题,提供了一整套平台级的高可用消息推送解决方案。实践中,借助于该平台,不仅能提能显著提高消息到达率,还能提高研发效率,并道出了移动开发基础设施的平台化架构思路。推送基础移动互联网蓬勃发展的今天,大部分手机 APP 都提供了消息推送功能,如新闻客户端的热点新闻推荐,IM 工具的聊天消息提醒,电商产品促销信息,企业应用的通知和审批流程等等。推送对于提高产品活跃度、提高功能模块使用率、提升用户粘性、提升用户留存率起到了重要作用,作为 APP 运营中一个关键的免费渠道,对消息推送的合理运用能有效促进目标的实现。推送最早诞生于 Email 中,用于提醒新的消息,而移动互联网 时代则更多的运用在了移动客户端程序。要获取服务器的数据,通常有两种方式:第一种是客户端 PULL(拉)方式,即每隔一段时间去服务器获取是否有数据;第二种是服务端 PUSH(推)方式,服务器在有数据的时候主动发给客户端。很显然,PULL 方案优点是简单但是实时性较差,我们也可以通过提高查询频率来提高实时性,但这又会造成电量、流量的消耗过高,反之 PUSH 方案基于 TCP 长连接方式实现,消息实时性好,但是由于要保持 APP 客户端和服务端的长连接心跳,也会带来额外的电量和流量消耗。因此在整体架构设计中需要折中平衡,目前主流的推送实现方式都是基于 PUSH 的方案。客户端和服务器定期的建立连接,通过消息队列等方式来查询是否有新的消息,需要控制连接和查询的频率,频率不能过慢或过快,过慢会导致部分消息更新不及时,过快会消耗更多的资源(流量、电量等),对用户体验有较大伤害。通过短信发送推送消息,并在客户端植入短信拦截模块(主要针对 Android 平台),可以实现对短信进行拦截并提取其中的内容转发给 App 应用处理,这个方案借助于运营商的短消息,能够保证最好的实时性和到达率,但此方案对于成本要求较高,开发者需要为每一条 SMS 支付费用。移动 Push 推送基于 TCP 长连接实现, 客户端主动和服务器建立 TCP 长连接之后, 客户端定期向服务器发送心跳包用于保持连接, 有消息的时候, 服务器直接通过这个已经建立好的 TCP 连接通知 客户端。尽管长连接也会造成一定的开销,对于轮询和 SMS 方案的硬伤来说,目前已经是最优的方式,而且通过良好的设计,可以将损耗降至最低。不过,随着客户端数量和消息并发量的上升,对于消息服务器的性能和稳定性要求提出了非常大的考验。因此,就难度而言,此方式代价最高。基于 TCP 长连接的方式是主流的推送方式,基于该推送方式逐步发展出系统级、应用级一系列的推送解决方案。iOS 在系统层面与苹果 APNs(Apple Push Notification service)服务器建立连接,应用通过观察者模式向 ioS 系统注册关注的消息,系统收到 APNs Server 消息后转发到相应的应用程序,整个过程很清晰,并且所有 APP 都共用同一个系统级的连接,减少了系统开销,虽然 APNs 能无障碍的访问,但实际使用过程中,发现延时和丢消息的情况偶有发生。Android 的 C2DM(Android Cloud to Device Messaging)采取与 iOS 类似的机制,都是由系统层面来支持消息推送,但是由于 Google 的服务在国内不能稳定的访问,此方案对于中国用户来说基本是无法使用的。除了 Google 官方提供的方案,中国众多的手机厂商在其定制的系统中也内置了推送功能,如小米、华为等。鉴于 Android 平台 C2DM 推送的不可用性,国内涌现出大量的第三方推送服务提供商,采用第三方推送服务的系统流程如下图:图 1:消息推送流程目前应用最为广泛的第三方推送服务提供商包括个推、极光、友盟、小米、华为、BAT 等,绝大部分 APP 都会优先考虑采用第三方推送服务。第三方服务在开发成本和消息到达率上表现都不错,但所有信息会经过第三方服务器,对于信息敏感类 APP 而言,有必要考虑自建一套消息推送服务,能最大化保证安全,但对于自建推送服务,如果从零开始来做需要解决几个难点:第一,移动推送服务器对 App 客户端海量长连接的维护管理。第二,App 客户端如何保证 Push Service 常驻,对于 Android 我们可以通过发现 push service 不存在可以定时拉起的方式。第三,通信协议的制定,我们可以采用开源的 XMPP 方式实现,也可以自定义协议,不管哪种方式我们都要保证消息传送的到达率的准确性。第四,在移动互联网网络环境下,经常出现弱网环境,特别是 2G、3G 等网络不稳定的情况下,如果保证消息在弱网环境下不重、不丢也是一个挑战。无论是第三方推送服务,还是自建推送服务,在实际的使用过程中,发现都存在以下问题:应用服务端与推送服务强耦合。当推送服务不可用时,造成整个业务系统无法推送,甚无法正常工作。缺乏 ACK 机制。推送的过程是异步的,从应用服务端发送到推送服务时,可以得知发送是否成功,但是从第三方推送服务下发到 APP 时,无法得知客户端是否接收到。iOS 平台中,从推送服务发送到苹果 APNs 服务时,同样无法确定 APNs 是否收到。同时,第三方推送服务通常使用共享的推送通道,受其他推送方的影响,可能造成消息的延迟和丢失。服务会被杀死。尤其在 Android 平台上,后台推送 service 会被各种主动或者被动原因 kill 掉,导致消息丢失。缺乏消息的持久化。对于推送服务而言,消息推送是来一条推一条,无法追溯历史消息和消息状态。缺乏重传机制。整个推送过程涉及多个环节,当其中某个环节出现问题,造成客户端接收不到推送的消息时,就导致消息丢失,再无法接收到。客户端接入逻辑复杂。每接入一个新的 APP,都要进行重复的接入工作,接入逻辑完全一致,代码无法复用,需要在不同项目中拷贝。客户端与推送服务的 SDK 强耦合。客户端使用推送服务的接口,而各推送服务提供的接口不统一,如果需要替换推送服务,那么接入部分代码需完全重写。缺乏数据 监控和统计。每个应用每天推送了多少消息,成功到达 app 多少,失败多少,目前均没有统计。为了解决以上问题,我们考虑基于第三方消息推送服务构建一套移动消息推送中间件平台,该消息平台采用了低耦合的分层架构设计(如图 2 所示),分为三层:接入层、传输层和应用层。其中接入层是业务方调用的入口,我们采用异步消息队列的方式提供了较高的业务系统发送消息的速度,并且具备了消息缓冲功能,即使高峰期的海量消息推送对整个平台冲击较少,保护了推送系统;传输层会从接入层接收消息并进行解析,对推送消息进行合法性检查校验,如果消息不合法直接丢弃,同时将合法的消息进行协议转换并发送到对应的第三方推送平台;应用层主要是提供统一的 SDK 供业务使用,封装适配第三方推送平台的 SDK 接口到统一的接口 SDK 中,这样业务 APP 使用方只关注统一封装的 SDK 即可实现业务消息的操作,而不需要考虑各种滤重、校验等通用操作。主要功能包括:屏蔽推送接口,实现业务与推送服务解耦,提供一套通用的客户端 SDK,简化客户端接入。实现多点接入,可同时接入多套推送服务,根据历史推送成功率动态选择最优推送路径,当一条路径失效可选择备用路径进行推送,保证消息推送万无一失。引入消息持久化机制,方便追溯和统计。引入消息的 ACK 机制和重传机制,提高消息的到达率。实现数据 监控和统计机制,提供相关数据的统计分析,和报警预警功能。提供 web 管理后台,便于进行 APP 设置、推送设置、查看数据报表,提高系统维护的工作效率。整个系统设计由三部分组成:移动推送平台、客户端 SDK、应用管理界面(第三方推送服务和自建推送服务统称为推送服务)。图 2:系统架构移动推送平台提供统一的服务,对于应用层屏蔽推送服务接口,且实现推送服务可动态轮替。推送平台将接收到的消息持久化到数据库中,方便进行消息推送失败后的重发,以及后续数据的统计分析。客户端 SDK 对 App 提供统一的使用接口,屏蔽推送服务 SDK 使用细节,且实现多种推送 SDK 可替换,隐藏 SDK 复杂的接入过程,方便使用。应用管理系统面向 App 开发人员,实现应用申请,推送服务 配置,消息查询与管理,数据统计与分析。消息推送涉及的主要模块是消息推送平台和客户端 SDK,主要流程如下图所示:图 3:消息推送中间件核心流程正常情况下,消息推送过程如下:系统接收到业务方的推送请求后,首先进行权限的验证,这包括应用 appKey 的验证、接口参数的验证、黑名单验证等。验证不通过,返回错误信息;验证通过后,为此条消息分配一个唯一 id(uuid),将消息内容持久化到数据库中,此时消息的状态为待发送。消息进入推送队列中,将之后推送接口请求的响应返回给业务方。推送队列的消费者从队列中取出待发送的消息,标记该条消息的状态为发送中,然后调用第三方推送服务接口进行发送。如果调用成功,那么标记该消息的状态为发送成功客户端未收到。客户端 SDK 在收到推送后,回调服务端接口,发送收到推送的回执;服务端收到客户端回执后,标记消息状态为发送成功客户端已收到。对于推送过程中可能出现的异常情况,总结如下:在调用第三方推送服务接口时,可能出现调用失败的情况;此时需要标记消息的状态为发送失败,留待重发。在调用第三方推送服务接口成功后、第三方推送服务在下发至客户端的过程中,可能由于某种原因,造成客户端无法收到消息;此时消息的状态为发送成功客户端未收到,对于这种状态,需要重发。客户端在收到推送的消息后、向服务端发送 ACK 回执时,可能由于网络环境的问题,造成服务端没有收到客户端发送的回执,此时消息的状态为发送成功客户端未收到,对于这种状态,需要重发。消息在重发 N 次(N 次可配置)、仍然没有进入发送成功客户端已收到的状态,那么将不再进行自动重发;管理界面将提供手动重发消息的操作入口,如有需要,可以手动再进行重发。监控 平台对于一直重复不成功的消息会报警通知操作人员,这样操作人员可以及时通过手动方式处理。根据消息发送流程,可以得到消息在生命周期中状态的变迁如下图:图 4:消息状态机消息重发主要存在三种场景:系统启动时,查询所有的发送失败或发送成功未收到客户端回执的消息,加载到推送队列重发;系统运行时,后台线程定时查询需要重发的消息,进入推送队列;手动触发时,直接将消息加入推送队列。由于消息推送中间件服务通常要求高可用,为分布式部署,消息重发必须保证在单一节点执行,且保证只发送一次。需采用分布式锁的方式,保证重发只发一次,主流实现方式有三种:ZooKeeper:通过竞争创建临时节点的方式获取锁。Redis:Redlock 是 Redis 作者的提出了一种分布式锁的算法,基于 Redis 实现,该算法实现了一种更安全、可靠的分布式锁管理。数据库:如使用 MySQL 的 GET_LOCK 函数对于每种锁机制的特点本文不详细介绍,根据实际应用需要任选一种即可。由于 iOS 平台和 Android 平台的差异,消息重发需要考虑平台差异性。使用第三方推送时,如果 iOS 应用在前台运行,那么将通过第三方推送维护的长连接,以透传的方式直接下发到 APP,称为应用内消息;而当 APP 在后台时,则第三方推送将消息推送到 APNs,由 APNs 推送到 APP,称为 APNs 通知。当通过 APNs 推送时,手机在收到消息后将在顶部的通知栏出现相关推送内容,这一行为是系统级别的,APP 无法控制。可能会出现这一问题:当 APP 在后台或者手机锁屏的情况下,如果服务端重发了消息,手机的通知栏将出现多条通知。因此,考虑当 APP 在后台时,针对 iOS 平台的消息不再进行重发;只有当 APP 进入前台,才重新进行重发。APP 的活动状态通过第三方推送服务的 api 可以获取到。Android 平台不存在该问题。由于消息重发可能会造成客户端收到重复消息,需要在客户端进行消息去重。服务端为每一条消息分配了一个唯一 id,重发时唯一 id 不变。客户端需要保存收到的每一条消息,在接收到新消息时首先根据唯一 id 判断是否已经收到了这条消息,如收到则不响应。客户端保存消息可以采用 sqlite 数据库。客户端 SDK 与服务端的通信过程使用 appKey 和 appSecret 进行权限控制。appKey 是服务端为每个 app 分配的唯一标识,appSecret 是服务端为每个 app 分配的秘钥。客户端 SDK 在请求服务端 HTTP 接口时,会将 appKey+appSecret 做一次签名,将签名值作为签名 sign 参数,与其他请求参数(业务参数 +appKey)一同传到服务端;服务端拿到请求参数后,也先用 appKey+appSecret 做一次签名,比较和客户端传来的 sign 参数是否一致,从而完成权限验证过程。为了能够实现灵活控制推送与否,可实现黑名单管理的功能。处于黑名单内的 app 客户端不再进行消息的推送。黑名单控制的粒度到账号级别,也可以根据实际业务需要进行分组管理。在某些业务场景中,需要对消息进行过滤,分析,做出相应的处理甚至预警,借助于消息推送平台,都能方便的实现。客户端 SDK 是基于推送服务的 SDK 封装实现,对外提供统一的使用接口。SDK 的使用者不再关注具体使用了哪些第三方推送、推送服务的接入细节。实现与推送服务的充分解耦,降低开发和使用成本。由于 iOS 和 Android 平台的差异性,在客户端 SDK 的封装上存在差异,下面分别介绍两个平台的 SDK 封装方式。SDK 提供启动和停止的方法;同时定义一个 protocol,包含 SDK 提供的接口。SDK 在收到消息或出现错误时将会回调 protocol 中的接口。由于推送的接入涉及 AppDelegate 的生命周期方法,为避免 SDK 使用者关注这些繁琐的细节,SDK 使用 Aspects 的方式,将推送时相应的处理函数 hook 到 AppDelegate 的生命周期方法上。在 Android 中使用 Receiver 组件来接收收到的消息。一个基本的配置如下所示:流程如下:当推送服务的 SDK 在接收到推送过来的消息后,将发送广播,这个广播的用 intent-filter 标识,当应用中的 Receiver 代码注册了这个 intent-filter,就可以接收到广播,并进行后续处理。图 5:后台管理示意图消息后台管理系统提供应用申请、应用服务 配置、推送服务 配置、消息查询与管理等功能。1、应用申请填写应用名、应用描述等信息后,生成该应用唯一的 appKey 和 appSecret。2、应用服务 配置为应用选择要使用的移动端通用 服务,可供选择的有推送、反馈、版本发布。3、推送服务 配置为应用配置推送服务,可供选择个推、极光等;以及推送时使用的优先级顺序。4、消息查询与管理查看应用所发出的消息,包括消息所属应用、所属账号、消息的状态、最终发送成功的第三方渠道、消息的来源、发送者 ip 等信息5、数据统计通过分析 message 表中的各消息的状态,可统计各应用消息的发送成功率和到达率,以及哪个第三方推送的更优,方便选择。同时,提供每日、每周、每月推送消息量的统计,并提供统计图表。消息推送平台通过无状态设计、统一存储、冗余部署方式保证了高可用,对应的状态数据统一存储到 MySQL、Redis 中保证各个无状态实例共享 数据。对于消息的接收处理我们通过纯异步、动态多线程的方式提供了推送平台的高性能。同时对于异步接收的消息我们通过 log append 的方式保证消息先落地然后再进行处理,进一步确保系统在异常过程中我们可以随时恢复消息,保证不丢失。通过质量保障、全方位多维度监控体系(基础监控、错误日志监控、发送数据波动监控、进程监控等监控指标)保障系统在出现问题时实现秒级报警、及时处理保证了消息推送平台的高稳定性。本文介绍了一种基于第三方或自建推送服务、但又不强依赖特定推送服务的通用移动消息推送中间件平台,可以实现安全、稳定、可靠的消息推送功能,并提供完善的数据统计,在实际应用中,可以结合邮件、短信、网站消息、用户留言等打造成更加通用的企业消息平台。
周一至周日 9:00-18:00(全国免费咨询)
您正在使用移动设备访问,是否切换到手机版?
欢迎访问APISTORE
免费试用,在线咨询
工作时间:& 9:00-24:00

我要回帖

更多关于 第三方支付技术方案 的文章

 

随机推荐