前端uniapp+后端springboot 详细教程《实现微信小程序授权登录》(附完整前后端项目demo)

news2024/11/13 14:42:16

实现微信小程序授权登录

      • 1、前端技术栈
        • 1.1、uniapp
        • 1.2、前端封装工具
        • 1.3、Hbuilderx构建uniapp项目
      • 2、后端技术栈
        • 2.1、创建springboot后端项目
        • 2.2、数据库准备
        • 2.3、创建实体类
        • 2.4、后端工具类
        • 2.5、mapper和service接口
        • 2.5、Service实现类
        • 2.6、微信用户的控制层Controller

微信小程序官方登录流程图:
在这里插入图片描述
参考微信小程序登录官网文档

1、前端技术栈

1.1、uniapp

使用uniapp构建一套代码多端使用的前端框架项目

1.2、前端封装工具

  • dateUtil.js:
    功能:
    1. 时间日期格式化
    2. 传入日期是否和当前日期的比较
    完整代码:

    // 判断传入日期是否和当前日期比较 
     const judgeDate=(toDate)=>{
    	return new Date().getTime()-new Date(toDate).getTime();
    }
    
    var timeFormat = function (msTime) {
        let time = new Date(msTime);
        let yy = time.getFullYear();
        let MM = time.getMonth() + 1;
        let dd = time.getDate();
        let hh = time.getHours() < 10 ? "0" + time.getHours() : time.getHours();
        let min =
            time.getMinutes() < 10 ? "0" + time.getMinutes() : time.getMinutes();
        let sec =
            time.getSeconds() < 10 ? "0" + time.getSeconds() : time.getSeconds();
        return yy + "-" + MM + "-" + dd + " " + hh + ":" + min + ":" + sec;
    }
    
    export {timeFormat,judgeDate}
    
  • requestUtil.js:
    功能:
    1. 定义公共的url
    2. 后端请求工具封装
    完整代码:

    // 同时发送异步代码的次数
    let ajaxTimes = 0;
    
    // 定义公共的url
    const baseUrl = "http://localhost:8866";
    
    /**
     * 返回baseUrl
     */
    export const getBaseUrl = () => {
      return baseUrl;
    }
    
    /**
     * 后端请求工具类
     * @param {*} params 请求参数
     */
    export const requestUtil = (params) => {
    
      let header = {
        ...params.header
      };
    
      // 拼接header 带上token
      header["token"] = uni.getStorageSync("token");
    
      ajaxTimes++;
    
      // 显示加载中 效果
      wx.showLoading({
        title: "加载中",
        mask: true
      });
    
      var start = new Date().getTime();
    
      // 模拟网络延迟加载
      while (true)
        if (new Date().getTime() - start > 1000 * 1) break;
    
      return new Promise((resolve, reject) => {
        wx.request({
          ...params,
          header: header,
          url: baseUrl + params.url,
          success: (result) => {
            resolve(result.data);
          },
          fail: (err) => {
            uni.showToast({
              icon: 'error',
              title: '连接服务器失败',
              duration: 3000
            })
            reject(err);
          },
          complete: () => {
            ajaxTimes--;
            if (ajaxTimes === 0) {
              //  关闭正在等待的图标
              wx.hideLoading();
            }
          }
        });
      })
    }
    
  • stringUtil.js:
    功能:
    1. 判断字符串是否为空
    完整代码:

    //判断字符串是否为空
    export const isEmpty = (str) => {
      if (str === '' || str.trim().length === 0) {
        return true
      } else {
        return false;
      }
    }
    

1.3、Hbuilderx构建uniapp项目

在这里插入图片描述
项目结构:
在这里插入图片描述

app.vue中,写两个方法:

  1. 在onLaunch生命周期函数中调用wx.login()获取code(前提是在微信开发者工具中登录微信账号,而且在uniapp中设置微信小程序AppId),code的作用是后端接受到code,通过code参数向微信后台发送请求,它是实现微信临时登录的url中的一个非常重要的参数。
  2. 三个重要参数
  • appid:应用ID
  • secret:应用密钥
  • js_code:前台传给我们的code
  1. wxlogin方法
    携带code参数发送请求给后端来获取token和openid
<script>
  import {
    requestUtil
  } from "./utils/requestUtil.js"
  export default {
    onLaunch: function() {
      console.log('App Launch')
      wx.login({
        timeout: 5000,
        success: (res) => {
          console.log(res)
          this.wxlogin(res.code);
        }
      });
    },
    onShow: function() {
      console.log('App Show')
    },
    onHide: function() {
      console.log('App Hide')
    },
    methods: {
      /**
       * 请求后端获取用户token
       * @param {} code 
       */
      async wxlogin(code) {
        console.log("code=" + code)
        // 发送请求 获取用户的token
        const result = await requestUtil({
          url: "/user/wxlogin",
          data: {
            code: code
          },
          method: "post"
        });
        console.log("token=" + result.token);
        console.log("openid=" + result.openid);
        if (result.code == 0) {
          console.log("登录成功")
          uni.setStorageSync("token", result.token);
          uni.setStorageSync("openid", result.openid);
        } else {
          console.log("登录失败,报错信息:" + result.msg);
          uni.showToast({
            icon: 'error',
            title: result.msg,
            duration: 3000
          })
        }
      }
    }
  }
