原先公众号的登录注册由于session的频繁失效,导致需要用户频繁登录,这样用户体验极差。我试过增大session的失效时间,但是随着用户的增多,过长生命周期的session对服务器来说也是一笔大开支;我接着以openId为key将用户的登录状态保存到redis数据库中,但是按照之前项目的设计,只有从公众号菜单栏点击进来的链接才能获取到openId,对于非菜单栏的链接,是获取不到openId的,去网上搜索了很久,变换了无数的关键词去搜索,都没能找到满意的方案,就在快放弃之时,突然灵光一闪,脑海中浮现出一个使用微信的静默授权去获取的openId的思路,这对于刚接触公众号开发的我来说,多少还是有些小成就滴。
具体的逻辑是这样的:用户注册的时候,暂时不获取微信openId,做的只是简单的在我们自己的网站(后面统一称为第三方网站)注册账户,注册成功后直接跳转到登录界面。登录的时候,前端调用直接发ajax请求访问后端提供的接口,这个接口其实也不直接涉及微信openId的相关信息,登录做的只是去第三方网站校验当前登录用户的用户名和密码是否正确,校验通过之后,这个登录接口会返回一个后台拼接好的发送微信静默授权的url(简称wxUrl),然后前端直接调用 localtion.href=wxUrl 方法执行这个wxUrl链接。而真正将用户微信和第三方账户绑定在一起的操作,是在这个wxUrl里设置的回调地址里(这个接口是由第三方自己定义的接口)完成的,具体的等下直接看代码。至于如何实现用户在公众号在第一次登录后,用户的登录状态为永久登录,这就需要用到filter过滤器(也不一定要用这个,我这个没办法,项目一开始没有用到框架,如果你用的springmvc,可以使用拦截器)对用户的请求进行拦截,拦截的目的主要是判断当前session是否已过期(通过判断session域对象中是否能获取到openId、用户名等你定义的信息)。如果session域对象中没有openId,则再次发送微信静默授权获取openId,因为第一次登录的时候,我们已经以openId为key将一些信息保存到redis数据库中,此次通过静默授权获取到openId,相当于拿到了redis的key,然后直接取出该key对应的redis的value,重新给当前session复制一遍用户的信息,然后继续用户原来的请求就好了。
后面分享的代码省去了一些比较隐私的个人业务,希望各位理解;顺便声明,分享出来的都是业务层的代码,至于原生servlet的控制层的代码,我就不写了,实在不会的怕是得回去复习复习了。
下面开始上代码,首先是用户的登录相关的类,这里的登录接口,需要
package servlet;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import pojo.Result;
import util.Constant;
import util.redis.RedisUtils;
public class UserLoginService {
private static final Logger logger = Logger.getLogger(UserLoginService.class);
/**
* 登录接口
* @param request
* @return
*/
public Result login(HttpServletRequest request) {
Result result = new Result();
try {
//核心业务隐身符1:此处省略传入参数的校验,记住一点:永远不要相信前端传来的数据,所有核心数据后台都要校验
//核心业务隐身符2:此处继续省略判断用户名和密码是否正确的检验,这里就要涉及到数据库的相关操作了,此处不赘述,假设用户名和密码校验通过了,记为b=true
boolean b=true;
if(b){
//高能预警,核心代码来了
//设置令牌,这个很关键,用来保存我们自己网站的用户资料,如用户名,id等
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
String token =name + "##" +id ;
RedisUtils.set(uuid, token, 60);// 设置token有效期为60秒,这个时间不要设置太长,并且一旦使用后立即失效
//用户微信获取授权成功之后,会将请求转发回你指定的回调地址,
String redirectUrl = "https://网站域名/项目名/wxLoginRedirectServlet";
//需要对你的回调地址进行编码,这个步很重要
redirectUrl=java.net.URLEncoder.encode(redirectUrl, "utf-8");
//当用户登录校验成功之后,将这个url返回给前端的路径,前端需要调用location.href=wxUrl 方法.
//Constant.APP_ID是 第三方用户唯一凭证,需要你登录自己的公众号查找,具体方法请自行百度
//&connect_redirect=1这个参数很重要,微信服务器有时候会不太靠谱,会对这个请求处理两次,实际生产环境出现过这个问题,故加上这个参数
String wxUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + Constant.APP_ID
+ "&redirect_uri=" + redirectUrl + "&response_type=code&scope=snsapi_userinfo&state=" + uuid
+ "&connect_redirect=1#wechat_redirect";
logger.info("最终生成的微信授权路径:"+wxUrl);
result.setUrl(wxUrl);
result.setSuccess(true);
result.setMsg("信息验证通过");
}else{
//如果用户名和密码不存在,返回错误提示即可,更严格些的,应该做用户每日输入次数的记录与限制,比如连续输错三次,需要输入图形验证码;连续输错6次,则只能重置登录密码或明日再来
result.setSuccess(false);
result.setMsg("用户名或密码错误");
return result;
}
} catch (Exception e) {
result.setSuccess(false);
result.setMsg("网络异常:" + e.getMessage());
logger.info("网络异常:" + e.getMessage());
}
return result;
}
}
Result.java类不提供,这个只是一个简单的实体类,你要喜欢,用Map来存储返回值也是一样的,大家真的没必要纠结这一点。RedisUtils.java工具类,请访问我的另一篇博客https://blog.csdn.net/weixin_42023666/article/details/89287418 ,直接复制粘贴到你的项目便能使用。
好吧,我还是太心软了,怕大家不记得原生servlet给ajax请求返回数据的方式了,下面分享一个给大家参考一下,至于web.xml文件中的servlet请求的映射路径,大家自己动手了,全当复习吧。
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON;
import pojo.Result;
/**
*
* @ClassName: JsonServlet
* @Description: TODO(这里用一句话描述这个类的作用)
* @author hqq
*
*/
public class UserLoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private UserLoginService service=new UserLoginService();
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Result result=service.login(request);
String json = JSON.toJSONString(result);
PrintWriter out = response.getWriter();
out.write(json);
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
下面分享的就是大家最喜欢的,我们前面的登录接口中拼接的微信静默授权url里面的回调接口,即 https://网站域名/项目名/wxLoginRedirectServlet 这个回调接口的代码了,大家接好了:
package servlet;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import pojo.WeixinOauth2Token;
import pojo.WeixinUserInfo;
import util.WXAdvancedUtil;
import util.WXUtil;
import util.redis.RedisUtils;
/**
* @ClassName: RedirectServlet
* @Description: TODO(这里用一句话描述这个类的作用)
* @author hqq
*
*/
@SuppressWarnings("serial")
public class WxLoginRedirectServlet extends HttpServlet{
private static final Logger logger = Logger.getLogger(WxLoginRedirectServlet.class);
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
logger.info("获取到请求的服务器地址:"+request.getServerName());
//你的网站的全域名,https://www.*.com/ ;
//因为微信完 第三方网站的微信授权请求的后,是使用转发方式转发到我们制定的url的,所以此处获取的域名还是我们自己网站的域名。这个判断也是为了防止恶意请求
String mainUrl="";
if(!mainUrl.contains(request.getServerName())){
logger.info("非法请求:"+mainUrl);
response.sendRedirect("/你的项目名/个人中心.jsp");
return;
}
String code = request.getParameter("code");//获取到code,这个code应该是微信那边定义的
String state=request.getParameter("state");//第三方网站(即我们自己)自定义的参数,可以存储一些重要信息和防伪信息,因为这个接口是完全暴露的,所以必须要有防伪措施,防止恶意请求来搞事
//如果这两个字段都为空值,就可以判定为而已请求
if(StringUtils.isBlank(code)||StringUtils.isBlank(state)){
logger.info("非法请求,缺少必要的参数");
return;
}
logger.info("获取到的code是 :" + code+",state="+state);
String openId = null;
// 用户同意授权,默认强行同意
if (!"authdeny".equals(code)) {
logger.info("授权成功----");
String appId = Constant.APP_ID;// 第三方用户唯一凭证
String appSecret = Constant.APP_SECRET;// 第三方用户唯一凭证密钥
// 获取网页授权access_token
WeixinOauth2Token weixinOauth2Token = WXAdvancedUtil.getOauth2AccessToken(appId, appSecret, code);
if (weixinOauth2Token != null) {
// 用户标识
openId = weixinOauth2Token.getOpenId();
// 获取用户信息
}
logger.info("微信授权获取到用户的openId是:" + openId);
//判断redis数据库中,是否存在以这个用户的openId为key的键值,对应的value是用户初次绑定时存储的信息
String userInfo = RedisUtils.get(openId);
if(StringUtils.isNotBlank(userInfo)){//如果存在,说明用户不是第一次绑定
//判断该微信号绑定的是否是该手机号
//userInfo =name + "##" +id ;
String[] infos = userInfo.split("##");
logger.info("最初的session是失效了,但是redis中的值还在");
//本次session会话为新 会话,可以保存一些你必须要的数据
session.setAttribute("name", infos[0]);//
session.setAttribute("id", infos[1]);
session.setAttribute("openId", openId);
session.setMaxInactiveInterval(60*60);//本次的session有效期设置成一个小时,这个配置按需设置,不知道有没有效哈
logger.info("session中保存用户信息成功,跳转到原先的执行页面-:"+state);//这是用户权限拦截时,在过滤的过程中
response.sendRedirect(state);//继续走用户原来的请求
return;
}else{//用户第一次绑定微信公众号
try {
String token = RedisUtils.get(state);//从redis数据库获取登录时用户保存的信息
//token失效,需要重新登录,理论上token的值设置成60秒有效已经足够,超过这个时间,系统理应不予处理
if(StringUtils.isBlank(token)){
logger.info("本次token失效,绑定失败,重回登录页面");
response.sendRedirect("/你的项目名/个人中心.jsp");//重定向到个人中心,由拦截器决定是否放行
return;
}
logger.info("token未失效,token对应的值是:"+token);
//token =name + "##" +id ;
String[] split = token.split("##");
//核心业务隐身符;此处需判断当前用户id(也就是我们自己网站的用户)是否已经绑定了其它的用户的微信,具体实现方法
//是通过用户id去数据库查询用户记录,查询我们自己的数据库中当前用户记录的openId字段,记为dbOpenId;
//具体sql省略,每个项目建的表和表字段都不同,所以不要纠结,但是理论上都会有用户id和用户的微信openId一对一的关系存在
String dbOpenId="假设从数据库查到的当前用户id对应的openId的值是这个";
//数据库中有该记录
if(StringUtils.isNotBlank(dbOpenId)){
//1.判断这个openid与当前登录使用的openId是否一致,防止别的微信登录该帐号
if(!openId.equals(dbOpenId)){
logger.info("该帐号已绑定别的微信帐号,请联系客服解绑后在登录");
session.setAttribute("msg", "该帐号已绑定别的微信帐号,请联系客服解绑后在登录");
response.sendRedirect("/你的项目名/个人中心.jsp");
return;
}
}else{//没有,是第一次绑定
// 获取接口访问凭证
String accessToken = WXUtil.getToken(appId, appSecret).getAccessToken();
// 获取微信用户的信息
WeixinUserInfo wxInfo = WXAdvancedUtil.getUserInfo(accessToken, openId);
logger.info("绑定用户openId开始:" + openId + ",用户accessToken:" + accessToken);
//判断昵称是否有emoj图片
String nickName=wxInfo.getNickname();
if(WXUtil.containsEmoji(nickName)){
nickName=WXUtil.filterEmoji(nickName);
wxInfo.setNickname(nickName);
}
//核心业务隐身符,将微信用户的信息保存到你的数据库中,方便后期使用,微信用户的具体信息都在WeixinUserInfo类中,大家自己取就好了
}
session.setAttribute("name", split[0]);
session.setAttribute("id", split[1]);
session.setAttribute("openId", openId);
session.setMaxInactiveInterval(60*60);//本次的session有效期设置成一个小时,这个配置按需设置,不知道有没有效哈
RedisUtils.set(openId, token);
//redis数据库中保存所有登录的openId
RedisUtils.sAdd("LOGIN_USER_OPENID", openId);
response.sendRedirect("/你的项目名/个人中心.jsp");//跳转到个人中心页面
return;
} catch (Exception e) {
logger.info("绑定微信异常:"+e.getMessage());
}
}
}
//用户不同意授权,或者用户未绑定微信号,跳转到登录注册页面
response.sendRedirect("/你的项目名/个人中心.jsp");//跳转到个人中心页面
return;
}
}
实体类WeixinOauth2Token.java ,WeixinUserInfo.java 和 工具类WXAdvancedUtil.java ,WXUtil.java 是直接百度的,没什么技术含量,这里顺便贴出来吧:
package pojo;
/**
* 网页授权信息
*/
public class WeixinOauth2Token {
// 网页授权接口调用凭证
private String accessToken;
// 凭证有效时长
private int expiresIn;
// 用于刷新凭证
private String refreshToken;
// 用户标识
private String openId;
// 用户授权作用域
private String scope;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public int getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getOpenId() {
return openId;
}
public void setOpenId(String openId) {
this.openId = openId;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
WeixinUserInfo.java
package pojo;
/**
* 微信用户的基本信息
*/
public class WeixinUserInfo {
// 用户的标识
private String openId;
// 关注状态(1是关注,0是未关注),未关注时获取不到其余信息
private int subscribe;
// 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
private String subscribeTime;
// 昵称
private String nickname;
// 用户的性别(1是男性,2是女性,0是未知)
private int sex;
// 用户所在国家
private String country;
// 用户所在省份
private String province;
// 用户所在城市
private String city;
// 用户的语言,简体中文为zh_CN
private String language;
// 用户头像
private String headImgUrl;
private String unionId;
private String remark;
private String groupId;
private String errcode;
private String errmsg;
private String accessToken;
@Override
public String toString() {
return "WeixinUserInfo [openId=" + openId + ", subscribe=" + subscribe
+ ", subscribeTime=" + subscribeTime + ", nickname=" + nickname
+ ", sex=" + sex + ", country=" + country + ", province="
+ province + ", city=" + city + ", language=" + language
+ ", headImgUrl=" + headImgUrl + ", unionId=" + unionId
+ ", remark=" + remark + ", groupId=" + groupId + ", errcode="
+ errcode + ", errmsg=" + errmsg + ", accessToken="
+ accessToken + "]";
}
public String getUnionId() {
return unionId;
}
public void setUnionId(String unionId) {
this.unionId = unionId;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getErrcode() {
return errcode;
}
public void setErrcode(String errcode) {
this.errcode = errcode;
}
public String getErrmsg() {
return errmsg;
}
public void setErrmsg(String errmsg) {
this.errmsg = errmsg;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getOpenId() {
return openId;
}
public void setOpenId(String openId) {
this.openId = openId;
}
public int getSubscribe() {
return subscribe;
}
public void setSubscribe(int subscribe) {
this.subscribe = subscribe;
}
public String getSubscribeTime() {
return subscribeTime;
}
public void setSubscribeTime(String subscribeTime) {
this.subscribeTime = subscribeTime;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getHeadImgUrl() {
return headImgUrl;
}
public void setHeadImgUrl(String headImgUrl) {
this.headImgUrl = headImgUrl;
}
}
工具类WXAdvancedUtil.java
package util;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
import message.resp.Article;
import message.resp.Music;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import others.CommUtil;
import others.FileUpload;
import pojo.SNSUserInfo;
import pojo.Token;
import pojo.WeixinGroup;
import pojo.WeixinMedia;
import pojo.WeixinOauth2Token;
import pojo.WeixinQRCode;
import pojo.WeixinUserInfo;
import pojo.WeixinUserList;
/**
* 微信高级接口
*/
public class WXAdvancedUtil {
private static Logger logger = LoggerFactory.getLogger(WXAdvancedUtil.class);
/**
* 获取网页授权凭证
*
* @param appId
* 公众账号的唯一标识
* @param appSecret
* 公众账号的密钥
* @param code
* @return WeixinAouth2Token
*/
public static WeixinOauth2Token getOauth2AccessToken(String appId,
String appSecret, String code) {
WeixinOauth2Token wat = null;
// 拼接请求地址
String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
requestUrl = requestUrl.replace("APPID", appId);
requestUrl = requestUrl.replace("SECRET", appSecret);
requestUrl = requestUrl.replace("CODE", code);
// 获取网页授权凭证
JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null);
if (null != jsonObject) {
try {
wat = new WeixinOauth2Token();
wat.setAccessToken(jsonObject.getString("access_token"));
wat.setExpiresIn(jsonObject.getInt("expires_in"));
wat.setRefreshToken(jsonObject.getString("refresh_token"));
wat.setOpenId(jsonObject.getString("openid"));
wat.setScope(jsonObject.getString("scope"));
} catch (Exception e) {
wat = null;
int errorCode = jsonObject.getInt("errcode");
String errorMsg = jsonObject.getString("errmsg");
logger.info("获取网页授权凭证失败 errcode:{} errmsg:{}", errorCode,
errorMsg);
}
}
return wat;
}
/**
* 获取用户信息
*
* @param accessToken
* 接口访问凭证
* @param openId
* 用户标识
* @return WeixinUserInfo
*/
public static WeixinUserInfo getUserInfo(String accessToken, String openId) {
WeixinUserInfo weixinUserInfo = null;
// 拼接请求地址
String requestUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID";
requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken).replace(
"OPENID", openId);
// 获取用户信息
JSONObject jsonObject = CommonUtil
.httpsRequest(requestUrl, "GET", null);
if (null != jsonObject) {
try {
weixinUserInfo = new WeixinUserInfo();
// 用户的标识
weixinUserInfo.setOpenId(jsonObject.getString("openid"));
// 关注状态(1是关注,0是未关注),未关注时获取不到其余信息
weixinUserInfo.setSubscribe(jsonObject.getInt("subscribe"));
// 用户关注时间
weixinUserInfo.setSubscribeTime(jsonObject.getString("subscribe_time"));
// 昵称
weixinUserInfo.setNickname(jsonObject.getString("nickname"));
// 用户的性别(1是男性,2是女性,0是未知)
weixinUserInfo.setSex(jsonObject.getInt("sex"));
// 用户所在国家
weixinUserInfo.setCountry(jsonObject.getString("country"));
// 用户所在省份
weixinUserInfo.setProvince(jsonObject.getString("province"));
// 用户所在城市
weixinUserInfo.setCity(jsonObject.getString("city"));
// 用户的语言,简体中文为zh_CN
weixinUserInfo.setLanguage(jsonObject.getString("language"));
// 用户头像
weixinUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));
weixinUserInfo.setGroupId(jsonObject.getString("groupid"));
weixinUserInfo.setRemark(jsonObject.getString("remark"));
weixinUserInfo.setUnionId(jsonObject.getString("unionid"));
weixinUserInfo.setAccessToken(accessToken);
} catch (Exception e) {
if (0 == weixinUserInfo.getSubscribe()) {
logger.info("用户{}已取消关注", weixinUserInfo.getOpenId());
weixinUserInfo.setSubscribe(weixinUserInfo.getSubscribe());
} else {
String errorCode = jsonObject.getString("errcode");
String errorMsg = jsonObject.getString("errmsg");
logger.info("获取用户信息失败 errcode:{} errmsg:{}", errorCode,
errorMsg);
weixinUserInfo.setErrcode(""+errorCode);
weixinUserInfo.setErrmsg(errorMsg);
}
}
}
return weixinUserInfo;
}
}
工具类 WXUtil.java
package util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pojo.Token;
import pojo.WeixinOauth2Token;
/**
* 微信开发相关工具
*/
public class WXUtil {
private static Logger log = LoggerFactory.getLogger(WXUtil.class);
/**
* 检测是否有emoji字符
*
* @param source
* @return 一旦含有就抛出
*/
public static boolean containsEmoji(String source) {
if (StringUtils.isBlank(source)) {
return false;
}
int len = source.length();
for (int i = 0; i < len; i++) {
char codePoint = source.charAt(i);
if (isEmojiCharacter(codePoint)) {
//do nothing,判断到了这里表明,确认有表情字符
return true;
}
}
return false;
}
private static boolean isEmojiCharacter(char codePoint) {
return (codePoint == 0x0) ||
(codePoint == 0x9) ||
(codePoint == 0xA) ||
(codePoint == 0xD) ||
((codePoint >= 0x20) && (codePoint <= 0xD7FF)) ||
((codePoint >= 0xE000) && (codePoint <= 0xFFFD)) ||
((codePoint >= 0x10000) && (codePoint <= 0x10FFFF));
}
/**
* 过滤emoji 或者 其他非文字类型的字符
*
* @param source
* @return
*/
public static String filterEmoji(String source) {
source = source.replaceAll("[\\ud800\\udc00-\\udbff\\udfff\\ud800-\\udfff]", "*");
if (!containsEmoji(source)) {
return source;//如果不包含,直接返回
}
//到这里铁定包含
StringBuilder buf = null;
int len = source.length();
for (int i = 0; i < len; i++) {
char codePoint = source.charAt(i);
if (isEmojiCharacter(codePoint)) {
if (buf == null) {
buf = new StringBuilder(source.length());
}
buf.append(codePoint);
} else {
buf.append("*");
}
}
if (buf == null) {
return source;//如果没有找到 emoji表情,则返回源字符串
} else {
if (buf.length() == len) {//这里的意义在于尽可能少的toString,因为会重新生成字符串
buf = null;
return source;
} else {
return buf.toString();
}
}
}
/**
* 获取接口访问凭证
*
* @param appid
* 凭证
* @param appsecret
* 密钥
* @return
*/
public static Token getToken(String appid, String appsecret) {
Token token = new Token();
String accesstoken=null;
Connection con = DBUtil.getConnection();
try{
con.setAutoCommit(false);
PreparedStatement ptmt = null;
ResultSet rs = null;
String sql="select * from table where date_add(updatetime, interval 90 minute)>now() and id=1";
ptmt = con.prepareStatement(sql);
rs=ptmt.executeQuery();
if(rs.next()) {
accesstoken=rs.getString(1);
}
if(StringUtils.isEmpty(accesstoken)){
String requestUrl = Constant.TOKEN_URL.replace("APPID", appid).replace(
"APPSECRET", appsecret);
// 发起GET请求获取凭证
JSONObject jsonObject = CommonUtil
.httpsRequest(requestUrl, "GET", null);
if (null != jsonObject) {
try {
accesstoken=jsonObject.getString("access_token");
// token.setAccessToken(jsonObject.getString("access_token"));
token.setExpiresIn(jsonObject.getInt("expires_in"));
sql="update struck2.t_wechat_token set token=?,updatetime=NOW() where id=1";
ptmt = con.prepareStatement(sql);
ptmt.setString(1, accesstoken);
int result = ptmt.executeUpdate();
} catch (JSONException e) {
token = null;
// 获取token失败
log.error("获取token失败 errcode:{} errmsg:{}",
jsonObject.getInt("errcode"),
jsonObject.getString("errmsg"));
}
}
}
token.setAccessToken(accesstoken);
}catch(Exception e){
}finally{
try {
if (con != null) {
con.commit();
con.close();
}
} catch (SQLException e) {
try {
con.rollback();
} catch (Exception e1) {
}
}
}
return token;
}
}
最后分享拦截器,用户通过微信公众号向服务器发起的所有要拥有权限才能访问的请求,都应该通过过滤器进行拦截,过滤器内判断用户session没失效,则放行;session失效,判断是否之前已绑定账户,如果绑定,给当前session存储一些自己需要的信息,重新用户原来的访问;如果没有绑定关系,则需要前往登录绑定,下面上代码:
package web;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import util.Constant;
import util.redis.RedisUtils;
public class wechatFilter implements Filter {
private static final Logger logger = Logger.getLogger(wechatFilter.class);
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession session = req.getSession();
logger.info("开始进入拦截器...");
//判断当前用户是否已经登录,因为登录后session域对象会保存该值
String openId = (String)session.getAttribute("openId");
if(StringUtils.isNotBlank(openId)){
//再次判断这个openId是否还存在
boolean b = RedisUtils.sIsMember("LOGIN_USER_OPENID", openId);
if(b){
chain.doFilter(req, response);
return ;
}else{
//二次校验,防止session没失效,但是redis中用户信息已被删除的情况
session.invalidate();
}
}
//将用户当前的请求和参数重新拼接,等重新为用户获取权限之后之后再执行该请求
logger.info("session失效了,发送静默授权获取openId");
String url="https://网站域名"+req.getServletPath();
//获取请求的参数
String queryString = req.getQueryString();
if(null !=queryString){
url+="?"+queryString;
}
logger.info("本次请求的完整url="+url);
//编码url
url = java.net.URLEncoder.encode(url, "utf-8");
//重定向的域名
String redirectUrl = "https://网站域名/项目名/wxLoginRedirectServlet";
redirectUrl = java.net.URLEncoder.encode(redirectUrl, "utf-8");
//微信获取用户静默授权的url
//Constant.APP_ID是 第三方用户唯一凭证,需要你登录自己的公众号查找,具体方法请自行百度
String wxUrl="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+Constant.APP_ID+"&redirect_uri="+
redirectUrl+"&response_type=code&scope=snsapi_userinfo&state="+url+"&connect_redirect=1#wechat_redirect";
res.sendRedirect(wxUrl);
logger.info("拦截结束,跳转到微信授权操作");
return;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
至此,你们已经完成了80%,剩下的20%,需要各位根据自己的业务需求添加自己的业务数据和逻辑。大体思路和主要的核心代码已贴出,大家按需cv。至于注册的接口,大家可以根据自己的需求来编写,注册接口没有用户微信openId,所有和微信openId相关的操作,都在过滤器和调用微信静默授权接口后的回调函数中。
如果有更好的方法或者发现了什么问题,欢迎赐教;但是说归说,别爆粗口,我没收谁的钱也没有要求谁来点赞,甚至没有要求谁来看,我只是在自己的一亩三分地里写点小文章,记录一些自己曾经走过的路,然后顺便给以后可能和我有同样问题的人提供一些可能的借鉴,同时希望有人提供更好的思路,让我还能再有所提升。素质不好的人,请远离我的评论区,这样你好,我好,大家都好,谢谢!
---------------------
作者:今儿ge休息
来源:CSDN
原文:https://blog.csdn.net/weixin_42023666/article/details/89284688
版权声明:本文为博主原创文章,转载请附上博文链接!