push数据结构设计

根据目前使用的极光推送,

设计一个合理的服务端,而且满足一切需求的数据结构很重要,其实也很简单

[color=red][i]可能会有的需求: 推送消息给已注册用户、推送消息给所有用户、推送消息给匿名用户[/i][/color]


这样的一个需求下,我们需要在app启动时,即保存用户设备did,不管有没有登录


[b]上传机制: 用户设备did 上传的时机很重要[/b]

1. app启动时上传
2. 用户登录/切换登录时上传


数据结构如下:




CREATE TABLE `mz_android_profile` (
`pid` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id, 自增长',
`uid` bigint(20) NOT NULL COMMENT '用户id',
`dtype` tinyint(4) NOT NULL COMMENT '设备类型 1:android phone 2: android pad',
`did` varchar(128) NOT NULL COMMENT '设备唯一标识',
`createtime` datetime NOT NULL COMMENT '创建时间',
`updatetime` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`pid`),
UNIQUE KEY `AK_Key_2` (`did`),
KEY `AK_Key_3` (`uid`,`dtype`)
) ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8 COMMENT='推送相关信息表'





以上是Android 设备token did的数据结构, ios也可以设计类似的结构,这样一个结构即可满足以上所有的需求(实现自己的推送逻辑,而不是使用极光的后台)


[b]接口实现:[/b]




