在我们的软件开发中,系统通知和推送是必不可少的一部分,本篇文章将针对Android端和服务端分别讲解如何完整的实现一个系统通知与推送的功能
,文章更注重逻辑、思想,不会讲那些基本极光sdk集成内容,如果需要请直接去极光推送官网查看文档。
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())) {}
//判断应用是否在运行代码(无论在前台还是后台)private boolean getCurrentTask(Context context) {ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);List
//判断启动调用方法
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});}
对于服务端,我们先来规范一下数据结构,需要给移动端提供哪些内容,需要往后台管理员操作时需要向表中添加那些数据。以下是数据库中的用到的表,消息通知表和通知类别表。表的结构以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,代表的含义是是否需要打开,需要打开的话是否需要登录。
后台管理员在发送通知的时候的逻辑:
过滤代码以及注释如下:
/*
* 验证系统消息或者通知发送次数
* */
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});});
};
文章中针对一对一&#xff0c;外键在原模型中的情况下
进行联合查询&#xff0c;如果其他情况应该怎么处理&#xff0c;例如场景多对多情况下查询&#xff0c;一对多情况
服务器端&#xff0c;我们在开发一些post类型的路由的时候一定要考虑被黑的情况&#xff0c;被模拟多次post请求&#xff0c;例如场景&#xff0c;获取样正码接口&#xff0c;意见反馈接口&#xff0c;发送消息通知接口。
好久没有写博客了&#xff0c;基本思想讲到这里&#xff0c;如果有小伙伴觉得有问题的地方&#xff0c;可以与我沟通哦。
本人公众号&#xff0c;记录学习成长&#xff0c;各种干货&#xff0c;能帮助到你哦。