J加粗样式ava后台流程图
微信三方登录工作流程
1.客户端发送授权码请求
2.当扫码成功之后,服务器向客户端返回对应的授权码(微信服务器调用回调接口)
3.通过授权码获取token(java程序发送的get请求)
4.通过token获取资源
官方给的API文档
参数说明
参数 是否必须 说明
appid 是 应用唯一标识
redirect_uri 是 请使用urlEncode对链接进行处理
response_type 是 填code
scope 是 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login
state 否 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验
注意:回调地址要改成本机的地址 C:\Windows\System32\drivers\etc
hosts文件 加上一段 127.0.0.1 填上回调域名
# For example: # localhost name resolution is handled within DNS itself. # 127.0.0.1 localhost # //填上回调域名 127.0.0.1 bugtracker.********.cn
后台controller层代码 ,前端发送请求到 /wechat/tologin到这个接口
@Controller @RequestMapping("/wechat") public class WechatController {
@Autowired private IWechatService wechatService; //拉起二维码 @RequestMapping("/tologin") public String toLogin() {
String codeURl = WechatConstant.CODE_URL.replace("APPID", WechatConstant.APPID) .replace("REDIRECT_URI", WechatConstant.REDIRECT_URI); System.out.println("这是拉取二维码的接口"); //重定向跳转地址,二维码就出来了 return "redirect:" + codeURl; }
//替换的回调地址, public static final String REDIRECT_URI="http://bugtracker..cn/wechat/callback";
用户扫描二维码点击确认成功后,会跳转到本机127.0.0.1/wechat/callback接口,并且携带授权码Code
//二维码扫描成功后,跳转到回调接口,并且携带授权码 @RequestMapping("/callback") public String callback(String code) {
System.out.println(code + "后台获取到的COde"); return "redirect:http://localhost:8080/callback.html?code=" + code; //可以跳指定网址 //return "redirect:http://www.baidu.com"; }
前端创建callback.html页面,页面用到了一个方法,获取?后面的数据 相当于获取Code授权码
* js动态获取?后面的参数,并且封装成一个json对象 * @returns {
Object} */ function getParam(){
var url=location.search; var param = new Object(); if(url.indexOf("?")!=-1){
var str = url.substr(1) strs = str.split("&"); for(var i=0;i<strs.length;i++){
param[strs[i].split("=")[0]]=unescape(strs[i].split("=")[1]); } } return param; }
let param = getParam(); 就已经拿到授权码Code 发送请求到/wechat/handleCallback接口
<script type="text/javascript"> new Vue({
el:"#app", mounted(){
//获取当前页面?后面的参数,最终封装成一个json对象 let param = getParam(); //处理回调接口 this.$http.post("/wechat/handleCallback", param).then(res => {
let {
success, message, resultObj} = res.data; //如果success为true,并且openid有值,就证明它要跳转到绑定界面,用户和微信进行绑定 if(success&&resultObj.openid){
location.href = "/binder.html?openid="+resultObj.openid }else if(success&& resultObj.token){
//代表已经登录了 //把登录用户保存到浏览器里面 localStorage.setItem("token", resultObj.token); localStorage.setItem("loginUser", JSON.stringify(resultObj.loginUser)); location.href = "/index.html"; } }); } }) </script>
后台wechat/handleCallback接口
//处理回调接口 @PostMapping("/handleCallback") @ResponseBody //这里需要返回json数据 public AjaxResult handleCallback(@RequestBody Map<String, String> param) {
try {
//处理回调接口,拿到授权码Code 进行业务逻辑 Map<String, Object> map = wechatService.handleCallback(param.get("code")); //先不看下面的代码 Object openid = map.get("openid"); System.out.println(openid+"返回值openid,看看是否有值"); return AjaxResult.me().setResultObj(map); } catch (Exception e) {
e.printStackTrace(); return new AjaxResult(e.getMessage()); } }
//token对应的url地址 "https://api.weixin..com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
public class HttpClientUtils {
/ * 发送get请求 * @param url 请求地址 * @return 返回内容 json */ public static String httpGet(String url){
// 1 创建发起请求客户端 try {
HttpClient client = new HttpClient(); // 2 创建要发起请求-tet GetMethod getMethod = new GetMethod(url); // getMethod.addRequestHeader("Content-Type", // "application/x-www-form-urlencoded;charset=UTF-8"); getMethod.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET,"utf8"); // 3 通过客户端传入请求就可以发起请求,获取响应对象 client.executeMethod(getMethod); // 4 提取响应json字符串返回 String result = new String(getMethod.getResponseBodyAsString().getBytes("utf8")); return result; } catch (IOException e) {
e.printStackTrace(); } return null; } }
<dependency> <groupId>com.alibaba
groupId> <artifactId>fastjson
artifactId> <version>1.2.58
version>
dependency>
代码看着多,一步一步来就好了,通过授权码 才能拿到令牌和openid(微信用户唯一标志)
@Override public Map<String,Object> handleCallback(String code) {
//获取token的url地址 String tokenUrl = WechatConstant.TOKEN_URL.replace("APPID", WechatConstant.APPID) .replace("SECRET", WechatConstant.SECRET) .replace("CODE", code); //通过授权码Code获取token令牌,json字符串需要转成对象才能取值 String tokenJsonStr = HttpClientUtils.httpGet(tokenUrl); //把json字符串转为json对象 JSONObject jsonObject = JSONObject.parseObject(tokenJsonStr); //获取token String access_token = jsonObject.getString("access_token"); //微信用户唯一标志 String openid = jsonObject.getString("openid");
//通过token获取微信用户资源 String userinfo_url = WechatConstant.USERINFO_URL.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid); //发送get请求 String userinfoJsonstr = HttpClientUtils.httpGet(userinfo_url); //把用户信息数据转为json对象 jsonObject = JSONObject.parseObject(userinfoJsonstr); //通过openid查询数据库中是否有对应的数据(注意:联表查询, t_wxuser和t_logininfo表) Wechat wechat = wechatMapper.loadByOpenid(openid); //创建一个map集合,封装数据,然后返回给前端,方便绑定微信用户 Map<String, Object> map = new HashMap<>(); //如果Wechat为空,就证明是第一次扫码,第一次扫码就要先添加微信用户 if (wechat == null) {
wechat = new Wechat(); //设置属性值 唯一标志 wechat.setOpenid(openid); //设置昵称 wechat.setNickname(jsonObject.getString("nickname")); //设置性别 wechat.setSex(jsonObject.getInteger("sex")); //设置地址 国家、省份、城市 wechat.setAddress(jsonObject.getString("country")+" " + jsonObject.getString("province")+" " +jsonObject.getString("city")); //设置头像 wechat.setHeadimgurl(jsonObject.getString("headimgurl")); //保存微信用户基本信息 wechatMapper.save(wechat); //返回某个状态,然后前端根据这个状态跳转到绑定界面 map.put("openid", openid); return map; }else{
//如果wechat不为空,就证明不是第一次扫码 LoginInfo loginInfo = wechat.getLoginInfo(); //如果loginInfo为空,依然跳转到绑定界面 if (loginInfo == null) {
//返回某个状态,然后前端根据这个状态跳转到绑定界面 map.put("openid", openid); return map; } //使用redisTemplate先要引入 redis jar包 下面附上了 //loginInfo不为空,就证明是已经绑定过了,直接登录即可 String token = UUID.randomUUID().toString(); redisTemplate.opsForValue().set(token,loginInfo,30, TimeUnit.MINUTES); map.put("token", token); map.put("loginUser", loginInfo); return map; } }
<dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-data-redis
artifactId>
dependency>
callback.html
//处理回调接口 this.$http.post("/wechat/handleCallback", param).then(res => {
let {
success, message, resultObj} = res.data; //如果success为true,并且openid有值,就证明它要跳转到绑定界面,用户和微信进行绑定 if(success&&resultObj.openid){
location.href = "/binder.html?openid="+resultObj.openid }else if(success&& resultObj.token){
//代表已经登录了 //把登录用户保存到浏览器里面 localStorage.setItem("token", resultObj.token); localStorage.setItem("loginUser", JSON.stringify(resultObj.loginUser)); location.href = "/index.html"; } }); }
发送验证码上一篇文章已经讲过了
binder.html 跳转到这个页面是携带有openid 微信唯一标志的
//准备参数 let param = {
"phone": this.user.phone}; //发送短信请求 this.$http.post("/code/sendBinderCode", param).then((res) => {
let {
success, message} = res.data; //提示用户一分钟以内不能连续发送多次 if(!success){
this.errorMsg = message; } });
//微信绑定手机发送验证码 @PostMapping("/sendBinderCode") public AjaxResult sendBinderCode(@RequestBody User user) {
try {
verificationService.sendBinderCode(user.getPhone()); return AjaxResult.me(); } catch (DiyException e) {
e.printStackTrace(); return new AjaxResult(e.getMessage()); }
@Override public void sendBinderCode(String phone) throws DiyException {
//调用方法发送绑定验证码 sendCode(phone, VerificationConstant.USER_BINDER); } //发送验证码,不同常量不同短信 public void sendCode(String phone,String constant) throws DiyException {
//随机生成4位字符 String value = StrUtils.getComplexRandomString(4); //1分钟以内只能发送1次验证码 //先通过key 取值 key 就是手机号 加绑定标识常量 String valueCode = (String) redisTemplate.opsForValue().get(phone + ":" + constant); //判断存在redis中的验证码是否为空 //如果不为空且有效期在一分钟之内 System.out.println(valueCode + "测试验证码加时间戳"); if (!StringUtils.isEmpty(valueCode)) {
//获取验证码第一次创建的时间,截取时间戳 String beginTime = valueCode.split(":")[1]; //现在的时间 减去第一次创建的时候 如果小于1分钟之内 if ((System.currentTimeMillis()) - Long.valueOf(beginTime) <= 60 * 1000) {
throw new DiyException("一分内不能发送多次验证码"); }//超过1分钟且小于5分钟 //还是发送第一次的验证码过去 value = valueCode.split(":")[0]; }//如果通过key获取到的验证码为空,证明已经超过5分钟 redisTemplate.opsForValue().set(phone + ":" + constant, value + ":" + System.currentTimeMillis(), 5, TimeUnit.MINUTES); //发送验证码 String str = "尊敬的用户,你的验证码为:" + value + ",请在5分钟之内使用"; //调用接口发送短信,这是真的发到手机里,需要发送短信就解开注释 , //SendMsgUtils.send(phone, str); System.out.println("发送成功" + value); }
前台拿到验证码登陆 手机号和验证码全正确并且没有过期的情况,点击注册后发送请求到/wechat/binder 并且携带user对象,user对象有 phone 手机号 code验证码 type账号类型 默认是1,还有openid
//给注册按钮注册事件 register(){
let param = getParam(); //添加openid属性 this.user.openid = param.openid; //校验 this.$http.post("/wechat/binder", this.user).then(res => {
let {
success, message,resultObj} = res.data; if(!success){
//这是后台抛出的异常信息 this.errorMsg = message; }else{
localStorage.setItem("token",resultObj.token); //JSON.stringify是json对象转换成字符串 localStorage.setItem("loginUser",JSON.stringify(resultObj.loginUser)); location.href = "/index.html"; } }); }
后台 使用loginInfoDto临时对象接收数据,
@Data public class LoginInfoDto {
//手机号 private String phone; //验证码 private String code; //用户名 private String username; //密码 private String password; //重得密码 private String configpassword; //类型 1为门户用户 0为后台管理 private Integer type; //微信用户唯一标志 private String openid; }
//绑定 @PostMapping("/binder") @ResponseBody public AjaxResult binder(@RequestBody LoginInfoDto loginInfoDto) {
System.out.println(loginInfoDto+"绑定的微信手机号信息"); try {
Map<String, Object> map = wechatService.binder(loginInfoDto); return AjaxResult.me().setResultObj(map); } catch (Exception e) {
e.printStackTrace(); return new AjaxResult(e.getMessage()); } }
@Override public Map<String,Object> binder(LoginInfoDto loginInfoDto) throws CustomException {
//校验数据 checkData(loginInfoDto); //通过手机号码和type在t_logininfo中查询是否有数据 LoginInfo loginInfo = logininfoMapper.loadByUsernameAndType(loginInfoDto.getPhone(), loginInfoDto.getType()); //如果为空,就要新建账号 if (loginInfo == null) {
//创建LoginInfo对象,创建后返回主键 loginInfo = createLoginInfo(loginInfoDto); //保存logininfo对象 logininfoMapper.save(loginInfo); //创建User对象 User user = createUser(loginInfo); //保存user对象 userMapper.save(user); } //绑定微信 wechatMapper.binder(loginInfo.getId(), loginInfoDto.getOpenid()); //查询出来的数据为空--》添加logininfo 添加user 绑定微信用户 直接登录 //查询出来的数据不为空---》直接绑定, 直接登录 //一下代码就是直接在登录 String token = UUID.randomUUID().toString(); redisTemplate.opsForValue().set(token,loginInfo,30, TimeUnit.MINUTES); Map<String, Object> map = new HashMap<>(); map.put("token", token); map.put("loginUser", loginInfo); return map; }
校验数据,难理解的应该就是验证码是否过期,手机发送验证码 设置的有效期为5分钟,我们通过手机号加绑定常量 来取value值的时候 如果为空,就代表已经过期了
private void checkData(LoginInfoDto loginInfoDto) throws CustomException {
//判断手机号是否为空 if(StringUtils.isEmpty(loginInfoDto.getPhone())){
throw new CustomException("手机号码不为空!!"); } //判断验证码是否为空 if(StringUtils.isEmpty(loginInfoDto.getCode())){
throw new CustomException("验证码不能为空!!"); } //判断验证码是否过期,验证码需要通过手机号加上绑定常量来取, String codeValue = (String) redisTemplate.opsForValue().get(loginInfoDto.getPhone() + ":" + VerificationConstant.USER_BINDER); if(StringUtils.isEmpty(codeValue)){
throw new CustomException("验证码已过期!!"); } //判断验证码是否正确,通过手机号加常量取的验证码是带有时间戳的,需要分割 取索引0的字符串 if(!loginInfoDto.getCode().toUpperCase().equals(codeValue.split(":")[0].toUpperCase())){
throw new CustomException("验证码错误!!"); } //判断账号类型是否为null,openid是否为空, if(loginInfoDto.getType()==null || StringUtils.isEmpty(loginInfoDto.getOpenid())){
throw new CustomException("请完善基本信息!!"); } }
创建loginInfo对象
/ * 创建LoginInfo对象 * @param loginInfoDto * @return */ private LoginInfo createLoginInfo(LoginInfoDto loginInfoDto) {
LoginInfo loginInfo = new LoginInfo(); //设置手机 loginInfo.setPhone(loginInfoDto.getPhone()); //设置类型 loginInfo.setType(loginInfoDto.getType()); //返回对象 return loginInfo; }
创建User对象
/ * 创建用户对象 * @param loginInfo * @return */ private User createUser(LoginInfo loginInfo) {
User user = new User(); BeanUtils.copyProperties(loginInfo,user); user.setState(PethomeConstant.OK); user.setLoginInfo(loginInfo); return user; }
附上WechatMapper.xml
<mapper namespace="cn.baidu.user.mapper.WechatMapper"> <resultMap id="WechatResultMap" type="Wechat"> <id column="id" property="id"/> <result column="openid" property="openid"/> <result column="nickname" property="nickname"/> <result column="sex" property="sex"/> <result column="address" property="address"/> <result column="headimgurl" property="headimgurl"/> <association property="loginInfo" javaType="LoginInfo"> <id column="lid" property="id"/> <result column="lusername" property="username"/> <result column="lphone" property="phone"/> <result column="lemail" property="email"/>
association>
resultMap>
<select id="loadByOpenid" resultMap="WechatResultMap"> SELECT w.*,l.id lid,l.phone lphone,l.username lusername,l.email lemail FROM t_wxuser w LEFT JOIN t_logininfo l ON w.logininfo_id = l.id WHERE w.openid=#{openid}
select> <insert id="save" useGeneratedKeys="true" keyColumn="id" keyProperty="id"> INSERT INTO t_wxuser(openid, nickname, sex, address, headimgurl, logininfo_id) VALUES ( #{openid}, #{nickname}, #{sex}, #{address}, #{headimgurl}, #{loginInfo.id} )
insert> <update id="binder"> UPDATE t_wxuser SET logininfo_id=#{logininfoId} WHERE openid=#{openid}
update>
mapper>
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/216312.html原文链接:https://javaforall.net
