公众号对接获取openid全历程
- 一、背景
- 二、选型
- 三、开始修改若依框架
- 四、自己搭后端框架
- 五、前端框架uni-app修改
- 六、对接获取公众号登录用户openId
- 七、总结
一、背景
老板接了朋友的一个公众号需求,要求做一个简单的疫苗预约系统。功能是获取当前登录用户,记录用户选择的疫苗,针次,时间,并且用户可以查看自己预约的记录。我刚接到这个需求感觉也简单,给的时间呢也只有一周,包括程序的开发,服务的部署,公众号相关信息的配置。虽然是个很小的功能,没想到期间碰到过很多坑,公众号也是我第一次全流程的做,记录一下这期间的坎坷。
二、选型
一般接到这种小功能的开发,我们一般会在开源框架市场找几个相似的项目然后修修改改,达到甲方想要的使用预期。前三天基本都是浪费在选型这一块了,本来也想着,选好开源项目,改起来也快,最后敲定选用若依uni-app这款,可没想到,最后改了半天,最后忍无可忍,自己手写了一套后端,前端代码,框架当然用的还是springboot+uni-app。。。
三、开始修改若依框架
若依这套框架也是典型的租户型框架,uniapp启动后是一个登录界面,而公众号要做默认登录,是不要这个登录界面的,那怎么跳过这个登录界面,直接登录进去呢,一开始想的是默认获取用户openid直接授权进去,这就要对应的修改后端生成token的方式,然后每一个页面访问的header还要携带这个token。
除此之外,登录进去后才展示的业务界面还需要画一下,那就先把页面画出来,一共做了四个页面,用户首页(展示预约疫苗入口和用户预约记录),用户信息维护(点击预约疫苗后先维护自己的信息,包括姓名,身份证号,手机号),预约界面(三个相互隐藏的栏位,疫苗栏位,针次栏位,时间栏位),疫苗说明界面。这里说一下微信公众号静默获取的信息只能获取到openId,unionid应该也可以获取,我没试过。获取openId的过程后面会说明。这里让用户自己维护手机号和身份证号是因为可能会给长辈预约疫苗。
页面画完之后,数据都是用的前端代码模拟的值,流程无碍之后,准备后端添加接口,业务表是甲方直接提供,使用的是sqlServer数据库,若依用的数据库版本是mysql,最后又找了一下,找到了若依sqlserver版本的后端框架,发现没法改,主要还是认证这一个部分,如果要改造认证这一部分,又要花费很多工作时间,改造过程中可能还会产生新的问题或者bug都是没法预估的,不是说若依不好用,只能说没必要用这种shiro认证框架,太冗余了。
四、自己搭后端框架
不需要认证,获取到当前登录用户的openID进行数据隔离和查询也可以保证数据的安全性。
-
新建一个springboot项目
-
删除多余的文件夹
-
修改pom.xml的一些配置和添加我所需要的maven依赖,sqlSerer驱动,mybatisplus,springboot启动的依赖自带的,fastjson,druid数据池,多数据源,lombok,当然hutool也要
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- 阿里JSON解析器 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.4</version> </dependency> <!-- 动态数据源 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.2</version> </dependency> <!-- SqlServer 数据库连接包 --> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>sqljdbc4</artifactId> <version>4.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-core</artifactId> <version>3.4.1</version> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.18</version> </dependency> </dependencies>
-
感觉又回到了刚学spring框架的那个时候了,做项目基本用开源框架或者公司自己的产品框架开发,很少自己搭spring项目了。
-
然后修改pom.xml的打包配置选项
<build> <finalName>yuyue</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includeSystemScope>true</includeSystemScope> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> </configuration> </execution> </executions> </plugin> <!--指定JDK编译版本 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <nonFilteredFileExtensions> <nonFilteredFileExtension>woff</nonFilteredFileExtension> <nonFilteredFileExtension>woff2</nonFilteredFileExtension> <nonFilteredFileExtension>eot</nonFilteredFileExtension> <nonFilteredFileExtension>ttf</nonFilteredFileExtension> <nonFilteredFileExtension>svg</nonFilteredFileExtension> </nonFilteredFileExtensions> </configuration> <version>3.1.0</version> </plugin> </plugins> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> <include>**/*.json</include> <include>**/*.ftl</include> </includes> </resource> </resources> </build>
-
使用yaml配置文件,配置内容如下
server: port: 8080 servlet: context-path: / compression: enabled: true min-response-size: 1024 mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/* spring: datasource: druid: stat-view-servlet: enabled: true loginUsername: admin loginPassword: 123456 allow: web-stat-filter: enabled: true dynamic: datasource: master: # 本地 url: jdbc:sqlserver://localhost:1433;DatabaseName=Lanshan username: sa password: root # mybatis plus 设置 mybatis-plus: mapper-locations: classpath*:com/yuyue/**/xml/*Mapper.xml # configuration: #关闭二级缓存,默认开启 # cache-enabled: false # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: # 关闭MP3.0自带的banner banner: false db-config: # 主键类型 0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)"; id-type: 4 # 默认数据库表下划线命名 table-underline: true # 配置逻辑删除 logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl configuration-properties: # 配置流程引擎参数,详情可见 DatabaseConfiguration blobType: BLOB boolValue: TRUE # 不要设置库名,否则会出现双库名 bug prefix: '' # Mybatis输出sql日志 logging: level: com.yuyue: debug
-
然后就写相关的实体类,mapper,service,controller。
-
写完到这里就基本没问题了,可以正常启动。
五、前端框架uni-app修改
前端框架虽然没有从0开始做,还是用的若依的uniapp,但是做了其他额外的工作量,打开首页的页面修改成我的首页,我所新加的页面都要在permission.js中排除掉,要不然会重定向到首页。去掉若依下面的固定栏位。剩下的就是添加接口js文件,然后在页面中将数据的获取方式从模拟值修改为访问后端接口返回。
这些都还是比较简单,过程有点繁琐而已,当前面都改完之后,这个项目最大的难点来了,就是openid的获取。根据微信官方文档所写,获取openid分为两部分,根据appid获取到微信返回回来的code,然后根据code和appid和app密钥获取该用户的openId。需要公众号提供的参数如下:
- appId
- app密钥secret
以下介绍对接获取微信公众号登录用户openId的过程
六、对接获取公众号登录用户openId
前提:以上程序开发完成并且配置到服务里面,服务证书访问并申请开启域名访问。
首先需要公众号进行如下的一些配置和操作
-
配置网页授权域名
配置网页授权域名的时候,保存的时候微信会校验一个文件,如下图
下载上图提到的文件,开启域名后需要同步开启80端口访问,然后将文件上传到服务器80端口的根目录,如果不知道根目录位置,可以找一下nginx或者网站的配置项。域名填写的内容如下示例:www.xxx.com,不用加前缀,不用加端口号。然后点击保存,如果提示域名活路径格式不正确说明你上传的文件放错位置了,文件放上去之后,确保访问
www.xxx.com/上传文件名
可以访问到。 -
公众号添加IP访问白名单
将服务器的外网ip填写进去即可。
-
提供公众号的appid和secret
-
绑定微信公众号的开发者微信号
作为开发人员,如果不绑定这个东西就属于是隧洞摸黑前行,永远也想不到有多少个坑在等你。
需要开发人员先关注该公众号,然后将你的微信号提供出来,在公众号中进行开发者绑定。
以上,公众号的配置暂告一段落。
接下来,程序开始修改获取openId。
程序修改后,如何验证正确性以及中间过程访问的参数是否正确,这个时候我们需要用到微信开发者工具
打开这个软件,然后用绑定公众号开发者的微信号登录,访问服务器部署的url,就能看到调用的接口了(做这种对接就不要想着本地调试了)。唯一麻烦的只有每次修改完代码后,需要将代码打包放在服务器然后解压。
-
根据appId获取微信返回回来的CODE
访问url:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
需要修改的参数是:APPID,REDIRECT_URI
APPID就是以上公众号提供出来的appId,REDIRECT_URI是重定向结果的url,这个重定向是微信官方那边做的,我们不用管如何重定向,需要注意的是重定向的url需要使用javaScript函数encodeURIComponent()进行编码
示例:
redirectToAuth() { const redirectUri = encodeURIComponent('https://www.xxx.cn') const authUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='+this.appid+'&redirect_uri='+redirectUri+'&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect' window.location.href = authUrl; } // 访问后页面会刷新重定向为 // https://www.xxx.cn?CODE=xxxxxxxxx
-
处理微信返回来的CODE
接下来要根据CODE和appid和secret获取到用户的openid,记住,这个过程一定要在后端完成,不可在前端代码完成,这个地方有坑,差点被坑哭了(>_<)。使用前端返回openid,接口会提示跨域异常,没有返回数据。
首先获取url中的CODE参数
const code = (new URLSearchParams(window.location.search)).get('code')
接下来发起后端调用,我这里使用hutool的http工具访问,如下示例
访问url:https://api.weixin.qq.com/sns/oauth2/access_token
String resultOpen = HttpUtil.get("https://api.weixin.qq.com/sns/oauth2/access_token?" + "appid=" + appid +//公众号ID "&secret=" + secret//公众号密钥 "&code=" + code + //前端 传入 code "&grant_type=authorization_code"); JSONObject jsonObject = JSONObject.parseObject(resultOpen); if (jsonObject.containsKey("errcode")) { String s = codeMap.get(code); if(StrUtil.isBlank(s)) { return Result.error(resultOpen); }else{ return Result.ok(s); } } openId = jsonObject.get("openid").toString();
正常返回的参数如下
{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE", "is_snapshotuser": 1, "unionid": "UNIONID" }
错误返回的参数如下
{"errcode":40029,"errmsg":"invalid code"}
-
当我们正确拿到这个openid就可以为所欲为了(不是),就可以正常进行后续的业务操作了。
-
最后让客户将我们项目的访问链接配置在公众号的菜单就可以啦
七、总结
真正开发的时间其实是从周五下午开始,期间我还在忙另一个正在上线的项目。这个功能虽然要求不多,但是涉及到对接,期间的坑属实不少,看着网上一篇有一篇的攻略,却总是抓不住要点,就openid要后端才能获取这个,很多网上教程说直接用前端代码获取,可能之前可以,但是现在不行了。客户下周一验收此功能,可以算得上3天搞定一个公众号对接案例,涉及到对接微信的东西不多,只有一个获取用户openid,这一个小东西就能碰到很多坑。
经过这次项目,可以看得出制定方案,选型有多么重要,真正干活其实方向对了很快就出活了,如果方向不对,比如这次公众号开发就是要改造若依框架,可能2周都出不来。