21、生鲜电商平台-通知模块设计与架构

说明:对于一个生鲜的B2B平台而言,通知对于我们实际的运营而言来讲分为三种方式:

          1. 消息推送:(采用极光推送)

          2. 主页弹窗通知。(比如:现在有什么新的活动,有什么新的优惠等等)

          3. 短信通知.(对于短信通知,这个大家很熟悉,我们就说下我们如何从代码层面对短信进行分层的分析与架构)

1. 消息推送

说明:目前市场上的推送很多,什么极光推送,环信,网易云等等,都可以实现秒级别的推送,我们经过了市场调研与稳定性考察,最终选择了极光推送。

极光推送,市面上有很大的文档与实例,我这边就不详细讲解了,因为文档很清晰,也的确很简单。

相关的核心功能与代码如下:

1. 功能划分

1.1向所有的人推送同一个消息。

1.2 具体的某个人,或者某类人推送消息,自己简单的进行了一个SDK等封装

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.jiguang.common.ClientConfig;
import cn.jiguang.common.resp.APIConnectionException;
import cn.jiguang.common.resp.APIRequestException;
import cn.jpush.api.JPushClient;
import cn.jpush.api.push.PushResult;
import cn.jpush.api.push.model.Options;
import cn.jpush.api.push.model.Platform;
import cn.jpush.api.push.model.PushPayload;
import cn.jpush.api.push.model.audience.Audience;
import cn.jpush.api.push.model.notification.AndroidNotification;
import cn.jpush.api.push.model.notification.IosNotification;
import cn.jpush.api.push.model.notification.Notification;
/**
 * 激光推送
 */
public class Jdpush {

    private static final Logger log = LoggerFactory.getLogger(Jdpush.class);

    // 设置好账号的app_key和masterSecret
    public static final String APPKEY = "";

    public static final String MASTERSECRET = "";

        /**
         * 推送所有
         */
        public static PushPayload buildPushObjectAndroidIosAllAlert(String message){
            return PushPayload.newBuilder()
                    .setPlatform(Platform.android_ios())
                    .setAudience(Audience.all())//推送所有;
                    .setNotification(Notification.newBuilder()
                            .addPlatformNotification(AndroidNotification.newBuilder()
                                    .addExtra("type", "infomation")
                                    .setAlert(message)
                                    .build())
                            .addPlatformNotification(IosNotification.newBuilder().setSound("callu")
                                    .addExtra("type", "infomation")
                                    .setAlert(message)
                                    .build())
                            .build())
                    .setOptions(Options.newBuilder()
                            .setApnsProduction(false)//true-推送生产环境 false-推送开发环境(测试使用参数)
                            .setTimeToLive(90)//消息在JPush服务器的失效时间(测试使用参数)
                            .build())
                    .build();
        }

        /**
         * 推送 指定用户集合;
         */
        public static PushPayload buildPushObjectAndroidIosAliasAlert(List<String> userIds,String message){
            return PushPayload.newBuilder()
                    .setPlatform(Platform.android_ios())
                    .setAudience(Audience.alias(userIds))//推送多个;
                    .setNotification(Notification.newBuilder()
                            .addPlatformNotification(AndroidNotification.newBuilder()
                                    .addExtra("type", "infomation")
                                    .setAlert(message)
                                    .build())
                            .addPlatformNotification(IosNotification.newBuilder().setSound("callu")
                                    .addExtra("type", "infomation")
                                    .setAlert(message)
                                    .build())
                            .build())
                    .setOptions(Options.newBuilder()
                            .setApnsProduction(false)//true-推送生产环境 false-推送开发环境(测试使用参数)
                            .setTimeToLive(90)//消息在JPush服务器的失效时间(测试使用参数)
                            .build())
                    .build();
        }

        /**
         * 推送单个人;
         */
        public static PushPayload buildPushObjectAndroidIosAliasAlert(String userId,String message){
            return PushPayload.newBuilder()
                    .setPlatform(Platform.android_ios())
                    .setAudience(Audience.alias(userId))//推送单个;
                    .setNotification(Notification.newBuilder()
                            .addPlatformNotification(AndroidNotification.newBuilder()
                                    .addExtra("type", "infomation")
                                    .setAlert(message)
                                    .build())
                            .addPlatformNotification(IosNotification.newBuilder().setSound("callu")
                                    .addExtra("type", "infomation")
                                    .setAlert(message)
                                    .build())
                            .build())
                    .setOptions(Options.newBuilder()
                            .setApnsProduction(false)//true-推送生产环境 false-推送开发环境(测试使用参数)
                            .setTimeToLive(90)//消息在JPush服务器的失效时间(测试使用参数)
                            .build())
                    .build();
        }

