本文中含有需要您注意的重要提示信息,忽略該信息可能對您的業務造成影響,請務必仔細閱讀。
功能概述
支持授權公眾號給Quick Tracking,可以將公眾號行為事件(如:關注、菜單訪問、取消關注等)上報到Quick Tracking中用于公眾號的用戶行為分析。
1 操作說明
下述表格為公眾號事件采集的必要步驟,請按照順序操作:
步驟序號 | 描述 | 操作明細 |
1 | 在微信公眾號平臺開啟開發者配置 | 微信公眾號平臺可點擊跳轉 警告 再次提示:若需要采集微信公眾號事件,需成為公眾號開發者,成為開發者后,公眾號平臺中的「自動回復、自定義菜單」將無法使用,已經配置的信息也將失效,需要您自主維護。操作流程可見微信官方說明 |
2 | 在QuickTracking平臺配置公眾號采集 | 接入的操作明細可點擊查看 |
3 | 將微信公眾號的事件轉發至QuickTracking中 | 該步驟的操作說明可點擊查看 |
4 | 在QuickTracking中進行數據分析 | 具體可分析的事件列表可點擊2 事件及事件屬性查看 |
1.1 接入公眾號
產品路徑:「數據采集」-「數據源管理」-「公眾號管理」
點擊「接入公眾號」按鈕,彈出右側「接入數據源」頁面完成新建接入公眾號,完成接入需要填寫如下信息:
「基礎信息」
公眾號名稱:填寫要接入的公眾號名稱
公眾號ID:填寫公眾號ID,公眾號ID不可重復。獲取方式見下圖:
「分析設置」
歸屬組織:默認為當前組織不可修改,如需在不同組織下接入公眾號,需在左側組下拉框切換組織。
歸屬應用:在下拉選擇框選擇當前歸屬組織下的應用。
「接收微信消息」
加密模式:分為安全模式與明文模式;
安全模式:EncodingAESKey 用于消息體加解密密鑰,與微信公眾號平臺填寫一致。
明文模式:不使用消息體加解密功能,安全系數較低。
EncodingAESKey 獲取地址:公眾號后臺-基礎配置-復制EncodingAESKey。
Token獲取地址:公眾號后臺-基礎配置-復制Token
(注:微信公眾號后臺URL為用戶填寫地址,微信公眾號將數據發至填寫地址后,再同步至Quick Tracking。參考文檔)
「匿名用戶唯一標識」
必選項,設置以Union或OpenID來作為匿名用戶的唯一標識。
OpenID:用戶唯一標識;
UnionID:用于區分用戶的唯一值,設置為UnionID后,需獲取access_token。
「獲取access_token」
如果有其他業務同時使用了Token,用戶需自搭建的中控服務,來統一獲取、維護access_token,在分析平臺中可從中控服務來獲取access_token。(注:選擇unionID為匿名用戶唯一標識,用戶需配置一個控制中心。Quick Tracking通過中控中心來獲取token,在微信公眾號獲取對應的unionID(因每次調用獲取token后token會進行刷新,其他位置使用的token也會刷新改變。且獲取token次數微信開發平臺有限制,用戶可以從控制中心獲取token,且可控制token的周期))
中控服務器地址:填寫自建的中控服務地址可以獲取到token中控服務地址;
access_token解析:從中控服務器獲取的返回值中解析access_token,通過JSONPah表達式解析,填寫 JSONPath表達式,點擊「測試」,測試是否解析成功,解析成功則可正常獲取。
完成以上步驟點擊保存完成創建。
1.2 公眾號管理
公眾號操作區主要由以下幾個部分組成:
左側組織篩選區:切換不同組織下接入的公眾號
數據展示區:展示已接入的公眾號列表,數據包含公眾號名稱、公眾號APPID、創建人、歸屬應用和操作。注意:分析設置中,一個公眾號只能選擇歸屬一個應用。
操作區:操作區包含「編輯」與「操作」,點擊「編輯」后和新增的界面一致,點擊刪除則表示從應用中移除了公眾號的關聯。
1.3 數據分析
接入公眾號后的應用,以及有“接入公眾號后的應用”的分析視圖和小站,系統屬性中需要有公眾號事件參與分析,其中參與分析的系統屬性如下表格:
系統屬性 | 屬性值 |
平臺 | 公眾號AppID@wechatoa |
平臺類型 | 公眾號 |
事件類型 | 自定義事件 |
事件名稱&事件編碼 | 具體可點擊2 事件及事件屬性查看 |
設備ID | openid(默認),可以在公眾號管理中改為Unionid |
設備是否首日啟動 | 根據歸屬應用內的數據一同打標 |
透出公眾號事件的分析模型有:事件分析、漏斗分析、留存分析、分布分析、間隔分析、路徑分析、生命周期、歸因分析、人群管理。
2 事件及事件屬性
微信公眾號事件是由微信定義、生成和發送的,開發者僅為訂閱這些數據,無法進行自定義。具體可訂閱的事件列表如下:
事件名稱 | 事件編碼 | 事件屬性名稱 (分析展示) | 屬性Key | 屬性類型 | 說明 | |
菜單點擊(預置) | $$_menu_click | 消息類型 | MsgType | string | 消息類型:event | |
事件類型 | Event | string | 事件類型:CLICK | |||
事件key | EventKey | string | 事件 KEY 值,與自定義菜單接口中 KEY 值對應 | |||
菜單ID | MenuID | string | 指菜單ID,如果是個性化菜單,則可以通過這個字段,知道是哪個規則的菜單被點擊了。 | |||
掃描信息 | ScanCodeInfo | string | 掃描信息 | |||
掃描類型 | ScanType | string | 掃描類型:一般是qrcode | |||
掃描結果 | ScanResult | string | 掃描結果,即二維碼對應的字符串信息 | |||
發送的圖片信息 | SendPicsInfo | string | 發送的圖片信息 | |||
發送的圖片數量 | Count | number | 發送的圖片數量 | |||
發送的位置信息 | SendLocationInfo | string | 發送的位置信息 | |||
X坐標信息 | Location_X | string | X坐標信息 | |||
Y坐標信息 | Location_Y | string | Y坐標信息 | |||
精度 | Scale | number | 精度,可理解為精度或者比例尺、越精細的話 scale越高 | |||
地理位置 | Label | string | 地理位置的字符串信息 | |||
朋友圈POI名稱 | Poiname | string | 朋友圈 POI 的名字,可能為空 | |||
關注公眾號 (預置) | $$_official_account_follow | 是否是掃碼關注 | is_scan | string | 是否是掃碼關注(將原始事件的關注公眾號和掃碼關注公眾號融合) | |
事件key | EventKey | string | 事件 KEY 值,qrscene_為前綴,后面為二維碼的參數值 | |||
事件類型 | Event | string | 事件類型:subscribe(訂閱) | |||
二維碼ticket | Ticket | string | 二維碼的ticket,可用來換取二維碼圖片 | |||
取消關注公眾號 (預置) | $$_official_account_unfollow | 消息類型 | MsgType | string | 消息類型:event | |
事件類型 | Event | string | 事件類型:unsubscribe(取消訂閱) | |||
掃碼打開公眾號 (預置) | $$_scan_open_official_account | 消息類型 | MsgType | string | 消息類型:event | |
事件類型 | Event | string | 事件類型:SCAN | |||
事件key | EventKey | string | 事件 KEY 值,是一個32位無符號整數,即創建二維碼時的二維碼scene_id | |||
二維碼ticket | Ticket | string | 二維碼的ticket,可用來換取二維碼圖片 | |||
上報地理位置 (預置) | $$_send_localtion | 事件類型 | Event | string | 事件類型:LOCATION | |
緯度(地理位置) | Latitude | number | 地理位置緯度 | |||
經度(地理位置) | Longitude | number | 地理位置經度 | |||
精度(地理位置) | Precision | number | 地理位置精度 | |||
訂閱消息發送 (預置) | $$_send_msg_popup | 模板 id | TemplateId | string | 模板 id(一次訂閱可能有多條通知,帶有多個 id) | |
消息id | MsgID | string | 消息 id | |||
推送結果碼 | ErrorCode | string | 推送結果狀態碼(0表示成功) | |||
推送結果 | ErrorStatus | string | 推送結果狀態碼文字含義 | |||
訂閱彈窗點擊 (預置) | $$_click_msg_popup | 模板 id | TemplateId | string | 模板 id(一次訂閱可能有多條通知,帶有多個 id) | |
操作行為 | SubscribeStatusString | string | 用戶點擊行為(同意“accept”、取消“reject”發送通知) | |||
操作場景 | PopupScene | string | 1 :彈窗來自 H5 頁面 2 :彈窗來自圖文消息 | |||
訂閱消息管理 (預置) | $$_manager_msg_popup | 模板 id | TemplateId | string | 模板 id(一次訂閱可能有多條通知,帶有多個 id) | |
操作行為 | SubscribeStatusString | string | 用戶點擊行為(僅推送用戶拒收通知) | |||
模板消息送達 (預置) | $$_template_send_finish | 消息類型 | MsgType | string | 消息類型:event | |
事件類型 | Event | string | 事件為模板消息發送結束 | |||
消息id | MsgID | string | 消息id | |||
發送狀態 | Status | string | 發送狀態為成功(success)/拒絕(user block)/失敗(system failed) | |||
群發消息送達 (預置) | $$_mass_send_finish | 消息類型 | MsgType | string | 消息類型:event | |
事件類型 | Event | string | 事件類型:MASSSENDJOBFINISH | |||
消息id | MsgID | string | 群發的消息ID | |||
發送狀態 | Status | string | 群發的結果,為“send success”或“send fail”或“err(num)”。但send success時,也有可能因用戶拒收公眾號的消息、系統錯誤等原因造成少量用戶接收失敗。err(num)是審核失敗的具體原因,可能的情況如下:err(10001):涉嫌廣告, err(20001):涉嫌政治, err(20004):涉嫌社會, err(20002):涉嫌色情, err(20006):涉嫌違法犯罪, err(20008):涉嫌欺詐, err(20013):涉嫌版權, err(22000):涉嫌互推(互相宣傳), err(21000):涉嫌其他, err(30001):原創校驗出現系統錯誤且用戶選擇了被判為轉載就不群發, err(30002): 原創校驗被判定為不能群發, err(30003): 原創校驗被判定為轉載文且用戶選擇了被判為轉載就不群發, err(40001):管理員拒絕, err(40002):管理員30分鐘內無響應、超時 | |||
粉絲總數 | TotalCount | number | tag_id下粉絲數;或者openid_list中的粉絲數 | |||
實際發送粉絲數 | FilterCount | number | 過濾(過濾是指特定地區、性別的過濾、用戶設置拒收的過濾,用戶接收已超4條的過濾)后,準備發送的粉絲數,原則上,FilterCount 約等于 SentCount + ErrorCount | |||
發送成功粉絲數 | SentCount | number | 發送成功的粉絲數 | |||
發送失敗粉絲數 | ErrorCount | number | 發送失敗的粉絲數 | |||
文章序號 | ArticleIdx | string | 群發文章的序號,從1開始 | |||
文章狀態 | UserDeclareState | string | 用戶聲明文章的狀態 | |||
系統校驗狀態 | AuditState | string | 系統校驗的狀態 | |||
相似原創文URL | OriginalArticleUrl | string | 相似原創文的URL | |||
相似原創文類型 | OriginalArticleType | string | 相似原創文的類型 | |||
是否能轉載 | CanReprint | string | 是否能轉載 | |||
是否需要替換為原創文內容 | NeedReplaceContent | string | 是否需要替換成原創文內容 | |||
是否需要注明轉載來源 | NeedShowReprintSource | string | 是否需要注明轉載來源 | |||
整體校驗結果 | CheckState | string | 整體校驗結果1-未被判為轉載,可以群發,2-被判為轉載,可以群發,3-被判為轉載,不能群發 | |||
文章URL | ArticleUrl | string | 群發文章的URL | |||
公眾號推文發布 (預置) | $$_publish_job_finish | 發布任務id | publish_id | string | 發布任務id | |
發布狀態 | publish_status | string | 發布狀態,0:成功、1:發布中、2:原創失敗,、3: 常規失敗、4:平臺審核不通過、 5:成功后用戶刪除所有文章、6: 成功后系統封禁所有文章 | |||
文章id | article_id | string | 當發布狀態為0時(即成功)時,返回圖文的 article_id,可用于“客服消息”場景 | |||
文章數量 | count | number | 當發布狀態為0時(即成功)時,返回文章數量 | |||
文章編號 | idx | string | 當發布狀態為0時(即成功)時,返回文章對應的編號 | |||
文章URL | article_URL | string | 當發布狀態為0時(即成功)時,返回圖文的永久鏈接 | |||
發布失敗文章編號 | fail_idx | string | 當發布狀態為2或4時,返回不通過的文章編號,第一篇為 1;其他發布狀態則為空 | |||
公眾號消息接收 (預置) | $$_receive_msg | 消息類型 | MsgType | string | 消息類型:text | |
文本消息內容 | Content | string | 文本消息內容 | |||
消息id | MsgId | string | 消息id,64位整型 | |||
消息的數據ID | MsgDataId | string | 消息的數據ID(消息如果來自文章時才有) | |||
文章序號 | Idx | string | 多圖文時第幾篇文章,從1開始(消息如果來自文章時才有) | |||
圖片鏈接 | PicUrl | string | 圖片鏈接(由系統生成) | |||
媒體id | MediaId | string | 圖片消息媒體id,可以調用獲取臨時素材接口拉取數據。 | |||
語音格式 | Format | string | 語音格式:amr | |||
語音識別結果 | Recognition | string | 語音識別結果,UTF8編碼 | |||
縮略圖媒體id | ThumbMediaId | string | 視頻消息縮略圖的媒體id,可以調用多媒體文件下載接口拉取數據。 | |||
X坐標信息 | Location_X | number | 地理位置緯度 | |||
Y坐標信息 | Location_Y | number | 地理位置經度 | |||
精度 | Scale | number | 地圖縮放大小 | |||
地理位置 | Label | string | 地理位置信息 | |||
消息標題 | Title | string | 消息標題 | |||
消息描述 | Description | string | 消息描述 | |||
消息鏈接 | Url | string | 消息鏈接 |
3 事件采集
3.1 服務端埋點參數獲取
進入管理控制臺-->點擊采集信息
主域名:服務端埋點數據上報地址
副域名:默認和主域名一致,私部署客戶可以提供副域名作為備用域名
ServiceSecret和ServiceID為校驗參數,注意保密不可泄漏。
code為開發者ID(AppID),必填來自微信公號平臺,且需要在采集管理-數據源管理-公眾號管理中配置。
注意:因為上述信息涉及系統安全,所以只有管理員權限才可以看到!
3.2 請求參數
接口地址:https://<收數域名+端口>/server/thirdPartyDataSource
請求參數content-type:application/json
請求JSON參數:
JSON字段名 | 數據類型 | 是否必傳 | 字段描述 | 示例 |
sign | string | 是 | 校驗簽名 | f564cae6a8ad458648id9d607a124322 |
app_id | string | 是 | 對應服務端埋點中的serviceID | OA8kI9Jis7YJNh5uh |
appkey | string | 是 | 應用key,從QT管理后臺獲取 | 9moqdsuia8hvxm7k8shf82n |
ts | string | 是 | 上報的毫秒級時間戳 | 1659493170125 |
code | string | 是 | 開發者ID(AppID),來著微信官方 | wxd66151142ae9b9ae |
weChatOAData | string | 是 | 微信公眾號傳來的xml |
|
3.3 鑒權sign生成規則
先將除了sign字段的其他所有請求字段的key和value封裝成一個map對象
將這個map對象序列化成JSON字符,生成時需要將key按照自然排序即ascii碼排序,java可使用JSONObject.toJSONString(mapObject, SerializerFeature.MapSortField),
獲取serviceSecret
sign = MD5(排序完生成的JSON字符 + serviceSecret)
將計算得到的sign放到原始的map對象中,再序列化成JSON字符即為最終完整的JSON請求報文
3.4 響應說明
響應數據content-type:application/json
響應的JSON字符中有兩個字段,分別為code、message
code | message | 描述 |
Httpapi_300_200 | 上報成功 | 成功 |
Httpapi_300_101 | 非法的簽名 | 簽名校驗失敗,請參考sign生成規則說明 |
Httpapi_300_102 | 上報的數據類型非JSON格式 | 請求參數的數據格式不是JSON格式 |
Httpapi_300_103 | 缺少必要字段 | 參考請求參數表,檢查是否遺漏了必填的參數 |
Httpapi_300_104 | 用戶屬性缺少必要字段 | 參考請求參數表,檢查是否遺漏了必填的參數 |
Httpapi_300_105 | 非法事件id | 事件ID不正確 |
Httpapi_300_106 | ak/sk不正確 | |
Httpapi_300_107 | 微信公眾號上報日志非標準xml格式 | |
Httpapi_300_108 | 上報的微信公眾號消息不合法 | |
Httpapi_300_109 | 不可識別的微信公眾加密類型 | |
Httpapi_300_110 | 獲取unionid失敗 |
3.5 java demo參考
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.gson.Gson;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
/**
* ThirdPartyDataSourceApiDemo 接入第三方數據源demo
*
* @author chengtao
*/
public class ThirdPartyDataSourceApiDemo {
private static final Logger logger = LoggerFactory.getLogger(ThirdPartyDataSourceApiDemo.class);
/**
* 主域名
*/
private static final String API_URL = "*****";
/**
* 對應服務端埋點信息中的:ServiceID
*/
private static final String SERVICE_ID = "*****";
/**
* 對應服務端埋點信息中的:ServiceSecret
*/
private static final String SERVICE_SECRET = "*****";
/**
* client日志
*/
private static final OkHttpClient client = new OkHttpClient();
/**
* 獲取微信公眾號日志信息并轉發到QT收數(需要在微信公眾號管理后臺中配置該接口)
*
* @param request
* @throws IOException
*/
@RequestMapping("/token")
@ResponseBody
public void token(HttpServletRequest request) throws IOException {
//獲取微信公眾號官方請求中的全部參數信息,并封裝成json
Map<String, String[]> paramMap = request.getParameterMap();
JSONObject json = paramMapToJson(paramMap);
//向報文中添加服務ID
json.put("app_id",SERVICE_ID);
//向報文中添加ts
json.put("ts", String.valueOf(System.currentTimeMillis()));
//填寫公眾號ID
String code = "******";
json.put("code", code);
//獲取微信公眾號xml數據
String weChatOAData = new String(InputStreamUtil.getBytes(request));
json.put("weChatOAData", weChatOAData);
//計算簽名
String sign = MD5Util.md5(JSONObject.toJSONString(json, SerializerFeature.MapSortField) + SERVICE_SECRET);
json.put("sign", sign);
String desJsonString = JSON.toJSONString(json, SerializerFeature.DisableCircularReferenceDetect);
Request newRequest = new Request.Builder()
.URL(API_URL + "/server")
.post(RequestBody.create(MediaType.parse("application/json"),
desJsonString))
.build();
try {
Response response = client.newCall(newRequest).execute();
if (!response.isSuccessful()) {
System.out.printf("[DEMO] 發送日志失敗 %s%n", response);
} else {
System.out.printf("[DEMO] 發送成功 %s%n",
response.body().string());
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static JSONObject paramMapToJson(Map<String, String[]> paramMap) {
Map<String, Object> bodyMap = new HashMap<>();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
String key = entry.getKey();
String[] value = entry.getValue();
if (value.length == 1) {
bodyMap.put(key, value[0]);
} else {
bodyMap.put(key, value);
}
}
return JSON.parseObject(new Gson().toJson(bodyMap));
}
private static String getBodyString(HttpServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
logger.error("read body error", e);
throw new RuntimeException("read body error");
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
logger.error("close inputStream error", e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.error("close reader error", e);
}
}
}
return sb.toString();
}
}
public class MD5Util {
public static String md5(String s) {
char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] btInput = s.getBytes();
// 獲得MD5摘要算法的 MessageDigest 對象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字節更新摘要
mdInst.update(btInput);
// 獲得密文
byte[] md = mdInst.digest();
// 把密文轉換成十六進制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
/**
* 單元測試
*/
public static void main(String[] args) {
StringBuilder dtskey = new StringBuilder("abc12390");
dtskey.append('\0').append("~!@#$%^&*()_+");
System.out.println(MD5Util.md5(dtskey.toString()));
System.out.println(MD5Util.md5("20121221"));
System.out.println(MD5Util.md5("加密"));
}
}