前言
- 由于微信官方是无法与我们自己的登录表集成
- 此篇文章带你写微信公众号根据自己的表进行设计登录系统
- 项目技术: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; |
| |
| |
| |
| |
| |
| |
| |
| @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; |
| |
| |
| |
| @ApiModelProperty(value = "微信用户openId", name = "openId", example = "token") |
| private String openId; |
| |
| |
| |
| |
| @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"; |
| |
| 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; |
| |
| |
| |
| |
| |
| |
| |
| @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) { |
| |
| String token = request.getHeader("cnpc-wx-token"); |
| |
| if (StringUtils.isBlank(token)) { |
| token = request.getParameter("cnpc-wx-token"); |
| } |
| |
| |
| if (StringUtils.isBlank(token)) { |
| throw new CnpcException("token不能为空", 401); |
| } |
| |
| SysUserTokenEntity sysUserTokenEntity = sysUserTokenService.selectOne(new EntityWrapper<SysUserTokenEntity>().eq("token", token)); |
| |
| if (sysUserTokenEntity == null || sysUserTokenEntity.getExpireTime().isBefore(LocalDateTime.now())) { |
| throw new CnpcException("token失效,请重新登录", 401); |
| } |
| |
| |
| request.setAttribute(USER_KEY, sysUserTokenEntity.getOpenId()); |
| return true; |
| } |
| } |
| |
创建微信方法注解
| import java.lang.annotation.*; |
| |
| |
| |
| |
| |
| |
| |
| @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; |
| |
| |
| |
| |
| |
| |
| |
| |
| @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 { |
| |
| 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拦截校验
| |
| |
| |
| |
| @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(); |
| |
| factory.setMaxFileSize("1024MB"); |
| |
| factory.setMaxRequestSize("1024MB"); |
| return factory.createMultipartConfig(); |
| } |
| |
| @Override |
| public void addResourceHandlers(ResourceHandlerRegistry registry) { |
| |
| registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/"); |
| |
| |
| WebMvcConfigurer.super.addResourceHandlers(registry); |
| |
| } |
| |
| |
| |
| |
| |
| @Override |
| public void addCorsMappings(CorsRegistry registry) { |
| registry.addMapping("/service/wx/**"); |
| } |
| |
| |
| |
| |
| |
| @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(); |
| } |
| }); |
| } |
| |
| |
| |
| |
| |
| |
| @Override |
| public void addInterceptors(InterceptorRegistry registry) { |
| registry.addInterceptor(serviceAuthorizationInterceptor()).addPathPatterns("/service/wx/**"); |
| } |
| |
| |
| |
| |
| |
| |
| @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("captcha.jpg") |
| public voidcaptcha(@SysUserLogin SysUserTokenEntity sysUserTokenEntity) throws IOException { |
| System.out.println(sysUserTokenEntity); |
| |
| } |
以上就完成啦!光看没有用,重要的还是实践啊
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具