        /**
         * 推送所有
         */
        public static PushResult pushAlias(String alert){
            ClientConfig clientConfig = ClientConfig.getInstance();
            JPushClient jpushClient = new JPushClient(MASTERSECRET, APPKEY, null, clientConfig);
            PushPayload payload = buildPushObjectAndroidIosAllAlert(alert);
            try {
                return jpushClient.sendPush(payload);
            } catch (APIConnectionException e) {
                log.error("Connection error. Should retry later. ", e);
                return null;
            } catch (APIRequestException e) {
                log.error("Error response from JPush server. Should review and fix it. ", e);
                log.info("HTTP Status: " + e.getStatus());
                log.info("Error Code: " + e.getErrorCode());
                log.info("Error Message: " + e.getErrorMessage());
                log.info("Msg ID: " + e.getMsgId());
                return null;
            }
        }

        /**
         * 推送 指定用户集合;
         */
        public static PushResult pushAlias(List<String> userIds,String alert){
            ClientConfig clientConfig = ClientConfig.getInstance();
            JPushClient jpushClient = new JPushClient(MASTERSECRET, APPKEY, null, clientConfig);
            PushPayload payload = buildPushObjectAndroidIosAliasAlert(userIds,alert);
            try {
                return jpushClient.sendPush(payload);
            } catch (APIConnectionException e) {
                log.error("Connection error. Should retry later. ", e);
                return null;
            } catch (APIRequestException e) {
                log.error("Error response from JPush server. Should review and fix it. ", e);
                log.info("HTTP Status: " + e.getStatus());
                log.info("Error Code: " + e.getErrorCode());
                log.info("Error Message: " + e.getErrorMessage());
                log.info("Msg ID: " + e.getMsgId());
                return null;
            }
        }

        /**
         * 推送单个人;
         */
        public static PushResult pushAlias(String userId,String alert){
            ClientConfig clientConfig = ClientConfig.getInstance();
            JPushClient jpushClient = new JPushClient(MASTERSECRET, APPKEY, null, clientConfig);
            PushPayload payload = buildPushObjectAndroidIosAliasAlert(userId,alert);
            try {
                return jpushClient.sendPush(payload);
            } catch (APIConnectionException e) {
                log.error("Connection error. Should retry later. ", e);
                return null;
            } catch (APIRequestException e) {
                log.error("Error response from JPush server. Should review and fix it. ", e);
                log.info("HTTP Status: " + e.getStatus());
                log.info("Error Code: " + e.getErrorCode());
                log.info("Error Message: " + e.getErrorMessage());
                log.info("Msg ID: " + e.getMsgId());
                return null;
            }
        }

}

2. 业务通知

说明:有些事情,我们希望用户打开APP就知道某些事情,这个时候我们就需要做一个首页通知机制,由于这种机制是用户主动接受,因此,我们需要进行系统设计与架构

2.1 存储用户的推送消息。

2.2 统计那些用户看了与没看。

数据库设计如下:

CREATE TABLE `buyer_notice` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT ‘自动增加ID‘,
  `buyer_id` bigint(20) DEFAULT NULL COMMENT ‘买家ID‘,
  `content` varchar(60) DEFAULT NULL COMMENT ‘内容‘,
  `status` int(11) DEFAULT NULL COMMENT ‘状态,0为未读,1为已读‘,
  `create_time` datetime DEFAULT NULL COMMENT ‘创建时间‘,
  `update_time` datetime DEFAULT NULL COMMENT ‘最后更新时间,已读时间‘,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=262 DEFAULT CHARSET=utf8 COMMENT=‘买家通知‘;

说明:字段相对比较简单,就是买家ID,内容,读取状态等等,

           业务逻辑为:当用户进入系统,我们系统代码查询业务逻辑的时候,也查询 下这个表是否存在通知,如果已经有的,就不用弹窗,没有就弹窗,强迫用户选择已读或者未读。

相对而言业务比较简单

/***
 * 买家进入首页,看到的通知
 */
@RestController
@RequestMapping("/buyer")
public class NoticeController extends BaseController{

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

    public static final String CONTENT="平台下单时间调整为上午10:00到晚上23:59";

    @Autowired
    private NoticeService noticeService;