</script>

<style>
  /*每个页面公共css */
</style>

2、后端技术栈

  • springboot后端技术框架
  • mybatis-plus数据持久层框架

2.1、创建springboot后端项目

利用idea工具,使用spring initializr初始化创建一个空的springboot项目

springboot版本选择2.3.2.RELEASE。

  1. 修改pom.xml
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>


        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>


        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.40</version>
        </dependency>

        <!-- JWT -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- spring boot redis 缓存引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- lettuce pool 缓存连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!-- hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.3</version>
        </dependency>

        <!-- 验证码依赖-->
        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>0.0.9</version>
        </dependency>

        <!-- 添加Httpclient支持 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.2.RELEASE</version>
            </plugin>
        </plugins>
    </build>
  1. 创建application.yml
server:
  port: 8866
  servlet:
    context-path: /

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db-wxlogin?serverTimezone=Asia/Shanghai
    username: root
    password: 123456


mybatis-plus:
  global-config:
    db-config:
      id-type: auto
  configuration:
    map-underscore-to-camel-case: true
    auto-mapping-behavior: full
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml


weixin:
  jscode2sessionUrl: https://api.weixin.qq.com/sns/jscode2session
  appid: wxa4aa78831ea93858  #修改自己的微信小程序 appId
  secret: a2efb3b611d96b2dee615b7a4dee451a   #修改自己的微信小程序 appSecret

2.2、数据库准备

创建名称db-wxlogin的数据库

创建t_wxuserinfo数据表
CREATE TABLE `t_wxuserinfo` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '用户编号',
  `openid` varchar(30) DEFAULT NULL COMMENT '用户唯一标识',
  `nick_name` varchar(50) DEFAULT NULL COMMENT '用户昵称',
  `avatar_url` varchar(200) DEFAULT NULL COMMENT '用户头像图片的 URL',
  `register_date` datetime DEFAULT NULL COMMENT '注册日期',
  `last_login_date` datetime DEFAULT NULL COMMENT '最后登录日期',
  `status` char(1) DEFAULT '1' COMMENT '状态 0 可用 1 封禁',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

2.3、创建实体类

  • WxUserInfo类
    对应数据库表t_wxuserinfo
package com.tigerhhzz.springbootvote.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;

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

/**
 * 微信用户信息实体
 *
 * @author tigerhhzz
 * @date 2023/5/17 15:34
 */
@TableName("t_wxUserInfo")
@Data
public class WxUserInfo implements Serializable {

    private Integer id; // 用户编号

    private String openid; // 用户唯一标识

    private String nickName="微信用户"; // 用户昵称

    private String avatarUrl="default.png"; // 用户头像图片的 URL

    @JsonSerialize(using=CustomDateTimeSerializer.class)
    private Date registerDate; // 注册日期

    @JsonSerialize(using=CustomDateTimeSerializer.class)
    private Date lastLoginDate; // 最后登录日期

    private String status="0"; // 用户状态 状态 0 可用 1 封禁

    //查询时,则不返回该字段的值    设置该字段在数据库表中不存在
    @TableField(select = false,exist = false)
    private String code; // 微信用户code 前端传来的


}

  • 页面响应实体类
package com.tigerhhzz.springbootvote.entity;

import java.util.HashMap;
import java.util.Map;


/**
 * 页面响应entity
 *
 * @author tigerhhzz
 * @date 2023/5/17 15:34
 */
public class R extends HashMap<String, Object> {

    private static final long serialVersionUID = 1L;

    public R() {
        put("code", 0);
    }

    public static R error() {
        return error(500, "未知异常,请联系管理员");
    }

    public static R error(String msg) {
        return error(500, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);
        return r;
    }

    public static R ok(Map<String, Object> map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok() {
        return new R();
    }

    @Override
    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}

  • jwt验证信息
package com.tigerhhzz.springbootvote.entity;

import io.jsonwebtoken.Claims;

/**
 * jwt验证信息
 *
 * @author tigerhhzz
 * @date 2023/5/17 17:05
 */
public class CheckResult {

    private int errCode;

    private boolean success;

    private Claims claims;

    public int getErrCode() {
        return errCode;
    }

    public void setErrCode(int errCode) {
        this.errCode = errCode;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public Claims getClaims() {
        return claims;
    }

    public void setClaims(Claims claims) {
        this.claims = claims;
    }

}


  • 微信小程序配置文件
package com.tigerhhzz.springbootvote.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 微信小程序配置文件
 *
 * @author tigerhhzz
 * @date 2023/5/17 16:55
 */
@Component
@ConfigurationProperties(prefix = "weixin")
@Data
public class WeixinProperties {

    private String jscode2sessionUrl; // 登录凭证校验请求地址

