微信小程序结合Java后端实现登录注册

登录,微信api
后台获取微信token,包括openid在内

登录流程
1. 小程序发起wx.login请求获取jscode
2. 获取jscode,发送到自己的后端
3. 自己的后台发请求带上jscode, appid(当前用户的唯一标识), secretkey
 到微信的后端获取session_key, openid
4. 返回session_key或者openid到页面
5. 页面再发请求到后台验证openid 是否存在, 存在即登录。
不存在 保存openid到个人数据库,即注册。
6. 登录成功后,返回jwt token签名
7. 页面通过微信app.js 的globalData保存openid,jwt token信息

微信用户,授权信息,相关实体类

微信用户表

create table forum_info.wei_xin_user
(
    id  int auto_increment  primary key,
    user_name   varchar(30) null,
    user_pwd    varchar(30) null,
     # 这里的密码没用,登录只是验证open_id, 
     # 所以说小程序登录是不需要密码的额。它问你是否授权。
     # 然后收集你的其它个人信息。
    create_date date        null,
    wx_open_id  varchar(30) null
);

实体类

package top.bitqian.entity;

import java.util.Date;
import java.io.Serializable;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 *  微信用户注册实体类~
 * </p>
 *
 * @author echo lovely
 * @since 2020-12-04
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class WeiXinUser implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Integer id;

    private String userName;

    private String userPwd;

    private Date createDate;

    private String wxOpenId;


}

获取微信的token

文档里面的字段搬过来。

package top.bitqian.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * <p>
 *     调用微信接口 返回授权信息~
 * </p>
 * @author echo lovely
 * @date 2020/12/4 21:56
 */

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Code2Session {
    
    

    //用户唯一标识
    private String openid;
    // 会话密钥
    private String session_key;
    // 用户在开放平台的唯一标识符
    private String unionid;
    // 错误码
    private Integer errcode;
    // 错误信息
    private String errmsg;

}

常量抽取

package top.bitqian.config;

/**
 * @author echo lovely
 * @date 2020/12/4 22:03
 */

public class WeiXinPostParamConstant {
    
    

    /**
     * 小程序appid
     */
    public static String APP_ID = "小程序开发者官网生成";

    /**
     * 小程序 appSecret
     */
    public static String SECRET = "小程序开发者官网生成";

    /**
     * 登录时获取的 code
     */
    public static String JS_CODE = "";

    /**
     * 授权类型,此处只需填写 authorization_code
     * grant_type ctrl + shit + u
     */
    public static String GRANT_TYPE = "";

}

httpClient工具,用于发送请求获取token

nbplus pom

这里是boot,省略了version


        <!-- http工具类 -->
        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <!--<version>4.5.6</version>-->
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <!--<version>4.4.10</version>-->
        </dependency>

工具类

package top.bitqian.config;

import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
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.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;


/**
 * HTTPClient 工具类.
 * @author echo lovely
 * @date 2020/12/4 21:44
 */

@Configuration
@SuppressWarnings(value = "all")
public class HttpClientUtil {
    
    

