微信登录

微信登录

网站应用微型登录开发指南:

参考文档:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

1. 返回微信登录参数

  1. 添加配置

    1
    2
    3
    4
    wx.open.app_id=wxed9954c01bb89b47
    wx.open.app_secret=a7482517235173ddb4083788de60b90e
    wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback
    AR.baseUrl=http://localhost:3000
  2. 添加配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    @Component
    public class ConstantWxPropertiesUtils implements InitializingBean {

    @Value("${wx.open.app_id}")
    private String appId;

    @Value("${wx.open.app_secret}")
    private String appSecret;

    @Value("${wx.open.redirect_url}")
    private String redirect_url;

    @Value("${AR.baseUrl}")
    private String ARBaseUrl;

    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;

    public static String AR_BASE_URL;


    @Override
    public void afterPropertiesSet() throws Exception {
    WX_OPEN_APP_ID = appId;
    WX_OPEN_APP_SECRET = appSecret;
    WX_OPEN_REDIRECT_URL = redirect_url;
    AR_BASE_URL = ARBaseUrl;
    }
    }
  3. 添加接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 返回生成二维码需要的参数
    * @return
    */
    @GetMapping("getLoginParam")
    @ResponseBody
    public Result genQrConnect(){
    try {
    HashMap<String, Object> map = new HashMap<>();
    map.put("appid", ConstantWxPropertiesUtils.WX_OPEN_APP_ID);
    map.put("scope","snsapi_login");
    String wxOpenRedirectUrl = ConstantWxPropertiesUtils.WX_OPEN_REDIRECT_URL;
    wxOpenRedirectUrl = URLEncoder.encode(wxOpenRedirectUrl, "utf-8");
    map.put("redirectUri",wxOpenRedirectUrl);
    map.put("state",System.currentTimeMillis()+"");
    return Result.ok(map);
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    return null;
    }
    }
  4. 前端封装api请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import request from '@/utils/request'

    const api_name = `/api/ucenter/wx`

    export default {
    getLoginParam() {
    return request({
    url: `${api_name}/getLoginParam`,
    method: `get`
    })
    }
    }
  5. 引入微信js

    1
    2
    3
    4
    5
    //初始化微信js
    const script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
    document.body.appendChild(script)
  6. 添加组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    weixinLogin() {
    this.dialogAtrr.showLoginType = 'weixin'

    WechatApi.getLoginParam().then(response => {
    var obj = new WxLogin({
    self_redirect:true,
    id: 'weixinLogin', // 需要显示的容器id
    appid: response.data.appid, // 公众号appid wx*******
    scope: response.data.scope, // 网页默认即可
    redirect_uri: response.data.redirectUri, // 授权成功后回调的url
    state: response.data.state, // 可设置为简单的随机数加session用来校验
    style: 'black', // 提供"black"、"white"可选。二维码的样式
    href: '' // 外部css文件url,需要https
    })
    })
    }