    /**
     * 查询消息
     */
    @RequestMapping(value = "/notice/index", method = { RequestMethod.GET, RequestMethod.POST })
    public JsonResult noticeIndex(HttpServletRequest request, HttpServletResponse response,Long buyerId){
        try
        {
            if(buyerId==null)
            {
                return new JsonResult(JsonResultCode.FAILURE, "请求参数有误,请重新输入","");
            }

            Notice notice=this.noticeService.getNoticeByBuyerId(buyerId);

            if(notice==null)
            {

                int result=this.noticeService.insertNotice(buyerId, CONTENT);

                if(result>0)
                {
                    notice=this.noticeService.getNoticeByBuyerId(buyerId);
                }
            }
            return new JsonResult(JsonResultCode.SUCCESS, "查询信息成功", notice);

        }catch(Exception ex){
            logger.error("[NoticeController][noticeIndex] exception :",ex);
            return new JsonResult(JsonResultCode.FAILURE, "系统错误,请稍后重试","");
        }
    }

    /**
     * 更新消息
     */
    @RequestMapping(value = "/notice/update", method = { RequestMethod.GET, RequestMethod.POST })
    public JsonResult noticeUpdate(HttpServletRequest request, HttpServletResponse response,Long buyerId){
        try
        {
            if(buyerId==null)
            {
                return new JsonResult(JsonResultCode.FAILURE, "请求参数有误,请重新输入","");
            }

            int result=this.noticeService.updateBuyerNotice(buyerId);

            if(result>0)
            {
                return new JsonResult(JsonResultCode.SUCCESS, "更新成功","");
            }else
            {
                return new JsonResult(JsonResultCode.FAILURE, "更新失败","");
            }
        }catch(Exception ex){
            logger.error("[NoticeController][noticeUpdate] exception :",ex);
            return new JsonResult(JsonResultCode.FAILURE, "系统错误,请稍后重试","");
        }
    }
}

3. 短信通知模块的设计

说明:市面上短信供应商很多,可能大家就是关注一个价格与及时性的问题,目前我们找的一个稍微便宜点的供应商:http://api.sms.cn/

内容其实就是短信的发送而言。

接口文档很简单:

参数名    参数字段    参数说明
ac    接口功能    接口功能,传入值请填写 send
format    返回格式    可选项,有三参数值:json,xml,txt 默认json格式
uid    用户账号    登录名
pwd    用户密码    32位MD5加密md5(密码+uid)
如登录密码是:123123 ,uid是:test;
pwd=md5(123123test)
pwd=b9887c5ebb23ebb294acab183ecf0769
encode    字符编码    可选项,默认接收数据是UTF-8编码,如提交的是GBK编码字符,需要添加参数 encode=gbk
mobile    接收号码    同时发送给多个号码时,号码之间用英文半角逗号分隔(,);小灵通需加区号
如:13972827282,13072827282
mobileids    消息编号    可选项
该参数用于发送短信收取状态报告用,格式为消息编号+逗号;与接收号码一一对应,可以重复出现多次。
消息编号:全部由数字组成接收状态报告的时候用到,该消息编号的格式可就为目标号码+当前时间戳整数,精确到毫秒,确保唯一性。供收取状态报告用 如: 1590049111112869461937;

content    短信内容    变量模板发送,传参规则{"key":"value"}JSON格式,key的名字须和申请模板中的变量名一致,多个变量之间以逗号隔开。示例:针对模板“短信验证码{$code},您正在进行{$product}身份验证,请在10分钟内完成操作!”,传参时需传入{"code":"352333","product":"电商平台"}
template    模板短信ID    发送变量模板短信时需要填写对应的模板ID号,进入平台-》短信设置-》模板管理

对此,我们如何进行业务研究与处理呢?

1. 短信验证码的长度与算法。

2. 代码的模板进行封装。

3. 短信工具类的使用方便

1. 短信验证码生成算法:

import org.apache.commons.lang3.RandomStringUtils;

/**
 * 短信验证码
 *
 */
public final class SmsCode {

    /**
     * 默认产生的验证码数目
     */
    private static int DEFAULT_NUMBER = 6;

    /**
     * 产生的随机号码数目
     *
     * @param number
     * @return
     */
    public static String createRandomCode(int number) {
        int num = number <= 3 ? DEFAULT_NUMBER : number;
        return RandomStringUtils.randomNumeric(num);
    }
}

简单粗暴的解决问题:

2. 短信内容的封装:

/***
 * 短信消息对象
 */
public class SmsMessage
{
    /**
     * 账号,目前就是手机号码,采用的是手机号码登陆
     */
    private String account;

    /*
     * 产生的验证码
     */
    private String code;

    /**
     * 对应的短信模板,目前短信验证码是401730
     */
    private String template;

    public SmsMessage() {
        super();
    }

