硅谷课堂第十课-营销模块和公众号菜单管理

2023-10-18 17:50

本文主要是介绍硅谷课堂第十课-营销模块和公众号菜单管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

硅谷课堂第十一天-营销管理模块和公众号菜单管理

文章目录

    • 硅谷课堂第十一天-营销管理模块和公众号菜单管理
      • 一、优惠券列表接口
        • 1、编写获取用户信息接口
          • 1.1、创建service_user模块
          • 1.2、生成相关代码
          • 1.3、创建启动类
          • 1.4、创建配置文件
          • 1.5、编写UserInfocontroller
          • 1.6、配置网关
        • 2、创建模块定义远程接口
          • 2.1、创建模块
          • 2.2、service_client引入依赖
          • 2.3、定义远程调用的接口
        • 3、编写Service实现方法
          • 3.1、service_activity引入依赖
          • 3.2、service_activity添加注解
          • 3.3、CouponInfoServiceImpl实现方法
        • 4、配置网关
          • 4.1、配置路由规则
        • 5、整合优惠券前端
          • 5.1、定义接口
          • 5.2、创建路由
          • 5.3、创建vue页面
      • 二、微信公众号
        • 1、注册公众号
        • 2、公众号功能介绍
        • 3、微信公众平台测试帐号
          • 3.1、申请测试帐号
          • 3.2、查看测试号管理
          • 3.3、关注公众号
        • 4、开发业务介绍
      • 三、后台管理系统-公众号菜单管理
        • 1、需求分析
          • 1.1、微信自定义菜单说明
          • 1.2、硅谷课堂自定义菜单
          • 1.3、数据格式
          • 1.4、管理页面
        • 2、搭建菜单管理后端环境
          • 2.1、创建模块service_wechat
          • 2.2、生成菜单相关代码
          • 2.3、创建启动类和配置文件
          • 2.4、配置网关
        • 3、开发菜单管理接口
          • 3.1、编写MenuController
          • 3.2、编写Service
        • 4、同步菜单(获取access_token)
          • 4.1、文档查看
          • 4.2、service_wechat添加配置
          • 4.3、添加工具类
          • 4.4、复制HttpClient工具类
          • 4.5、添加Menucontroller方法
          • 4.6、测试
        • 5、同步菜单(功能实现)
          • 5.1、添加配置类
          • 5.2、定义Service方法
          • 5.3、实现Service方法
          • 5.4、controller方法
        • 6、删除菜单
          • 6.1、service接口
          • 6.2、service接口实现
          • 6.3、controller方法
        • 7、开发菜单管理前端
          • 7.1、添加路由
          • 7.2、定义接口
          • 7.3、编写页面
        • 8、公众号菜单功能测试

一、优惠券列表接口

1、编写获取用户信息接口

(1)获取优惠券详情时候,需要获取使用者的昵称和手机号,所以使用远程调用实现此功能。

1.1、创建service_user模块

image-20220228155648641

image-20220228155622148

1.2、生成相关代码

image-20220301085746611

1.3、创建启动类
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.atguigu.ggkt.user.mapper")
public class ServiceUserApplication {public static void main(String[] args) {SpringApplication.run(ServiceUserApplication.class, args);}}
1.4、创建配置文件
# 服务端口
server.port=8304
# 服务名
spring.application.name=service-user# 环境设置:dev、test、prod
spring.profiles.active=dev# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/glkt_user?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root#返回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# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
1.5、编写UserInfocontroller

实现根据用户id获取用户信息接口

@RestController
@RequestMapping("/admin/user/userInfo")
public class UserInfoController {@Autowiredprivate UserInfoService userService;@ApiOperation(value = "获取")@GetMapping("inner/getById/{id}")public UserInfo getById(@PathVariable Long id) {return userService.getById(id);}
}
1.6、配置网关

在网关配置文件配置路径

