1. 程式人生 > >21、生鮮電商平臺-通知模組設計與架構

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