项目开源代码
项目视频功能演示
网页部分功能实现
微信公众号部分功能实现
总体功能如下图所示,包含面向管理的网页平台、面向用户的微信公众号:
1、项目开发工具
持久层框架——MyBatis-plus
官网:https://baomidou.com/
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
1.1、特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 支持关键词自动转义:支持数据库关键词(order、key…)自动转义,还可自定义关键词
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
- 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击
MyBatis-plus封装了Mapper注入的过程,以及通用的CURD操作、分页查询操作,并通过条件构造器对查询条件进行封装,简化了接口的实现。
1.2、MyBatis-Plus实现CRUD操作
Mybatis-Plus封装了面向Dao的BaseMapper以及面向业务逻辑的IService,封装了一系列数据库操作方法。
-
通用 Service CRUD 封装IService 接口
get
查询单行
remove
删除
update
修改
list
查询集合
page
分页 -
通用 CRUD 封装BaseMapper 接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis内部对象注入容器。
insert
插入
delete
删除
update
修改
select
查询
Mybatis-Plus主键策略
(1)ID_WORKER
MyBatis-Plus默认的主键策略是:ID_WORKER 全局唯一ID
(2)自增策略
-
要想主键自增需要配置如下主键策略
-
需要在创建数据表的时候设置主键自增
实体字段中配置 @TableId(type = IdType.AUTO)
@TableId(type = IdType.AUTO)
private Long id;
逻辑删除
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
(1)数据库中添加 deleted字段
ALTER TABLE `user` ADD COLUMN `deleted` boolean
(2)实体类添加deleted字段
并加上 @TableLogic 注解
@TableLogic
private Integer deleted;
1.3、MyBatis-Plus条件构造器
Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : Entity 对象封装操作类,不是用lambda语法
UpdateWrapper : Update 条件封装,用于Entity对象更新操作
AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
注意:以下条件构造器的方法入参中的 column
均表示数据库字段
1.3.1、QueryWrapper 使用
(1)ge、gt、le、lt
@Test
public void testSelect() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", 28);
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
(2)eq、ne
注意:seletOne返回的是一条实体记录,当出现多条时会报错
@Test
public void testSelectOne() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "Tom");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name = ?
(3)like、likeLeft、likeRight
selectMaps返回Map集合列表
@Test
public void testSelectMaps() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.like("name", "e")
.likeRight("email", "t");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);//返回值是Map列表
maps.forEach(System.out::println);
}
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND name LIKE ? AND email LIKE ?
(4)orderByDesc、orderByAsc
@Test
public void testSelectListOrderBy() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 ORDER BY id DESC
1.3.2、LambdaQueryWrapper 使用
@Test
public void testLambdaQuery() {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getAge,30);
queryWrapper.like(User::getName,"张");
List<User> list = userMapper.selectList(queryWrapper);
System.out.println(list);
}
SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND age = ? AND name LIKE ?
后端
SpringBoot
简化新Spring应用的初始搭建以及开发过程
SpringCloud
Spring Cloud Alibaba Nacos
Nacos有注册中心和配置中心的作用,而项目中主要涉及其注册中心的架构。
如下图所示,Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给 Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存。
在项目开发中需要把所有涉及的微服务注册到注册中心,下面以service_vod微服务为例,其他模块注册步骤相同。
配置前准备工作
- 首先下载nacos客户端,随后进行服务启动。
- 启动方式,cmd打开,执行命令: startup.cmd -m standalone。
- 访问:http://localhost:8848/nacos
- 用户名密码:nacos/nacos
在service模块配置pom
① 配置Nacos客户端的pom依赖
<!-- 服务注册 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
② 配置service_vod
配置application.properties,在客户端微服务中添加注册Nacos服务的配置信息
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
③ 添加Nacos客户端注解
在service_vod微服务启动类中添加注解
@EnableDiscoveryClient
启动客户端微服务
启动注册中心
启动已注册的微服务,可以在Nacos服务列表中看到被注册的微服务,如下图所示。
Spring Cloud Gateway
网关提供API全托管服务,丰富的API管理功能,辅助企业管理大规模的API,以降低管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等功能。一般来说网关对外暴露的URL或者接口信息,我们统称为路由信息。网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务,使用网关有诸多好处:
(1)简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;
(2)降低函数间的耦合度。 一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性
(3)解放开发人员把精力专注于业务逻辑的实现。由网关统一实现服务路由(灰度与ABTest)、负载均衡、访问控制、流控熔断降级等非业务相关功能,而不需要每个服务 API 实现时都去考虑。
Spring cloud Gateway发出请求。然后再由Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway web handler。Handler再通过指定的过滤器链将请求发送到实际的服务执行业务逻辑,然后返回。一个简易的实现过程如下图所示。
在项目中实现网关的配置,同时网关可以用来解决跨域问题。
创建网关模块
(1)在ggkt_parent下创建service_gateway
(2)引入网关依赖
<!-- 网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
(3)创建启动类
(4)配置路由规则
编写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
spring.cloud.gateway.routes[0].id=service-vod
#设置路由的uri
spring.cloud.gateway.routes[0].uri=lb://service-vod
#设置路由断言,代理servicerId为auth-service的/auth/路径
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标签的去掉
Spring Cloud Feign
在实际项目开发中,某一种微服务可能需要调用其他微服务的接口获取相关信息,举例来说,在营销管理模块中需要获取已经使用到的优惠券列表,其中需要查询用户信息,则需要实现对service_user的接口调用。此时可以利用Spring Cloud Feign实现不同微服务之间的远程调用。
具体实现方式:
① 创建模块定义远程接口
首先创建远程调用接口模块service_user_client:
ggkt_parent -> service_client -> service_user_client
② service_client引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<scope>provided </scope>
</dependency>```
③ 定义远程调用的接口
@FeignClient(value = "service-user")
public interface UserInfoFeignClient {
@GetMapping("/admin/user/userInfo/inner/getById/{id}")
UserInfo getById(@PathVariable Long id);
}
④ 在需要调用其他接口的模块service_activity引入依赖
<dependencies>
<dependency>
<groupId>com.work</groupId>
<artifactId>service_user_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
⑤ service_activity启动类中添加注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.work")
@ComponentScan(basePackages = "com.work")
public class ServiceActivityApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceActivityApplication.class, args);
}
}
Lombok插件
Lombok用来简化实体类,需要安装Lombok插件,安装后引入依赖,Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。使用时在实体类加@Data注解。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
MyBatis-Plus代码生成器
用来生成一系列工程文件。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
Swagger
用于生成、描述、调用和可视化 RESTful 风格的 Web 服务的接口文档,可以根据在代码中使用自定义的注解来生成接口文档,适用于前后端分离项目。
访问路径:http://localhost:端口号/swagger-ui.html
常用注解使用:
@Api: 用于类,标识这个类是swagger的资源
@ApiOperation: 用于方法,描述 Controller类中的 method接口
@ApiParam: 用于参数,单个参数描述,与 @ApiImplicitParam不同的是,他是写在参数左侧的。如( @ApiParam(name="username",value="用户名")Stringusername)
@ApiModel: 用于类,表示对类进行说明,用于参数用实体类接收
@ApiProperty:用于方法,字段,表示对model属性的说明或者数据操作更改
实现效果:
EasyExcel
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
在课程分类管理模块,实现对数据的导入导出功能,这一功能通过EasyExcel实现。
1. EasyExcel写操作
(1)pom中引入xml相关依赖
(2)创建实体类
设置表头和添加的数据字段
@Data
public class SubjectEeVo {
@ExcelProperty(value = "id" ,index = 0)
private Long id;
@ExcelProperty(value = "课程分类名称" ,index = 1)
private String title;
@ExcelProperty(value = "上级id" ,index = 2)
private Long parentId;
@ExcelProperty(value = "排序" ,index = 3)
private Integer sort;
}
(3)实现写操作
创建方法循环设置要添加到Excel的数据
//循环设置要添加的数据,最终封装到list集合中
//课程分类导出
@Override
public void exportData(HttpServletResponse response) {
try {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("课程分类", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");
List<Subject> dictList = baseMapper.selectList(null);
List<SubjectEeVo> dictVoList = new ArrayList<>(dictList.size());
for(Subject dict : dictList) {
SubjectEeVo dictVo = new SubjectEeVo();
BeanUtils.copyProperties(dict,dictVo);
dictVoList.add(dictVo);
}
EasyExcel.write(response.getOutputStream(), SubjectEeVo.class).sheet("课程分类").doWrite(dictVoList);
} catch (IOException e) {
e.printStackTrace();
}
}
最终实现效果
2. EasyExcel读操作
(1)创建读取操作的监听器
@Component
public class SubjectListener extends AnalysisEventListener<SubjectEeVo> {
@Autowired
private SubjectMapper dictMapper;
//一行一行读取
@Override
public void invoke(SubjectEeVo subjectEeVo, AnalysisContext analysisContext) {
//调用方法添加数据库
Subject subject = new Subject();
BeanUtils.copyProperties(subjectEeVo,subject);
dictMapper.insert(subject);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
(3)调用实现最终的读取
@Override
public void importData(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(),
SubjectEeVo.class,subjectListener).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
前端
Vue.js:web 界面的渐进式框架
Node.js: JavaScript 运行环境
Axios:Axios 是一个基于 promise 的 HTTP 库
NPM:包管理器
Babel:转码器
Webpack:打包工具
微信公众号端
腾讯云对象存储与视频点播
欢拓云直播:直播平台
内网穿透
2、项目数据库设计及工程目录结构
数据库实现
工程实际结构
模块说明
3、通用开发步骤
3.1 后端接口开发
后端接口实现基于SpringBoot的Maven工程。
① 通过代码生成器生成文件基本结构
② 添加接口开发所需的依赖至 pom
文件中
③ 创建启动类,添加所需注解。
如: @SpringBootApplication
@MapperScan(“com.work.glkt.vod.mapper”)
@ComponentScan(basePackages = “com.work”)
@EnableDiscoveryClient
④ 在Resourse中添加配置文件applications.property
⑤ 编写接口
3.2 前端开发
前端开发采用vue-admin-template模板框架,基于vue+elementUI。结构如下图所示:
├── build # 构建相关
├── mock # 项目mock 模拟数据
├── public # 静态资源
│ │── favicon.ico # favicon图标
│ └── index.html # html模板
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── icons # 项目所有 svg icons
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
│ └── settings.js # 配置文件
├── tests # 测试
├── .env.xxx # 环境变量配置
├── .eslintrc.js # eslint 配置项
├── .babelrc # babel-loader 配置
├── .travis.yml # 自动化CI配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
- 1.在router路由index.js文件中添加路由信息:
{
path: '/vod',
component: Layout,
redirect: '/vod/teacher/list',
name: 'vod',
meta: { title: '讲师管理', icon: 'el-icon-s-help' },
children: [
{
path: 'teacher/list',
name: 'TeacherList',
component: () => import('@/views/vod/teacher/list'),
meta: { title: '讲师列表', icon: 'table' }
},
{
path: 'teacher/create',
name: 'TeacherCreate',
component: () => import('@/views/vod/teacher/form'),
meta: { title: '添加讲师', icon: 'tree' }
}
- 2.在view中添加路由跳转页面文件
list.vue
和form.vue
。
- 3.在api中定义需要调用的接口,定义路径、参数等信息。例如:
//讲师删除
removeTeacherId(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
//讲师添加
saveTeacher(teacher) {
return request({
url: `${api_name}/saveTeacher`,
method: 'post',
data:teacher
})
}
- 4.在vue页面实现对接口的前端实现。
4、网页平台功能开发
5、微信公众号平台开发
6、令人心痛的Bug及解决办法
6.1 后端接口开发
1.在新版的MyBatis-Plus中分页插件更新了配置方法,使用以前的方法会报错。
2.注意接口的GET/POST等方法不要写错。
3.在实现service方法时,若需要对数据库中的数据进行访问,需要进行非空判断,否则报错。
4.登录改为本地时,跨域请求一直解决不了。
解决方法:在使用网关解决跨域请求前,采用@CrossOrigin注解暂时性解决跨域问题。注意Get/Post的Mapping注解不要写错。
5.报全局异常错误。
解决办法:因为maven的加载机制,默认在src/main/java中只会加载java文件,需要把xml文件放到resource中。
6.错误信息:Your ApplicationContext is unlikely to start due to a @ComponentScan of the default package.
解决办法:springboot主函数在src/main/java根目录下时,会默认扫描所有的包,提示程序不希望这么做,加上@ComponentScan("zxw")限制一下扫描包的范围就可以了
7.采用Feign时,注入接口失败,检查了@EnableFeignClients(basePackages = “com.work”);@ComponentScan(basePackages = “com.work”)都没问题,仍然报错注入Bean失败。
解决办法:在远程调用的接口中,如遇到@PathVariable注释等局部变量,应在括号中注明value值,即@PathVariable(value = "id")。
6.2 前端开发
1.在将es6转成es5时,安装babel用vscodoe使用失败,需要在shell里更改权限。
2.vue-mobile模板启动报错
报错信息:Node sass does not yet support your current environment: Windows 64-bit with Unsupported runtime
解决:在package.json中改node-sass版本
6.3 其他
1.测试模板消息时,显示该用户未关注,但实际已经关注了测试号。
解决办法:在微信端管理部分把用户移除,重新关注,多刷新几次。
2.在检查了所有内网穿透域名设置无误等情况下,微信公众号内访问任何页面都显示whitelabel error page。
解决办法:在同步菜单中view.put()中加前端访问地址,重启后端接口程序,最重要的是要在网页部分点击同步菜单,数据才会进行刷新。
3.涉及头像与视频等文件上传失败。
解决办法:由于采用了网关,后续需要在前端的view文件中将相应的端口号均改为8333,与网关对应。
4.关于依赖爆红的问题:
- 解决办法:
1.在idea的settings中检查Maven的加载路径以及Repository位置是否与自己的匹配,这里就是错的,实际上我的Maven在D盘。
2.*注意!*一定要检查自己的idea版本与安装的Maven版本是否匹配,如我安装的是idea2017,而与之匹配的Maven版本为3.5.*,我原来安装的是3.8.6,导致报错。
3.查找其他的依赖引入语句。