`
xiaoliang330
  • 浏览: 115999 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

push数据结构设计

 
阅读更多


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

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

可能会有的需求: 推送消息给已注册用户、推送消息给所有用户、推送消息给匿名用户


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


上传机制: 用户设备did 上传的时机很重要

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也可以设计类似的结构,这样一个结构即可满足以上所有的需求(实现自己的推送逻辑,而不是使用极光的后台)









接口实现:



/**
 * 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实例<br>
	 * 
	 * 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;
		}

	}

	/**
	 * 发送消息给单个用户(一对一的业务 ,如: 商家有新单的消息提示) <br>
	 * <b>注: 此处发送的JPush 类型为自定义的,可 通知栏显示, 可APP内部处理, 区别于 只是通知栏显示的消息</b>
	 * 
	 * @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<String, Object> customFields) {

		// 获取当前用户 登录的设备信息
		Map<String, String> 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<String, Object> 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<String, String> extras = null;

		if (MapUtils.isNotEmpty(customFields)) {

			//
			// 获取需要跳转的业务类型:跳转至APP首页 / 跳至商家新单列表/ 跳至... <br>
			// 具体业务类型 由 MessagePushType 中定义, 类型的定义需要服务端与客户端实现对接,扩展性不是很好

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

			if (customFields.containsKey("params")) {
				extras = new HashMap<String, String>();
				@SuppressWarnings("unchecked")
				Map<String, Object> tmp = (Map<String, Object>) customFields.get("params");
				for (Entry<String, Object> 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<String, String> getAndroidProfile(long uid, short dtype) {

		try {

			String json = HttpClientUtils.doGet(CommonConstants.USER_DOMAIN + "/api/v1/deviceToken/android/queryByUidAndDtype?uid=" + uid
					+ "&dtype=" + dtype);
			RestResult<Map<String, String>> result = JsonUtils.parseObject(json, new TypeReference<RestResult<Map<String, String>>>() {
			});
			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处于前台时, 内部方式显示) <br>
	 * 自定义消息 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处于前台时, 内部方式显示) <br>
	 * 自定义消息 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<String, String> 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处于前台时, 内部方式显示) <br>
	 * 自定义消息 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();
	}

	/**
	 * 
	 * <b>通知栏显示</b><br>
	 * 广播式消息 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<String, String> extras) {
		return PushPayload.newBuilder().//
				setPlatform(Android).//
				setAudience(Audience.tag(tag)).//
				setNotification(Notification.android(content, title, extras)).//
				build();
	}

	/**
	 * <b>通知栏显示</b><br>
	 * 广播式消息 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<String, String> extras) {
		return PushPayload.newBuilder().//
				setPlatform(Android).//
				setAudience(Audience.alias(alias)).//
				setNotification(Notification.android(content, title, extras)).//
				build();

	}

	/**
	 * <b>通知栏显示</b><br>
	 * 广播式消息 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<String, String> extras) {
		return PushPayload.newBuilder().//
				setPlatform(Android).//
				setAudience(Audience.registrationId(regId)).//
				setNotification(Notification.android(content, title, extras)).//
				build();
	}

	/**
	 * <b>通知栏显示</b><br>
	 * 广播式消息 push
	 * 
	 * @param content 消息内容
	 * @return
	 */
	public static PushPayload notifyAll(String content) {
		return PushPayload.alertAll(content);
	}

}







分享到:
评论

相关推荐

    数据结构设计--电梯模拟系统.doc

    电梯模拟系统的设计是一个典型的计算机科学问题,涉及到数据结构和算法的应用。在这个系统中,主要的知识点包括堆栈、队列的使用,以及如何通过模块化设计来模拟电梯的运行过程。 1. **堆栈(Stack)**:在该系统中...

    数据结构程序设计

    在“数据结构程序设计”中,我们通常会学习一系列经典的数据结构,如数组、链表、栈、队列、树、图、哈希表等,并学习如何用编程语言实现它们。 数组是最基础的数据结构,它是一系列相同类型元素的集合,通过索引...

    括号匹配数据结构设计

    括号匹配数据结构设计 括号匹配问题是计算机科学中的一种基本问题,旨在判断给定的表达式中括号是否正确配对。括号匹配问题可以应用于程序设计语言的语法分析、编译器设计、文本编辑器等领域。为了解决括号匹配问题...

    2020—2021学年浙教版(2020)七年级下册教案-第13课初识数据结构.pdf

    根据提供的文件内容,本节课的主要内容是让学生初识数据结构,并理解算法的三种基本控制结构,包括顺序结构、分支结构和循环结构。...同时,学生应该能够体会到良好的数据结构设计对于编写高效算法的重要性。

    程序设计与数据结构

    - **栈**:栈是一种后进先出(LIFO)的数据结构,主要操作包括压栈(push)和弹栈(pop)。 - **队列**:队列是一种先进先出(FIFO)的数据结构,主要操作包括入队(enqueue)和出队(dequeue)。 2. **非线性结构...

    数据结构课程设计--栈的应用

    在计算机科学中,数据结构是组织、存储和处理数据的方式,它是算法设计的基础。栈是一种特殊的数据结构,遵循“后进先出”(LIFO)原则,即最后存入的元素最先被取出。本课程设计的重点是探讨栈在解决实际问题,特别...

    数据结构课程设计

    【数据结构课程设计——带括号的算术表达式求值】 在计算机科学中,数据结构与算法分析是至关重要的基础课程,它涉及到如何高效地组织和处理数据。本次课程设计的主题是“带括号的算术表达式求值”,这是一个典型的...

    数据结构教程 by 李春葆

    数据结构是计算机科学中的核心课程,它探讨了如何在计算机中高效地组织和管理数据,以便于进行快速的存取和处理。李春葆教授的数据结构教程是一本广泛使用的教材,它深入浅出地介绍了这一领域的基本概念和算法。在这...

    个人数据结构程序设计汇总

    这个"个人数据结构程序设计汇总"压缩包包含了数据结构学习过程中的各个章节实验,为学习者提供了丰富的实践素材。下面将对每个章节的实验进行详细讲解。 1. **线性表实验**:线性表是最基本的数据结构,包括数组和...

    数据结构车厢调度课程设计

    "数据结构车厢调度课程设计" 本资源摘要信息将对数据结构车厢调度课程设计进行详细的知识点总结。 一、数据结构基础 在本课程设计中,我们使用了数据结构中的栈来实现车厢调度问题。栈是一种常用的数据结构,它...

    数据结构课程设计报告书

    2. **栈**:栈是一种后进先出(LIFO)的数据结构,它具有“压栈”(PUSH)和“弹栈”(POP)操作。在这个系统中,栈用于表示停车场的停车位,当车辆进入时压栈,离开时弹栈,确保车辆的进出顺序符合实际情况。 3. *...

    数据结构课程设计(可视化栈及队列的演示)

    在这个"数据结构课程设计(可视化栈及队列的演示)"项目中,我们将深入理解两种基本的数据结构:栈和队列,并在VC++6.0环境下利用系统控件进行可视化展示。 栈是一种后进先出(LIFO, Last In First Out)的数据结构...

    数据结构投影教案数据结构数据结构数据结构

    在编程和软件工程中,理解和掌握数据结构至关重要,因为它直接影响到算法的设计和程序的性能。以下将对标题和描述中提及的知识点进行详细阐述。 1. **数据结构概述** 数据结构是一门研究数据组织方式的学科,它...

    数据结构课后答案.pdf

    3. **栈(Stack)**:栈是一种后进先出(LIFO)的数据结构,仅允许在一端进行插入(push)和删除(pop)操作。常见的栈应用包括函数调用的实现、括号匹配检查、深度优先搜索(DFS)算法等。 4. **队列(Queue)**:...

    大二数据结构课程设计

    在大二阶段进行的数据结构课程设计是一门至关重要的学习任务,它涵盖了计算机科学与技术的基础核心内容。数据结构是计算机科学中的基石,它研究如何高效地存储和组织数据,以便于进行各种操作,如查找、插入和删除。...

    数据结构课程设计《马的遍历问题》.pdf

    "数据结构课程设计《马的遍历问题》" 本文旨在解决马的遍历问题,设计一个算法来遍历中国象棋棋盘上的一匹马,要求输出所走过的每个位置的坐标,并能够动态地标注行走过程。 问题描述 马的遍历问题是中国象棋棋盘...

    数据结构课程设计:重言式的判别

    栈是一种后进先出(LIFO)的数据结构,可以实现元素的压入(push)、弹出(pop)、查看栈顶元素(gettop)以及初始化(initstack)等操作。 2. **二叉树(bitree)**:表示逻辑表达式的运算结构。二叉树的节点包含...

    数据结构 C C++ JAVA

    C++则在C的基础上增加了面向对象的特性,使得数据结构的设计更加模块化,可以利用类和对象来封装数据和操作,提高代码的可读性和复用性。Java作为一种完全的面向对象语言,提供了垃圾回收机制,减轻了程序员对内存...

Global site tag (gtag.js) - Google Analytics