怎么制作自定义二维码,分享免费创意二维码生成器
本人最近在做一个saas模式的产品开发,公众号只有一个,但服务的客户有多种,在各客户下又有各自的用户。现在有这么一个需求,当用户扫描客户提供的公众号二维码时,会出现对应的客户欢迎语,并且显示客户的logo界面。前提是每个客户的logo是不同的。是不是有点绕?讲明白点,就如你一个公众号,要被多个商家使用,每个商家都有自己的用户群,那用户在扫码关注公众号,进入公众号需要显示每个商家自己的独特logo。
正常的关注公众号二维码图片是可以去公众号开发者后台下载。但这是统一的二维码,无法区分商家。这个时候,我们就需要自己去生成公众号的关注二维码。这个二维码跟网上自动生成的功能不一样。毕竟你扫码后,还得跟第三方的腾讯连接。
一、java编辑生成二维码接口
参数微信公众平台接口https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542
我们生成一个永久带字符串的二维码,我们只需要传一个商家的id,就能识别用户关注时,是扫了哪一个二维码,从而显示对应的商家logo
controller层
@apioperation(value = "创建公众号二维码")
@responsebody
public result createqrcode(
@apiparam(name = "type", value = "类型(1:临时二维码;2:永久参数为数字的二维码;3:永久参数为字符串的二维码)") @requestparam() integer type,
@apiparam(name = "validtime", value = "临时二维码的有效时间(秒,最高2592000秒(30天))") @requestparam(required = false) integer validtime,
@apiparam(name = "intparameter", value = "数字参数") @requestparam(required = false) integer intparameter,
@apiparam(name = "strparameter", value = "字符串参数") @requestparam(required = false) string strparameter,
httpservletrequest request
){
return wechatpushservice.createqrcode(type,validtime,intparameter,strparameter, this.getuserid(request));
}
业务逻辑层
//获取公众号二维码
private final static string get_perpetual_qrcode_url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=access_token";
//获取ticket对应的二维码图
private final static string get_ticket_qrcode_url = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";
public result createqrcode(integer type, integer validtime, integer intparameter, string strparameter,string userid) {
string accesstoken = wechatpushservice.getgzhaccesstokendefaultcfg();//获取公众号token
string requesturl = get_perpetual_qrcode_url.replace("access_token", accesstoken);//替换url的参数
jsonobject json = new jsonobject();
jsonobject actioninfojson = new jsonobject();
jsonobject scenejson = new jsonobject();
string filename = "/sys/qrcode/"+strparameter+".jpg";//图片的下载路径
if(type == 3){//生成永久带字符串参数的二维码
json.put("action_name","qr_limit_str_scene");//固定值
scenejson.put("scene_str",strparameter);//strparameter是商家id的参数,也是要跟二维码一同生成的参数
actioninfojson.put("scene",scenejson);
json.put("action_info",actioninfojson);
//{"action_name": "qr_limit_str_scene", "action_info": {"scene": {"scene_str": "test"}}} 调用公众号接口的参数格式,json值
map<string, object> map = requestutils.json(requesturl, json);//post方法调用第三方公众号接口
string ticket = map.containskey("ticket")?map.get("ticket").tostring():"";//从返回参数中获取二维码ticket值
if(org.apache.commons.lang.stringutils.isnotempty(ticket)){//使用ticket的值再去调用另外一个接口,下载二维码图片
file file1 = new file(filepath+"sys/qrcode/");//自己要把图片下载的路径
if(!file1.exists()){//判断文件路径是否存在,不存在就创建
file1.mkdirs();
}
downloadpicture(get_ticket_qrcode_url+ urlencoder.encode(ticket),filepath+filename);//下载图片
}
}
return resultutil.success(filename);
}
/**
* 获取默认公众号访问令牌
*/
public string getgzhaccesstokendefaultcfg() {
if (stringutils.isempty(defaultgzhappid) || stringutils.isempty(defaultgzhsecret)) {
initialize();//读取配置文件里公众号的值(appid和appsecret),这两个值在公众号里有,公众号的接口大多需要这两个参数去获取token
}
return getgzhaccesstoken(defaultgzhappid,defaultgzhsecret);
}
// 获取企业号access_token
private final static string company_access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=corpid&secret=corpsecret";
// 获取开放平台的access_token、openid等认证信息
private final static string get_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=appid&secret=secret&code=code&grant_type=authorization_code";
/**
* 获取微信公众号访问令牌
* @param appid appid
* @param appsecret appsecret
*/
public string getgzhaccesstoken(string appid, string appsecret) {
string accesstoken = "";
try {
accesstoken = redisservice.getdataservice("wx_accesstoken").getdata().tostring();//从redis缓存中获取token,
} catch (exception e) {
log.error("从缓存微信公众号token失败");
}
if (stringutils.isempty(accesstoken)) {//如果缓存没有token,或过期了,将重新去获取一次
string requesturl = company_access_token_url.replace("corpid", appid).replace("corpsecret", appsecret);//替换参数
map<string, object> map = requestutils.json(requesturl, null);//post接口调用第三方接口
// 如果请求成功
if (null != map) {
system.out.print("###############################" + map.tostring());
try {
accesstoken = (string) map.get("access_token");
redisservice.stradd("wx_accesstoken", accesstoken, 700);// (存到缓存中,避免经常去拿token,将近两小时)
} catch (exception e) {
log.error("获取微信公众号token失败,或token保存至缓存失败 ####accesstoken" + accesstoken);
}
}
}
return accesstoken;
}
post和get请求工具类,在获取图片时,需要使用get
import org.springframework.http.mediatype;
import java.io.*;
import java.net.httpurlconnection;
import java.net.url;
import java.net.urlconnection;
import java.util.map;
/**
* 工具类
*/
public class requestutils {
@suppresswarnings("unchecked")
public static map<string, object> json(string url, map<string, object> params){
string content = null;
if(params != null){
content = jsonutils.convert(params);
}
string result = post(url, content, mediatype.application_json, mediatype.application_json);
if(result != null){
return jsonutils.convert(result, map.class);
}
return null;
}
public static void main(string[] args) {
string post = post("http://www.baidu.com", "", mediatype.application_json);
system.out.println(post);
}
public static string post(string strurl, string content) {
return post(strurl, content, null);
}
public static string post(string strurl, string content, mediatype mediatype) {
return post(strurl, content, mediatype, mediatype);
}
public static string post(string strurl, string content, mediatype sendmediatype, mediatype receivemediatype) {
try {
url url = new url(strurl);// 创建连接
httpurlconnection connection = (httpurlconnection) url.openconnection();
connection.setdooutput(content != null);
connection.setdoinput(true);
connection.setusecaches(false);
connection.setinstancefollowredirects(true);
connection.setrequestmethod("post"); // 设置请求方式
if(sendmediatype != null) {
connection.setrequestproperty("accept", receivemediatype.tostring()); // 设置接收数据的格式
}
if(sendmediatype != null) {
connection.setrequestproperty("content-type", sendmediatype.tostring()); // 设置发送数据的格式
}
connection.connect();
if(content != null) {
outputstreamwriter out = new outputstreamwriter(connection.getoutputstream(), "utf-8"); // utf-8编码
out.write(content);
out.flush();
out.close();
}
int code = connection.getresponsecode();
system.out.println(code);
inputstream is = connection.getinputstream();
if (is == null) {
is = connection.geterrorstream();
}
// 读取响应
int length = (int) connection.getcontentlength();// 获取长度
if (length != -1) {
byte[] data = new byte[length];
byte[] temp = new byte[1024];
int readlen = 0;
int destpos = 0;
while ((readlen = is.read(temp)) > 0) {
system.arraycopy(temp, 0, data, destpos, readlen);
destpos += readlen;
}
string result = new string(data, "utf-8"); // utf-8编码
return result;
}
} catch (ioexception e) {
e.printstacktrace();
}
return null; // 自定义错误信息
}
public static string get(string url) {
bufferedreader in = null;
try {
url realurl = new url(url);
// 打开和url之间的连接
urlconnection connection = realurl.openconnection();
// 设置通用的请求属性
connection.setrequestproperty("accept", "*/*");
connection.setrequestproperty("connection", "keep-alive");
connection.setrequestproperty("user-agent", "mozilla/4.0 (compatible; msie 6.0; windows nt 5.1;sv1)");
connection.setconnecttimeout(5000);
connection.setreadtimeout(5000);
// 建立实际的连接
connection.connect();
// 定义 bufferedreader输入流来读取url的响应
in = new bufferedreader(new inputstreamreader(connection.getinputstream()));
stringbuffer sb = new stringbuffer();
string line;
while ((line = in.readline()) != null) {
sb.append(line);
}
return sb.tostring();
} catch (exception e) {
e.printstacktrace();
}finally {
try {
if (in != null) {
in.close();
}
} catch (exception e2) {
e2.printstacktrace();
}
}
return null;
}
}
接口写完后,你可以单元测试调用一下,生成的二维码就可以扫一下,是不是会跳到你对应的公众号界面。自定义二维码已经生成了,但现在跟普通的二维码没区别,因为没有触发事件。接下来,编写一个能让公众号调用你的方法的接口。让公众号告诉你,有人关注或取消关注。
二、事件触发接口
controller层
//微信推送事件 url
@requestmapping("/openwx/getticket")
public void getticketmessage(httpservletrequest request, httpservletresponse response)
throws exception {
wechatpushservice.getticketmessage(request,response);
}
事件触发逻辑层,看下面代码时,先看官方的文档,这样更能理解返回参数:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543
public string getticketmessage(httpservletrequest request, httpservletresponse response) throws exception {
system.out.println("1.收到微信服务器消息");
map<string, string> wxdata=parsexml(request);
if(null != wxdata){
string key = wxdata.get("fromusername")+ "__"
+ wxdata.get("tousername")+ "__"
+ wxdata.get("msgid") + "__"
+ wxdata.get("createtime");
result keyredisresult = redisservice.getdataservice(key);
system.out.println(keyredisresult.getstatus());
if(keyredisresult.getstatus() == 200){//防止公众重复推送消息,所以第一次把消息送缓存中,如果存在了就不处理
return null;
}
redisservice.stradd(key,"1",3600);//不存在的话,放缓存里,记得加一个失效时间,避免一直存在,占用资源
string event = wxdata.get("event");
system.out.println("event"+event);
if(event.equals("subscribe") || event.equals("scan")){//扫码带参数的二维码进入的
string eventkey = wxdata.get("eventkey");//获取参数
string fromusername = wxdata.get("fromusername");//openid
if(eventkey.indexof("_") != -1){//初次关注
eventkey = eventkey.substring(eventkey.indexof("_")+1);
}
system.out.println("eventkey:"+eventkey);
map map = (map)result.getdata();
textmessage textmessage=new textmessage();
textmessage.settousername(wxdata.get("fromusername")); //这里的tousername 是刚才接收xml中的fromusername
textmessage.setfromusername(wxdata.get("tousername")); //这里的fromusername 是刚才接收xml中的tousername 这里一定要注意,否则会出错
textmessage.setcreatetime(new date().gettime());
textmessage.setmsgtype("text");
textmessage.setcontent("欢迎您关注"+map.get("departmenttopname")+"电子送达");
messageutil messageutil = messageutil.getinstance();
string xml=messageutil.textmessagetoxml(textmessage);
system.out.println("xml:"+xml);
response.setcharacterencoding("utf-8");
printwriter out = response.getwriter();
out.print(xml);//用户关注时,发一个欢迎语给用户
out.close();
}
}
return null;
}
三、触发接口写完后,需要去公众号后台去设置你的接口服务器,让公众号知道你的接口地址。
修改配置,服务器地址为你部署的地址,必须对方能连上,而且需要80端口(如果80端口被占用,可以使用nginx做转发),在配置的时候,公众号会尝试调用,调用不到你的接口,会直接提醒你。
配置完后,点击启动。这个时候你再去关注你刚才生成的参数二维码,就会有反映了。记得在事件触发接口中,增加你的业务。用户关注或取消关注时,你要做什么。
另外,在启动配置后,你会发现,你的公众号自定义菜单不见了,这个时候不要慌。接下往下看。
启动菜单
这个时候公众号上的小菜单就有了。但公众号后台自定义菜单还是看不到?那怎么修改菜单呢?
很简单,先把前面开启的服务器配置给停止了,然后再改你的菜单,修改完菜单后,你再开始服务器。到此就完成了生成及事件监听的过程