#service-user模块配置
#设置路由id
spring.cloud.gateway.routes[3].id=service-user
#设置路由的uri
spring.cloud.gateway.routes[3].uri=lb://service-user
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[3].predicates= Path=/*/user/**
2、创建模块定义远程接口
2.1、创建模块

在ggkt_parent -> service_client -> service_user_client

image-20220228163546714

2.2、service_client引入依赖
<dependencies><dependency><groupId>com.atguigu</groupId><artifactId>service_util</artifactId><version>0.0.1-SNAPSHOT</version><scope>provided </scope></dependency><dependency><groupId>com.atguigu</groupId><artifactId>model</artifactId><version>0.0.1-SNAPSHOT</version><scope>provided </scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided </scope></dependency><!-- 服务调用feign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><scope>provided </scope></dependency>
</dependencies>
2.3、定义远程调用的接口
@FeignClient(value = "service-user")
public interface UserInfoFeignClient {@GetMapping("/admin/user/userInfo/inner/getById/{id}")UserInfo getById(@PathVariable Long id);}
3、编写Service实现方法
3.1、service_activity引入依赖
<dependencies><dependency><groupId>com.atguigu</groupId><artifactId>service_user_client</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
</dependencies>
3.2、service_activity添加注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.atguigu")
@ComponentScan(basePackages = "com.atguigu")
public class ServiceActivityApplication {public static void main(String[] args) {SpringApplication.run(ServiceActivityApplication.class, args);}
}
3.3、CouponInfoServiceImpl实现方法

远程调用,根据用户id获取用户信息

@Service
public class CouponInfoServiceImpl extends ServiceImpl<CouponInfoMapper, CouponInfo> implements CouponInfoService {@Autowiredprivate CouponUseService couponUseService;@Autowiredprivate UserInfoFeignClient userInfoFeignClient;//获取已使用优惠券列表@Overridepublic IPage<CouponUse> selectCouponUsePage(Page<CouponUse> pageParam, CouponUseQueryVo couponUseQueryVo) {//获取条件Long couponId = couponUseQueryVo.getCouponId();String couponStatus = couponUseQueryVo.getCouponStatus();String getTimeBegin = couponUseQueryVo.getGetTimeBegin();String getTimeEnd = couponUseQueryVo.getGetTimeEnd();//封装条件QueryWrapper<CouponUse> wrapper = new QueryWrapper<>();if(!StringUtils.isEmpty(couponId)) {wrapper.eq("coupon_id",couponId);}if(!StringUtils.isEmpty(couponStatus)) {wrapper.eq("coupon_status",couponStatus);}if(!StringUtils.isEmpty(getTimeBegin)) {wrapper.ge("get_time",getTimeBegin);}if(!StringUtils.isEmpty(getTimeEnd)) {wrapper.le("get_time",getTimeEnd);}//调用方法查询IPage<CouponUse> page = couponUseService.page(pageParam, wrapper);//封装用户昵称和手机号List<CouponUse> couponUseList = page.getRecords();couponUseList.stream().forEach(item->{this.getUserInfoBycouponUse(item);});return page;}//封装用户昵称和手机号private CouponUse getUserInfoBycouponUse(CouponUse couponUse) {Long userId = couponUse.getUserId();if(!StringUtils.isEmpty(userId)) {UserInfo userInfo = userInfoFeignClient.getById(userId);if(userInfo != null) {couponUse.getParam().put("nickName", userInfo.getNickName());couponUse.getParam().put("phone", userInfo.getPhone());}}return couponUse;}
}
4、配置网关
4.1、配置路由规则

(1)service_gateway配置文件

#service-activity模块配置
#设置路由id
spring.cloud.gateway.routes[2].id=service-activity
#设置路由的uri
spring.cloud.gateway.routes[2].uri=lb://service-activity
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[2].predicates= Path=/*/activity/**
5、整合优惠券前端
5.1、定义接口

1)创建api -> activity -> couponInfo.js

image-20220228150024735

import request from '@/utils/request'const api_name = '/admin/activity/couponInfo'export default {getPageList(page, limit) {return request({url: `${api_name}/${page}/${limit}`,method: 'get'})},getById(id) {return request({url: `${api_name}/get/${id}`,method: 'get'})},save(role) {return request({url: `${api_name}/save`,method: 'post',data: role})},updateById(role) {return request({url: `${api_name}/update`,method: 'put',data: role})},removeById(id) {return request({url: `${api_name}/remove/${id}`,method: 'delete'})},removeRows(idList) {return request({url: `${api_name}/batchRemove`,method: 'delete',data: idList})},getPageCouponUseList(page, limit, searchObj) {return request({url: `${api_name}/couponUse/${page}/${limit}`,method: 'get',params: searchObj})}
}
5.2、创建路由

(1)router -> index.js定义路由

{path: '/activity',component: Layout,redirect: '/couponInfo/list',name: 'Activity',meta: { title: '营销活动管理', icon: 'el-icon-football' },alwaysShow: true,children: [{path: 'couponInfo/list',name: 'CouponInfo',component: () => import('@/views/activity/couponInfo/list'),meta: { title: '优惠券列表' }},{path: 'couponInfo/add',name: 'CouponInfoAdd',component: () => import('@/views/activity/couponInfo/form'),meta: { title: '添加' },hidden: true},{path: 'couponInfo/edit/:id',name: 'CouponInfoEdit',component: () => import('@/views/activity/couponInfo/form'),meta: { title: '编辑', noCache: true },hidden: true},{path: 'couponInfo/show/:id',name: 'CouponInfoShow',component: () => import('@/views/activity/couponInfo/show'),meta: { title: '详情', noCache: true },hidden: true}]
},
5.3、创建vue页面

(1)创建views -> activity-> couponInfo-> 页面

image-20220228150356741

(2)list.vue

<template><div class="app-container"><!-- 工具条 --><el-card class="operate-container" shadow="never"><i class="el-icon-tickets" style="margin-top: 5px"></i><span style="margin-top: 5px">数据列表</span><el-button class="btn-add" size="mini" @click="add()">添加</el-button></el-card><!-- banner列表 --><el-tablev-loading="listLoading":data="list"element-loading-text="数据正在加载......"borderfithighlight-current-row><el-table-columnlabel="序号"width="70"align="center"><template slot-scope="scope">{{ (page - 1) * limit + scope.$index + 1 }}</template></el-table-column><el-table-column prop="couponName" label="购物券名称" /><el-table-column prop="couponType" label="购物券类型"><template slot-scope="scope">{{ scope.row.couponType == 'REGISTER' ? '注册卷' : '推荐赠送卷' }}</template></el-table-column><el-table-column label="规则"><template slot-scope="scope">{{ '现金卷:' + scope.row.amount + '元' }}</template></el-table-column><el-table-column label="使用范围 ">所有商品</el-table-column><el-table-column prop="publishCount" label="发行数量" /><el-table-column prop="expireTime" label="过期时间" /><el-table-column prop="createTime" label="创建时间" /><el-table-column label="操作" width="150" align="center"><template slot-scope="scope"><router-link :to="'/activity/couponInfo/edit/'+scope.row.id"><el-button size="mini" type="text" >修改</el-button></router-link><el-button size="mini" type="text" @click="removeDataById(scope.row.id)">删除</el-button><router-link :to="'/activity/couponInfo/show/'+scope.row.id"><el-button size="mini" type="text" >详情</el-button></router-link></template></el-table-column></el-table><!-- 分页组件 --><el-pagination:current-page="page":total="total":page-size="limit":page-sizes="[5, 10, 20, 30, 40, 50, 100]"style="padding: 30px 0; text-align: center;"layout="sizes, prev, pager, next, jumper, ->, total, slot"@current-change="fetchData"@size-change="changeSize"/></div>
</template><script>
import api from '@/api/activity/couponInfo'export default {data() {return {listLoading: true, // 数据是否正在加载list: null, // banner列表total: 0, // 数据库中的总记录数page: 1, // 默认页码limit: 10, // 每页记录数searchObj: {}, // 查询表单对象multipleSelection: [] // 批量选择中选择的记录列表}},// 生命周期函数:内存准备完毕,页面尚未渲染created() {console.log('list created......')this.fetchData()},// 生命周期函数:内存准备完毕,页面渲染成功mounted() {console.log('list mounted......')},methods: {// 当页码发生改变的时候changeSize(size) {console.log(size)this.limit = sizethis.fetchData(1)},add(){this.$router.push({ path: '/activity/couponInfo/add' })},// 加载banner列表数据fetchData(page = 1) {console.log('翻页。。。' + page)// 异步获取远程数据(ajax)this.page = pageapi.getPageList(this.page, this.limit, this.searchObj).then(response => {this.list = response.data.recordsthis.total = response.data.total// 数据加载并绑定成功this.listLoading = false})},// 重置查询表单resetData() {console.log('重置查询表单')this.searchObj = {}this.fetchData()},// 根据id删除数据removeDataById(id) {// debuggerthis.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => { // promise// 点击确定,远程调用ajaxreturn api.removeById(id)}).then((response) => {this.fetchData(this.page)if (response.code) {this.$message({type: 'success',message: '删除成功!'})}}).catch(() => {this.$message({type: 'info',message: '已取消删除'})})}}
}
</script>

(3)form.vue

<template><div class="app-container"><el-form label-width="120px"><el-form-item label="优惠券名称"><el-input v-model="couponInfo.couponName"/></el-form-item><el-form-item label="优惠券类型"><el-radio-group v-model="couponInfo.couponType"><el-radio label="1">注册卷</el-radio><el-radio label="2">推荐购买卷</el-radio></el-radio-group></el-form-item><el-form-item label="发行数量"><el-input v-model="couponInfo.publishCount"/></el-form-item><el-form-item label="领取时间"><el-date-pickerv-model="couponInfo.startTime"type="date"placeholder="选择开始日期"value-format="yyyy-MM-dd" />至<el-date-pickerv-model="couponInfo.endTime"type="date"placeholder="选择开始日期"value-format="yyyy-MM-dd" /></el-form-item><el-form-item label="过期时间"><el-date-pickerv-model="couponInfo.expireTime"type="datetime"placeholder="选择开始日期"value-format="yyyy-MM-dd HH:mm:ss" /></el-form-item><el-form-item label="直播详情"><el-input v-model="couponInfo.ruleDesc" type="textarea" rows="5"/></el-form-item><el-form-item><el-button type="primary" @click="saveOrUpdate">保存</el-button><el-button @click="back">返回</el-button></el-form-item></el-form></div>
</template><script>import api from '@/api/activity/couponInfo'const defaultForm = {id: '',couponType: '1',couponName: '',amount: '0',conditionAmount: '0',startTime: '',endTime: '',rangeType: '1',ruleDesc: '',publishCount: '',perLimit: '1',useCount: '0',receiveCount: '',expireTime: '',publishStatus: ''
}export default {data() {return {couponInfo: defaultForm,saveBtnDisabled: false,keyword: '',skuInfoList: []}},// 监听器watch: {$route(to, from) {console.log('路由变化......')console.log(to)console.log(from)this.init()}},// 生命周期方法(在路由切换,组件不变的情况下不会被调用)created() {console.log('form created ......')this.init()},methods: {// 表单初始化init() {// debuggerif (this.$route.params && this.$route.params.id) {const id = this.$route.params.idthis.fetchDataById(id)} else {// 对象拓展运算符:拷贝对象,而不是赋值对象的引用this.couponInfo = { ...defaultForm }}},saveOrUpdate() {this.saveBtnDisabled = true // 防止表单重复提交if (!this.couponInfo.id) {this.saveData()} else {this.updateData()}},// 新增saveData() {api.save(this.couponInfo).then(response => {// debuggerif (response.code) {this.$message({type: 'success',message: response.message})this.$router.push({ path: '/activity/couponInfo/list' })}})},// 根据id更新记录updateData() {api.updateById(this.couponInfo).then(response => {debuggerif (response.code) {this.$message({type: 'success',message: response.message})this.$router.push({ path: '/activity/couponInfo/list' })}})},back() {this.$router.push({ path: '/activity/couponInfo/list' })},// 根据id查询记录fetchDataById(id) {api.getById(id).then(response => {// debuggerthis.couponInfo = response.data})}}
}
</script>

(4)show.vue

<template><div class="app-container"><h4>优惠券信息</h4><table class="table table-striped table-condenseda table-bordered" width="100%"><tbody><tr><th width="15%">优惠券名称</th><td width="35%"><b style="font-size: 14px">{{ couponInfo.couponName }}</b></td><th width="15%">优惠券类型</th><td width="35%">{{ couponInfo.couponType == 'REGISTER' ? '注册卷' : '推荐赠送卷' }}</td></tr><tr><th>发行数量</th><td>{{ couponInfo.publishCount }}</td><th>每人限领次数</th><td>{{ couponInfo.perLimit }}</td></tr><tr><th>领取数量</th><td>{{ couponInfo.receiveCount }}</td><th>使用数量</th><td>{{ couponInfo.useCount }}</td></tr><tr><th>领取时间</th><td>{{ couponInfo.startTime }}至{{ couponInfo.endTime }}</td><th>过期时间</th><td>{{ couponInfo.expireTime }}</td></tr><tr><th>规则描述</th><td colspan="3">{{ couponInfo.ruleDesc }}</td></tr></tbody></table><h4>优惠券发放列表&nbsp;&nbsp;&nbsp;</h4><el-tablev-loading="listLoading":data="list"stripeborderstyle="width: 100%;margin-top: 10px;"><el-table-columnlabel="序号"width="70"align="center"><template slot-scope="scope">{{ (page - 1) * limit + scope.$index + 1 }}</template></el-table-column><el-table-column prop="param.nickName" label="用户昵称" /><el-table-column prop="param.phone" label="手机号" /><el-table-column label="使用状态"><template slot-scope="scope">{{ scope.row.couponStatus == 'NOT_USED' ? '未使用' : '已使用' }}</template></el-table-column><el-table-column prop="getTime" label="获取时间" /><el-table-column prop="usingTime" label="使用时间" /><el-table-column prop="usedTime" label="支付时间" /><el-table-column prop="expireTime" label="过期时间" /></el-table><!-- 分页组件 --><el-pagination:current-page="page":total="total":page-size="limit":page-sizes="[5, 10, 20, 30, 40, 50, 100]"style="padding: 30px 0; text-align: center;"layout="sizes, prev, pager, next, jumper, ->, total, slot"@current-change="fetchData"@size-change="changeSize"/><div style="margin-top: 15px;"><el-form label-width="0px"><el-form-item><el-button @click="back">返回</el-button></el-form-item></el-form></div></div>
</template><script>
import api from '@/api/activity/couponInfo'
export default {data() {return {listLoading: false, // 数据是否正在加载couponId: null,couponInfo: {},list: null, // banner列表total: 0, // 数据库中的总记录数page: 1, // 默认页码limit: 10, // 每页记录数searchObj: {} // 查询表单对象}},// 监听器watch: {$route(to, from) {console.log('路由变化......')console.log(to)console.log(from)this.init()}},// 生命周期方法(在路由切换,组件不变的情况下不会被调用)created() {console.log('form created ......')this.couponId = this.$route.params.id// 获取优惠券信息this.fetchDataById()this.fetchData()},methods: {// 根据id查询记录fetchDataById() {api.getById(this.couponId).then(response => {//this.couponInfo = response.data})},// 当页码发生改变的时候changeSize(size) {console.log(size)this.limit = sizethis.fetchData(1)},// 加载banner列表数据fetchData(page = 1) {console.log('翻页。。。' + page)// 异步获取远程数据(ajax)this.page = pagethis.searchObj.couponId = this.couponIdapi.getPageCouponUseList(this.page, this.limit, this.searchObj).then(response => {this.list = response.data.recordsthis.total = response.data.total// 数据加载并绑定成功this.listLoading = false})},back() {this.$router.push({ path: '/activity/couponInfo/list' })}}
}
</script>
<style>.app-container h4 {color: #606266;}
</style>

二、微信公众号

1、注册公众号

微信公众平台:https://mp.weixin.qq.com/

image-20220228165007415

硅谷课堂要求基于H5,具有微信支付等高级功能的,因此需要注册服务号,订阅号不具备支付功能。

注册步骤参考官方注册文档:https://kf.qq.com/faq/120911VrYVrA151013MfYvYV.html,

注册过程仅做了解,有公司运营负责申请与认证。

2、公众号功能介绍

我们在微信公众平台扫码登录后可以发现管理页面左侧菜单栏有丰富的功能:

image-20220301090331369

大概可以分为这几大模块:
首页内容与互动数据广告与服务设置与开发新功能

作为开发人员,首先应该关注的是设置与开发模块;而作为产品运营人员与数据分析人员,关注的是内容与互动、数据及广告与服务模块。

首先我们不妨各个功能模块都点击看一看,大概了解下我们能做些什么。可以确认的是,这个微信公众平台当然不只是给开发人员使用的,它提供了很多非技术人员可在UI界面上交互操作的功能模块。

如配置消息回复、自定义菜单、发布文章等:

20190407160449399

这个时候我们可能会想:这些功能好像非技术人员都能随意操作,那么还需要我们技术人员去开发吗?

答案是: 如果只是日常简单的推送文章,就像我们关注的大多数公众号一样,那确实不需要技术人员去开发;但是,如果你想将你们的网站嵌入进去公众号菜单里(这里指的是把前端项目的首页链接配置在自定义菜单),并且实现微信端的独立登录认证、获取微信用户信息、微信支付等高级功能,或者觉得UI交互的配置方式无法满足你的需求,你需要更加自由、随心所欲的操作,那么我们就必须启用开发者模式了,通过技术人员的手段去灵活控制公众号。

这里有一点需要注意,如果我们决定技术人员开发公众号,必须启用服务器配置,而这将导致UI界面设置的自动回复和自定义菜单失效!

我们在 设置与开发 - 基本配置 - 服务器配置 中点击启用:
20190407160626882

20190407160653127

至于服务器配置中的选项代表什么意思、如何填写,我们下面再讲。

3、微信公众平台测试帐号
3.1、申请测试帐号

微信公众平台接口测试帐号:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login&token=399029368&lang=zh_CN

image-20220302093450482

image-20220304085821302

3.2、查看测试号管理

(1)其中appID和appsecret用于后面菜单开发使用

(2)其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。本地测试,url改为内网穿透地址。

image-20220302092856088

3.3、关注公众号

image-20220307090752935

image-20220308092749842

4、开发业务介绍

硅谷课堂涉及的微信公众号功能模块:自定义菜单、消息、微信支付、授权登录等

三、后台管理系统-公众号菜单管理

1、需求分析
1.1、微信自定义菜单说明

微信自定义菜单文档地址:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html

微信自定义菜单注意事项:

  1. 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
  2. 一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“…”代替。
  3. 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
1.2、硅谷课堂自定义菜单

一级菜单:直播、课程、我的

二级菜单:根据一级菜单动态设置二级菜单,直播(近期直播课程),课程(课程分类),我的(我的订单、我的课程、我的优惠券及关于我们)

说明:

​ 1、二级菜单可以是网页类型,点击跳转H5页面

​ 2、二级菜单可以是消息类型,点击返回消息

1.3、数据格式

自定义菜单通过后台管理设置到数据库表,数据配置好后,通过微信接口推送菜单数据到微信平台。

表结构(menu):

image-20220302094536733

image-20220302094144684

表示例数据:

image-20220302094354278

1.4、管理页面

(1)页面功能“列表、添加、修改与删除”是对menu表的操作

(2)页面功能“同步菜单与删除菜单”是对微信平台接口操作

image-20220307091011118

2、搭建菜单管理后端环境
2.1、创建模块service_wechat

(1)在service下创建子模块service_wechat

image-20220302095309078

(2)引入依赖

    <dependencies><dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-mp</artifactId><version>4.1.0</version></dependency></dependencies>
2.2、生成菜单相关代码

image-20220302095941199

2.3、创建启动类和配置文件

(1)启动类

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.atguigu")
@MapperScan("com.atguigu.ggkt.wechat.mapper")
@ComponentScan(basePackages = "com.atguigu")
public class ServiceWechatApplication {public static void main(String[] args) {SpringApplication.run(ServiceWechatApplication.class, args);}
}

(2)配置文件

# 服务端口
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:3306/glkt_wechat?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root#返回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.StdOutImplmybatis-plus.mapper-locations=classpath:com/atguigu/ggkt/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
2.4、配置网关
#service-wechat模块配置
#设置路由id
spring.cloud.gateway.routes[4].id=service-wechat
#设置路由的uri
spring.cloud.gateway.routes[4].uri=lb://service-wechat
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[4].predicates= Path=/*/wechat/**
3、开发菜单管理接口
3.1、编写MenuController
@RestController
@RequestMapping("/admin/wechat/menu")
public class MenuController {@Autowiredprivate MenuService menuService;//获取所有菜单,按照一级和二级菜单封装@GetMapping("findMenuInfo")public Result findMenuInfo() {List<MenuVo> list = menuService.findMenuInfo();return Result.ok(list);}//获取所有一级菜单@GetMapping("findOneMenuInfo")public Result findOneMenuInfo() {List<Menu> list = menuService.findMenuOneInfo();return Result.ok(list);}@ApiOperation(value = "获取")@GetMapping("get/{id}")public Result get(@PathVariable Long id) {Menu menu = menuService.getById(id);return Result.ok(menu);}@ApiOperation(value = "新增")@PostMapping("save")public Result save(@RequestBody Menu menu) {menuService.save(menu);return Result.ok(null);}@ApiOperation(value = "修改")@PutMapping("update")public Result updateById(@RequestBody Menu menu) {menuService.updateById(menu);return Result.ok(null);}@ApiOperation(value = "删除")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {menuService.removeById(id);return Result.ok(null);}@ApiOperation(value = "根据id列表删除")@DeleteMapping("batchRemove")public Result batchRemove(@RequestBody List<Long> idList) {menuService.removeByIds(idList);return Result.ok(null);}
}
3.2、编写Service

(1)MenuService定义方法

public interface MenuService extends IService<Menu> {//获取全部菜单List<MenuVo> findMenuInfo();//获取一级菜单List<Menu> findOneMenuInfo();
}

(2)MenuServiceImpl实现方法

@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {//获取全部菜单@Overridepublic List<MenuVo> findMenuInfo() {List<MenuVo> list = new ArrayList<>();List<Menu> menuList = baseMapper.selectList(null);List<Menu> oneMenuList = menuList.stream().filter(menu -> menu.getParentId().longValue() == 0).collect(Collectors.toList());for(Menu oneMenu : oneMenuList) {MenuVo oneMenuVo = new MenuVo();BeanUtils.copyProperties(oneMenu, oneMenuVo);List<Menu> twoMenuList = menuList.stream().filter(menu -> menu.getParentId().longValue() == oneMenu.getId()).sorted(Comparator.comparing(Menu::getSort)).collect(Collectors.toList());List<MenuVo> children = new ArrayList<>();for(Menu twoMenu : twoMenuList) {MenuVo twoMenuVo = new MenuVo();BeanUtils.copyProperties(twoMenu, twoMenuVo);children.add(twoMenuVo);}oneMenuVo.setChildren(children);list.add(oneMenuVo);}return list;}//获取一级菜单@Overridepublic List<Menu> findOneMenuInfo() {QueryWrapper<Menu> wrapper = new QueryWrapper<>();wrapper.eq("parent_id",0);List<Menu> list = baseMapper.selectList(wrapper);return list;}
}
4、同步菜单(获取access_token)
4.1、文档查看

1)进行菜单同步时候,需要获取到公众号的access_token,通过access_token进行菜单同步

接口文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

image-20220302105826122

(2)调用方式

image-20220302110114779

image-20220302110145875

4.2、service_wechat添加配置
# 硅谷课堂微信公众平台appId
wechat.mpAppId: wx09f201e9013e81d8
# 硅谷课堂微信公众平台api秘钥
wechat.mpAppSecret: 6c999765c12c51850d28055e8b6e2eda
4.3、添加工具类
@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;@Overridepublic void afterPropertiesSet() throws Exception {ACCESS_KEY_ID = appid;ACCESS_KEY_SECRET = appsecret;}
}
4.4、复制HttpClient工具类

image-20220302110913649

4.5、添加Menucontroller方法
    //获取access_token@GetMapping("getAccessToken")public Result getAccessToken() {try {//拼接请求地址StringBuffer buffer = new StringBuffer();buffer.append("https://api.weixin.qq.com/cgi-bin/token");buffer.append("?grant_type=client_credential");buffer.append("&appid=%s");buffer.append("&secret=%s");//请求地址设置参数String url = String.format(buffer.toString(),ConstantPropertiesUtil.ACCESS_KEY_ID,ConstantPropertiesUtil.ACCESS_KEY_SECRET);//发送http请求String tokenString = HttpClientUtils.get(url);//获取access_tokenJSONObject jsonObject = JSONObject.parseObject(tokenString);String access_token = jsonObject.getString("access_token");//返回return Result.ok(access_token);} catch (Exception e) {e.printStackTrace();return Result.fail(null);}}
4.6、测试
5、同步菜单(功能实现)

接口文档: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处理微信平台接口。

5.1、添加配置类
@Component
public class WeChatMpConfig {@Autowiredprivate ConstantPropertiesUtil constantPropertiesUtil;@Beanpublic WxMpService wxMpService(){WxMpService wxMpService = new WxMpServiceImpl();wxMpService.setWxMpConfigStorage(wxMpConfigStorage());return wxMpService;}@Beanpublic WxMpConfigStorage wxMpConfigStorage(){WxMpDefaultConfigImpl wxMpConfigStorage = new WxMpDefaultConfigImpl();wxMpConfigStorage.setAppId(ConstantPropertiesUtil.ACCESS_KEY_ID);wxMpConfigStorage.setSecret(ConstantPropertiesUtil.ACCESS_KEY_SECRET);return wxMpConfigStorage;}
}
5.2、定义Service方法

MenuService

void syncMenu();
5.3、实现Service方法

MenuServiceImpl

    @Autowiredprivate WxMpService wxMpService;/*** 说明:* 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。* 一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“...”代替。* 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。*/@SneakyThrows@Overridepublic void syncMenu() {List<MenuVo> menuVoList = this.findMenuInfo();//菜单JSONArray buttonList = new JSONArray();for(MenuVo oneMenuVo : menuVoList) {JSONObject one = new JSONObject();one.put("name", oneMenuVo.getName());JSONArray subButton = new JSONArray();for(MenuVo twoMenuVo : oneMenuVo.getChildren()) {JSONObject view = new JSONObject();view.put("type", twoMenuVo.getType());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);}one.put("sub_button", subButton);buttonList.add(one);}//菜单JSONObject button = new JSONObject();button.put("button", buttonList);this.wxMpService.getMenuService().menuCreate(button.toJSONString());}
5.4、controller方法
@ApiOperation(value = "同步菜单")
@GetMapping("syncMenu")
public Result createMenu() throws WxErrorException {menuService.syncMenu();return Result.ok(null);
}
6、删除菜单
6.1、service接口
void removeMenu();
6.2、service接口实现
@SneakyThrows
@Override
public void removeMenu() {wxMpService.getMenuService().menuDelete();
}
6.3、controller方法
@ApiOperation(value = "删除菜单")
@DeleteMapping("removeMenu")
public Result removeMenu() {menuService.removeMenu();return Result.ok(null);
}
7、开发菜单管理前端
7.1、添加路由

(1)src -> router -> index.js添加路由

{path: '/wechat',component: Layout,redirect: '/wechat/menu/list',name: 'Wechat',meta: {title: '菜单管理',icon: 'el-icon-refrigerator'},alwaysShow: true,children: [{path: 'menu/list',name: 'Menu',component: () => import('@/views/wechat/menu/list'),meta: { title: '菜单列表' }}]
},
7.2、定义接口

(1)src -> api -> wechat -> menu.js定义接口

import request from '@/utils/request'const api_name = '/admin/wechat/menu'export default {findMenuInfo() {return request({url: `${api_name}/findMenuInfo`,method: `get`})},findOneMenuInfo() {return request({url: `${api_name}/findOneMenuInfo`,method: `get`})},save(menu) {return request({url: `${api_name}/save`,method: `post`,data: menu})},getById(id) {return request({url: `${api_name}/get/${id}`,method: `get`})},updateById(menu) {return request({url: `${api_name}/update`,method: `put`,data: menu})},syncMenu() {return request({url: `${api_name}/syncMenu`,method: `get`})},removeById(id) {return request({url: `${api_name}/remove/${id}`,method: 'delete'})},removeMenu() {return request({url: `${api_name}/removeMenu`,method: `delete`})}
}
7.3、编写页面

(1)创建views -> wechat -> menu -> list.vue

<template><div class="app-container"><!-- 工具条 --><el-card class="operate-container" shadow="never"><i class="el-icon-tickets" style="margin-top: 5px"></i><span style="margin-top: 5px">数据列表</span><el-button class="btn-add" size="mini" @click="remove" style="margin-left: 10px;">删除菜单</el-button><el-button class="btn-add" size="mini" @click="syncMenu">同步菜单</el-button><el-button class="btn-add" size="mini" @click="add">添 加</el-button></el-card><el-table:data="list"style="width: 100%;margin-bottom: 20px;"row-key="id"borderdefault-expand-all:tree-props="{children: 'children'}"><el-table-column label="名称" prop="name" width="350"></el-table-column><el-table-column label="类型" width="100"><template slot-scope="scope">{{ scope.row.type == 'view' ? '链接' : scope.row.type == 'click' ? '事件' : '' }}</template></el-table-column><el-table-column label="菜单URL" prop="url" ></el-table-column><el-table-column label="菜单KEY" prop="meunKey"  width="130"></el-table-column><el-table-column label="排序号" prop="sort"  width="70"></el-table-column><el-table-column label="操作" width="170" align="center"><template slot-scope="scope"><el-button v-if="scope.row.parentId > 0" type="text" size="mini" @click="edit(scope.row.id)">修改</el-button><el-button v-if="scope.row.parentId > 0" type="text" size="mini" @click="removeDataById(scope.row.id)">删除</el-button></template></el-table-column></el-table><el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" ><el-form ref="flashPromotionForm" label-width="150px" size="small" style="padding-right: 40px;"><el-form-item label="选择一级菜单"><el-selectv-model="menu.parentId"placeholder="请选择"><el-optionv-for="item in list":key="item.id":label="item.name":value="item.id"/></el-select></el-form-item><el-form-item v-if="menu.parentId == 1" label="菜单名称"><el-selectv-model="menu.name"placeholder="请选择"@change="liveCourseChanged"><el-optionv-for="item in liveCourseList":key="item.id":label="item.courseName":value="item"/></el-select></el-form-item><el-form-item v-if="menu.parentId == 2" label="菜单名称"><el-selectv-model="menu.name"placeholder="请选择"@change="subjectChanged"><el-optionv-for="item in subjectList":key="item.id":label="item.title":value="item"/></el-select></el-form-item><el-form-item v-if="menu.parentId == 3" label="菜单名称"><el-input v-model="menu.name"/></el-form-item><el-form-item label="菜单类型"><el-radio-group v-model="menu.type"><el-radio label="view">链接</el-radio><el-radio label="click">事件</el-radio></el-radio-group></el-form-item><el-form-item v-if="menu.type == 'view'" label="链接"><el-input v-model="menu.url"/></el-form-item><el-form-item v-if="menu.type == 'click'" label="菜单KEY"><el-input v-model="menu.meunKey"/></el-form-item><el-form-item label="排序"><el-input v-model="menu.sort"/></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false" size="small">取 消</el-button><el-button type="primary" @click="saveOrUpdate()" size="small">确 定</el-button></span></el-dialog></div>
</template>
<script>
import menuApi from '@/api/wechat/menu'
//import liveCourseApi from '@/api/live/liveCourse'
import subjectApi from '@/api/vod/subject'
const defaultForm = {id: null,parentId: 1,name: '',nameId: null,sort: 1,type: 'view',meunKey: '',url: ''
}
export default {// 定义数据data() {return {list: [],liveCourseList: [],subjectList: [],dialogVisible: false,menu: defaultForm,saveBtnDisabled: false}},// 当页面加载时获取数据created() {this.fetchData()// this.fetchLiveCourse()this.fetchSubject()},methods: {// 调用api层获取数据库中的数据fetchData() {console.log('加载列表')menuApi.findMenuInfo().then(response => {this.list = response.dataconsole.log(this.list)})},// fetchLiveCourse() {//   liveCourseApi.findLatelyList().then(response => {//     this.liveCourseList = response.data//     this.liveCourseList.push({'id': 0, 'courseName': '全部列表'})//   })// },fetchSubject() {console.log('加载列表')subjectApi.getChildList(0).then(response => {this.subjectList = response.data})},syncMenu() {this.$confirm('你确定上传菜单吗, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {return menuApi.syncMenu();}).then((response) => {this.fetchData()this.$message.success(response.message)}).catch(error => {console.log('error', error)// 当取消时会进入catch语句:error = 'cancel'// 当后端服务抛出异常时:error = 'error'if (error === 'cancel') {this.$message.info('取消上传')}})},// 根据id删除数据removeDataById(id) {// debuggerthis.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => { // promise// 点击确定,远程调用ajaxreturn menuApi.removeById(id)}).then((response) => {this.fetchData(this.page)if (response.code) {this.$message({type: 'success',message: '删除成功!'})}}).catch(() => {this.$message({type: 'info',message: '已取消删除'})})},// -------------add(){this.dialogVisible = truethis.menu = Object.assign({}, defaultForm)},edit(id) {this.dialogVisible = truethis.fetchDataById(id)},fetchDataById(id) {menuApi.getById(id).then(response => {this.menu = response.data})},saveOrUpdate() {this.saveBtnDisabled = true // 防止表单重复提交if (!this.menu.id) {this.saveData()} else {this.updateData()}},// 新增saveData() {menuApi.save(this.menu).then(response => {if (response.code) {this.$message({type: 'success',message: response.message})this.dialogVisible = false;this.fetchData(this.page)}})},// 根据id更新记录updateData() {menuApi.updateById(this.menu).then(response => {if (response.code) {this.$message({type: 'success',message: response.message})this.dialogVisible = false;this.fetchData(this.page)}})},// 根据id查询记录fetchDataById(id) {menuApi.getById(id).then(response => {this.menu = response.data})},subjectChanged(item) {console.info(item)this.menu.name = item.titlethis.menu.url = '/course/' + item.id},liveCourseChanged(item) {console.info(item)this.menu.name = item.courseNameif(item.id == 0) {this.menu.url = '/live'} else {this.menu.url = '/liveInfo/' + item.id}}}
}
</script>
8、公众号菜单功能测试

(1)在手机公众号可以看到同步之后的菜单

image-20220304090729849

})
},