2. 处理微信回调

  1. 添加HttpClient工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    public class HttpClientUtils {

    public static final int connTimeout=10000;
    public static final int readTimeout=10000;
    public static final String charset="UTF-8";
    private static HttpClient client = null;

    static {
    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
    cm.setMaxTotal(128);
    cm.setDefaultMaxPerRoute(128);
    client = HttpClients.custom().setConnectionManager(cm).build();
    }

    public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
    SocketTimeoutException, Exception {
    return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
    SocketTimeoutException, Exception {
    return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String get(String url) throws Exception {
    return get(url, charset, null, null);
    }

    public static String get(String url, String charset) throws Exception {
    return get(url, charset, connTimeout, readTimeout);
    }

    /**
    * 发送一个 Post 请求, 使用指定的字符集编码.
    *
    * @param url
    * @param body RequestBody
    * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
    * @param charset 编码
    * @param connTimeout 建立链接超时时间,毫秒.
    * @param readTimeout 响应超时时间,毫秒.
    * @return ResponseBody, 使用指定的字符集编码.
    * @throws ConnectTimeoutException 建立链接超时异常
    * @throws SocketTimeoutException 响应超时
    * @throws Exception
    */
    public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
    throws ConnectTimeoutException, SocketTimeoutException, Exception {
    HttpClient client = null;
    HttpPost post = new HttpPost(url);
    String result = "";
    try {
    if (StringUtils.isNotBlank(body)) {
    HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
    post.setEntity(entity);
    }
    // 设置参数
    Builder customReqConf = RequestConfig.custom();
    if (connTimeout != null) {
    customReqConf.setConnectTimeout(connTimeout);
    }
    if (readTimeout != null) {
    customReqConf.setSocketTimeout(readTimeout);
    }
    post.setConfig(customReqConf.build());

    HttpResponse res;
    if (url.startsWith("https")) {
    // 执行 Https 请求.
    client = createSSLInsecureClient();
    res = client.execute(post);
    } else {
    // 执行 Http 请求.
    client = HttpClientUtils.client;
    res = client.execute(post);
    }
    result = IOUtils.toString(res.getEntity().getContent(), charset);
    } finally {
    post.releaseConnection();
    if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
    ((CloseableHttpClient) client).close();
    }
    }
    return result;
    }


    /**
    * 提交form表单
    *
    * @param url
    * @param params
    * @param connTimeout
    * @param readTimeout
    * @return
    * @throws ConnectTimeoutException
    * @throws SocketTimeoutException
    * @throws Exception
    */
    public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
    SocketTimeoutException, Exception {

    HttpClient client = null;
    HttpPost post = new HttpPost(url);
    try {
    if (params != null && !params.isEmpty()) {
    List<NameValuePair> formParams = new ArrayList<NameValuePair>();
    Set<Entry<String, String>> entrySet = params.entrySet();
    for (Entry<String, String> entry : entrySet) {
    formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
    }
    UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
    post.setEntity(entity);
    }

    if (headers != null && !headers.isEmpty()) {
    for (Entry<String, String> entry : headers.entrySet()) {
    post.addHeader(entry.getKey(), entry.getValue());
    }
    }
    // 设置参数
    Builder customReqConf = RequestConfig.custom();
    if (connTimeout != null) {
    customReqConf.setConnectTimeout(connTimeout);
    }
    if (readTimeout != null) {
    customReqConf.setSocketTimeout(readTimeout);
    }
    post.setConfig(customReqConf.build());
    HttpResponse res = null;
    if (url.startsWith("https")) {
    // 执行 Https 请求.
    client = createSSLInsecureClient();
    res = client.execute(post);
    } else {
    // 执行 Http 请求.
    client = HttpClientUtils.client;
    res = client.execute(post);
    }
    return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
    } finally {
    post.releaseConnection();
    if (url.startsWith("https") && client != null
    && client instanceof CloseableHttpClient) {
    ((CloseableHttpClient) client).close();
    }
    }
    }

    /**
    * 发送一个 GET 请求
    */
    public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
    throws ConnectTimeoutException,SocketTimeoutException, Exception {

    HttpClient client = null;
    HttpGet get = new HttpGet(url);
    String result = "";
    try {
    // 设置参数
    Builder customReqConf = RequestConfig.custom();
    if (connTimeout != null) {
    customReqConf.setConnectTimeout(connTimeout);
    }
    if (readTimeout != null) {
    customReqConf.setSocketTimeout(readTimeout);
    }
    get.setConfig(customReqConf.build());

    HttpResponse res = null;

    if (url.startsWith("https")) {
    // 执行 Https 请求.
    client = createSSLInsecureClient();
    res = client.execute(get);
    } else {
    // 执行 Http 请求.
    client = HttpClientUtils.client;
    res = client.execute(get);
    }

    result = IOUtils.toString(res.getEntity().getContent(), charset);
    } finally {
    get.releaseConnection();
    if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
    ((CloseableHttpClient) client).close();
    }
    }
    return result;
    }

    /**
    * 从 response 里获取 charset
    */
    @SuppressWarnings("unused")
    private static String getCharsetFromResponse(HttpResponse ressponse) {
    // Content-Type:text/html; charset=GBK
    if (ressponse.getEntity() != null && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
    String contentType = ressponse.getEntity().getContentType().getValue();
    if (contentType.contains("charset=")) {
    return contentType.substring(contentType.indexOf("charset=") + 8);
    }
    }
    return null;
    }

    /**
    * 创建 SSL连接
    * @return
    * @throws GeneralSecurityException
    */
    private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
    try {
    SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
    public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
    return true;
    }
    }).build();

    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

    @Override
    public boolean verify(String arg0, SSLSession arg1) {
    return true;
    }

    @Override
    public void verify(String host, SSLSocket ssl)
    throws IOException {
    }

    @Override
    public void verify(String host, X509Certificate cert)
    throws SSLException {
    }

    @Override
    public void verify(String host, String[] cns,
    String[] subjectAlts) throws SSLException {
    }
    });
    return HttpClients.custom().setSSLSocketFactory(sslsf).build();

    } catch (GeneralSecurityException e) {
    throw e;
    }
    }
    }
  2. 在controller中添加回调接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    //微信扫码后回调方法
    @GetMapping("callback")
    public String callBack(String code, String state){

    if (StringUtils.isEmpty(state) || StringUtils.isEmpty(code)) {
    log.error("非法回调请求");
    throw new AppointmentRegisterException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
    }

    //使用code和appid以及appscrect换取access_token
    StringBuffer baseAccessTokenUrl = new StringBuffer()
    .append("https://api.weixin.qq.com/sns/oauth2/access_token")
    .append("?appid=%s")
    .append("&secret=%s")
    .append("&code=%s")
    .append("&grant_type=authorization_code");

    String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
    ConstantWxPropertiesUtils.WX_OPEN_APP_ID,
    ConstantWxPropertiesUtils.WX_OPEN_APP_SECRET,
    code);

    //使用httpClient请求这个地址
    try {
    String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
    //从返回的json数据中 拿到access_token 和 openid
    JSONObject jsonObject = JSONObject.parseObject(accessTokenInfo);
    String access_token = jsonObject.getString("access_token");
    String openid = jsonObject.getString("openid");

    //根据openid判断数据库中是否存在扫码人的信息
    UserInfo userInfo = userInfoService.selectWxInfoOpenId(openid);
    if(userInfo == null){
    //通过两个值获取扫码人的信息
    String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
    "?access_token=%s" +
    "&openid=%s";
    String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
    String resultInfo = HttpClientUtils.get(userInfoUrl);
    JSONObject resultUserInfoJson = JSONObject.parseObject(resultInfo);
    //解析用户昵称和头像
    String nickname = resultUserInfoJson.getString("nickname");
    String headimgurl = resultUserInfoJson.getString("headimgurl");
    //获取扫码人的信息添加到数据库中
    userInfo = new UserInfo();
    userInfo.setNickName(nickname);
    userInfo.setOpenid(openid);
    userInfo.setStatus(1);

    userInfoService.save(userInfo);
    }

    //返回name和token字符串
    Map<String, String> map = new HashMap<>();
    String name = userInfo.getName();
    if(StringUtils.isEmpty(name)) {
    name = userInfo.getNickName();
    }
    if(StringUtils.isEmpty(name)) {
    name = userInfo.getPhone();
    }
    map.put("name", name);
    //判断userInfo中是否有手机号,如果为空,则返回openid
    //如果不为空,则返回openid值是空字符串
    //前端判断:如果openid不为空,绑定手机号,如果openid为空,则不需要绑定手机号
    if(StringUtils.isEmpty(userInfo.getPhone())) {
    map.put("openid", userInfo.getOpenid());
    } else {
    map.put("openid", "");
    }
    //使用JWT生成token字符串
    String token = JwtHelper.createToken(userInfo.getId(), name);
    map.put("token", token);
    //跳转到前端页面中
    return "redirect:" + ConstantWxPropertiesUtils.AR_BASE_URL + "/weixin/callback?token="+map.get("token")+"&openid="+map.get("openid")+"&name="+ URLEncoder.encode(map.get("name"),"utf-8");

    } catch (Exception e) {
    e.printStackTrace();
    return null;
    }
    }
  3. 根据openid查询用户是否注册,编写service

    1
    2
    3
    4
    5
    6
    7
    8
    //根据openid查询
    @Override
    public UserInfo selectWxInfoOpenId(String openid) {
    QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("openid",openid);
    UserInfo userInfo = baseMapper.selectOne(queryWrapper);
    return userInfo;
    }
  4. 添加回调页面

    根据返回路径/weixin/callback

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <template>
    <!-- header -->
    <div>
    </div>
    <!-- footer -->
    </template>
    <script>
    export default {
    layout: "empty",
    data() {
    return {
    }
    },
    mounted() {
    let token = this.$route.query.token
    let name = this.$route.query.name
    let openid = this.$route.query.openid
    // 调用父vue方法
    window.parent['loginCallback'](name, token, openid)
    }
    }
    </script>
  5. 在myheader.vue中的mounted中添加方法

    1
    2
    3
    4
    5
    6
    // 微信登录回调处理
    let self = this;
    window["loginCallback"] = (name,token, openid) => {
    debugger
    self.loginCallback(name, token, openid);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    loginCallback(name, token, openid) {
    // 打开手机登录层,绑定手机号,改逻辑与手机登录一致
    if(openid != null) {
    this.userInfo.openid = openid

    this.showLogin()
    } else {
    this.setCookies(name, token)
    }
    }
  6. 服务器绑定手机号

    我们根据openid判断需不需要绑定手机号,修改UserInfoServiceImpl方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //绑定手机号码
    UserInfo userInfo = null;
    if(!StringUtils.isEmpty(loginVo.getOpenid())) {
    userInfo = this.selectWxInfoOpenId(loginVo.getOpenid());
    if(null != userInfo) {
    userInfo.setPhone(loginVo.getPhone());
    this.updateById(userInfo);
    } else {
    throw new AppointmentRegisterException(ResultCodeEnum.DATA_ERROR);
    }
    }

    //如果userInfo为空,进行正常手机登录
    if (userInfo == null){
    //4 判断是否为第一次登录:根据手机号查询数据库,如果不存在数据,那么就是第一次登录
    QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
    wrapper.eq("phone",phone);
    userInfo = baseMapper.selectOne(wrapper);
    if (null == userInfo){ //第一次登录
    //添加信息到数据库中
    userInfo = new UserInfo();
    userInfo.setName("");
    userInfo.setPhone(phone);
    userInfo.setStatus(1);

    baseMapper.insert(userInfo);
    }
    }

最后就可以实现微信登录功能


本站由 Cccccpg 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。