热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发过程中的系统通知与推送的全面讲解

前言:在我们的软件开发中,系统通知和推送是必不可少的一部分,本篇文章将针对Android端和服务端分别讲解如何完整的实现一个系统通知与推

前言:

在我们的软件开发中,系统通知和推送是必不可少的一部分,本篇文章将针对Android端和服务端分别讲解如何完整的实现一个系统通知与推送的功能
,文章更注重逻辑、思想,不会讲那些基本极光sdk集成内容,如果需要请直接去极光推送官网查看文档。

学前准备

Android

  • 了解广播的基本使用,创建广播的几种方式
  • ActivityManage和packpageManage的区别
  • 第三方推送基本功能(例如极光推送)

服务器端

  • 服务端了解基本node开发知识
  • Mysql数据库有一些了解(本篇文章使用sequelize)

Andrid端

以流程图方式开始:

是应用已启动

image

应用未启动

image

流程图每一步骤逻辑进行详细讲解

  1. 收到推送消息入口,android开发者首先要知道推送的目的是什么,是否需要用户打开通知栏,跳转到详细信息(逻辑一)。
    代码如下:

if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {int notifactionId = bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID);Log.d(TAG, "[JPushReceiver] 接收到推送下来的通知的ID: " + notifactionId);} else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {}

  1. 判断如果是需要打开的通知栏之后,需要判断是否启动了应用,代码如下(逻辑二)

//判断应用是否在运行代码(无论在前台还是后台)private boolean getCurrentTask(Context context) {ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);List appProcessInfos = activityManager.getRunningTasks(50);for (RunningTaskInfo process : appProcessInfos) {if (process.baseActivity.getPackageName().equals(context.getPackageName())|| process.topActivity.getPackageName().equals(context.getPackageName())) {return true;}}return false;}
//判断启动调用方法
if (getCurrentTask(context)) { }else{ }

2.1判断后当应用属于启动状态,判断该通知消息显示是否需要用户登录,如果不需要用户登录则直接打开消息通知详情界面。

2.2用户属于启动状态,判断该通知消息是否需要用户登录,如果需要用户登录,需要先跳转登录界面,登录成功后跳转消息通知详细界面。

上面2.1与2.2代码如下

Intent pushIntent = new Intent();pushIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);pushIntent.putExtra("pushMessage", pushMessage);/*** 需要登陆且当前没有登陆才去登陆页面*/if (pushMessage.messageType != null && pushMessage.messageType.equals("2")&& !AccountManager.isSignIn()) {pushIntent.setClass(context, LoginActivity.class);pushIntent.putExtra("fromPush", true);} else {/*** 不需要登陆或者已经登陆的Case,直接跳转到内容显示页面*/pushIntent.setClass(context, PushMessageActivity.class);}context.startActivity(pushIntent);

3.判断如果是需要打开的通知栏以后,如果应用没有启动,首先启动应用。

3.1应用启动以后,仍然是判断用户是否需要登录,查看通知消息,用户不需要登录就可以进入消息详情界面,需要使用startactivities方法,它的特点是只创建一个activity,返回后创建参数前面的activity。

3.2如果用户需要登录,方法一致,不过先跳转到loginActivity。代码如下
第三部分代码实现如下

Intent mainIntent = new Intent(context, MainActivity.class);mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (pushMessage.messageType != null&& pushMessage.messageType.equals("2")) {Intent loginIntent = new Intent();loginIntent.setClass(context, LoginActivity.class);loginIntent.putExtra("fromPush", true);loginIntent.putExtra("pushMessage", pushMessage);context.startActivities(new Intent[]{mainIntent, loginIntent});} else {Intent pushIntent = new Intent(context, PushMessageActivity.class);pushIntent.putExtra("pushMessage", pushMessage);context.startActivities(new Intent[]{mainIntent, pushIntent});}

Android端代码实现过程反思

  • startActivities的基本使用,是否在栈中创建两个activity呢。
  • Intent.FLAG_ACTIVITY_NEW_TASK 需要复习一下。
  • 通过判断用户是否登录可以联想到开发其它项目时候游客模式的使用。

Android开发过程中的几点注意事项

  • 注意首先理清需求,我们到底要实现哪些逻辑,也就是这几个if else。然后再动手写代码。

服务端

对于服务端,我们先来规范一下数据结构,需要给移动端提供哪些内容,需要往后台管理员操作时需要向表中添加那些数据。以下是数据库中的用到的表,消息通知表和通知类别表。表的结构以sequlize的当时和在mysql中显示两种样式展示。comment是对字段的描述

消息通知表结构如下图