// -------------
add(){this.dialogVisible = truethis.menu = Object.assign({}, defaultForm)
},edit(id) {this.dialogVisible = truethis.fetchDataById(id)
},fetchDataById(id) {menuApi.getById(id).then(response => {this.menu = response.data})
},saveOrUpdate() {this.saveBtnDisabled = true // 防止表单重复提交if (!this.menu.id) {this.saveData()} else {this.updateData()}
},// 新增
saveData() {menuApi.save(this.menu).then(response => {if (response.code) {this.$message({type: 'success',message: response.message})this.dialogVisible = false;this.fetchData(this.page)}})
},// 根据id更新记录
updateData() {menuApi.updateById(this.menu).then(response => {if (response.code) {this.$message({type: 'success',message: response.message})this.dialogVisible = false;this.fetchData(this.page)}})
},// 根据id查询记录
fetchDataById(id) {menuApi.getById(id).then(response => {this.menu = response.data})
},subjectChanged(item) {console.info(item)this.menu.name = item.titlethis.menu.url = '/course/' + item.id
},liveCourseChanged(item) {console.info(item)this.menu.name = item.courseNameif(item.id == 0) {this.menu.url = '/live'} else {this.menu.url = '/liveInfo/' + item.id}}

}
}

#### 8、公众号菜单功能测试**(1)在手机公众号可以看到同步之后的菜单**[外链图片转存中...(img-vLZOSCfb-1658118481923)]