    private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);

    private static PoolingHttpClientConnectionManager connectionManager;
    private static String DEFAULT_STR = "";
    private static String UTF_8 = "UTF-8";
    private final static int CONNECT_TIMEOUT = 3000;// 连接超时毫秒 ps:表示建立连接的超时时间
    private final static int SOCKET_TIMEOUT = 10000;// 传输超时毫秒 ps:表示数据传输处理时间
    private final static int REQUESTCONNECT_TIMEOUT = 2000;// 从线程池获取连接超时时间毫秒
    private final static int MAX_TOTAL = 50;// 线程池的最大连接数
    private final static int CONNECT_DEFAULT_ROUTE = 5;// 每个路由默认基础的连接数

    private static void init() {
    
    
        if (connectionManager == null) {
    
    
            connectionManager = new PoolingHttpClientConnectionManager();
            connectionManager.setMaxTotal(MAX_TOTAL);// 整个连接池最大连接数
            // 可用空闲连接过期时间,重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过,释放socket重新建立
            //connectionManager.setValidateAfterInactivity(50000);
            connectionManager.setDefaultMaxPerRoute(CONNECT_DEFAULT_ROUTE);// 每路由最大连接数,默认值是2
        }
    }

    /**
     * 通过连接池获取HttpClient
     *
     * @return
     */
    private static CloseableHttpClient getHttpClient() {
    
    
        init();
        Builder builder = RequestConfig.custom();
        RequestConfig config = builder.setSocketTimeout(SOCKET_TIMEOUT)
                .setConnectTimeout(CONNECT_TIMEOUT)
                .setConnectionRequestTimeout(REQUESTCONNECT_TIMEOUT)
                .build();
        CloseableHttpClient client = HttpClients.custom()
                .setMaxConnPerRoute(CONNECT_DEFAULT_ROUTE)
                .disableConnectionState().setDefaultRequestConfig(config)
                .setConnectionManager(connectionManager).build();
        return client;
    }

    /**
     * @param url
     * @return
     * @throws IOException
     */
    public static String httpGetRequest(String url) throws Exception {
    
    
        HttpGet httpGet = new HttpGet(url);
        return getResult(httpGet);
    }

    public static String httpGetRequest(String url, Map<String, Object> params) throws Exception {
    
    
        URIBuilder ub = new URIBuilder();
        ub.setPath(url);

        ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
        ub.setParameters(pairs);

        HttpGet httpGet = new HttpGet(ub.build());
        return getResult(httpGet);
    }

    public static String httpGetRequest(String url, Map<String, Object> headers
            , Map<String, Object> params) throws Exception {
    
    
        URIBuilder ub = new URIBuilder();
        ub.setPath(url);

        ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
        ub.setParameters(pairs);

        HttpGet httpGet = new HttpGet(ub.build());
        for (Map.Entry<String, Object> param : headers.entrySet()) {
    
    
            httpGet.addHeader(param.getKey(), String.valueOf(param.getValue()));
        }
        return getResult(httpGet);
    }

    public static String httpPostRequest(String url) throws IOException {
    
    
        HttpPost httpPost = new HttpPost(url);
        return getResult(httpPost);
    }

    public static String httpPostRequest(String url, Map<String, Object> params) throws Exception {
    
    
        HttpPost httpPost = new HttpPost(url);
        ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
        httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));
        return getResult(httpPost);
    }

    public static String httpPostRequest(String url, String jsonParams) throws Exception {
    
    
        HttpPost httpPost = new HttpPost(url);
        StringEntity se = new StringEntity(jsonParams, UTF_8);
        httpPost.setEntity(se);
        httpPost.setHeader("Content-Type", "application/json");
        return getResult(httpPost);
    }

    public static String httpPostXMLDataRequest(String url, String xmlData) throws Exception {
    
    
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(new StringEntity(xmlData, UTF_8));
        return getResult(httpPost);
    }

    /**
     * post
     *
     * @param url     (a=3&b=2 形式)
     * @param headers 请求头
     * @param params  参数
     * @return
     * @throws IOException
     */
    public static String httpPostRequest(String url, Map<String, Object> headers
            , Map<String, Object> params) throws Exception {
    
    
        HttpPost httpPost = new HttpPost(url);

        for (Map.Entry<String, Object> param : headers.entrySet()) {
    
    
            httpPost.addHeader(param.getKey(), String.valueOf(param.getValue()));
        }

        ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
        httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));


        return getResult(httpPost);
    }

    private static ArrayList<NameValuePair> covertParams2NVPS(Map<String, Object> params) {
    
    
        ArrayList<NameValuePair> pairs = new ArrayList<NameValuePair>();
        for (Map.Entry<String, Object> param : params.entrySet()) {
    
    
            pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));
        }
        return pairs;
    }

    /**
     * post
     *
     * @param url     (JSON 形式)
     * @param headers 请求头
     * @param params  参数
     * @return
     * @throws IOException
     */
    public static String httpPostRequest(String url, Map<String, Object> headers, String jsonParams
    ) throws Exception {
    
    
        HttpPost httpPost = new HttpPost(url);

        for (Map.Entry<String, Object> param : headers.entrySet()) {
    
    
            httpPost.setHeader(param.getKey(), String.valueOf(param.getValue()));
        }

        StringEntity se = new StringEntity(jsonParams, UTF_8);
        httpPost.setEntity(se);

        return getResult(httpPost);
    }

    /**
     * 处理Http请求
     *
     * @param request
     * @return string
     * @throws IOException
     */
    private static String getResult(HttpRequestBase request) throws IOException {
    
    
        CloseableHttpClient httpClient = getHttpClient();
        CloseableHttpResponse response = null;
        InputStream in = null;
        try {
    
    
            response = httpClient.execute(request);
            HttpEntity entity = response.getEntity();
            in = response.getEntity().getContent();
            if (entity != null) {
    
    
                String result = EntityUtils.toString(entity, Charset.forName(UTF_8));
                response.close();
                return result;
            }
        } catch (ConnectTimeoutException e) {
    
    
            // 连接超时异常
            logger.error("connect timeout {}", e);
        } catch (SocketTimeoutException e) {
    
    
            // 读取超时异常
            logger.error("read timeout {}", e);
        } catch (ClientProtocolException e) {
    
    
            // 该异常通常是协议错误导致:比如构造HttpGet对象时传入协议不对(将'http'写成'htp')or响应内容不符合
            logger.error("protocol exception {}", e);
        } catch (ParseException e) {
    
    
            // 解析异常
            logger.error("parse exception {}", e);
        } catch (IOException e) {
    
    
            // 该异常通常是网络原因引起的,如HTTP服务器未启动等
            logger.error("network exception {}", e);
        } catch (Exception e) {
    
    
            logger.error("other exception {}", e);
        } finally {
    
    
            if (response != null) {
    
    
                try {
    
    
                    response.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
            //in.close();作用就是将用完的连接释放,下次请求可以复用
            //这里特别注意的是,如果不使用in.close();而仅仅使用response.close();结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。
            if (in != null) {
    
    
                try {
    
    
                    in.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }

        return DEFAULT_STR;
    }

    public static void main(String[] args) {
    
    
        String str = null;
        try {
    
    
            str = HttpClientUtil.httpGetRequest("https://www.baidu.com");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println(str);
    }
}

请求微信后台,获取openid,返回的是json

json转实体类用

  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
  </dependency>

   /**
     * 获取openid session_key
     * @param jsCode 小程序请求到的jsCode
     * @return 授权信息~
     */
    @Override
    public Code2Session getWinXinJson(String jsCode) {
    
    

        // https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
        StringBuilder url = new StringBuilder();
        url.append("https://api.weixin.qq.com/sns/jscode2session?appid=");
        url.append(WeiXinPostParamConstant.APP_ID);
        url.append("&secret=");
        url.append(WeiXinPostParamConstant.SECRET);
        url.append("&js_code=");
        url.append(jsCode);
        url.append("&grant_type=authorization_code");

        try {
    
    
            String weiXinJson = HttpClientUtil.httpGetRequest(url.toString());
            System.out.println(weiXinJson);

            return new ObjectMapper().readValue(weiXinJson, Code2Session.class);

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        return null;
    }

登录或者注册完整业务实现,登录成功返回jwt签名

我的java json web token签名实现,工具类

cotroller实现

package top.bitqian.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.bitqian.config.AuthTokenCheck;
import top.bitqian.entity.Code2Session;
import top.bitqian.entity.WeiXinUser;
import top.bitqian.service.WeiXinUserService;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * 微信登录 controller~
 * @author echo lovely
 * @date 2020/12/4 21:36
 */

@RestController
@Slf4j
public class WeiXinUserController {
    
    

    @Autowired
    private WeiXinUserService weiXinUserService;

    // 调用微信的接口获取 app_id
    @RequestMapping("/getCode/{jsCode}")
    public Code2Session getWinXinJson(@PathVariable("jsCode") String jsCode) {
    
    

        return weiXinUserService.getWinXinJson(jsCode);
    }

    // 用户提交wx_id 过来注册
    @RequestMapping("/wx_user/register")
    public boolean doRegister(WeiXinUser user) {
    
    

        // 账号存在..
        WeiXinUser tmpUser = weiXinUserService.getWeiXinUserByOpenId(user.getWxOpenId());

        if (tmpUser != null) {
    
    
            return false;
        }

        // 不存在,即注册
        user.setCreateDate(new Date(System.currentTimeMillis()));
        weiXinUserService.addWeiXinUser(user);

        return true;
    }

    @RequestMapping("/wx_user/login")
    public String doLogin(WeiXinUser weiXinUser) throws Exception {
    
    

        return weiXinUserService.doLogin(weiXinUser);
    }

    @AuthTokenCheck
    @RequestMapping("/wx_user/msg")
    public String testJwtToken(HttpServletRequest request) {
    
    

        Object userId = request.getAttribute("id");

        System.out.println("userId from page token~=======>" + userId);

        return "auth by token~" + userId;
    }


}

业务实现

package top.bitqian.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import top.bitqian.config.HttpClientUtil;
import top.bitqian.config.JwtUtil;
import top.bitqian.config.WeiXinPostParamConstant;
import top.bitqian.entity.Code2Session;
import top.bitqian.entity.WeiXinUser;
import top.bitqian.mapper.WeiXinUserMapper;
import top.bitqian.service.WeiXinUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author echo lovely
 * @since 2020-12-04
 */
@Service
public class WeiXinUserServiceImpl extends ServiceImpl<WeiXinUserMapper, WeiXinUser> implements WeiXinUserService {
    
    

    /**
     * 获取openid session_key
     * @param jsCode 小程序请求到的jsCode
     * @return 授权信息~
     */
    @Override
    public Code2Session getWinXinJson(String jsCode) {
    
    

        // https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
        StringBuilder url = new StringBuilder();
        url.append("https://api.weixin.qq.com/sns/jscode2session?appid=");
        url.append(WeiXinPostParamConstant.APP_ID);
        url.append("&secret=");
        url.append(WeiXinPostParamConstant.SECRET);
        url.append("&js_code=");
        url.append(jsCode);
        url.append("&grant_type=authorization_code");

        try {
    
    
            String weiXinJson = HttpClientUtil.httpGetRequest(url.toString());
            System.out.println(weiXinJson);

            return new ObjectMapper().readValue(weiXinJson, Code2Session.class);

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        return null;
    }

    @Autowired
    private WeiXinUserMapper userMapper;

   public WeiXinUser getWeiXinUserByOpenId(String openId) {
    
    

       WeiXinUser tmpUser = new WeiXinUser();
       tmpUser.setWxOpenId(openId);
       QueryWrapper<WeiXinUser> queryWrapper = new QueryWrapper<>(tmpUser);

       return userMapper.selectOne(queryWrapper);

    }

    @Override
    public String doLogin(WeiXinUser user) throws Exception {
    
    

        // 登录需要:
        // 1. 根据小程序传来的openid验证数据库中的id,看是否存在~
        WeiXinUser weiXInUser = getWeiXinUserByOpenId(user.getWxOpenId());

        System.out.println("doLogin----->" + weiXInUser);

        if (weiXInUser != null) {
    
    
            // 2. 存在 返回jwt签名~  页面保存
            return JwtUtil.createToken(weiXInUser);
        }

        // 2. 不存在 return ""
        return null;
    }

    @Override
    public void addWeiXinUser(WeiXinUser user) {
    
    

       //  userMapper.addWeiXinUser(user);
        userMapper.insert(user);
    }
}

设置拦截器对要访问的方法拦截,并判断jwt token

增加注解,灵活判断哪些controlelr方法需要配token验证

package top.bitqian.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 要给哪些方法进行token验证
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuthTokenCheck {
    
    

}

拦截器

package top.bitqian.config;

import com.auth0.jwt.interfaces.Claim;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 拦截器
 * @author echo lovely
 * @date 2020/12/5 20:28
 */

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    
    

    /**
     * 拦截器需要被注册进..
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(new TokenInterceptor());
    }


}

class TokenInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
    
    

        System.out.println("进来l...");
        // 对方法进行拦截
        if (! (handler instanceof HandlerMethod)) {
    
    
            return true;
        }

        // 方法对象
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        Method methodTarget = handlerMethod.getMethod();

        // 对加了AuthTokenCheck注解 的方法进行token验证~
        boolean isTokenTarget = methodTarget.isAnnotationPresent(AuthTokenCheck.class);

        if (isTokenTarget) {
    
    
            // 进行token验证, 头部里面的token
            // String authorizeToken = request.getHeader("authorize_token");
            String authorizeToken = request.getParameter("authorize_token");

            try {
    
    
                Map<String, Claim> claimMap = JwtUtil.parseToken(authorizeToken);
                // 解析获取token中的用户id~ 也可根据相应的键获取其它信息
                Integer id = claimMap.get("id").asInt();

                String userName = claimMap.get("userName").asString();

                System.out.println(id + "\t" + userName);

                // 放入request中
                request.setAttribute("id", id);

                return true;
            } catch (Exception e) {
    
    

                return false;
            }

        }

        return true;

    }
}

猜你喜欢

转载自blog.csdn.net/qq_44783283/article/details/110734333
今日推荐