'use strict'
module.exports = function (sequelize, DataTypes) {return sequelize.define('Notice', {id: { type: DataTypes.BIGINT(11), autoIncrement: true, primaryKey: true, unique: true, comment:'主键' },noticeStyleId: { type: DataTypes.BIGINT(11), field: 'notice_style_id', allowNull: false, comment:'子级Id通知类型 0版本更新 1重大活动,后续添加' },noticeTypeId: { type: DataTypes.BIGINT(11), field: 'notice_type_id', allowNull: false, comment:'父级Id:通知类型 0只通知不需要打开 1不需登陆就可以查看 2登陆后方可查看' },noticeContent: { type: DataTypes.STRING, field: 'notice_content', allowNull: false, comment:'通知内容' },noticeInitiator: { type: DataTypes.INTEGER, field: 'notice_initiator', allowNull: false,defaultValue:0, comment:'通知发起人 0管理员 1运营者' },noticeTarget: { type: DataTypes.INTEGER, field: 'notice_target', allowNull: false, comment:'通知对象 0 all 1男 2女 3vip 4 非vip' },noticeImg: {type: DataTypes.STRING, field: 'notice_img', comment:'通知图片'},status:{type: DataTypes.INTEGER,allowNull:false,defaultValue:0,comment:'通知状态'},deleted: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false, comment:'是否已删除'},createdOn: { type: DataTypes.DATE, field: 'created_on', allowNull: false, defaultValue: DataTypes.NOW },updatedOn: { type: DataTypes.DATE, field: 'updated_on', allowNull: false, defaultValue: DataTypes.NOW }},{underscore: false,timestamps: false,freezeTableName: true,tableName: 'notice',comment: '系统通知表',charset: 'utf8',collate: 'utf8_general_ci'});
}

通知类别表结构
如下图

'use strict'
module.exports = function (sequelize, DataTypes) {return sequelize.define('NoticeCategory', {id: { type: DataTypes.BIGINT(11), autoIncrement: true, primaryKey: true, unique: true, comment:'主键' },noticeTitle: { type: DataTypes.STRING, field: 'notice_title', allowNull: false, defaultValue: "系统通知", comment:'通知标题' },noticeEngTitle: { type: DataTypes.STRING, field: 'notice_eng_title', allowNull: false, defaultValue: " System notification", comment:'通知标题英文' },FatherLevelId: { type: DataTypes.INTEGER, field: 'father_level_id', allowNull: false, comment:'父类级别ID,例如存放 0是只发送不可打开的 1可打开,不需登陆 2登陆后可打开' },//rel: { type: DataTypes.STRING, field: 'rel', allowNull: false,defaultValue:0, comment:'表中主键id和父级别id的关联 前父,后主1,2' },status:{type: DataTypes.INTEGER,allowNull:false,defaultValue:0,comment:'通知状态'},deleted: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false, comment:'是否已删除'},createdOn: { type: DataTypes.DATE, field: 'created_on', allowNull: false, defaultValue: DataTypes.NOW },},{underscore: false,timestamps: false,freezeTableName: true,tableName: 'noticeCategory',comment: '系统通知类型表',charset: 'utf8',collate: 'utf8_general_ci'});
}

需要注意的是二者之间的关系是一对一,通知表中的noticeStyleId,也就是通知表的外键对应通知类别表的id,同时在类别表中包含父类id,代表的含义是是否需要打开,需要打开的话是否需要登录。

后台管理员在发送通知的时候的逻辑:

  1. 对于管理端,需要一个接口路由,这个路由应该是post请求,但是这种post提交类型的接口需要考虑一个重要的点,被频繁post攻击,也就是被黑。所以需要一个中间件进行过滤。(包括一天内只能发送通知的次数,3分钟内不能频繁发送通知等)

过滤代码以及注释如下:

/*
* 验证系统消息或者通知发送次数
* */
exports.sendNoticeMiddle &#61; (req, res, next) &#61;> {let NoticeDa &#61; new Notice;NoticeDa.findAll({where: {noticeInitiator: 0}, order: &#39;id Desc&#39;, limit: 3}).then((result) &#61;> {if (result.length > 0) {//判断距离上传发送通知时间不小于3分钟if (new Date().getTime() - result[0].createdOn.getTime() <180000) {return res.json({code: -1, message: &#39;请不要频繁发送推送通知哦&#xff01;&#39;})}//每天提交次数不能超过三次if (result.length > 2) {if (new Date().getDay() &#61;&#61; result[2].createdOn.getDay()) {return res.json({code: -1, message: &#39;每天只能发送三次推送通知哦&#xff01;&#39;})}}}next();})
};

2.中间件过滤完成后&#xff0c;根据post的向数据库中插入数据&#xff0c;同时发送通知&#xff0c;并且把消息类型以json的合适发送&#xff0c;代码如下。

//发送通知 --------注意请求参数也是file
exports.sendNotice &#61; (req, res, next) &#61;> {let form &#61; new formidable.IncomingForm();form.keepExtensions &#61; true;form.parse(req, function (err, fields, files) {if (!fields.content) {return res.json({code: -1, message: &#39;请简单描述您反馈的问题&#39;})}let data &#61; {noticeTypeId: fields.noticeTypeId,noticeContent: fields.noticeContent,noticeInitiator: fields.noticeInitiator,noticeTarget: fields.noticeTarget,noticeImg: &#39;&#39;,createdOn: new Date(),updatedOn: new Date()};if (files && files.file) {//pathlet dirpath &#61; &#39;public/files/notice&#39;;//创建目录文件if (!tool.mkdirSync(dirpath, &#39;777&#39;)) {return res.json({code: -1, message: &#39;服务器繁忙&#xff01;&#39;})}let filePath &#61; files.file.path;let fileExt &#61; filePath.substring(filePath.lastIndexOf(&#39;.&#39;));if ((&#39;.jpg.jpeg.png.gif&#39;).indexOf(fileExt.toLowerCase()) > -1) {//以当前时间戳对上传文件进行重命名let fileName &#61; new Date().getTime() &#43; &#39;-&#39; &#43; fields.noticeTitle &#43; fileExt;let targetFile &#61; dirpath &#43; &#39;/&#39; &#43; fileName;let readStream &#61; fs.createReadStream(filePath);let writeStream &#61; fs.createWriteStream(targetFile);readStream.pipe(writeStream);//修改路径相对路径data.noticeImg &#61; targetFile;}}//向通知类别表中插入数据时候的id noticeStyleIdlet categoryData &#61; {noticeEngTitle: fields.noticeTitle,noticeTitle: fields.noticeTitle,FatherLevelId: fields.noticeTypeId,createdOn: new Date()};let NoticeCategoryDa &#61; new NoticeCategory();let NoticeDa &#61; new Notice();NoticeCategoryDa.creat(categoryData).then((result) &#61;> {if (result) {data.noticeStyleId &#61; result.id;NoticeDa.create(data).then((result) &#61;> {if (result) {console.log("推送通知添加成功")client.push().setPlatform(&#39;ios&#39;, &#39;android&#39;).setAudience(JPush.tag(&#39;555&#39;, &#39;666&#39;), JPush.alias(&#39;666,777&#39;)).setNotification(&#39;Hi, JPush&#39;, JPush.ios(&#39;ios alert&#39;), JPush.android(&#39;android alert&#39;, null, 1)).setMessage(&#39;msg content&#39;).setOptions(null, 60).send().then(function(result) {res.json({code: 0, message: &#39;通知发送成功&#39;});}).catch(function(err) {res.json({code: 0, message: &#39;通知发送失败&#39;});});}})}});})
};

