1. 程式人生 > >極光訊息推送伺服器端開發實現推送(上)

極光訊息推送伺服器端開發實現推送(上)

以前一直使用的極光的手動輸入推送內容然後推送到客戶端,今天遇到了推送頻率比較高且比較有規律的內容,比如事實天氣。這樣就需要用我們自己的伺服器來自動生成推送內容了。


可以看到,上面兩句話很醒目,我們看看它封裝的REST API是個什麼東西,再點進去看看


上面兩句話讀了一下,大笑看來我們的運氣還不錯,這個應該就是了。

進去看了半天,大概明白意思了,已經幫助我們封裝好了,現在我們只需要下載jar包和它提供的例項文件。


進去後發現又對RESI API進行了更詳細的說明,比如引數、頻率限制等。

好吧,我們看看推送訊息和通知,如下圖點選進入


進去之後就看到了真真需要的api介面了,這裡有各個方法和引數的說明。如果有的朋友還覺得看不懂,簡單,直接下載示例程式碼(抄襲誰不會啊偷笑


我下載官方示例程式碼,新建立了一個專案如下:


訊息傳送端程式碼:

package com.meritit.tuisong.service;
import java.util.HashMap;
import java.util.Map;

import cn.jpush.api.ErrorCodeEnum;
import cn.jpush.api.IOSExtra;
import cn.jpush.api.JPushClient;
import cn.jpush.api.MessageResult;

public class JPushClientExample {

	private static final String appKey ="5d30bebd28335593a13454861";	//必填,例如466f7032ac604e02fb7bda89

	private static final String masterSecret = "0e0cc80c6f6a4703bec9ed191";//"13ac09b17715bd117163d8a1";//必填,每個應用都對應一個masterSecret

	private static JPushClient jpush = null;

	/**
	 * 儲存離線的時長。秒為單位。最多支援10天(864000秒)。
	 * 0 表示該訊息不儲存離線。即:使用者線上馬上發出,當前不線上使用者將不會收到此訊息。
	 * 此引數不設定則表示預設,預設為儲存1天的離線訊息(86400秒)。
	 */
	private static long timeToLive =  60 * 60 * 24;  

	public static void main(String[] args) {
		/*
		 * Example1: 初始化,預設傳送給android和ios,同時設定離線訊息存活時間
		 * jpush = new JPushClient(masterSecret, appKey, timeToLive);
		 */

		/*		
		 * Example2: 只發送給android
		 * jpush = new JPushClient(masterSecret, appKey, DeviceEnum.Android);
		 */

		/*
		 * Example3: 只發送給IOS
		 * jpush = new JPushClient(masterSecret, appKey, DeviceEnum.IOS);
		 */

		/*
		 * Example4: 只發送給android,同時設定離線訊息存活時間
		 * jpush = new JPushClient(masterSecret, appKey, timeToLive, DeviceEnum.Android);
		 */


		jpush = new JPushClient(masterSecret, appKey, timeToLive);

		/*
		 * 是否啟用ssl安全連線, 可選
		 * 引數:啟用true, 禁用false,預設為非ssl連線
		 */
		//jpush.setEnableSSL(true);


		//測試傳送訊息或者通知
		testSend();
	}

	private static void testSend() {
		// 在實際業務中,建議 sendNo 是一個你自己的業務可以處理的一個自增數字。
		// 除非需要覆蓋,請確保不要重複使用。詳情請參考 API 文件相關說明。
		int sendNo = getRandomSendNo();
		String msgTitle = "+;//jpush\"\"";
		String msgContent = "\\&;w\"\"a--【\npush】";

		/*
		 * IOS裝置擴充套件引數,
		 * 設定badge,設定聲音
		 */

		Map<String, Object> extra = new HashMap<String, Object>();
		IOSExtra iosExtra = new IOSExtra(10, "WindowsLogonSound.wav");
		extra.put("ios", iosExtra);

		//對所有使用者傳送通知, 更多方法請參考文件
		MessageResult msgResult = jpush.sendCustomMessageWithAppKey(sendNo,msgTitle, msgContent);
		//MessageResult msgResult  = jpush.sendNotificationWithAlias(sendNo, "a", msgTitle, msgContent);

		//覆蓋指定msgId的訊息,msgId可以從msgResult.getMsgid()獲取。
		//MessageResult msgResult = jpush.sendNotificationWithAppKey(sendNo, msgTitle, msgContent, 0, extra,msgResult.getMsgid());


		if (null != msgResult) {
			System.out.println("伺服器返回資料: " + msgResult.toString());
			if (msgResult.getErrcode() == ErrorCodeEnum.NOERROR.value()) {
				System.out.println(String.format("傳送成功, sendNo= %s,messageId= %s",msgResult.getSendno(),msgResult.getMsg_id()));
			} else {
				System.out.println("傳送失敗, 錯誤程式碼=" + msgResult.getErrcode() + ", 錯誤訊息=" + msgResult.getErrmsg());
			}
		} else {
			System.out.println("無法獲取資料");
		}

	}

	public static final int MAX = Integer.MAX_VALUE;
	public static final int MIN = (int) MAX/2;

	/**
	 * 保持 sendNo 的唯一性是有必要的
	 * It is very important to keep sendNo unique.
	 * @return sendNo
	 */
	public static int getRandomSendNo() {
		return (int) (MIN + Math.random() * (MAX - MIN));
	}

}

執行結果如下:



訊息接收端程式碼:

package com.meritit.tuisong.service;
import java.util.List;

import cn.jpush.api.JPushClient;
import cn.jpush.api.receive.ReceiveResult;


public class ReceiveClientExample {

	private static final String appKey ="5d30bebd28335593a13454861";	//必填,例如466f7032ac604e02fb7bda89

	private static final String masterSecret = "0e0cc80c6f6a4703bec9ed191";//"13ac09b17715bd117163d8a1";//必填
	
	
	public static void main(String[] args) {
		JPushClient JPushClient = new JPushClient(masterSecret, appKey);
		
		String msgId = "1236722141";
		
		String[] msgIds = {"1236722141","910981248","911034889"};
		
		//獲取一條
		ReceiveResult receiveResult =  JPushClient.getReceived(msgId);
		if(receiveResult == null){
			System.out.println("獲取receive 資料失敗!"+receiveResult);
		}else{
			//gson toJson 之後,NULL值的欄位會被過濾掉
			System.out.println("received result:"+receiveResult.toString());
		}
	
	
		// 獲取多條
		List<ReceiveResult> receiveResults = JPushClient.getReceiveds(msgIds);
		if(receiveResults == null ){
			System.out.println("獲取receive 資料失敗!");
		}else{
			System.out.println("成功獲取了:"+receiveResults);
		}
	
	}
}
執行結果:

測試已經成功,下面我們來看看原始碼是怎麼做的,其他的其實只是資料封裝,我們來看看關鍵的一句程式碼

MessageResult msgResult = jpush.sendCustomMessageWithAppKey(sendNo,msgTitle, msgContent);

檢視原始碼,如下:

	public MessageResult sendCustomMessageWithAppKey(int sendNo, String msgTitle, String msgContent) {
		CustomMessageParams p = new CustomMessageParams();
		p.setReceiverType(ReceiverTypeEnum.APPKEYS);
		return sendCustomMessage(p, sendNo, msgTitle, msgContent, null, null);
	}
發現實際是呼叫的sendCustomMessage方法
	protected MessageResult sendCustomMessage(CustomMessageParams p, int sendNo, String msgTitle, String msgContent, String msgContentType, Map<String, Object> extra) {
		if (null != msgContentType) {
			p.getMsgContent().setContentType(msgContentType);
		}
		if (null != extra) {
			p.getMsgContent().setExtra(extra);
		}
		return sendMessage(p, sendNo, msgTitle, msgContent);
	}
這裡進行了空值判斷,實際又呼叫了sendMessage方法
	protected MessageResult sendMessage(MessageParams p, int sendNo, String msgTitle, String msgContent) {
		p.setSendNo(sendNo);
		p.setAppKey(this.getAppKey());
		p.setMasterSecret(this.masterSecret);
		p.setTimeToLive(this.timeToLive);
		p.setSendDescription(this.getSendDescription());
		for (DeviceEnum device : this.getDevices()) {
			p.addPlatform(device);
		}
		if (null != msgTitle) {
			p.getMsgContent().setTitle(msgTitle);
		}
		p.getMsgContent().setMessage(msgContent);
	
		return sendMessage(p);
	}
在這裡將引數封裝到訊息物件中呼叫sendMessage
	protected MessageResult sendMessage(MessageParams params) {
		return httpClient.sendPush(BaseURL.ALL_PATH, enableSSL, params);
	}
再進到sendPush方法中看看,哦,大概明白了,實際上是用的http請求傳送訊息的。
	public MessageResult sendPush(final String path, final boolean enableSSL, final MessageParams messageParams) {
		MessageResult messageResult = ValidateRequestParams.vidateParams(messageParams);
		if(messageResult != null) return messageResult;

		String pushResult = sendPost(path, enableSSL, parse(messageParams),RequestTypeEnum.PUSH.value(),null);
		return gson.fromJson(pushResult, MessageResult.class);
	}
關鍵看倒數第二行程式碼
	private String sendPost( String path, final boolean enableSSL, String params,Integer reqeustType,String authCode){
		return sendRequest(path, enableSSL, params, "POST", reqeustType,authCode);
	}

	private String sendRequest(String path, final boolean enableSSL, String params,String method,Integer reqeustType,String authCode){
		HttpURLConnection conn = null;
		DataOutputStream outStream = null;
		StringBuffer sb = new StringBuffer();

		try {
			if (enableSSL) {
				initSSL();
			}			

			URL url = new URL(BaseURL.getUrlForPath(path,enableSSL,reqeustType));
			conn = (HttpURLConnection) url.openConnection();
			conn.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT);
			conn.setReadTimeout(DEFAULT_SOCKET_TIMEOUT);
			conn.setUseCaches(false);
			conn.setDoOutput(true);
			conn.setRequestMethod(method);
			conn.setRequestProperty("Connection", "Keep-Alive");
			conn.setRequestProperty("Charset", CHARSET);
			if(authCode != null && !authCode.isEmpty()){
				conn.setRequestProperty("Authorization", authCode);
			}

			if(method.equals("POST")){
				byte[] data = params.getBytes(CHARSET);
				conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
				conn.setRequestProperty("Content-Length", String.valueOf(data.length));
				outStream = new DataOutputStream(conn.getOutputStream());
				outStream.write(data);
				outStream.flush();
			}

			if (conn.getResponseCode() == 200) {
				logger.info("Congratulations!The request was successful. response status is 200");
				InputStream in = conn.getInputStream();
				InputStreamReader reader = new InputStreamReader(in, CHARSET);
				char[] buff = new char[1024];
				int len;
				while ((len = reader.read(buff)) > 0) {
					sb.append(buff, 0, len);
				}
			} else {		
				logger.log(Level.WARNING,"Sorry!The request was fault. response " +
						"status = "+conn.getResponseCode()+",errormsg = "+conn.getHeaderField(0));

				String errmsg = "";
				if(reqeustType == RequestTypeEnum.RECEIVE.value()){
					errmsg = ErrorCodeEnum.errorMsg(conn.getResponseCode());
					errmsg = errmsg == null ? conn.getHeaderField(0) : errmsg;
				}else{
					errmsg = conn.getHeaderField(0);
				}
				BaseResult result = new BaseResult(errmsg,conn.getResponseCode());
				return result.toString();
			}

		}
		catch (SocketTimeoutException e) {		
			logger.log(Level.SEVERE,"God! the server throw SocketTimeout Exception." +
					"please check it out the error message:"+e.getMessage());
			BaseResult baseResult = new BaseResult(e.getMessage().toString(),ErrorCodeEnum.CONNECTIONTIMEOUT.value());
			return baseResult.toString();
		}
		catch (ConnectException e) {
			logger.log(Level.SEVERE,"God! the server throw Connect Exception ." +
					"please check it out the error message:"+e.getMessage());
			BaseResult baseResult = new BaseResult(e.getMessage().toString(),ErrorCodeEnum.CONNECTIONREFUSED.value());
			return baseResult.toString();
		}
		catch (UnknownHostException e) {
			logger.log(Level.SEVERE,"God! the server throw UnknownHost Exception ." +
					"please check it out the error message:"+e.getMessage());
			BaseResult baseResult = new BaseResult(e.getMessage().toString(),ErrorCodeEnum.CONNECTIONREFUSED.value());
			return baseResult.toString();
		}
		catch (Exception e) {
			logger.log(Level.SEVERE,"God! the server throw exception." +
					"please check it out the error message:"+e.getMessage());
			BaseResult baseResult = new BaseResult(e.getMessage().toString(),ErrorCodeEnum.UnknownException.value());
			return baseResult.toString();
		} 
		finally {		
			if (null != outStream) {
				try {
					outStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (null != conn) {
				conn.disconnect();
			}
		}
		return sb.toString();
	}

學過Android的朋友對這個應該很熟悉吧!比如裡面的URL請求地址,看第9行程式碼
URL url = new URL(BaseURL.getUrlForPath(path,enableSSL,reqeustType));
	public  static String getUrlForPath(final String path,boolean enableSSL,Integer type) {
		return getHostname(enableSSL,type) + path;
	}

	private static String getHostname(boolean enableSSL,Integer type) {
		if(type == RequestTypeEnum.PUSH.value())
			return enableSSL? HOST_NAME_SSL :HOST_NAME;
		
		if(type == RequestTypeEnum.RECEIVE.value())
			return enableSSL? RECEIVE_HOST_NAME:RECEIVE_HOST_NAME;
		
		return null;
	}
在這裡進行判斷,如果enableSSL為false則傳送訊息請求地址為HOST_NAME,實際上這個enableSSL在BaseClient類中預設為false
public boolean enableSSL = false;
HOST_NAME就是官方文件中所說的預設請求地址:
public static String HOST_NAME = "http://api.jpush.cn:8800";

public static  String RECEIVE_HOST_NAME = "https://report.jpush.cn:443";