接入文档地址:https://developers.e.qq.com/docs/start
由于腾讯的广告业务做的太大,所以接起来还挺麻烦的,比起百度难接入。页面上的客服也是爱答不理的,反正等了一下午也没回我消息!就自己看,也不知道投放部门准备怎么接,就让开发自己看文档!看的头大!!!
这是腾讯给出的文档说明。
1、用 公用 qq (这个不用说了为啥了吧)注册为开发者后,创建应用程序等着审核,创建的几个应用程序不要相似度太高,要不通过不了,一般一个就够了。或者一个申请一种权限。
2、创建数据源,(可以通过可视化界面申请创建,请看官方文档)
上传行为数据。重点说下这个步骤:
这个步骤的目的是为了在管理后台知道我们有多少用户的转化率,大概是为了腾讯更好的推广产品吧。
要想上传行为数据,必须有access-token,这个token有两种获取方式:
1、直接获取
2、通过refresh_token获取
这两种方式有何区别:
第一种建议第一次的时候使用,因为获取的时候需要授权码,授权码是通过回调带回到自己填写的地址的,当带回到自己填写的地址后,我们就可以使用redis等工具把它暂存器来,然后调用获取access-token方法,这个时候我们就可以去获取access-token和 refresh_token,然后把这两者都存起来,每经过23小时左右(access-token 有效期24小时)使用refresh_token 刷新一次 access-token,这样就不会再出现access-token 过期的情况了。
另外:难免出现意外情况,再token过期的时候,可以使用 Springboot 的邮件系统给自己或者组员发送一个带有授权验证的超链接,任何一个组员或者全部成员看到后,都可以手动认证(因为腾讯做了图片的滑块校验),最后跳转到自定义的授权成功页面即可,就不再影响业务了!
我们会及时上报行为数据,
我们会再接到用户的点击行为的时候,把需要的上报链接封装好,预留一些字段,如时间戳和token等,等用户激活的时候再替换下链接做个上传。
在开发人员这里需要的几个接口或自动任务
1、授权成功的回调接口
这是腾讯给出的引导客户进行授权认证时的链接,里面有一个redirect_url,这个算是需要开发的第一个接口,当认证成功后,腾讯还会回调的这个地址,示例如下
http://www.example.com/response?authorization_code=6a6b6c6d&state=112233
我们需要把这个 authorization_code 字段保存下,用来请求 access_token。
2、广告点击行为接收接口 即feedback-url
这个用来获取用户的点击行为数据,在这里我们也可以定义自己的参数,如我们想知道这些数据是在哪投放的广告,可以加个 adcome=tencent 它会在调用的时候给我们带回来,这样我们就可以区分了。完整的url示例如下
http://www.example.com/feedback_url?adcome=tencent
3、自动任务使用refresh_token 刷新 access_token
这个可以使用 Springboot 开发,挺简单的,不懂的可以参考这个 SpringBoot 学习系列(五) - 快速开发定时任务程序
我们可以使用定时的自动任务 多少毫秒刷新一次,如我的代码像这样:
我们是把这些需要的token存入了redis里面,这样我们使用的时候直接去取就行了。
另外需要注意的是:我们开发的程序难免会有意外情况,这时候我们最好加个邮件提醒,发邮件也很简单,可以参考:SpringBoot 学习系列(六) - 简单五步搞定邮件发送 像我们发邮件,在邮件里面注明链接,这样相关人员或者开发者本人收到邮件后,可以直接点击去获取最新的 au_code。
目前还不是很完善,等待激活程序搞定后再来补充!目前项目还未上线!
***********************************************************************************************
已经开始推广,有个小问题
数据对不上渠道
解决方法:数据上报的时候用了自定义的行为id字段,导致对不上,去掉即可。
页面上的客服依然那么的不懂业务!!!!
注意:在创建应用的时候,无需申请太多的权限,一般只需要数据上报权限即可,在说明里面最好别写测试等字眼,否则不容易被审核通过!
大部分关键源码如下:xxx 为公司名,YYY 为项目名 请酌情替换
AuthController.java
package com.XXX.YYY.controller;
import com.XXX.YYY.schedulertask.GetAndRefreshTokenTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("/")
@Slf4j
public class AuthController {
@Autowired
private GetAndRefreshTokenTask task;
// redis 操作类
@Autowired
private RedisTemplate redisTemplate;
// refresh_token access_token 的键
private final String TOKEN_KEY ="tf_token_key";
// 修改支持多账户 规则 authorization_code_+账户id state_+账户id
private final String AUTHORIZATION_CODE_PREFIX ="authorization_code_";
private final String AUTHORIZATION_CODE ="authorization_code";
private final String STATE_PREFIX ="state_";
private final String STATE="state";
/**
* 授权成功跳转页面
* 特殊说明:目前请求授权地址为:
* 安卓 https://developers.e.qq.com/oauth/authorize?client_id=xxxxxxxx&redirect_uri=http://YYY.XXX.com/tf_auth&state=xxx_tf_auth_ida
* IOS https://developers.e.qq.com/oauth/authorize?client_id=xxxxxxxx&redirect_uri=http://YYY.XXX.com/tf_auth&state=xxx_tf_auth_idb
* @param mv 视图
* @param request 请求
* @return 成功页面
*/
@GetMapping("/tf_auth")
public ModelAndView test(ModelAndView mv,HttpServletRequest request) {
String domain="http://YYY.XXX.com/tf_auth";
String url = domain+"?"+request.getQueryString();
log.info("获取 authorization_code 的url: "+url);
// 分账户
String state = request.getParameter(STATE).trim();
// 操作类
HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
mv.setViewName("authSuccess");
String authorization_code = request.getParameter(AUTHORIZATION_CODE);
String accountId = state.substring(state.lastIndexOf("_")+1);
hashOperations.put(TOKEN_KEY, AUTHORIZATION_CODE_PREFIX+accountId, authorization_code);
hashOperations.put(TOKEN_KEY, STATE_PREFIX+accountId, state);
// 授权成功更新下 token
task.autoTaskJob();
return mv;
}
}
2、GetAndRefreshTokenTask.java
package com.XXX.YYY.schedulertask;
import com.alibaba.fastjson.JSONObject;
import com.XXX.YYY.utils.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
@Component
@Slf4j
public class GetAndRefreshTokenTask {
// refresh_token access_token 的键
private final String TOKEN_KEY ="tf_token_key";
// hash key
private final String ACCESS_TOKEN ="access_token";
private static final String REFRESH_TOKEN ="refresh_token";
private final String AUTHORIZATION_CODE ="authorization_code";
// 通过 Authorization Code 获取 Access Token 或刷新 Access Token
private static final String GET_TOKEN_URL="https://api.e.qq.com/oauth/token?";
private String ACCOUNT_PREFIX ="aso.tencent.account";
// 请求需要的几个字段
private static final String CLIENT_ID="client_id";
private static final String CLIENT_SECRET ="client_secret";
private static final String CLIENT_ID_VALUE="1108034188";
private static final String CLIENT_SECRET_VALUE="xLfe83U0xzwHCXLl";
// 请求的类型,可选值: authorization_code (授权码方式获取 token )、 refresh_token (刷新 token )
private static final String GRANT_TYPE="grant_type";
private static final String REDIRECT_URI="redirect_uri";
private static final String REDIRECT_URI_VALUE="http://YYY.XXX.com/tf_auth";
// 直接获取
private static String getAccessTokenUrl = "";
// 通过 refresh_token u获取
private static String getAccessTokenFromRefreshTokenUrl = "";
static {
StringBuilder sb = new StringBuilder(GET_TOKEN_URL);
sb.append(CLIENT_ID);
sb.append("=");
sb.append(CLIENT_ID_VALUE);
sb.append("&");
sb.append(CLIENT_SECRET);
sb.append("=");
sb.append(CLIENT_SECRET_VALUE);
sb.append("&");
sb.append(GRANT_TYPE);
sb.append("=");
sb.append("grant_type_value");
getAccessTokenFromRefreshTokenUrl = sb.toString();
sb.append("&");
sb.append(REDIRECT_URI);
sb.append("=");
sb.append(REDIRECT_URI_VALUE);
getAccessTokenUrl = sb.toString();
}
// 发件人地址
@Value("${spring.mail.fromMail.addr}")
private String from;
// 收件人地址
@Value("${spring.mail.toMail.addr}")
private String toMail;
@Autowired
private TemplateEngine templateEngine;
@Autowired
private Environment env;
@Autowired
private JavaMailSender mailSender;
@Autowired
private RedisTemplate redisTemplate;
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
// 23小时执行更新一次
@Scheduled(fixedRate = 82800000)
public void reportCurrentTime() {
autoTaskJob();
}
// 封装成方法 便于认证成功也可以调用
public void autoTaskJob(){
System.out.println("现在时间:" + dateFormat.format(new Date()));
HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
String[] accountArr = env.getProperty(ACCOUNT_PREFIX).split(",");
for(String account : accountArr){
String getTokenType = hashOperations.get(TOKEN_KEY, REFRESH_TOKEN+"_"+account) == null?"access":"refresh";
String requestUrl = "";
if("access".equals(getTokenType)){
String authorization_code = hashOperations.get(TOKEN_KEY, AUTHORIZATION_CODE+"_"+account).toString();
requestUrl = getAccessTokenUrl.replace("grant_type_value",AUTHORIZATION_CODE );
requestUrl += "&"+AUTHORIZATION_CODE+"="+authorization_code;
}else {
String refresh_token = hashOperations.get(TOKEN_KEY, REFRESH_TOKEN+"_"+account).toString();
requestUrl = getAccessTokenFromRefreshTokenUrl.replace("grant_type_value",REFRESH_TOKEN );
requestUrl += "&"+REFRESH_TOKEN+"="+refresh_token;
}
String returnMsg = HttpUtil.httpClientGet(requestUrl);
Map map = JSONObject.parseObject(returnMsg,Map.class);
if("0".equals(map.get("code").toString())){
Map data = (Map)map.get("data");
String access_token = data.get(ACCESS_TOKEN).toString();
String refresh_token = data.get(REFRESH_TOKEN).toString();
hashOperations.put(TOKEN_KEY, ACCESS_TOKEN+"_"+account, access_token);
hashOperations.put(TOKEN_KEY, REFRESH_TOKEN+"_"+account, refresh_token);
log.info(account+":更新token成功!");
}
else {
// 发送邮件提醒重新认证
sendReAuthMail(account);
log.error(account+":token更新失败!");
}
}
}
// 发送重新认证邮件
private void sendReAuthMail(String content){
//创建邮件正文
Context context = new Context();
context.setVariable("id", content);
String emailContent = templateEngine.process("emailTemplate", context);
sendHtmlMail( toMail,"广点通需要重新认证,请尽快处理!", emailContent);
}
/**
* 发送html邮件 目前支持发送给单个接收者
* @param to 收件人
* @param subject 邮件主题
* @param content 邮件内容
*/
private void sendHtmlMail(String to, String subject, String content) {
MimeMessage message = mailSender.createMimeMessage();
try {
//true表示需要创建一个multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
mailSender.send(message);
log.info("html邮件发送成功");
} catch (MessagingException e) {
log.error("发送html邮件时发生异常!", e);
}
}
}
3、HttpUtil.java
package com.XXX.YYY.utils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class HttpUtil {
public static String httpClientGet(String url) {
String str = "";
try{
// 获取http客户端
CloseableHttpClient client = HttpClients.createDefault();
// 通过httpget方式来实现我们的get请求
HttpGet httpGet = new HttpGet(url);
// 通过client调用execute方法,得到我们的执行结果就是一个response,所有的数据都封装在response里面了
CloseableHttpResponse Response = client.execute(httpGet);
// HttpEntity
// 是一个中间的桥梁,在httpClient里面,是连接我们的请求与响应的一个中间桥梁,所有的请求参数都是通过HttpEntity携带过去的
// 所有的响应的数据,也全部都是封装在HttpEntity里面
HttpEntity entity = Response.getEntity();
// 通过EntityUtils 来将我们的数据转换成字符串
str = EntityUtils.toString(entity, "UTF-8");
// EntityUtils.toString(entity)
System.out.println(str);
// 关闭
Response.close();
return str;
}
catch (Exception ex){
ex.printStackTrace();
return str;
}
}
}
4、application.yml
server:
port: 8081
# 邮件发送设置
spring:
mail:
host: smtp.xxx.com
username: name@staff.xxx.com
password: passwd
default-encoding: UTF-8
fromMail:
addr: name@staff.xxx.com
toMail:
addr: daguang0822@126.com
# ida idb 分别为申请的应用的id 七位数字 storeid1 storeid2 分别为上报的数据库的id 十位数字
tencent:
# ida 安卓第一个账户 qq: qq1 idb IOS第一个账户 qq:qq2
account: ida,idb
user_action_set_id_ida: storeid1
user_action_set_id_idb: storeid2
5、emailTemplate.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" >
<html lang="en">
<head>
<meta charset="UTF-8">
<title>广点通重新认证</title>
</head>
<body>
<br/>
<h3>账号异常,请及时处理</h3>
<br/>
如果您看到这封邮件!点击<a th:href="@{ https://developers.e.qq.com/oauth/authorize?client_id=xxxxxxxx&redirect_uri=http://XXX.YYY.com/tf_auth&state=xxx_tf_auth_{id}(id=${id})}">广点通链接</a>进行认证!
提示:qq和账户的对应关系
<br/>
qq:qq1 账户:ida
<br/>
qq: qq2 账户:idb
<br/>
<h3>谢谢!</h3>
</body>
</html>