登录流程时序图
流程分析:首先在小程序这一端,它自己会调用wx.login()获取code,这个code是用来和微信官方来做交互的,当用户获得code以后,我们再去调用小程序的一个官方的服务器,wx.request()发送code,可以看出来这个是微信服务器调用我们开发者的服务器,而开发者的服务器,根据这个code,发送到微信官方的接口服务器,就是调用第三方的服务器(Wechat Http Api)这个时候需要一些参数:appid+appsecret+code是微信小程序所需的参数,同时需要注意code只使用1次,使用1次后就会被销毁。当微信小程序拿到这些参数,判断你是有效的就会返回一个session_key(session_key可以把他当成是用户的会话讯息,当用户登录了你就会有一个session_key当然这个session_key是在微信的那一端的,我们自己的开发者服务器是没有的,所以我们要对这个session_key进行保存)+openid(openid可以理解为是用户的主键,他是唯一的,可以保存到数据库中作为用户的唯一标识用来绑定,这个操作一般是在用户和自己的某一个账号用来绑定完成之后,进行保存的)等。当有效的时候,会返回给开发者服务器,这个时候,开发者也会自己做一个处理,去自定义登录状态其实也是把我们的openid,和session_key进行关联,把他保存到我们自己的session中,如果是分布式环境可以将这些信息保存到redis中,然后我们登录成功,会把一个相应的状态传到我们小程序这一端,而小程序这一端,拿到状态之后就表示你这个用户是登录过的,这个时候也需要再小程序这一端也做一个标识,将自定义登录状态保存到本地storage中,就是我们手机的缓存中。如果我们发生业务的时候,就可以把用户信息携带到开发者服务器中,然后我们开发者服务通过自定义登录状态查询openid和session_key这个操作就相当于拦截器,判断用户的状态,限制用户的操作,这个时候如果自定义状态通过校验了,那么拦截器就放行操作一些相应的业务,然后再将我们业务这一端返回到我们的小程序中。
实战代码:
前台
<button class="goRegistBtn" type="warn" open-type='getUserInfo' bindgetUserinfo='doLogin'>微信登陆</button>
getUserInfo:获取用户信息
bindgetUserinfo:绑定事件
doLogin:js中定义的登录方法,判断用户是否是已经登录过的,如果没有登录过的,他会有一个授权的弹框,如果是没有授权过的他会把之前用户的一些相关信息进行输出。
JS
//登录
doLogin:function(e){
console.log(e.detail.errMsg)
console.log(e.detail.userInfo)
console.log(e.detail.raWData)
}
wx.login({
success:function(res){
console.log(res)
//获取登录的临时凭证code
var code = res.code
//调用后端,获取微信的session_key,secret
wx.request({
url:"http://127.0.0.1:8080/wxLogin?code="+code,
method:"POST",
success: function(result){
console.log(result);
}
})
}
})
后台
实体类 用于保存redis所需信息
package com.example.sell.wx;
public class WXSessionModel {
private String session_key;
private String openid;
public String getSession_key() {
return session_key;
}
public void setSession_key(String session_key) {
this.session_key = session_key;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
}
controller
package com.example.sell.wx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
/**
* @Author: Administrator
* @Description:
* @Date: 2019-11-03 14:27
* @Modified By:
*/
@RestController
public class WXLoginController {
@Autowired
private RedisOperator redisOperator;
@PostMapping("/wxLogin")
public HashMap<String,String> wxLogin(String code){
HashMap<String,String> result=new HashMap<String, String>();
HashMap<String,String> paraMap=new HashMap<String, String>();
System.out.println(code);
/**
* 登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。
* GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
* appid,secret 微信小程序 账户设置中存在 不能传递到前端
*
*/
String url="https://api.weixin.qq.com/sns/jscode2session";
paraMap.put("appid","123132");
paraMap.put("secret","123123");
paraMap.put("js_code","code");
paraMap.put("grant_type","authorization_code");
String wxResutl=HttpClientUtil.doGet(url,paraMap);
System.out.println(wxResutl);
WXSessionModel model = JsonUtils.jsonToPojo(wxResutl,WXSessionModel.class);
//存入session到redis
redisOperator.set("user-redis-session:"+model.getOpenid(),model.getSession_key(),60000);
return result;
}
}
redis工具类
package com.example.sell.wx;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
/**
* @Description: 使用redisTemplate的操作实现类
*/
@Component
public class RedisOperator {
// @Autowired
// private RedisTemplate<String, Object> redisTemplate;
@Autowired
private StringRedisTemplate redisTemplate;
// Key(键),简单的key-value操作
/**
* 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
*
* @param key
* @return
*/
public long ttl(String key) {
return redisTemplate.getExpire(key);
}
/**
* 实现命令:expire 设置过期时间,单位秒
*
* @param key
* @return
*/
public void expire(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:INCR key,增加key一次
*
* @param key
* @return
*/
public long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 实现命令:DEL key,删除一个key
*
* @param key
*/
public void del(String key) {
redisTemplate.delete(key);
}
// String(字符串)
/**
* 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
*
* @param key
* @param value
* @param timeout
* (以秒为单位)
*/
public void set(String key, String value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:GET key,返回 key所关联的字符串值。
*
* @param key
* @return value
*/
public String get(String key) {
return (String)redisTemplate.opsForValue().get(key);
}
// Hash(哈希表)
/**
* 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
*
* @param key
* @param field
* @param value
*/
public void hset(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
/**
* 实现命令:HGET key field,返回哈希表 key中给定域 field的值
*
* @param key
* @param field
* @return
*/
public String hget(String key, String field) {
return (String) redisTemplate.opsForHash().get(key, field);
}
/**
* 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
*
* @param key
* @param fields
*/
public void hdel(String key, Object... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
/**
* 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
*
* @param key
* @return
*/
public Map<Object, Object> hgetall(String key) {
return redisTemplate.opsForHash().entries(key);
}
// List(列表)
/**
* 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long lpush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* 实现命令:LPOP key,移除并返回列表 key的头元素。
*
* @param key
* @return 列表key的头元素。
*/
public String lpop(String key) {
return (String)redisTemplate.opsForList().leftPop(key);
}
/**
* 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long rpush(String key, String value) {
return redisTemplate.opsForList().rightPush(key, value);
}
}
jsong工具类
package com.example.sell.wx;
import java.util.List;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
*
* @Title: JsonUtils.java
* @Package com.lee.utils
* @Description: 自定义响应结构, 转换类
* Copyright: Copyright (c) 2016
* Company:Nathan.Lee.Salvatore
*
* @version V1.0
*/
public class JsonUtils {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 将对象转换成json字符串。
* <p>Title: pojoToJson</p>
* <p>Description: </p>
* @param data
* @return
*/
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
/**
* 将json结果集转化为对象
*
* @param jsonData json数据
* @param clazz 对象中的object类型
* @return
*/
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json数据转换成pojo对象list
* <p>Title: jsonToList</p>
* <p>Description: </p>
* @param jsonData
* @param beanType
* @return
*/
public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
HttpClient工具类
package com.example.sell.wx;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
public class HttpClientUtil {
public static String doGet(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPost(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url) {
return doPost(url, null);
}
public static String doPostJson(String url, String json) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
}
redis配置
# \u5f00\u53d1\u73af\u5883\u548c\u751f\u4ea7\u73af\u5883\u7684\u8d44\u6e90\u6587\u4ef6\u914d\u7f6e\u9694\u79bb
spring.profiles.active=dev
############################################################
#
# REDIS \u914d\u7f6e
#
############################################################
# Redis\u6570\u636e\u5e93\u7d22\u5f15\uff08\u9ed8\u8ba4\u4e3a0\uff09
spring.redis.database=1
# Redis\u670d\u52a1\u5668\u5730\u5740
#spring.redis.host=192.168.1.209
# Redis\u670d\u52a1\u5668\u8fde\u63a5\u7aef\u53e3
spring.redis.port=6379
# Redis\u670d\u52a1\u5668\u8fde\u63a5\u5bc6\u7801\uff08\u9ed8\u8ba4\u4e3a\u7a7a\uff09
spring.redis.password=imooc
# \u8fde\u63a5\u6c60\u6700\u5927\u8fde\u63a5\u6570\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09
spring.redis.pool.max-active=1000
# \u8fde\u63a5\u6c60\u6700\u5927\u963b\u585e\u7b49\u5f85\u65f6\u95f4\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09
spring.redis.pool.max-wait=-1
# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5927\u7a7a\u95f2\u8fde\u63a5
spring.redis.pool.max-idle=10
# \u8fde\u63a5\u6c60\u4e2d\u7684\u6700\u5c0f\u7a7a\u95f2\u8fde\u63a5
spring.redis.pool.min-idle=2
# \u8fde\u63a5\u8d85\u65f6\u65f6\u95f4\uff08\u6beb\u79d2\uff09
spring.redis.timeout=0
############################################################
#
# Server \u670d\u52a1\u7aef\u76f8\u5173\u914d\u7f6e
#
############################################################
# \u914d\u7f6eapi\u7aef\u53e3\u53f7
server.port=8080
############################################################
# Server - tomcat \u76f8\u5173\u5e38\u7528\u914d\u7f6e
############################################################
# tomcat\u7684URI\u7f16\u7801
server.tomcat.uri-encoding=UTF-8
日志配置
log4j.rootLogger=INFO,console,dailyFile
# TODO \u53d1\u5e03\u5230\u963f\u91cc\u4e91\u8bb0\u5f97\u6dfb\u52a0\uff0c\u53e6\u5916\u63a7\u5236\u53f0\u4e0d\u8f93\u51fa(\u53ea\u8f93\u51fawarn\u6216\u8005error\u4fe1\u606f)
#INFO,console,dailyFile
#log4j.logger.org.mybatis = DEBUG
log4j.logger.com.imooc.mapper=INFO
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.encoding=UTF-8
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%l] - [%p] %m%n
# \u5b9a\u671f\u6eda\u52a8\u65e5\u5fd7\u6587\u4ef6\uff0c\u6bcf\u5929\u90fd\u4f1a\u751f\u6210\u65e5\u5fd7
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.encoding=UTF-8
log4j.appender.dailyFile.Threshold=INFO
# TODO \u672c\u5730\u65e5\u5fd7\u5730\u5740\uff0c\u6b63\u5f0f\u73af\u5883\u8bf7\u52a1\u5fc5\u5207\u6362\u4e3a\u963f\u91cc\u4e91\u5730\u5740
log4j.appender.dailyFile.File=/imooc/logs/itzixi-web/log.log4j
log4j.appender.dailyFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%l] - [%p] %m%n