公众号自定义登录
前言
- 由于微信官方是无法与我们自己的登录表集成
- 此篇文章带你写微信公众号根据自己的表进行设计登录系统
- 项目技术:springboot + mybatisplus
- 注意:本篇只是基于微信公众号自定义登录逻辑,如果微信公众号还没有集成的话请先集成微信公众号,本项目采用的是java api的形式集成的哦
首先创建表、实体类、以及mapper层
表
CREATE TABLE `t_sys_user_token` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`open_id` varchar(100) NOT NULL COMMENT '微信用户openid',
`user_id` varchar(500) DEFAULT NULL COMMENT '用户ID',
`user_name` varchar(500) DEFAULT NULL COMMENT '用户名',
`full_name` varchar(500) DEFAULT NULL COMMENT '用户名称',
`token` varchar(100) NOT NULL COMMENT 'token',
`type` varchar(10) DEFAULT NULL COMMENT '用户类型',
`expire_time` datetime DEFAULT NULL COMMENT '过期时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `token` (`token`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统用户Token';
实体类
package com.cnpc.wechat.entity;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author wuzhenyong
* ClassName:SysUserTokenEntity.java
* date:2022-06-29 8:30
* Description:
*/
@ApiModel(value = "系统用户Token", description = "系统用户Token")
@Data
@TableName("t_sys_user_token")
public class SysUserTokenEntity {
@ApiModelProperty(value = "主键", name = "id", example = "token")
@TableId(type = IdType.INPUT)
private Long id;
/**
* 微信用户openId
*/
@ApiModelProperty(value = "微信用户openId", name = "openId", example = "token")
private String openId;
/**
* 用户ID
*/
@ApiModelProperty(value = "用户ID", name = "userId", example = "token")
private String userId;
/**
* 用户名
*/
@ApiModelProperty(value = "用户名", name = "userName", example = "token")
private String userName;
/**
* 用户名称
*/
@ApiModelProperty(value = "用户名称", name = "fullName", example = "token")
private String fullName;
@ApiModelProperty(value = "token凭证", name = "token", example = "token")
private String token;
/**
* 用户类型
*/
@ApiModelProperty(value = "用户类型", name = "type", example = "token")
private String type;
/**
* 过期时间
*/
@ApiModelProperty(value = "token过期时间", name = "expireTime", example = "token")
private LocalDateTime expireTime;
/**
* 更新时间
*/
@ApiModelProperty(value = "token更新时间", name = "updateTime", example = "token")
private LocalDateTime updateTime;
}
持久层就自己创建吧
调用微信登录方法
private final WxMpService wxService;
@Autowired
private WxUserTokenService wxUserTokenService;
@Autowired
private WxAccountFansService wxAccountFansService;
@GetMapping("/login")
@ApiOperation("微信登录方法")
public R login(@PathVariable String appid, @RequestParam String code) {
if (!this.wxService.switchover(appid)) {
throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid));
}
try {
WxMpOAuth2AccessToken accessToken = wxService.oauth2getAccessToken(code);
WxMpUser wxMpUser = wxService.oauth2getUserInfo(accessToken, null);
String type = "1";
//生成用户token并且返回前端 此方法逻辑自定义
SysUserTokenEntity tokenEntity = wxUserTokenService.createToken(wxMpUser.getOpenId(), type);
return R.ok()
.put("cnpc-wx-token", tokenEntity.getToken())
.put("expire", tokenEntity.getExpireTime().getTime() - System.currentTimeMillis());
} catch (WxErrorException e) {
e.printStackTrace();
return R.error(50000, e.getMessage());
}
}
创建微信请求token验证类
代码说明:
- request.getHeader(“cnpc-wx-token”); cnpc-wx-token是前端传入的header值,也就是说我们调用微信官方登录接口自己生成的一个token
- 生成token完成后保存到我们自己创建的表
- 然后再根据token查询我们自定义的表获取用户openId及其他信息(根据需求自定义)
package com.cnpc.wechat.interceptor;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.cnpc.common.exception.CnpcException;
import com.cnpc.wechat.entity.SysUserTokenEntity;
import com.cnpc.wechat.service.SysUserTokenService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
/**
* @author wuzhenyong
* ClassName:ServiceAuthorizationInterceptor.java
* date:2022-06-29 8:24
* Description: 微信客服中心相关token验证
*/
@Component
public class ServiceAuthorizationInterceptor extends HandlerInterceptorAdapter {
@Autowired
private SysUserTokenService sysUserTokenService;
public static final String USER_KEY = "sysUserOpenId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//从header中获取token
String token = request.getHeader("cnpc-wx-token");
//如果header中不存在token,则从参数中获取token
if (StringUtils.isBlank(token)) {
token = request.getParameter("cnpc-wx-token");
}
//token为空
if (StringUtils.isBlank(token)) {
throw new CnpcException("token不能为空", 401);
}
//查询token信息
SysUserTokenEntity sysUserTokenEntity = sysUserTokenService.selectOne(new EntityWrapper<SysUserTokenEntity>().eq("token", token));
//正常token
if (sysUserTokenEntity == null || sysUserTokenEntity.getExpireTime().isBefore(LocalDateTime.now())) {
throw new CnpcException("token失效,请重新登录", 401);
}
//设置userId到request里,后续根据openId,获取用户信息
request.setAttribute(USER_KEY, sysUserTokenEntity.getOpenId());
return true;
}
}
创建微信方法注解
import java.lang.annotation.*;
/**
* @author wuzhenyong
* ClassName:SysUserLogin.java
* date:2022-06-29 8:33
* Description: 系统用户登录注解
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysUserLogin {
}
创建微信方法注解实体解析
- 此解析类是为了解析微信请求方法中加入SysUserLogin注解的实体类进行解析,是为了让注解上的实体类赋值,查询我们数据库中的数据进行赋值
package com.cnpc.wechat.resolver;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.cnpc.wechat.annotation.SysUserLogin;
import com.cnpc.wechat.entity.SysUserTokenEntity;
import com.cnpc.wechat.interceptor.ServiceAuthorizationInterceptor;
import com.cnpc.wechat.service.SysUserTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* @author wuzhenyong
* ClassName:SysLoginUserHandlerMethodArgumentResolver.java
* date:2022-06-29 8:45
* Description: 系统用户方法注解实体解析
*/
@Component
public class SysLoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private SysUserTokenService sysUserTokenService;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(SysUserTokenEntity.class) && parameter.hasParameterAnnotation(SysUserLogin.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
NativeWebRequest request, WebDataBinderFactory factory) throws Exception {
//获取用户ID
Object object = request.getAttribute(ServiceAuthorizationInterceptor.USER_KEY, RequestAttributes.SCOPE_REQUEST);
if (object == null) {
return null;
}
//获取用户信息
SysUserTokenEntity sysUserTokenEntity = sysUserTokenService.selectOne(new EntityWrapper<SysUserTokenEntity>()
.eq("open_id", object)
.orderBy("expire_time", Boolean.FALSE)
.last("limit 1"));
return sysUserTokenEntity;
}
}
WebMvc配置类 配置微信请求拦截
- SysLoginUserHandlerMethodArgumentResolver 就是我们自定义的注解解析器
- /service/wx/** 此路径就是本项目定义的路径
- registry.addInterceptor(serviceAuthorizationInterceptor()).addPathPatterns(“/service/wx/**”); 添加我们自定义的token拦截校验
/**
* WebMvc配置
* @author YangMQ
*/
@Configuration
public class WebAppConfig implements WebMvcConfigurer {
private final Logger logger = LoggerFactory.getLogger(WebAppConfig.class);
@Autowired
private SysLoginUserHandlerMethodArgumentResolver sysLoginUserHandlerMethodArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(sysLoginUserHandlerMethodArgumentResolver);
}
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
//文件最大KB,MB
factory.setMaxFileSize("1024MB");
//设置总上传数据总大小
factory.setMaxRequestSize("1024MB");
return factory.createMultipartConfig();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");
// TODO Auto-generated method stub
WebMvcConfigurer.super.addResourceHandlers(registry);
}
/**
* 解决跨域问题
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/service/wx/**");
}
/**
* 统一异常处理
* @param exceptionResolvers
*/
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add(new HandlerExceptionResolver() {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
R r = new R();
//业务失败的异常,如“账号或密码错误”
if (e instanceof CnpcException) {
r = R.error(((CnpcException) e).getCode(), e.getMessage());
logger.error(e.getMessage());
} else if (e instanceof NoHandlerFoundException) {
r = R.error(ResultCode.NOT_FOUND.getCode(), "接口 [" + request.getRequestURI() + "] 不存在");
} else if (e instanceof ServletException) {
r = R.error(ResultCode.FAILURE.getCode(), e.getMessage());
} else {
r = R.error(ResultCode.INTERNAL_SERVER_ERROR.getCode(), "接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
e.getMessage());
} else {
message = e.getMessage();
}
logger.error(message, e);
}
responseResult(response, r);
return new ModelAndView();
}
});
}
/**
* 设置微信请求拦截路径
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(serviceAuthorizationInterceptor()).addPathPatterns("/service/wx/**");
}
/**
* 系统用户请求登录拦截 我们自定义的拦截器
*
* @return
*/
@Bean
public ServiceAuthorizationInterceptor serviceAuthorizationInterceptor() {
return new ServiceAuthorizationInterceptor();
}
private void responseResult(HttpServletResponse response, R r) {
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT");
response.setHeader("Access-Control-Max-Age", "3628800");
response.setStatus(200);
try {
response.getWriter().write(JSON.toJSONString(r));
} catch (IOException ex) {
logger.error(ex.getMessage());
}
}
}
登录方法
- 自己自定义就可以,账号密码,然后进行数据库查验
- 以上写的拦截器如果没有登录时可以拦截的
- 因为我们在拦截器中是判断了请求头中没有拦截器是无法请求的
自定义的登录
开发微信请求方法(项目业务代码)
@SysUserLogin SysUserTokenEntity sysUserTokenEntity 实体类就已经有我们在拦截器查询数据库赋值后的信息了哦
//类路径@RequestMapping("/service/wx/workOrderService")
@RequestMapping("captcha.jpg")
public voidcaptcha(@SysUserLogin SysUserTokenEntity sysUserTokenEntity) throws IOException {
System.out.println(sysUserTokenEntity);
// 以下逻辑自定义
}