3.两个表一对一的关系&#xff0c;联合查询通知列表的接口代码实现。

代码如下。

exports.getNoticeList &#61; (req, res, next) &#61;> {let noticeDa &#61; new Notice();let noticeCategory &#61; new NoticeCategory();//查询出所有list&#xff0c;去除类型为0的只通知不可查看的 关联查询let include &#61; [{association: noticeDa.model.belongsTo(noticeCategory.model, {foreignKey: &#39;noticeStyleId&#39;, targetKey: &#39;id&#39;}),//一对一 外键在源文件中&#xff0c;下面调用查找方法查找的是带外键的表 attribute要求的内容是noticeCategory中的数据required: true,attributes: [&#39;noticeTitle&#39;, &#39;noticeEngTitle&#39;],//注意这个不是表上显示的值where: {deleted: false}}];let page &#61; 1;let pages &#61; 6;if (req.query.quantity && !isNaN(parseInt(req.query.quantity))) {pages &#61; parseInt(req.query.quantity);}if (req.query.page && !isNaN(parseInt(req.query.page))) {page &#61; parseInt(req.query.page);}noticeDa.paginate({where: {$not: {noticeTypeId: &#39;0&#39;}, deleted: false},page: page,perPage: pages,options: {order: &#39;createdOn DESC&#39;, include: include}}).then((result) &#61;> {let items &#61; [];result.forEach(function (item) {items.push({id: item.id,noticeTitle: item.NoticeCategory.noticeTitle,noticeEngTitle: item.NoticeCategory.noticeEngTitle,noticeImg: item.noticeImg,noticeContent: item.noticeContent,noticeTypeId: item.noticeTypeId,noticeStyleId: item.noticeStyleId,})});res.json({code: 0, items: items});});
};

服务端的反思&#xff1a;

文章中针对一对一&#xff0c;外键在原模型中的情况下
进行联合查询&#xff0c;如果其他情况应该怎么处理&#xff0c;例如场景多对多情况下查询&#xff0c;一对多情况

服务端注意事项

服务器端&#xff0c;我们在开发一些post类型的路由的时候一定要考虑被黑的情况&#xff0c;被模拟多次post请求&#xff0c;例如场景&#xff0c;获取样正码接口&#xff0c;意见反馈接口&#xff0c;发送消息通知接口。

完结

好久没有写博客了&#xff0c;基本思想讲到这里&#xff0c;如果有小伙伴觉得有问题的地方&#xff0c;可以与我沟通哦。

本人公众号&#xff0c;记录学习成长&#xff0c;各种干货&#xff0c;能帮助到你哦。
在这里插入图片描述


推荐阅读
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 在Kubernetes上部署JupyterHub的步骤和实验依赖
    本文介绍了在Kubernetes上部署JupyterHub的步骤和实验所需的依赖,包括安装Docker和K8s,使用kubeadm进行安装,以及更新下载的镜像等。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
author-avatar
我们要走下去一直的
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有