也是这里篇幅比较多,我单独开一篇去复习
复习SpringCloud
SpringCloud系列学习:
Spring Cloud 上
Spring Cloud 中
Spring Cloud 下 (SpringCloud alibaba)
引入Nacos,把之前的注解打开
配置Nacos客户端的pom依赖
提前引入微服务相关依赖,具体的版本控制:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
配置service_vod
配置application.properties,在客户端微服务中添加注册Nacos服务的配置信息
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
添加Nacos客户端注解
在service_vod微服务启动类中添加注解
@EnableDiscoveryClient
POM文件
<dependencies>
<dependency>
<groupId>com.ccgroupId>
<artifactId>service_utilsartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
dependencies>
网关启动类要和跨域配置类放在一个包下,而且启动网关一定要提前启动nacos,不然启动不起来
把配置文件写一下,application.properties
# 服务端口
server.port=8333
# 服务名
spring.application.name=service-gateway
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true
#service-vod模块配置
#设置路由id,也就是Nacos里面注册的服务名字
spring.cloud.gateway.routes[0].id=service-vod
#设置路由的uri lb:load Balance 负载均衡
spring.cloud.gateway.routes[0].uri=lb://service-vod
#设置路由断言,代理servicerId为auth-service的/auth/路径
#实际上断言就是路径匹配 /a/vod/b/c/d...
spring.cloud.gateway.routes[0].predicates= Path=/*/vod/**
跨域概述
跨域本质是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是与当前页域名相同的路径,这能有效的阻止跨站攻击。因此:跨域问题 是针对ajax的一种限制。但是这却给我们的开发带来了不便,而且在实际生产环境中,肯定会有很多台服务器之间交互,地址和端口都可能不同。
之前我们通过服务器添加注解实现,现在我们跨域通过网关来解决跨域问题。
@Configuration
public class CorsConfig {
//处理跨域
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//配置允许的请求方式
config.addAllowedMethod("*");
//配置允许的请求源地址
config.addAllowedOrigin("*");
//配置允许的请求头
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());、
//配置请求路径使用对应的配置方式
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
这里配置好了跨域,之前的跨域注解一定去掉,如果没去掉就相当于跨域跨进去了再跨出来,就相当于没跨
**注意:**目前我们已经在网关做了跨域处理,那么service服务就不需要再做跨域处理了,将之前在controller类上添加过@CrossOrigin标签的去掉==
找到那个开发文件配置
修改接口为网关地址,让服务请求先打到网关上
这个模块很简单,只有一个订单的分页处理,把已有的订单展示一下就好了
#service-order模块配置
#设置路由的id
spring.cloud.gateway.routes[1].id=service-order
#设置路由的uri
spring.cloud.gateway.routes[1].uri=lb://service-order
#设置路由的断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[1].predicates= Path=/*/order/**
创建订单模块
service模块下创建service_order模块
用MP的代码生成器生成一下相关表的内容,就用之前模块下面的代码生成器就可以了,改一遍包路径和表
记得在service父模块把子模块的信息添加进去,要不然没依赖
在订单模块里,也要改一下,不改默认是吧最顶层的onlineClass作为父模块依赖
添加配置文件application.properties
# 服务端口
server.port=8302
# 服务名
spring.application.name=service-order
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3307/glkt_order?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:com/service_order/mapper/xml/*.xml
到这,基础环境搭建完毕,开始开发
在API目录下,新建Order文件夹,创建Order.js
把路由创建好
最后整合页面
创建views -> order -> orderInfo -> list.vue
CV样式
相关数据库
coupon info 优惠卷信息
coupon use 优惠卷使用情况
新建工程
父工程依赖修改
子模块POM
代码生成器生成相关类
# 服务端口
server.port=8303
# 服务名
spring.application.name=service-activity
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3307/glkt_activity?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:com/service_activity/mapper/xml/*.xml
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
GateWay网关注册相关信息
创建分页配置类
因为涉及到优惠卷的使用情况,所以要把用户信息拿到,才能知道这个优惠卷的使用情况如何。所以需要Nacos管理服务+Fegin远程接口调用
这个模块只负责远程调用
用户表
创建模块、生成代码、配置文件、配置网关,这些就不多赘述了,弄好之后是这样的
这里已经写好了关于用户返回的Controller,可以注意到,已经不是返回Result了,已经是返回UserInfo了
这写完了,就得创建远程调用的模块了
<parent>
<artifactId>onlineClassartifactId>
<groupId>comgroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>service_clientartifactId>
<packaging>pompackaging>
<modules>
<module>service_user_clientmodule>
modules>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>com.ccgroupId>
<artifactId>service_utilsartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.ccgroupId>
<artifactId>modelartifactId>
<version>0.0.1-SNAPSHOTversion>
<scope>provided scope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<scope>provided scope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
<scope>provided scope>
dependency>
dependencies>
创建完就是这样的
创建远程调用接口,和被调用接口进行讲解
最后,把这个模块在需要被调用的接口通过pom进行引入,最好是把他放在服务的父工程中,这样下面的下属子模块就不需要去再重复引入依赖了
<dependency>
<groupId>com.feginServicegroupId>
<artifactId>service_user_clientartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
能这么调用的前提是,你在对应的pom里有这样一段标签,相当于这个模块被调用时的 “标签” ,否则调用不了
还要在需要进行调用的模块主启动类上加入注解
现在就可以进行远程调用了
配好了直接注入就能用!
最好是在Service模块把这个远程调用模块的接口整体注入进父模块下,这样就不用Service下属子模块都跟着引入相关依赖了,非常的方便~
Controller位置:
ServiceImpl位置:
这里解释一下,有个继承类
其他参数就是扩展类
优惠卷这个实体类在最后的一步有个集成用户信息的步骤,就是通过实体类扩展实现的,getParam也就对应了BaseMapper中的para Map属性
具体可以在代码查看,这里篇幅有限
这些都写完,记得把这个服务注册进路由
#service-activity模块配置
#设置路由id
spring.cloud.gateway.routes[4].id=service-activity
#设置路由的uri
spring.cloud.gateway.routes[4].uri=lb://service-activity
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[4].predicates= Path=/*/activity/**
定义JS的API
创建api -> activity -> couponInfo.js**
把路由修改好
router -> index.js定义路由
定义好的样式
粘贴页面样式
最终页面效果
简介
微信公众平台:https://mp.weixin.qq.com/
硅谷课堂要求基于H5,具有微信支付等高级功能的,因此需要注册服务号,订阅号不具备支付功能。
注册步骤参考官方注册文档:https://kf.qq.com/faq/120911VrYVrA151013MfYvYV.html
注册过程仅做了解,有公司运营负责申请与认证。
如果只是日常简单的推送文章,就像我们关注的大多数公众号一样,那确实不需要技术人员去开发;但是,如果你想将你们的网站嵌入进去公众号菜单里(这里指的是把前端项目的首页链接配置在自定义菜单),并且实现微信端的独立登录认证、获取微信用户信息、微信支付等高级功能,或者觉得UI交互的配置方式无法满足你的需求,你需要更加自由、随心所欲的操作,那么我们就必须启用开发者模式了,通过技术人员的手段去灵活控制公众号。
这里有一点需要注意,如果我们决定技术人员开发公众号,必须启用服务器配置,而这将导致UI界面设置的自动回复和自定义菜单失效!
我们在 设置与开发 - 基本配置 - 服务器配置 中点击启用:
前面说了,个人无法去申请服务号,必须企业,但是我们可以通过测试账户进行学习
微信公众平台接口测试帐号:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login&token=399029368&lang=zh_CN
查看测试号管理
(1)其中appID和appsecret用于后面菜单开发使用
(2)其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。本地测试,url改为内网穿透地址。
硅谷课堂涉及的微信公众号功能模块:自定义菜单、消息、微信支付、授权登录等
微信自定义菜单说明
微信自定义菜单文档地址:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
微信自定义菜单注意事项:
在线课堂自定义菜单
一级菜单:直播、课程、我的
二级菜单:根据一级菜单动态设置二级菜单,直播(近期直播课程),课程(课程分类),我的(我的订单、我的课程、我的优惠券及关于我们)
说明:
1、二级菜单可以是网页类型,点击跳转H5页面
2、二级菜单可以是消息类型,点击返回消息
数据格式
自定义菜单通过后台管理设置到数据库表,数据配置好后,通过微信接口推送菜单数据到微信平台。
如图所示
(1)页面功能“列表、添加、修改与删除”是对menu表的操作
(2)页面功能“同步菜单与删除菜单”是对微信平台接口操作
表结构(menu):
新建模块
这里就不多说了,文件夹,注册网关,主启动类,代码生成、修改POM这些上面都有
配置文件
# 服务端口
server.port=8305
# 服务名
spring.application.name=service-wechat
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3307/glkt_wechat?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:src/main/java/com/service_wechat/mapper/xml/*.xml
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#公众号id和秘钥
# 硅谷课堂微信公众平台appId
wechat.mpAppId: wx09f201e9013e81d8
# 硅谷课堂微信公众平台api秘钥
wechat.mpAppSecret: 6c999765c12c51850d28055e8b6e2eda
除了常见的接口以外,这里有两个比较重要的接口
一个是获取一级菜单
另一个是获取全部菜单
位置:
(1)进行菜单同步时候,需要获取到公众号的access_token,通过access_token进行菜单同步
接口文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
这个是老师的测试号,当然可以换成自己的!
# 硅谷课堂微信公众平台appId
wechat.mpAppId: wx09f201e9013e81d8
# 硅谷课堂微信公众平台api秘钥
wechat.mpAppSecret: 6c999765c12c51850d28055e8b6e2eda
读取公众号API的工具
@Component
public class ConstantPropertiesUtil implements InitializingBean {
@Value("${wechat.mpAppId}")
private String appid;
@Value("${wechat.mpAppSecret}")
private String appsecret;
public static String ACCESS_KEY_ID;
public static String ACCESS_KEY_SECRET;
@Override
public void afterPropertiesSet() throws Exception {
ACCESS_KEY_ID = appid;
ACCESS_KEY_SECRET = appsecret;
}
}
接口文档:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
接口调用请求说明
http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
weixin-java-mp是封装好了的微信接口客户端,使用起来很方便,后续我们就使用weixin-java-mp处理微信平台接口。
@Component
public class WeChatMpConfig {
@Autowired
private ConstantPropertiesUtil constantPropertiesUtil;
@Bean
public WxMpService wxMpService(){
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
return wxMpService;
}
@Bean
public WxMpConfigStorage wxMpConfigStorage(){
WxMpDefaultConfigImpl wxMpConfigStorage = new WxMpDefaultConfigImpl();
wxMpConfigStorage.setAppId(ConstantPropertiesUtil.ACCESS_KEY_ID);
wxMpConfigStorage.setSecret(ConstantPropertiesUtil.ACCESS_KEY_SECRET);
return wxMpConfigStorage;
}
}
还是在Controller
要求的数据格式
service实现层
为什么直接用了JSON数组,是因为这里直接对接公众号的数据
不再有Controller的JSON转换解析,所以这里直接做成JSON
一层一层向上封装的
/**
* 说明:
* 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
* 一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“...”代替。
* 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,
* 如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。
* 测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
* 实际上就是把自己菜单的JSON格式转换成微信认识的格式
*/
@SneakyThrows
@Override
public void syncMenu() {
List<MenuVo> menuVoList = this.findMenuInfo();
//菜单,创建一个JSON格式数组,这里为什么用了JSON数组,是因为这里直接对接公众号
//不再有Controller的JSON转换解析,所以这里直接做成JSON
JSONArray buttonList = new JSONArray();
//一级菜单
for(MenuVo oneMenuVo : menuVoList) {
JSONObject one = new JSONObject();
//name是一级菜单的固定名称
one.put("name", oneMenuVo.getName());
//二级菜单
JSONArray subButton = new JSONArray();
for(MenuVo twoMenuVo : oneMenuVo.getChildren()) {
JSONObject view = new JSONObject();
//type是二级菜单的固定名称
view.put("type", twoMenuVo.getType());
//根据type的值进行判断,是按钮还是关键词触发返回信息
if(twoMenuVo.getType().equals("view")) {
view.put("name", twoMenuVo.getName());
view.put("url", "http://ggkt2.vipgz1.91tunnel.com/#"
+twoMenuVo.getUrl());
} else {
view.put("name", twoMenuVo.getName());
view.put("key", twoMenuVo.getMeunKey());
}
subButton.add(view);
}
//封装二级菜单,固定叫sub_button
one.put("sub_button", subButton);
//封装一级菜单
buttonList.add(one);
}
//菜单整体封装,一级+二级 一起叫button
JSONObject button = new JSONObject();
button.put("button", buttonList);
this.wxMpService.getMenuService().menuCreate(button.toJSONString());
}
src -> router -> index.js添加路由
src -> api -> wechat -> menu.js定义接口
创建views -> wechat -> menu -> list.vue CV页面
数据就同步上来了
如果更换了配置信息,就要在这里同步一下,不同步菜单的话数据上不来,因为微信公众号是不会主动请求更新的,需要咱们主动数据推送
输入特定词语,返回消息
1、“硅谷课堂”公众号实现根据关键字搜索相关课程,如:输入“java”,可返回java相关的一个课程;
2、“硅谷课堂”公众号点击菜单“关于我们”,返回关于我们的介绍
3、关注或取消关注等
参考文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
接入微信公众平台开发,开发者需要按照如下步骤完成:
1、填写服务器配置
2、验证服务器地址的有效性
3、依据接口文档实现业务逻辑
公众号服务器配置
在测试管理 -> 接口配置信息,点击“修改”按钮,填写服务器地址(URL)和Token,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
说明:本地测试,url改为内网穿透地址,这个内网穿透在后面会仔细讲解
验证来自微信服务器消息
(1)概述
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
参数 | 描述 |
---|---|
signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1、将token、timestamp、nonce三个参数进行字典序排序
2、将三个参数字符串拼接成一个字符串进行sha1加密
3、开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
实际上就是接收四个参数,而且使用HttpRequest对象来接收
两个方法名
/**
* 服务器有效性验证
* @param request
* @return
*/
public String verifyToken(HttpServletRequest request) {
private boolean checkSignature(String signature, String timestamp, String nonce)
完成之后,我们的校验接口就算是开发完成了。接下来就可以开发消息接收接口了。
接下来我们来开发消息接收接口,消息接收接口和上面的服务器校验接口地址是一样的,都是我们一开始在公众号后台配置的地址。只不过消息接收接口是一个 POST 请求。
在公众号后台配置的时候,消息加解密方式选择了明文模式,这样在后台收到的消息直接就可以处理了。微信服务器给我发来的普通文本消息格式如下:
<xml>
<ToUserName>ToUserName>
<FromUserName>FromUserName>
<CreateTime>1348831860CreateTime>
<MsgType>MsgType>
<Content>Content>
<MsgId>1234567890123456MsgId>
xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,文本为text |
Content | 文本消息内容 |
MsgId | 消息id,64位整型 |
看到这里,大家心里大概就有数了,当我们收到微信服务器发来的消息之后,我们就进行 XML 解析,提取出来我们需要的信息,去做相关的查询操作,再将查到的结果返回给微信服务器。
这里我们先来个简单的,我们将收到的消息解析并打印出来:
还是在同一个Controller里
/**
* 接收微信服务器发送来的消息
* @param request
* @return
* @throws Exception
*/
@PostMapping
public String receiveMessage(HttpServletRequest request) throws Exception {
解析XML明文信息
private Map parseXml(HttpServletRequest request) throws Exception {
前面已经说过了,要微信服务器和本地的服务器进行通信。
我们可以找到微信服务器,但是微信服务器找不到我们的本地主机
这个时候就需要内网穿透帮忙了
所谓的开通一个隧道,就是申请一个域名,让微信服务器可以通过这个域名(隧道)访问到你本地
注册用户
网址:https://ngrok.cc/login/register
实名认证
(1)注册成功之后,登录系统,进行实名认证,认证费2元,认证通过后才能开通隧道
开通隧道
(1)选择隧道管理 -> 开通隧道
最后一个是免费服务器,建议选择付费服务器,10元/月,因为免费服务器使用人数很多,经常掉线
(2)点击立即购买 -> 输入相关信息
(3)开通成功后,查看开通的隧道
这里开通了两个隧道,一个用于后端接口调用,一个用于公众号前端调用
启动隧道
(1)下载客户端工具
(2)选择windows版本
(3)解压,找到bat文件,双击启动
(4)输入隧道id,多个使用逗号隔开,最后回车就可以启动
tnnd,连不上
正常如果连上了应该是这个样子
花生壳官网 花生壳打钱!
注册下载什么的我就不说了,安装好配置一下
这样就可以使用了!
一共要生成两个,一个给前端做隧道,另一个给后端做穿透
毕竟两个项目都在本地跑,想让外网访问不用隧道穿透肯定是不行的
但是如果想用HTTP服务必须花钱,再开一个,如果是学生的话可以免费开通
我认证了一下,就可以免费用两个HTTP了,TCP连前端连不上
回到公众号这里,再来捋一遍,先看看自己的公众测试号
微信公众平台测试号后台
URL就不说了,就是配置好的内网穿透地址+后端服务器有效性验证 的路径
Token可以看后端代码
向测试公众号里发一个消息,看看后台是否可以接收到
可以看到,已经接受到了结构化的数据了
根据课程名称返回课程信息,就是根据课程名称进行模糊查询
service_vod模块创建接口
(1)创建CourseApiController方法,根据课程关键字查询课程信息
创建模块定义接口
service_client下创建子模块service_course_client,改Pom之类的就不多说了
把课程查询的远程调用整合到一个单独的远程调用子模块中
service_wechat引入更改写好的远程调用依赖
<dependency>
<artifactId>service_course_clientartifactId>
<groupId>com.courseFeginServicegroupId>
<version>0.0.1-SNAPSHOTversion>
dependency>
创建消息处理接口
将微信模块的MessageController方法进行更改,根据关键字内容进行校验
具体的消息结果处理在MessageServiceImpl,根据关键字返回比较固定,把这个基本代码改改就可以用了
把Nacos、内网隧道、服务都启动
(1)点击个人 -> 关于我们,返回关于我们的介绍
(2)在公众号输入关键字,返回搜索的课程信息
这种是非测试公众号的申请,测试公众号则不需要
实现目标
购买课程支付成功微信推送消息
模板消息实现
接口文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html
申请模板消息
首先我们需要知道,模板消息是需要申请的。
但是我们在申请时还是有一些东西要注意,这个在官方的文档有非常详细的说明。
https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Operation_Specifications.html
这个大家好好看看。选择行业的时候可要谨慎些,因为这个一个月只可以修改一次。
下面看看在哪里申请,硅谷课堂已经申请过,忽略
申请之后就耐心等待,审核通过之后就会出现“广告与服务”模板消息的菜单。
添加模板消息
审核通过之后,我们就可以添加模板消息,进行开发了。
我们点击模板消息进入后,直接在模板库中选择你需要的消息模板添加就可以了,添加之后就会在我的模板中。会有一个模板id,这个模板id在我们发送消息的时候会用到。
模板样式在一个word里面,下载下来
这里是截取一部分的内容
点击新增模板
从上面的模板示例中,拿到需要使用的模板,填写进去
添加成功就是这个样子
MessageController
添加方法
@GetMapping("/pushPayMessage")
public Result pushPayMessage() throws WxErrorException {
messageService.pushPayMessage(1L);
return Result.ok();
}
service接口
MessageService
void pushPayMessage(Long orderId);
service接口实现
(1)MessageServiceImpl类
@Autowired
private WxMpService wxMpService;
//TODO 暂时写成固定值测试,后续完善
@SneakyThrows
@Override
public void pushPayMessage(long orderId) {
String openid = "微信后台添加的自定义模板的openId";
WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
.toUser(openid)//要推送的用户openid
.templateId("模板的ID")//模板id
.url("http://ggkt2.vipgz1.91tunnel.com/#/pay/"+orderId)//点击模板消息要访问的网址
.build();
//3,如果是正式版发送消息,,这里需要配置你的信息
templateMessage.addData(new WxMpTemplateData("first", "亲爱的用户:您有一笔订单支付成功。", "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword1", "1314520", "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword2", "java基础课程", "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword3", "2022-01-11", "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword4", "100", "#272727"));
templateMessage.addData(new WxMpTemplateData("remark", "感谢你购买课程,如有疑问,随时咨询!", "#272727"));
String msg = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
System.out.println(msg);
}
访问一下这个接口,直接让他在公众号里推送模板信息
测试通过,推送成功
接口文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
说明:
1、严格按照接口文档实现;
2、应用授权作用域scope:scope为snsapi_userinfo
配置授权回调域名
这个是有企业注册了公众号的情况,个人是看不到这个页面的,只能拿本地测试玩玩
在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“设置与开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
本地测试配置内网穿透地址
这样的话就可以把本地服务器上运行的前端页面在公网进行访问了,也就是公众号开启新页面,可以访问
直接复制进工程中,资料里的前端页面叫
修改访问路径到你的内网隧道
启动公众号页面项目 这个直接运行就可以了 npm run serve
如果启动的时候报错看看这个文章
https://blog.csdn.net/m0_37629753/article/details/125199185
跑起来的样子
(1)全局处理授权登录,处理页面:/src/App.vue
说明1:访问页面时首先判断是否有token信息,如果没有跳转到授权登录接口
说明2:通过localStorage存储token信息
在HTML5中,加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间很小,只有几K),localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中localStorage会有所不同。它只能存储字符串格式的数据,所以最好在每次存储时把数据转换成json格式,取出的时候再转换回来。
(2)前端代码实现
wechatLogin() {
// 处理微信授权登录
let token = this.getQueryString('token') || '';
if(token != '') {
window.localStorage.setItem('token', token);
}
// 所有页面都必须登录,两次调整登录,这里与接口返回208状态
token = window.localStorage.getItem('token') || '';
if (token == '') {
let url = window.location.href.replace('#', 'guiguketan')
window.location = 'http://glkt.atguigu.cn/api/user/wechat/authorize?returnUrl=' + url
}
console.log('token2:'+window.localStorage.getItem('token'));
},
操作模块:service-user
引入微信依赖
<dependencies>
<dependency>
<groupId>com.github.binarywanggroupId>
<artifactId>weixin-java-mpartifactId>
<version>2.7.0version>
dependency>
<dependency>
<groupId>dom4jgroupId>
<artifactId>dom4jartifactId>
<version>1.1version>
dependency>
<dependency>
<groupId>com.aliyungroupId>
<artifactId>aliyun-java-sdk-coreartifactId>
dependency>
dependencies>
在配置文件中添加配置
#公众号id和秘钥
# 硅谷课堂微信公众平台appId
wechat.mpAppId: wx09f201e9013e81d8
## 硅谷课堂微信公众平台api秘钥
wechat.mpAppSecret: 6c999765c12c51850d28055e8b6e2eda
# 授权回调获取用户信息接口地址
wechat.userInfoUrl: http://内网隧道地址/api/user/wechat/userInfo
添加了一个工具类来读取配置文件的账户密码信息
另一个是微信的配置类
我这里实在连不上内网穿透了…
没做出来…
希望有小伙伴能留言给我讲讲QAQ
通过token传递用户信息
JWT工具
JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上
JWT最重要的作用就是对 token信息的防伪作用。
一个JWT由三个部分组成:公共部分、私有部分、签名部分。最后由这三者组合进行base64编码得到JWT。
主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。
(2)私有部分
用户自定义的内容,根据实际需要真正要封装的信息。
userInfo{用户的Id,用户的昵称nickName}
(3)签名部分
SaltiP: 当前服务器的Ip地址!{linux 中配置代理服务器的ip}
主要用户对JWT生成字符串的时候,进行加密{盐值}
base64编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以把base64编码解成明文,所以不要在JWT中放入涉及私密的信息。
(1)在service_utils模块添加依赖
<dependencies>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
dependency>
dependencies>
由于从微信用不了了,所以我这里就用本地前端工程做了
在VOD模块下
创建CourseApiController
编写CourseService
编写CourseServiceImpl
这里用到了Mapper,因为要多表查询
值得注意的点:
拿一个左外连接查询来说,这种重复性的SQL是可以进行复用的
course c
LEFT JOIN teacher t ON c.teacher_id = t.id
LEFT JOIN subject s1 ON c.subject_parent_id = s1.id
LEFT JOIN subject s2 ON c.subject_id = s2.id
用一个标签来进行复用
<sql id="唯一id">
course c
LEFT JOIN teacher t ON c.teacher_id = t.id
LEFT JOIN subject s1 ON c.subject_parent_id = s1.id
LEFT JOIN subject s2 ON c.subject_id = s2.id
</sql>
怎么用?
在需要使用复用标签的地方加入这个标签
<include refid="复用SQL的标签ID" />
还是在VOD模块下,新建VodApi控制器,来接收前端的请求
在配置文件中加入
application.properties添加tencent.video.appid=腾讯云播放器ID
如图找ID
整合点播视频播放前端
创建js定义接口
courseInfo.vue修改play方法
play(video) {
let videoId = video.id;
let isFree = video.isFree;
this.$router.push({ path: '/play/'+this.courseId+'/'+videoId })
},
<link href="//cloudcache.tencent-cloud.com/open/qcloud/video/tcplayer/tcplayer.css" rel="stylesheet">
<!-- 如需在IE8、9浏览器中初始化播放器,浏览器需支持Flash并在页面中引入 -->
<!--[if lt IE 9]>
<script src="//cloudcache.tencent-cloud.com/open/qcloud/video/tcplayer/ie8/videojs-ie8.js"></script>
<![endif]-->
<!-- 如果需要在 Chrome 和 Firefox 等现代浏览器中通过 H5 播放 HLS 格式的视频,需要在 tcplayer.v4.1.min.js 之前引入 hls.min.0.13.2m.js -->
<script src="//imgcache.qq.com/open/qcloud/video/tcplayer/libs/hls.min.0.13.2m.js"></script>
<!-- 引入播放器 js 文件 -->
<script src="//imgcache.qq.com/open/qcloud/video/tcplayer/tcplayer.v4.1.min.js"></script>
提交订单接口
创建Controller对象
编写OrderInfoService和OrderInfoServiceImpl
OrderInfoService
//生成点播课程订单
Long submitOrder(OrderFormVo orderFormVo);
不过这里得等等,因为下订单这个操作需要联动优惠卷和课程ID,所以还需要远程调用一下接口 一个是根据当前课程ID来拿到课程
另一个是操作优惠卷的(获取+优惠卷使用更新)
先获取课程信息
操作service_vod模块
添加课程信息获取方法
@ApiOperation("根据ID查询课程")
@GetMapping("inner/getById/{courseId}")
public Course getById(
@ApiParam(value = "课程ID", required = true)
@PathVariable Long courseId){
return courseService.getById(courseId);
}
操作service_activity模块
提前把要操作的接口准备好
对应的Service,更改为1就代表这个优惠卷已经被使用了
@Override
public void updateCouponInfoUseStatus(Long couponUseId, Long orderId) {
CouponUse couponUse = new CouponUse();
couponUse.setId(couponUseId);
couponUse.setOrderId(orderId);
couponUse.setCouponStatus("1");
couponUse.setUsingTime(new Date());
couponUseService.updateById(couponUse);
}
为上面的优惠卷接口创建一个远程调用接口,建Module过程我就不多说了,一遍又一遍的没必要多说了
到这里远程调用就算结束了,一会会回到上面的课程接口信息
(1)common模块引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
<version>2.6.0version>
dependency>
拷贝相关工具类
之前不是说过,LocalStorage是前端的一种存储方式,所以就可以从前端的LocalStorage里拿到对象ID
前端实现方式
// http request 拦截器
service.interceptors.request.use(config => {
//获取localStorage里面的token值
let token = window.localStorage.getItem('token') || '';
if (token != '') {
//把token值放到header里面
config.headers['token'] = token;
}
return config
},
err => {
return Promise.reject(err);
})
所有准备工作完成,准备接上面的内容,调用相关远程服务接口生成微服务
service_order引入依赖
这三个都是远程Feign调用的
<dependencies>
<dependency>
<groupId>com.courseFeginServicegroupId>
<artifactId>service_course_clientartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.feginServicegroupId>
<artifactId>service_user_clientartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.activityFeginServicegroupId>
<artifactId>service_activity_clientartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
dependencies>
订单业务具体工程实现 OrderInfoServiceImpl
@Autowired
private CourseFeignClient courseFeignClient;
@Autowired
private UserInfoFeignClient userInfoFeignClient;
@Autowired
private CouponInfoFeignClient couponInfoFeignClient;
//生成点播课程订单
@Override
public Long submitOrder(OrderFormVo orderFormVo) {
Long userId = AuthContextHolder.getUserId();
Long courseId = orderFormVo.getCourseId();
Long couponId = orderFormVo.getCouponId();
//查询当前用户是否已有当前课程的订单
LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderDetail::getCourseId, courseId);
queryWrapper.eq(OrderDetail::getUserId, userId);
OrderDetail orderDetailExist = orderDetailService.getOne(queryWrapper);
if(orderDetailExist != null){
return orderDetailExist.getId(); //如果订单已存在,则直接返回订单id
}
//查询课程信息
Course course = courseFeignClient.getById(courseId);
if (course == null) {
throw new GlktException(ResultCodeEnum.DATA_ERROR.getCode(),
ResultCodeEnum.DATA_ERROR.getMessage());
}
//查询用户信息
UserInfo userInfo = userInfoFeignClient.getById(userId);
if (userInfo == null) {
throw new GlktException(ResultCodeEnum.DATA_ERROR.getCode(),
ResultCodeEnum.DATA_ERROR.getMessage());
}
//优惠券金额
BigDecimal couponReduce = new BigDecimal(0);
if(null != couponId) {
CouponInfo couponInfo = couponInfoFeignClient.getById(couponId);
couponReduce = couponInfo.getAmount();
}
//创建订单
OrderInfo orderInfo = new OrderInfo();
orderInfo.setUserId(userId);
orderInfo.setNickName(userInfo.getNickName());
orderInfo.setPhone(userInfo.getPhone());
orderInfo.setProvince(userInfo.getProvince());
orderInfo.setOriginAmount(course.getPrice());
orderInfo.setCouponReduce(couponReduce);
orderInfo.setFinalAmount(orderInfo.getOriginAmount().subtract(orderInfo.getCouponReduce()));
orderInfo.setOutTradeNo(OrderNoUtils.getOrderNo());
orderInfo.setTradeBody(course.getTitle());
orderInfo.setOrderStatus("0");
this.save(orderInfo);
OrderDetail orderDetail = new OrderDetail();
orderDetail.setOrderId(orderInfo.getId());
orderDetail.setUserId(userId);
orderDetail.setCourseId(courseId);
orderDetail.setCourseName(course.getTitle());
orderDetail.setCover(course.getCover());
orderDetail.setOriginAmount(course.getPrice());
orderDetail.setCouponReduce(new BigDecimal(0));
orderDetail.setFinalAmount(orderDetail.getOriginAmount().subtract(orderDetail.getCouponReduce()));
orderDetailService.save(orderDetail);
//更新优惠券状态
if(null != orderFormVo.getCouponUseId()) {
couponInfoFeignClient.updateCouponInfoUseStatus(orderFormVo.getCouponUseId(), orderInfo.getId());
}
return orderInfo.getId();
}
到这里期内容就结束了~
来我主页看看下一部分把~
硅谷课堂笔记下