    public SmsMessage(String account, String code, String template) {
        super();
        this.account = account;
        this.code = code;
        this.template = template;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getTemplate() {
        return template;
    }

    public void setTemplate(String template) {
        this.template = template;
    }

    @Override
    public String toString() {
        return "{\"username\":\""+account+"\",\"code\":\""+code+"\"}";
    }

3.短信发送结果的封装:

/**
 * 短信发送结果
 */
public class SmsResult implements java.io.Serializable{

    private static final long serialVersionUID = 1L;

    private boolean success=false;

    private String message;

    public SmsResult() {
        super();
    }

    public SmsResult(String message)
    {
        super();
        this.success=false;
        this.message=message;
    }

    public SmsResult(boolean success, String message) {
        super();
        this.success = success;
        this.message = message;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("SmsResult [success=");
        builder.append(success);
        builder.append(", message=");
        builder.append(message);
        builder.append("]");
        return builder.toString();
    }
}

4. 短信发送工具类的封装

/**
 * 短信工具
 */
@Component
public class SmsUtil {

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

    @Value("#{applicationProperties[‘environment‘]}")
    private String environment;

    /**
     * 默认编码的格式
     */
    private static final String CHARSET="GBK";

    /**
     * 请求的网关接口
     */
    private static final String URL = "http://api.sms.cn/sms/";

    public boolean sendSms(SmsMessage smsMessage)
    {
        boolean result=true;

        logger.debug("[SmsUtil]当前的运行环境为:"+environment);

        //开发环境就直接返回成功
        if("dev".equalsIgnoreCase(environment))
        {
            return result;
        }

        Map<String, String> map=new HashMap<String,String>();

        map.put("ac","send");
        map.put("uid","");
        map.put("pwd","");
        map.put("template",smsMessage.getTemplate());
        map.put("mobile",smsMessage.getAccount());
        map.put("content",smsMessage.toString());

        try
        {
            String responseContent=HttpClientUtil.getInstance().sendHttpPost(URL, map,CHARSET);

            logger.info("SmsUtil.sendSms.responseContent:" + responseContent);

            JSONObject json = JSONObject.fromObject(responseContent);

            logger.info("SmsUtil.sendSms.json:" + json);

            String stat=json.getString("stat");

            if(!"100".equalsIgnoreCase(stat))
            {
                result=false;
            }

        }catch(Exception ex)
        {
            result=false;
            logger.error("[SmsUtil][sendSms] exception:",ex);
        }
        return result;
    }
}

补充说明;其实我可以用一个工具类来解决所有问题,为什么我没采用呢?

               1 。代码耦合度高,不变管理与扩展.(业务分析,其实就是三种情况,1,发送,2,内容,3,返回结果)

               2.   我采用代码拆分,一个类只做一件事情,几个类分别协同开发,达到最高程度的解耦,代码清晰,维护度高。

总结:关于这个消息的推送,我们也可以采用微信来通知,比如:你的订单到了,你的订单已经接受,钱已经到账等等,还有业务线上的推送等等,我这边

只是根据实际的运行情况,起到一个抛砖引玉的作用,目的只有一个原因,让大家有独立思考与分析业务的能力。

转载自-- https://www.cnblogs.com/jurendage/p/9095078.html

原文地址:https://www.cnblogs.com/lu-manman/p/10052347.html

时间: 2024-07-29 21:33:41

21、生鲜电商平台-通知模块设计与架构的相关文章

Java开源生鲜电商平台-通知模块设计与架构(源码可下载)

Java开源生鲜电商平台-通知模块设计与架构(源码可下载) 说明:对于一个生鲜的B2B平台而言,通知对于我们实际的运营而言来讲分为三种方式:           1. 消息推送:(采用极光推送)           2. 主页弹窗通知.(比如:现在有什么新的活动,有什么新的优惠等等)           3. 短信通知.(对于短信通知,这个大家很熟悉,我们就说下我们如何从代码层面对短信进行分层的分析与架构) 1. 消息推送 说明:目前市场上的推送很多,什么极光推送,环信,网易云等等,都可以实现秒

Java开源生鲜电商平台-监控模块的设计与架构(源码可下载)

Java开源生鲜电商平台-监控模块的设计与架构(源码可下载) 说明:Java开源生鲜电商平台-监控模块的设计与架构,我们谈到监控,一般设计到两个方面的内容: 1. 服务器本身的监控.(比如:linux服务器的CPU,内存,磁盘IO等监控) 2. 业务系统的监控.  (比如:业务系统性能的监控,SQL语句的监控,请求超时的监控,用户输入的监控,整个请求过程时间的监控,优化等等) 1. 服务器本身的监控 说明:由于Java开源生鲜电商平台采用的是阿里云的linux CentOS服务器,由于阿里云本身

16、生鲜电商平台-监控模块的设计与架构

说明:Java开源生鲜电商平台-监控模块的设计与架构,我们谈到监控,一般设计到两个方面的内容: 1. 服务器本身的监控.(比如:linux服务器的CPU,内存,磁盘IO等监控) 2. 业务系统的监控.  (比如:业务系统性能的监控,SQL语句的监控,请求超时的监控,用户输入的监控,整个请求过程时间的监控,优化等等) 1. 服务器本身的监控 说明:由于Java开源生鲜电商平台采用的是阿里云的linux CentOS服务器,由于阿里云本身是有监控预警的,但是我们不可能时刻去看,最好有集成自己的系统监

28、生鲜电商平台-库存管理设计与架构

说明:Java开源生鲜电商平台-库存管理设计与架构有以下几个功能 WMS的功能:1.业务批次管理 该功能提供完善的物料批次信息.批次管理设置.批号编码规则设置.日常业务处理.报表查询,以及库存管理等综合批次管理功能,使企业进一步完善批次管理,满足经营管理的需求. 2.保质期管理在批次管理基础上,针对物料提供保质期管理及到期存货预警,以满足食品和医yao行业的保质期管理需求.用户可以设置保质期物料名称.录入初始数据.处理日常单据,以及查询即时库存和报表等. 3.质量检验管理集成质量管理功能是与采购

32、生鲜电商平台-商品价格的设计与架构

说明:Java开源生鲜电商平台-商品价格的设计与架构,主要是对商品的价格进行研究与系统架构. 一.常见的电商价格 市场价(List Price):这个价格仅是用于显示,用于衬托网站销售价格的优惠程度: 销售价(Sales Price):亦称我们的价格.零售价等,如果没有任何优惠的(包括促销优惠.会员等级优惠等), 就按这个价格进行销售.所有的优惠规则均是基于这个价格进行计算. 特价(Special Price):优先级最高的定价,忽略所有的价格规则. SKU价格(SKU Price):同一个产品

23、生鲜电商平台-服务器部署设计与架构

补充说明:Java开源生鲜电商平台-服务器部署设计与架构,指的是通过服务器正式上线整个项目,进行正式的运营. 回顾整个章节,我们涉及到以下几个方面: 1. 买家端 2. 卖家端. 3. 销售端 4. 配送端. 5.系统运营端. 6.公司网址 目前根据业务的情况,采购了阿里云服务器,由于是创业,我身上没多少钱,只采购了一台阿里云.(具体配置如下与域名规划如下) 公司网址: http://www.netcai.com 买家端:  http://buyer.netcai.com 卖家端:  http:

24、生鲜电商平台-系统报表设计与架构

说明:任何一个运行的平台都需要一个很清楚的报表来显示,那么作为Java开源生鲜电商平台而言,我们应该如何设计报表呢?或者说我们希望报表来看到什么数据呢?           通过报表我们可以分析出目前整个公司的运营情况,以及下一步的调整方向,这样更加有理有据的掌握整个市场与决策. 设计基础维度:    1. 今日订单,今日营业额,总订单数,总营业额          2. 今日的注册买家,总的注册买家.          3. 实时的营收,实时的下单买家.          4. 今日下单买家,

Java开源生鲜电商平台-Java后端生成Token架构与设计详解(源码可下载)

Java开源生鲜电商平台-Java后端生成Token架构与设计详解(源码可下载) 目的:Java开源生鲜电商平台-Java后端生成Token目的是为了用于校验客户端,防止重复提交. 技术选型:用开源的JWT架构. 1.概述:在web项目中,服务端和前端经常需要交互数据,有的时候由于网络相应慢,客户端在提交某些敏感数据(比如按照正常的业务逻辑,此份数据只能保存一份)时,如果前端多次点击提交按钮会导致提交多份数据,这种情况我们是要防止发生的. 2.解决方法: ①前端处理:在提交之后通过js立即将按钮

8、生鲜电商平台-购物车模块的设计与架构

说明:任何一个电商无论是B2C还是B2B都有一个购物车模块,其中最重要的原因就是客户需要的东西放在一起,形成一个购物清单,确认是否有问题,然后再进行下单与付款. 1. 购物车数据库设计: 说明:业务需求: 1>购物车里面应该存放,那个买家,买了那个菜品的什么规格,有多少数量,然后这个菜品的加工方式如何.(如果存在加工方式的话,就会在这里显示处理.) 2>买家存在购物起送价.也就是用户放入购物车的商品的总价格如果低于配置的起送价,那么这个提交按钮就是灰色的.(不可能你点一个洋葱我们就送过去,成本