    private String appid; // 小程序 appId

    private String secret; // 小程序 appSecret


}

2.4、后端工具类

  • 日期工具类 DateUtil
package com.tigerhhzz.springbootvote.util;

import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * 日期工具类
 *
 * @author tigerhhzz
 * @date 2023/5/17 15:34
 */
public class DateUtil {

	/**
	 * 日期对象转字符串
	 * @param date
	 * @param format
	 * @return
	 */
	public static String formatDate(Date date,String format){
		String result="";
		SimpleDateFormat sdf=new SimpleDateFormat(format);
		if(date!=null){
			result=sdf.format(date);
		}
		return result;
	}
	
	/**
	 * 字符串转日期对象
	 * @param str
	 * @param format
	 * @return
	 * @throws Exception
	 */
	public static Date formatString(String str,String format) throws Exception{
		if(StringUtil.isEmpty(str)){
			return null;
		}
		SimpleDateFormat sdf=new SimpleDateFormat(format);
		return sdf.parse(str);
	}
	
	public static String getCurrentDateStr(){
		Date date=new Date();
		SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddhhmmssSSSSSSSSS");
		return sdf.format(date);
	}
	
	public static String getCurrentDatePath()throws Exception{
		Date date=new Date();
		SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd/");
		return sdf.format(date);
	}
	