/**
* Android push 主函数
*
* @author sky
* @date 2015-11-20
*/
public class AndroidPushMain {

private static final Logger logger = LoggerFactory.getLogger(AndroidPushMain.class);

private String appkey;
private String appMasterSecret;

private static AndroidPushMain instance = null;

/**
* phone 的推送client
*/
private static JPushClient phoneClient;
/**
* pad 的推送client
*/
private static JPushClient padClient;

private static final int maxRetryTimes = 3;// 重连次数

private AndroidPushMain() {
}

/**
* 获取AndroidPushMain实例

*
* singleton
*
* @return
*/
public static synchronized AndroidPushMain get() {

if (instance == null) {
instance = new AndroidPushMain();
}
return instance;
}

/**
* 根据android 设备类型 获取不同的 jpush client
*
* @param dtype 设备类型,当设备类型为PC时, 不支持推送
* @return
*/
private static JPushClient getClient(short dtype) {

if (dtype == DeviceType.PHONE.getValue()) {
if (phoneClient != null) {
logger.info("android#push#getPushClient | JPush phoneClient 已经实例化过, Get | dtype: {}", getDeviceTypeName(dtype));
return phoneClient;
}
} else if (dtype == DeviceType.PAD.getValue()) {

if (padClient != null) {
logger.info("android#push#getPushClient | JPush padClient 已经实例化过, Get | dtype: {}", getDeviceTypeName(dtype));
return padClient;
}

} else {
logger.error("android#push#getPushClient | 不支持的推送设备类型 PC | dtype: {}", getDeviceTypeName(dtype));
return null;
}

// TODO need to cache it
String appkey = getKey(dtype);
String appMasterSecret = getSecret(dtype);

if (dtype == DeviceType.PAD.getValue()) {
padClient = new JPushClient(appMasterSecret, appkey, maxRetryTimes);
Args.check(null != padClient, "Init AndroidJpush padClient Failure.");
return padClient;
} else {
phoneClient = new JPushClient(appMasterSecret, appkey, maxRetryTimes);
Args.check(null != phoneClient, "Init AndroidJpush phoneClient Failure.");
return phoneClient;
}

}

/**
* 发送消息给单个用户(一对一的业务 ,如: 商家有新单的消息提示)

* 注: 此处发送的JPush 类型为自定义的,可 通知栏显示, 可APP内部处理, 区别于 只是通知栏显示的消息
*
* @param uid 用户id
* @param dtype 设备类型 1:phone 2:pad 3:pc
* @param msg 消息内容
* @param title 消息标题
* @param customFields 自定义数据 (可传null表示没有自定义内容) 与前端约定的常用的自定义数据有消息类型 type 默认使用 customFields.put("type",
* MessagePushType.DEFAULT.getValue()); 其他如消息ID, msgId则暂未启用
*/
public void push2One(long uid, short dtype, String msg, String title, Map customFields) {

// 获取当前用户 登录的设备信息
Map profile = getAndroidProfile(uid, dtype);
if (profile == null || profile.size() <= 0) {

logger.error("AndroidPushMain#notify | 获取用户推送相关信息时发生错误, 没找到设备信息 | uid: {}, dtype: {}", uid, dtype);
return;
}

logger.info("AndroidPush#push2One | 发送消息给单个用户 | uid: {}, dtype: {}, msg: {}, title: {}, customFields: {}", uid, dtype, msg, title,
customFields);

String deviceToken = profile.get("deviceToken");


sendPush(deviceToken, title, msg, customFields, dtype, uid);

}

/**
*
* 底层发送接口
*
* @param deviceToken 设备唯一标识
* @param title 消息标题
* @param msg 消息内容
* @param customFields 自定义数据 (可传null表示没有自定义内容) 与前端约定的常用的自定义数据有消息类型 type 默认使用 customFields.put("type", MessagePushType.DEFAULT.getValue());
* 其他如消息ID, msgId则暂未启用
* @param dtype 设备类型 phone、pad
* @param uid 用户uid
* @author sky 2016-03-11
*/
public void sendPush(String deviceToken, String title, String msg, Map customFields, short dtype, long uid) {
// 目前通过 registrationId 来发送消息 消息类型为自定义类型


JPushClient jpushClient = getClient(dtype);

if (jpushClient == null) {
logger.error("AndroidPush#push2One | 获得的JPush client 为空 | dtype: {}", getDeviceTypeName(dtype));
return;
}

String type = "";
Map extras = null;

if (MapUtils.isNotEmpty(customFields)) {

//
// 获取需要跳转的业务类型:跳转至APP首页 / 跳至商家新单列表/ 跳至...

// 具体业务类型 由 MessagePushType 中定义, 类型的定义需要服务端与客户端实现对接,扩展性不是很好

type = String.valueOf(customFields.get("type"));

if (customFields.containsKey("params")) {
extras = new HashMap();
@SuppressWarnings("unchecked")
Map tmp = (Map) customFields.get("params");
for (Entry entry : tmp.entrySet()) {
extras.put(entry.getKey(), entry.getValue().toString());
}
}
}

PushPayload payload = (null == extras ? JPushPayloadWrapper.messageWithRegId(deviceToken, title, type, msg) : JPushPayloadWrapper
.messageWithRegId(deviceToken, title, type, msg, extras));

try {
PushResult result = jpushClient.sendPush(payload);

logger.info("AndroidPushMain#jpush | 消息推送完毕, 推送结果 | result: {}", result);

} catch (Exception e) {

if (e instanceof APIRequestException) {

APIRequestException ee = (APIRequestException) e;

logger.error(
"AndroidPushMain#jpush | 发送推送时发生错误 | uid: {}, deviceToken: {}, title: {}, pushMsg: {}, httpStatus: {}, errorCode: {}, errorMsg: {}, msgId: {}",
uid, deviceToken, title, msg, ee.getStatus(), ee.getErrorCode(), ee.getErrorMessage(), ee.getMsgId());

} else if (e instanceof APIConnectionException) {
logger.error("AndroidPushMain#jpush | 发送推送Connection error. Should retry later | uid: {}, deviceToken: {}, errorMsg: {} ",
uid, deviceToken, e.getMessage(), e);
}

}
}


/**
* 获取用户的android设备 push 相关信息
*
* @param uid 用户id
* @param dtype 设备类型 1:phone 2:pad
* @return
* @author sky
*/
public static Map getAndroidProfile(long uid, short dtype) {

try {

String json = HttpClientUtils.doGet(CommonConstants.USER_DOMAIN + "/api/v1/deviceToken/android/queryByUidAndDtype?uid=" + uid
+ "&dtype=" + dtype);
RestResult> result = JsonUtils.parseObject(json, new TypeReference>>() {
});
return result.getObject();

} catch (Exception e) {

logger.error("AndroidPushMain#getAndroidProfile | 获取用户Android设备信息发送错误 | uid: {}, dtype: {}, errorMsg: {}", uid, dtype,
e.getMessage());

}

return null;
}

private static String getKey(short dtype) {

return (String) EncryptionPropertyPlaceholderConfigurer.getConfig().get(getDeviceTypeName(dtype) + "_pushAppkey");

}

private static String getSecret(short dtype) {

return (String) EncryptionPropertyPlaceholderConfigurer.getConfig().get(getDeviceTypeName(dtype) + "_pushAppMasterSecret");

}

private static String getDeviceTypeName(short dtype) {
String type = "";
if (dtype == DeviceType.PC.getValue())
type = "pc";
else if (dtype == DeviceType.PAD.getValue())
type = "pad";
else
type = "phone";
return type;
}

public String getAppkey() {
return appkey;
}

public void setAppkey(String appkey) {
this.appkey = appkey;
}

public String getAppMasterSecret() {
return appMasterSecret;
}

public void setAppMasterSecret(String appMasterSecret) {
this.appMasterSecret = appMasterSecret;
}

}