这篇关于硅谷课堂第十课-营销模块和公众号菜单管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/234204

相关文章

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

安全管理体系化的智慧油站开源了。

AI视频监控平台简介 AI视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上进行简单的操作,就可以实现全视频的接入及布控。摄像头管理模块用于多种终端设备、智能设备的接入及管理。平台支持包括摄像头等终端感知设备接入,为整个平台提

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Windows如何添加右键新建菜单

Windows如何添加右键新建菜单 文章目录 Windows如何添加右键新建菜单实验环境缘起以新建`.md`文件为例第一步第二步第三步 总结 实验环境 Windows7 缘起 因为我习惯用 Markdown 格式写文本,每次新建一个.txt后都要手动修改为.md,真的麻烦。如何在右键新建菜单中添加.md选项呢? 网上有很多方法,这些方法我都尝试了,要么太麻烦,要么不凑效

Jenkins构建Maven聚合工程,指定构建子模块

一、设置单独编译构建子模块 配置: 1、Root POM指向父pom.xml 2、Goals and options指定构建模块的参数: mvn -pl project1/project1-son -am clean package 单独构建project1-son项目以及它所依赖的其它项目。 说明: mvn clean package -pl 父级模块名/子模块名 -am参数

寻迹模块TCRT5000的应用原理和功能实现(基于STM32)

目录 概述 1 认识TCRT5000 1.1 模块介绍 1.2 电气特性 2 系统应用 2.1 系统架构 2.2 STM32Cube创建工程 3 功能实现 3.1 代码实现 3.2 源代码文件 4 功能测试 4.1 检测黑线状态 4.2 未检测黑线状态 概述 本文主要介绍TCRT5000模块的使用原理,包括该模块的硬件实现方式,电路实现原理,还使用STM32类