1. 程式人生 > >Java開源生鮮電商平臺-通知模塊設計與架構(源碼可下載)

Java開源生鮮電商平臺-通知模塊設計與架構(源碼可下載)

bsp 數據庫設計 turn 指定 希望 except 就是 mobile apn

Java開源生鮮電商平臺-通知模塊設計與架構(源碼可下載)

說明:對於一個生鮮的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. 我采用代碼拆分,一個類只做一件事情,幾個類分別協同開發,達到最高程度的解耦,代碼清晰,維護度高。

總結:關於這個消息的推送,我們也可以采用微信來通知,比如:你的訂單到了,你的訂單已經接受,錢已經到賬等等,還有業務線上的推送等等,我這邊

只是根據實際的運行情況,起到一個拋磚引玉的作用,目的只有一個原因,讓大家有獨立思考與分析業務的能力。

Java開源生鮮電商平臺-通知模塊設計與架構(源碼可下載),如果需要下載的話,可以在我的github下面進行下載。

Java開源生鮮電商平臺-通知模塊設計與架構(源碼可下載)