/**
* 对JPush PushPayload 的本地业务需求包装
*
* @author sky
*
*/
public class JPushPayloadWrapper {

/**
* android 平台
*/
private static final Platform Android = Platform.android();

/**
* 可通知栏显示, 可APP 内部显示(怎么显示由APP决定:当APP处于后台时,通知栏显示; 当APP处于前台时, 内部方式显示)

* 自定义消息 push
*
* @param regId 注册id(唯一标识)
* @param title 消息标题
* @param contentType 内容类型 , 该值表达了这条push的业务类型, contentType的值在MessagePushType中被定义
* @param content 消息内容
* @return
*/
public static PushPayload messageWithRegId(String regId, String title, String contentType, String content) {
return PushPayload.newBuilder().//
setAudience(Audience.registrationId(regId)).//
setPlatform(Android).//
setMessage(Message.newBuilder().//
setTitle(title).//
setContentType(contentType).//
setMsgContent(content).//
build()).//
build();
}

/**
* 可通知栏显示, 可APP 内部显示(怎么显示由APP决定:当APP处于后台时,通知栏显示; 当APP处于前台时, 内部方式显示)

* 自定义消息 push
*
* @param regId 注册id(唯一标识)
* @param title 消息标题
* @param contentType 内容类型 , 该值表达了这条push的业务类型, contentType的值在MessagePushType中被定义
* @param content 消息内容
* @param extras 附件信息体 extras中包含了该条消息的业务类型数据:以type为键, value 为 MessagePushType 中定义的值
* @return
*/
public static PushPayload messageWithRegId(String regId, String title, String contentType, String content, Map extras) {
return PushPayload.newBuilder().//
setAudience(Audience.registrationId(regId)).//
setPlatform(Android).//
setMessage(Message.newBuilder().//
setTitle(title).//
setContentType(contentType).//
setMsgContent(content).//
addExtras(extras).build()).//
build();
}

/**
* 可通知栏显示, 可APP 内部显示(怎么显示由APP决定:当APP处于后台时,通知栏显示; 当APP处于前台时, 内部方式显示)

* 自定义消息 push
*
* @param alias 别名
* @param title 消息标题
* @param contentType 内容类型 , 该值表达了这条push的业务类型, contentType的值在MessagePushType中被定义
* @param content 消息内容
* @return
*/
public static PushPayload messageWithAlias(String alias, String title, String contentType, String content) {
return PushPayload.newBuilder().setAudience(Audience.alias(alias)).//
setPlatform(Android).//
setMessage(Message.newBuilder().//
setTitle(title).//
setContentType(contentType).//
setMsgContent(content).//

build()).//
build();
}

/**
*
* 通知栏显示

* 广播式消息 push, 通过标签来发送给用户
*
* @param tag 用户标签
* @param title 消息标题
* @param content 消息内容
* @param extras 附件信息体 extras中包含了该条消息的业务类型数据:以type为键, value 为 MessagePushType 中定义的值
* @return
*/
public static PushPayload notifyWithTag(String tag, String title, String content, Map extras) {
return PushPayload.newBuilder().//
setPlatform(Android).//
setAudience(Audience.tag(tag)).//
setNotification(Notification.android(content, title, extras)).//
build();
}

/**
* 通知栏显示

* 广播式消息 push,通过别名来发送给用户
*
* @param alias 别名
* @param title 消息标题
* @param content 消息内容
* @param extras extras中包含了该条消息的业务类型数据:以type为键, value 为 MessagePushType 中定义的值
* @return
*/
public static PushPayload notifyWithAlias(String alias, String title, String content, Map extras) {
return PushPayload.newBuilder().//
setPlatform(Android).//
setAudience(Audience.alias(alias)).//
setNotification(Notification.android(content, title, extras)).//
build();

}

/**
* 通知栏显示

* 广播式消息 push,通过注册deviceToken来发送给用户
*
* @param regId 注册id(唯一标识)
* @param title 消息标题
* @param content 消息内容
* @param extras extras中包含了该条消息的业务类型数据:以type为键, value 为 MessagePushType 中定义的值
* @return
*/
public static PushPayload notifyWithRegId(String regId, String title, String content, Map extras) {
return PushPayload.newBuilder().//
setPlatform(Android).//
setAudience(Audience.registrationId(regId)).//
setNotification(Notification.android(content, title, extras)).//
build();
}

/**
* 通知栏显示

* 广播式消息 push
*
* @param content 消息内容
* @return
*/
public static PushPayload notifyAll(String content) {
return PushPayload.alertAll(content);
}

}

你可能感兴趣的:(数据库)