	public static void main(String[] args) {
		try {
			System.out.println(getCurrentDateStr());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

  • httpClient 工具类
package com.tigerhhzz.springbootvote.util;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
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.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.util.PublicSuffixMatcher;
import org.apache.http.conn.util.PublicSuffixMatcherLoader;
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;
import org.springframework.stereotype.Component;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;



/**
 * httpClient 工具类
 *
 * @author tigerhhzz
 * @date 2023/5/17 15:34
 */
@Component
public class HttpClientUtil {
	
	/**
	 * 默认参数设置
	 * setConnectTimeout:设置连接超时时间,单位毫秒。
	 * setConnectionRequestTimeout:设置从connect Manager获取Connection 超时时间,单位毫秒。
	 * setSocketTimeout:请求获取数据的超时时间,单位毫秒。访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。 暂时定义15分钟
	 */
	private RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(600000).setConnectTimeout(600000).setConnectionRequestTimeout(600000).build();
	
	/**
	 * 静态内部类---作用:单例产生类的实例
	 * @author Administrator
	 *
	 */
	private static class LazyHolder {    
       private static final HttpClientUtil INSTANCE = new HttpClientUtil();    
       
    }  
	private HttpClientUtil(){}
	public static HttpClientUtil getInstance(){
		return LazyHolder.INSTANCE;    
	}
	
	/**
	 * 发送 post请求
	 * @param httpUrl 地址
	 */
	public String sendHttpPost(String httpUrl) {
		HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost  
		return sendHttpPost(httpPost);
	}
	
	/**
	 * 发送 post请求
	 * @param httpUrl 地址
	 * @param params 参数(格式:key1=value1&key2=value2)
	 */
	public String sendHttpPost(String httpUrl, String params) {
		HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost  
		try {
			//设置参数
			StringEntity stringEntity = new StringEntity(params, "UTF-8");
			stringEntity.setContentType("application/x-www-form-urlencoded");
			httpPost.setEntity(stringEntity);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return sendHttpPost(httpPost);
	}
	
	/**
	 * 发送 post请求
	 * @param httpUrl 地址
	 * @param maps 参数
	 */
	public String sendHttpPost(String httpUrl, Map<String, String> maps) {
		HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost  
		// 创建参数队列  
		List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
		for (String key : maps.keySet()) {
			nameValuePairs.add(new BasicNameValuePair(key, maps.get(key)));
		}
		try {
			httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8"));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return sendHttpPost(httpPost);
	}
	
	/**
	 * 发送Post请求
	 * @param httpPost
	 * @return
	 */
	private String sendHttpPost(HttpPost httpPost) {
		CloseableHttpClient httpClient = null;
		CloseableHttpResponse response = null;
		HttpEntity entity = null;
		String responseContent = null;
		try {
			// 创建默认的httpClient实例
			httpClient = HttpClients.createDefault();
			httpPost.setConfig(requestConfig);
			// 执行请求
			long execStart = System.currentTimeMillis();
			response = httpClient.execute(httpPost);
			long execEnd = System.currentTimeMillis();
			System.out.println("=================执行post请求耗时:"+(execEnd-execStart)+"ms");
			long getStart = System.currentTimeMillis();
			entity = response.getEntity();
			responseContent = EntityUtils.toString(entity, "UTF-8");
			long getEnd = System.currentTimeMillis();
			System.out.println("=================获取响应结果耗时:"+(getEnd-getStart)+"ms");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				// 关闭连接,释放资源
				if (response != null) {
					response.close();
				}
				if (httpClient != null) {
					httpClient.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return responseContent;
	}

	/**
	 * 发送 get请求
	 * @param httpUrl
	 */
	public String sendHttpGet(String httpUrl) {
		HttpGet httpGet = new HttpGet(httpUrl);// 创建get请求
		return sendHttpGet(httpGet);
	}
	
	/**
	 * 发送 get请求Https
	 * @param httpUrl
	 */
	public String sendHttpsGet(String httpUrl) {
		HttpGet httpGet = new HttpGet(httpUrl);// 创建get请求
		return sendHttpsGet(httpGet);
	}
	
	/**
	 * 发送Get请求
	 * @param httpGet
	 * @return
	 */
	private String sendHttpGet(HttpGet httpGet) {
		CloseableHttpClient httpClient = null;
		CloseableHttpResponse response = null;
		HttpEntity entity = null;
		String responseContent = null;
		try {
			// 创建默认的httpClient实例.

			
			httpClient = HttpClients.createDefault();

			httpGet.setConfig(requestConfig);
			// 执行请求
			response = httpClient.execute(httpGet);
			entity = response.getEntity();
			responseContent = EntityUtils.toString(entity, "UTF-8");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				// 关闭连接,释放资源
				if (response != null) {
					response.close();
				}
				if (httpClient != null) {
					httpClient.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return responseContent;
	}
	
	/**
	 * 发送Get请求Https
	 * @param httpGet
	 * @return
	 */
	private String sendHttpsGet(HttpGet httpGet) {
		CloseableHttpClient httpClient = null;
		CloseableHttpResponse response = null;
		HttpEntity entity = null;
		String responseContent = null;
		try {
			// 创建默认的httpClient实例.
			PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.load(new URL(httpGet.getURI().toString()));
			DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(publicSuffixMatcher);
			httpClient = HttpClients.custom().setSSLHostnameVerifier(hostnameVerifier).build();
			httpGet.setConfig(requestConfig);
			// 执行请求
			response = httpClient.execute(httpGet);
			entity = response.getEntity();
			responseContent = EntityUtils.toString(entity, "UTF-8");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				// 关闭连接,释放资源
				if (response != null) {
					response.close();
				}
				if (httpClient != null) {
					httpClient.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return responseContent;
	}

	/**
	 * 发送xml数据
	 * @param url
	 * @param xmlData
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static HttpResponse sendXMLDataByPost(String url, String xmlData)
			throws ClientProtocolException, IOException {
		HttpClient httpClient = HttpClients.createDefault();
		HttpPost httppost = new HttpPost(url);
		StringEntity entity = new StringEntity(xmlData);
		httppost.setEntity(entity);
		httppost.setHeader("Content-Type", "text/xml;charset=UTF-8");
		HttpResponse response = httpClient.execute(httppost);
		return response;
	}

	/**
	 * 获得响应HTTP实体内容
	 *
	 * @param response
	 * @return
	 * @throws IOException
	 * @throws UnsupportedEncodingException
	 */
	public static String getHttpEntityContent(HttpResponse response) throws IOException, UnsupportedEncodingException {
		HttpEntity entity = response.getEntity();
		if (entity != null) {
			InputStream is = entity.getContent();
			BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
			String line = br.readLine();
			StringBuilder sb = new StringBuilder();
			while (line != null) {
				sb.append(line + "\n");
				line = br.readLine();
			}
			return sb.toString();
		}
		return "";
	}


}

  • jwt加密和解密的工具类
package com.tigerhhzz.springbootvote.util;



import com.tigerhhzz.springbootvote.constant.JwtConstant;
import com.tigerhhzz.springbootvote.entity.CheckResult;
import io.jsonwebtoken.*;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;


/**
 * jwt加密和解密的工具类
 *
 * @author tigerhhzz
 * @date 2023/5/17 15:34
 */
public class JwtUtils {

    /**
     * 签发JWT
     * @param id
     * @param subject 可以是JSON数据 尽可能少
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, long ttlMillis) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        SecretKey secretKey = generalKey();
        JwtBuilder builder = Jwts.builder()
                .setId(id)
                .setSubject(subject)   // 主题
                .setIssuer("tigerhhzz")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            builder.setExpiration(expDate); // 过期时间
        }
        return builder.compact();
    }

    /**
     * 生成jwt token
     * @param username
     * @return
     */
    public static String genJwtToken(String username){
        return createJWT(username,username,60*60*1000);
    }

    /**
     * 验证JWT
     * @param jwtStr
     * @return
     */
    public static CheckResult validateJWT(String jwtStr) {
        CheckResult checkResult = new CheckResult();
        Claims claims = null;
        try {
            claims = parseJWT(jwtStr);
            checkResult.setSuccess(true);
            checkResult.setClaims(claims);
        } catch (ExpiredJwtException e) {
            checkResult.setErrCode(JwtConstant.JWT_ERRCODE_EXPIRE);
            checkResult.setSuccess(false);
        } catch (SignatureException e) {
            checkResult.setErrCode(JwtConstant.JWT_ERRCODE_FAIL);
            checkResult.setSuccess(false);
        } catch (Exception e) {
            checkResult.setErrCode(JwtConstant.JWT_ERRCODE_FAIL);
            checkResult.setSuccess(false);
        }
        return checkResult;
    }

    /**
     * 生成加密Key
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.decode(JwtConstant.JWT_SECERT);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }


    /**
     * 解析JWT字符串
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }

    public static void main(String[] args) throws InterruptedException {
        //小明失效 10s
        String sc = createJWT("1","小明", 60 * 60 * 1000);
        System.out.println(sc);
        System.out.println(validateJWT(sc).getErrCode());
        System.out.println(validateJWT(sc).getClaims().getId());
        System.out.println(validateJWT(sc).getClaims().getSubject());
        //Thread.sleep(3000);
        System.out.println(validateJWT(sc).getClaims());
        Claims claims = validateJWT(sc).getClaims();
        String sc2 = createJWT(claims.getId(),claims.getSubject(), JwtConstant.JWT_TTL);
        System.out.println(sc2);
    }

}

  • 字符串工具类
package com.tigerhhzz.springbootvote.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 字符串工具类
 * @author 
 *
 */
public class StringUtil {

	/**
	 * 判断是否是空
	 * @param str
	 * @return
	 */
	public static boolean isEmpty(String str){
		if(str==null||"".equals(str.trim())){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	 * 判断是否不是空
	 * @param str
	 * @return
	 */
	public static boolean isNotEmpty(String str){
		if((str!=null)&&!"".equals(str.trim())){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	 * 格式化模糊查询
	 * @param str
	 * @return
	 */
	public static String formatLike(String str){
		if(isNotEmpty(str)){
			return "%"+str+"%";
		}else{
			return null;
		}
	}
	
	/**
	 * 过滤掉集合里的空格
	 * @param list
	 * @return
	 */
	public static List<String> filterWhite(List<String> list){
		List<String> resultList=new ArrayList<String>();
		for(String l:list){
			if(isNotEmpty(l)){
				resultList.add(l);
			}
		}
		return resultList;
	}
	
	/**
	 * 去除html标签
	 */
	public static String stripHtml(String content) { 
	    // <p>段落替换为换行 
	    content = content.replaceAll("<p .*?>", "\r\n"); 
	    // <br><br/>替换为换行 
	    content = content.replaceAll("<br\\s*/?>", "\r\n"); 
	    // 去掉其它的<>之间的东西 
	    content = content.replaceAll("\\<.*?>", ""); 
	    // 去掉空格 
	    content = content.replaceAll(" ", ""); 
	    return content;   
	}
	
	/**
	 * 生成六位随机数
	 * @return
	 */
	public static String genSixRandomNum(){
		Random random = new Random();
		String result="";
		for (int i=0;i<6;i++)
		{
			result+=random.nextInt(10);
		}
		return result;
	}

	/**
	 * 生成由[A-Z,0-9]生成的随机字符串
	 * @param length  欲生成的字符串长度
	 * @return
	 */
	public static String getRandomString(int length){
		Random random = new Random();

		StringBuffer sb = new StringBuffer();

		for(int i = 0; i < length; ++i){
			int number = random.nextInt(2);
			long result = 0;

			switch(number){
				case 0:
					result = Math.round(Math.random() * 25 + 65);
					sb.append(String.valueOf((char)result));
					break;
				case 1:

					sb.append(String.valueOf(new Random().nextInt(10)));
					break;
			}
		}
		return sb.toString();
	}


}

2.5、mapper和service接口

微信用户mapper —WxUserInfoMapper

package com.tigerhhzz.springbootvote.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tigerhhzz.springbootvote.entity.WxUserInfo;
import org.springframework.stereotype.Repository;

/**
 * 微信用户mapper
 *
 * @author tigerhhzz
 * @date 2023/5/17 15:26
 */
@Repository
public interface WxUserInfoMapper extends BaseMapper<WxUserInfo> {

}

微信用户Service接口 —WxUserInfoService

package com.tigerhhzz.springbootvote.service;


import com.baomidou.mybatisplus.extension.service.IService;
import com.tigerhhzz.springbootvote.entity.WxUserInfo;


/**
 * 微信用户Service接口
 *
 * @author tigerhhzz
 * @date 2023/5/17 15:34
 */
public interface WxUserInfoService extends IService<WxUserInfo> {
}

2.5、Service实现类

WxUserInfoServiceImpl---- 实现WxUserInfoService 接口,并继承ServiceImpl实现类的泛型WxUserInfoMapper和WxUserInfo。

package com.tigerhhzz.springbootvote.service.impl;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tigerhhzz.springbootvote.entity.WxUserInfo;
import com.tigerhhzz.springbootvote.mapper.WxUserInfoMapper;
import com.tigerhhzz.springbootvote.service.WxUserInfoService;

import org.springframework.stereotype.Service;


/**
 * 微信用户Service实现类
 *
 * @author tigerhhzz
 * @date 2023/5/17 15:34
 */
@Service //("wxUserInfoService")
public class WxUserInfoServiceImpl extends ServiceImpl<WxUserInfoMapper, WxUserInfo> implements WxUserInfoService {

    //@Autowired
    //private WxUserInfoMapper wxUserInfoMapper;
}

2.6、微信用户的控制层Controller

两个控制层请求接口

  1. /user/wxlogin

功能: 通过前端发送请求携带的参数code以及后端配置文件中的微信小程序appid和微信小程序密钥,后端拼接url向微信后台发送请求。

		 String jscode2sessionUrl=weixinProperties.getJscode2sessionUrl()+"?appid="+weixinProperties.getAppid()+"&secret="+weixinProperties.getSecret()+"&js_code="+wxUserInfo.getCode()+"&grant_type=authorization_code";
		// https://api.weixin.qq.com/sns/jscode2session?appid=wxa4de78832ea93858&secret=a2efb3b602d96b2dee615b7a4dee451a&js_code=0b1JwPkl2xqHkb4VEjml2vVdua3JwPkq&grant_type=authorization_code
        //后端向微信后台送发请求 获取openid
        String result = httpClientUtil.sendHttpGet(jscode2sessionUrl);
        System.out.println(result); 
        //结果:{"session_key":"TPTXzC9MOe1owBJ8zrSWTw==","openid":"o2yqx5PBEW-ezFHA24ASqP0Lk1M0"}

通过拿到的openid,去数据库查询对应用户信息,如果没有openid的用户,进行新增操作;
如果存在openid的用户,进行更新操作。

最后利用jwt工具类生成token,返回前端

 // 利用jwt生成token返回到前端
  String token = JwtUtils.createJWT(openid, wxUserInfo.getNickName(), JwtConstant.JWT_TTL);
  Map<String,Object> resultMap=new HashMap<>();
  resultMap.put("token",token);
  resultMap.put("openid",openid);
  return R.ok(resultMap);
  1. /user/getUserInfo

功能: 通过前端发送请求,请求头中携带token参数,后端接受到token,然后进行token验证,拿到openid,通过openid去数据库中查询用户信息,并返回前端

//token验证
Claims claims = JwtUtils.validateJWT(token).getClaims();

获取当前微信登录用户信息:
在这里插入图片描述


WeixinUserController 完整代码:

package com.tigerhhzz.springbootvote.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.tigerhhzz.springbootvote.constant.JwtConstant;
import com.tigerhhzz.springbootvote.entity.R;
import com.tigerhhzz.springbootvote.entity.WxUserInfo;
import com.tigerhhzz.springbootvote.properties.WeixinProperties;
import com.tigerhhzz.springbootvote.service.WxUserInfoService;
import com.tigerhhzz.springbootvote.util.DateUtil;
import com.tigerhhzz.springbootvote.util.HttpClientUtil;
import com.tigerhhzz.springbootvote.util.JwtUtils;
import com.tigerhhzz.springbootvote.util.StringUtil;
import io.jsonwebtoken.Claims;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 微信用户Controller
 *
 * @author tigerhhzz
 * @date 2023/5/17 15:34
 */
@RequestMapping("/user")
@RestController
public class WeixinUserController {

    @Autowired
    private WxUserInfoService wxUserInfoService;

    @Autowired
    private WeixinProperties weixinProperties;

    @Autowired
    private HttpClientUtil httpClientUtil;


    /**
     * 微信用户登录
     * @return
     */
    @RequestMapping("/wxlogin")
    public R wxLogin(@RequestBody WxUserInfo wxUserInfo){
        //拼接后端发送请求的URL 例如 https://api.weixin.qq.com/sns/jscode2session?appid=wxa4de78832ea93858&secret=a2efb3b602d96b2dee615b7a4dee451a&js_code=0b1JwPkl2xqHkb4VEjml2vVdua3JwPkq&grant_type=authorization_code
        String jscode2sessionUrl=weixinProperties.getJscode2sessionUrl()+"?appid="+weixinProperties.getAppid()+"&secret="+weixinProperties.getSecret()+"&js_code="+wxUserInfo.getCode()+"&grant_type=authorization_code";
        System.out.println(jscode2sessionUrl);

        //后端向微信后台送发请求 获取openid
        String result = httpClientUtil.sendHttpGet(jscode2sessionUrl);
        System.out.println(result); //结果:{"session_key":"TPTXzC9MOe1owBJ8zrSWTw==","openid":"o2yqx5PBEW-ezFHA24ASqP0Lk1M0"}

        //
        JSONObject jsonObject= JSON.parseObject(result);//转换成object
        String openid = jsonObject.get("openid").toString();//获取object中openid字段的值;
        System.out.println(openid);


        // 插入用户到数据库  假如说 用户不存在 我们插入用户  如果用户存在 我们更新用户
        WxUserInfo resultWxUserInfo = wxUserInfoService.getOne(new QueryWrapper<WxUserInfo>().eq("openid", openid));
        if(resultWxUserInfo==null){
            System.out.println("不存在 插入用户");
            wxUserInfo.setOpenid(openid);
            wxUserInfo.setRegisterDate(new Date());
            wxUserInfo.setLastLoginDate(new Date());
            wxUserInfoService.save(wxUserInfo);
        }else{
            System.out.println("存在 更新用户");
            // resultWxUserInfo.setNickName(wxUserInfo.getNickName());
            // resultWxUserInfo.setAvatarUrl(wxUserInfo.getAvatarUrl());
            resultWxUserInfo.setLastLoginDate(new Date());
            wxUserInfoService.updateById(resultWxUserInfo);
        }
        if(resultWxUserInfo!=null && resultWxUserInfo.getStatus().equals("1")){ // 被禁用
            return R.error(400,"用户被禁用,具体请联系管理员!");
        }else{
            // 利用jwt生成token返回到前端
            String token = JwtUtils.createJWT(openid, wxUserInfo.getNickName(), JwtConstant.JWT_TTL);
            Map<String,Object> resultMap=new HashMap<>();
            resultMap.put("token",token);
            resultMap.put("openid",openid);
            return R.ok(resultMap);
        }
    }

    /**
     * 获取当前用户信息
     * @return
     */
    @RequestMapping("/getUserInfo")
    public R getUserInfo(@RequestHeader String token){
        System.out.println("/getUserInfo----token="+token);
        Claims claims = JwtUtils.validateJWT(token).getClaims();
        System.out.println("openid="+claims.getId());
        WxUserInfo currentUser = wxUserInfoService.getOne(new QueryWrapper<WxUserInfo>().eq("openid", claims.getId()));
        Map<String,Object> map=new HashMap<>();
        map.put("currentUser",currentUser);
        return R.ok(map);
    }


}

前端源码下载地址 https://download.csdn.net/download/weixin_43025151/87803315

后端源码下载地址:https://download.csdn.net/download/weixin_43025151/87803318

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/547788.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎么将png转jpg在线处理?图片转换格式工具分享

做设计的小伙伴经常需要进行图片格式转换&#xff0c;但是下载安装软件比较耗费时间&#xff0c;操作起来也很不方便&#xff0c;今天就来介绍一个在线格式转换的方法&#xff0c;下面以png转jpg在线&#xff08;https://www.yasuotu.com/geshi&#xff09;处理为例子给大家演示…

基本的排序算法

一、插入排序 基本思想&#xff1a;在待排序的元素中&#xff0c;假设前面n-1(其中n>2)个数已经是排好顺序的&#xff0c;现将第n个数插到前面已经排好的序列中&#xff0c;然后找到合适自己的位置&#xff0c;使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素…

JVM内存模型概述

当谈到Java虚拟机&#xff08;JVM&#xff09;的内存模型时&#xff0c;我们需要了解JVM如何管理内存并执行Java应用程序。理解JVM的内存模型对于编写高效、可靠的Java代码至关重要。本文将介绍JVM的内存模型&#xff0c;并通过图示进行说明。 JVM内存模型概述 JVM内存模型定义…

5年自动化测试经验的一些感悟—愿测试进阶路上的你不在迷茫

作为一个测试人&#xff0c;我们或多或少都听过或用过自动化&#xff0c;我们都曾在初入测试行业时&#xff0c;满怀期待的以为测试的尽头是不用测试员点点了&#xff0c;项目一提测&#xff0c;小手点下自动化的开关&#xff0c;瞬间测试的工作就完成了。 这就是我一开始从开…

Python-增加b站视频播放量

前言 本文是该专栏的第1篇&#xff0c;后面会持续更新对应平台干货知识&#xff0c;记得关注。 需求背景和说明&#xff1a;实现获取真实播放量&#xff0c;以及增加播放量 废话不多说&#xff0c;跟着笔者直接往下看正文&#xff0c;在文中将结合代码进行详细说明。&#xf…

【数据结构】从数据结构角度深入探究队列

队列是计算机科学中的一种基本数据结构&#xff0c;用于存储和管理数据。在计算机程序中&#xff0c;队列被广泛应用于任务调度、进程管理等场景。本文将介绍队列的概念、特点、常见操作以及应用。 文章目录 队列的概念队列的应用队列的存储结构队列接口的实现队列的初始化队尾…

plt.plot(x,y,color,linestyle,marker)函数参数详解

一、plt.plot()函数详解 plt.plot()函数是matplotlib库中用于绘制线条图的函数&#xff0c;它有多个参数可以控制绘图的各个方面。以下是常用的一些参数及其作用&#xff1a; x: x轴数据的列表或数组y: y轴数据的列表或数组linewidth: 线条的宽度&#xff0c;从0到无穷大的浮…

( 动态规划) 115. 不同的子序列 ——【Leetcode每日一题】

❓115. 不同的子序列 难度&#xff1a;困难 给你两个字符串 s 和 t &#xff0c;统计并返回在 s 的 子序列 中 t 出现的个数。 题目数据保证答案符合 32 位带符号整数范围。 示例 1&#xff1a; 输入&#xff1a;s “rabbbit”, t “rabbit” 输出&#xff1a;3 解释&…

智能变电站远程监控解决方案

智能变电站远程监控解决方案 一、项目背景 变电站是改变电压、控制和分配电能的场所&#xff0c;为了加强对变电站的监管力度&#xff0c;抓好供电作业的动态管理&#xff0c;及时、实时的掌握变电站的安全隐患的整改消除情况&#xff0c;确保安全生产贯穿于供电的全过程&…

如何禁用烦人的“insert”键

在我们日常的电脑使用中&#xff0c;有些按键可能会让我们感到非常烦恼&#xff0c;其中最常见的就是“insert”键。这个键位于键盘的右上角&#xff0c;通常会在我们不经意间被按下&#xff0c;导致我们的输入出现了一些奇怪的问题。如果你也曾经遇到过这个问题&#xff0c;那…

在网络安全领域中,主要有哪些技术方向?

入门Web安全、安卓安全、二进制安全、工控安全还是智能硬件安全等等&#xff0c;每个不同的领域要掌握的技能也不同。 当然入门Web安全相对难度较低&#xff0c;也是很多人的首选。主要还是看自己的兴趣方向吧。 本文就以下几个问题来说明网络安全大致学习过程&#x1f447; 网…

Vue3项目初始配置(更新中ing)

文章目录 别名路径联想配置方法 element plus按需引入并使用风格定制 eslint文件名不强制检测编辑.eslintrc.cjs 别名路径联想 在编写代码的过程中&#xff0c;一旦 输入 / , VSCode会立刻 联想出src下的所有子目录和文件, 统一文件路径访问不容易出 配置方法 在项目的根目…

相亲交友App开发解决方案及功能框架

相亲APP开发功能 1、注册登录&#xff1a;相亲APP开发平台是一款真实的交友软件&#xff0c;所以需要用户提交身份信息进行注册认证&#xff0c;也是为了保障用户的安全! 2、搜索功能&#xff1a;在线、新注册、距离近、同城老乡、星族速配等&#xff0c;都可以在相亲APP内进行…

详解GitHub

详解GitHub 什么是GitHub&#xff1f;GitHub能干什么&#xff1f;1.托管代码、历史版本管理2.搜索开源项目3.分享的同时会得到社区的回馈4.使用Github Pages服务&#xff0c;你可以免费搭建一个博客网站5.学习&#xff0c;能力提升6.提升自己的影响力 GitHub和Git的关系与区别敲…

MySQL基础(三十八)数据库备份与恢复

1 物理备份与逻辑备份 物理备份&#xff1a;备份数据文件&#xff0c;转储数据库物理文件到某一目录。物理备份恢复速度比较快&#xff0c;但占用空间比较大&#xff0c;MySQL中可以用 xtrabackup 工具来进行物理备份。 逻辑备份&#xff1a;对数据库对象利用工具进行导出工作…

Android如何自定义输入文本对话框?

文章目录 0.引言1.创建示例工程2.输入文本对话框布局和功能设计3.主程序调用输入文本对话框 0.引言 笔者研究的课题涉及到安卓软件开发&#xff0c;在开发过程中&#xff0c;发现普通的显示消息对话框一般可以调用android自带包实现&#xff0c;而要通过文本框输入交互&#xf…

代码随想录算法训练营第十一天|20. 有效的括号、1047. 删除字符串中的所有相邻重复项、150. 逆波兰表达式求值

今天的题目都是对栈的经典应用。 有效的括号 题目链接&#xff1a;力扣 解题思路&#xff1a;基于栈结构的特殊性&#xff0c;其非常适合做对称匹配类问题&#xff0c;其实如果知道了要用栈解这道题&#xff0c;在脑中模拟一遍&#xff0c;接下来的思路就是自然而然能够想到…

STM32 多路ADC同时扫描采样

背景 在项目实际应用中&#xff0c;刚好有需求需要使用多路ADC同时采样&#xff0c;这里就选择STM32 ADC多路ADC同时采样&#xff0c;这里简单说明下配置过程&#xff0c;以及使用步骤 原理图 如下图所示&#xff0c;使用四路ADC输入 ADC_Voltage -> 电压信号的采样&…

如何查看linux分区挂载在哪个目录?

一&#xff0c;简介 在linux系统中&#xff0c;如何查看磁盘分区是挂载在哪个目录呢&#xff1f;今天介绍一种方法&#xff0c;供参考。 二&#xff0c;图形化界面查看分区挂载方法 2.1 打开disk工具 2.2 点击查看对应的分区 看一个分区&#xff1a; 如上图所示&#xff0…

百度地图网页设计

一、百度地图api 1、百度搜索——百度地图API 进入——开放平台——开发文档——JavaScript API JavaScript API 首先是GL版本&#xff0c;是三维的效果&#xff0c;我们一般使用二维&#xff0c;选择下面v3.0版本 2、开发指南——注册账号 跟着提示来申请密钥就可。 二、…