项目简介
谷粒学院,是一个B2C模式的职业技能在线教育系统,分为前台用户系统和后台运营平台。
系统模块
权限:会员、系统运营
会员模块:门户、课程中心、用户中心、讲师、文章、问答
运营系统:会员管理、讲师管理、课程管理、文章资讯、问答管理、广告管理、统计分析、帮助中心、网站管理、短信管理、邮件管理、系统消息
插件功能:微信支付、微信登录、阿里云OSS、阿里云视频点播、阿里云短信
系统架构
架构设计需要考虑的几个方面:
性能:主要考虑访问频率,每个用户每天的访问次数。项目初始阶段用户的访问量并不大,如果考虑做运营推广,可能会迎来服务器访问量骤增,因此要考虑分布式部署,引入缓存。
可扩展性:系统功能会随着用户量的增加以及多变的互联网用户需求不断地扩展,因此考虑到系统的可扩展性的要求需要使用微服务架构,引入消息中间件
高可用:系统一旦宕机,将会带来不可挽回的损失,因此必须做负载均衡,甚至是异地多活这类复 杂的方案。如果数据丢失,修复将会非常麻烦,只能靠人工逐条修复,这个很难接受,因此需要考 虑存储高可靠。我们需要考虑多种异常情况:机器故障、机房故障,针对机器故障,我们需要设计MySQL 同机房主备方案;针对机房故障,我们需要设计 MySQL 跨机房同步方案。
安全性:系统的信息有一定的隐私性,例如用户的个人身份信息,不包含强隐私(例如玉照、情感)的信息,因此使用账号密码管理、数据库访问权限控制即可。
![](https://i-blog.csdnimg.cn/blog_migrate/4b7648660c2bb8142d0c88545787cf42.png)
模块架构
Service模块详解
Service-edu模块
-
resources 目录下创建文件 application.properties
-
创建 MP 代码生成器
-
编写后台管理 api 接口
@Autowired private TeacherService teacherService; //将数据库所有数据返回 @GetMapping public List<Teacher> list(){ return teacherService.list(null); } //根据id逻辑删除数据 @DeleteMapping("{id}") public boolean removeById(@PathVariable String id){ return teacherService.removeById(id); }
- MyBatis Plus常用插件:乐观锁、分页、逻辑删除、SQL 执行性能分析插件
乐观锁:
- 多线程的情况下,实现最基本的CAS操作
- 当数据库中存在version字段的时候,修改的时候会自动把该字段的verison的值加1
- 如果无version字段,或者version字段的值为null,都不会有影响,不会导致程序报错
分页插件:
- 为方法提供分页实现
逻辑删除插件:
- 向容器中加入ISqlInjector类
- 数据库增加字段deleted
- entity 增加字段deleted
- 配置文件增加(默认值为下面的,可写可不写)
SQL执行性能分析插件:
- 可获取查询效率
统一返回数据格式
项目中将响应封装成json格式返回。
统一返回的数据包含状态码、返回消息、数据这几部分内容。
实现步骤:
- 创建结果统一返回类
-
在 common 模块下创建子模块 common-utils
-
创建接口定义返回码
-
创建结果类
-
-
统一返回结果使用
-
在 service 模块中添加依赖
-
修改 Controller 中的返回结果
-
分页接口开发
- MyBatisPlusConfig中配置分页插件
@Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }
- 分页Controller方法
@ApiOperation(value = "分页讲师列表") @GetMapping("{page}/{limit}") public R pageList( @ApiParam(name = "page", value = "当前页码", required = true) @PathVariable Long page, @ApiParam(name = "limit", value = "每页记录数", required = true) @PathVariable Long limit){ Page<Teacher> pageParam = new Page<>(page, limit); teacherService.page(pageParam, null); List<Teacher> records = pageParam.getRecords(); long total = pageParam.getTotal(); return R.ok().data("total", total).data("rows", records); }
条件查询接口开发
- 创建查询对象
@ApiModel(value = "Teacher查询对象", description = "讲师查询对象封装") @Data public class TeacherQuery implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "教师名称,模糊查询") private String name; @ApiModelProperty(value = "头衔 1高级讲师 2首席讲师") private Integer level; @ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10") private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换 @ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10") private String end; }
- service
接口:
package com.guli.edu.service; public interface TeacherService extends IService<Teacher> { void pageQuery(Page<Teacher> pageParam, TeacherQuery teacherQuery); }
实现:
package com.guli.edu.service.impl; @Service public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements TeacherService { @Override public void pageQuery(Page<Teacher> pageParam, TeacherQuery teacherQuery) { QueryWrapper<Teacher> queryWrapper = new QueryWrapper<>(); queryWrapper.orderByAsc("sort"); if (teacherQuery == null){ baseMapper.selectPage(pageParam, queryWrapper); return; } String name = teacherQuery.getName(); Integer level = teacherQuery.getLevel(); String begin = teacherQuery.getBegin(); String end = teacherQuery.getEnd(); if (!StringUtils.isEmpty(name)) { queryWrapper.like("name", name); } if (!StringUtils.isEmpty(level) ) { queryWrapper.eq("level", level); } if (!StringUtils.isEmpty(begin)) { queryWrapper.ge("gmt_create", begin); } if (!StringUtils.isEmpty(end)) { queryWrapper.le("gmt_create", end); } baseMapper.selectPage(pageParam, queryWrapper); } }
- controller
@ApiOperation(value = "分页讲师列表") @GetMapping("{page}/{limit}") public R pageQuery( @ApiParam(name = "page", value = "当前页码", required = true) @PathVariable Long page, @ApiParam(name = "limit", value = "每页记录数", required = true) 456789 @PathVariable Long limit, @ApiParam(name = "teacherQuery", value = "查询对象", required = false) TeacherQuery teacherQuery){ Page<Teacher> pageParam = new Page<>(page, limit); teacherService.pageQuery(pageParam, teacherQuery); List<Teacher> records = pageParam.getRecords(); long total = pageParam.getTotal(); return R.ok().data("total", total).data("rows", records); }
自动填充封装
- 在service-base模块中添加
@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("gmtCreate", new Date(), metaObject); this.setFieldValByName("gmtModified", new Date(), metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("gmtModified", new Date(), metaObject); } }
- 在实体类中添加自动填充注解
@ApiModelProperty(value="创建时间") @TableField(fill=FieldFill.INSERT) private Date gmtCreate; @ApiModelProperty(value="更新时间") @TableField(fill=FieldFill.INSERT_UPDATE) private Date gmtModified;
- Controller方法定义
@ApiOperation(value = "新增讲师") @PostMapping public R save( @ApiParam(name = "teacher", value = "讲师对象", required = true) @RequestBody Teacher teacher){ teacherService.save(teacher); return R.ok(); }
- 根据id查询
@ApiOperation(value = "根据ID查询讲师") @GetMapping("{id}") public R getById( @ApiParam(name = "id", value = "讲师ID", required = true) @PathVariable String id){ Teacher teacher = teacherService.getById(id); return R.ok().data("item", teacher); }
- 根据id修改
@ApiOperation(value = "根据ID修改讲师") @PutMapping("{id}") public R updateById( @ApiParam(name = "id", value = "讲师ID", required = true) @PathVariable String id, @ApiParam(name = "teacher", value = "讲师对象", required = true) @RequestBody Teacher teacher){ teacher.setId(id); teacherService.updateById(teacher); return R.ok(); }
统一异常处理
- 创建统一异常处理器
/** * 统一异常处理类 */ @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public R error(Exception e){ e.printStackTrace(); return R.error(); } }
统一日志处理
- 配置日志级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
- 配置Logback日志
- 将错误日志输出到文件
- 将日志堆栈信息输出到文件
前端开发
ES6基本语法
- let声明变量:var声明的变量没有局部作用域,可以声明多次;let声明的变量有局部作用域,只能声明一次。
- const声明变量:只读变量。
Vue.js
构建用户界面的渐进式框架
Node.js
运行在服务端的JavaScript
几个重要模块
service-edu
主要实现讲师、课程、文章、评论的增删改查功能。
查询有三种:分页查询、条件查询、根据id查询(适用于页面回显)。
删除主要为逻辑删除。(在数据库表中多定义一个flag字段)
修改主要为普通的update的SQL语句。
增加主要为普通的insert的SQL语句。
在后台系统中我们需要实现讲师添加、删除以及讲师信息修改、查询功能,课程添加、删除、课程信息修改功能,文章添加、文章修改功能,评论添加、修改功能。
在前台系统中我们需要实现分页展示讲师列表功能、分页展示课程列表功能、根据课程获取对应评论功能、分页展示文章功能。
service-oss
主要实现基于阿里云OSS的图片上传功能。
这个上传功能的实现很简单,通过创建上传文件流,然后获取文件名称,为上传文件命名:日期+UUID(唯一标识符),然后返回上传文件的url。
service-vod
主要实现基于阿里云视频点播服务的视频添加和视频播放功能。
这个功能的实现也不难,主要通过阿里云视频点播服务中的sdk,将本地视频上传至阿里云,然后阿里云会为上传视频配置唯一id,获取id可以播放对应的视频。
service-msm
主要实现基于登录注册功能。
首先在注册功能中,我们需要填写用户昵称、手机号、邮箱、密码,并将数据封装成RegisterVo类保存于数据库的ucenter表中。
其次在登录功能中,有两种登录方式,一种是根据用户手机号获取验证码登录,一种是微信登录。
下面重点讲常见的登录注册方式。
1.单一服务器模式
2.SSO模式(单点登录模式)
3.Token
优点:- 无状态: token无状态,session有状态的
- 基于标准化: 你的API可以采用标准化的 JSON Web Token (JWT)
缺点:- 占用带宽
- 无法在服务器端销毁
4.JWT身份验证
JWT包括:JWT头、有效载荷、哈希签名。
5.OAuth2.0
6.微信登录如何实现?
service-order
基于微信支付sdk。
课程支付步骤:
-
课程分为免费课程和付费课程,如果是免费课程可以直接观看,如果是付费观看的课程,用户需下单支付后才可以观看
-
如果是免费课程,在用户选择课程,进入到课程详情页面时候,直接显示 “ 立即观看 ” ,用户点击立 即观看,可以切换到播放列表进行视频播放
-
如果是付费课程,在用户选择课程,进入到课程详情页面时候,会显示 “ 立即购买 ”
-
点击 “ 立即购买 ” ,会生成课程的订单,跳转到订单页面
-
点击 “ 去支付 ” ,会跳转到支付页面,生成微信扫描的二维码
-
使用微信扫描支付后,会跳转回到课程详情页面,同时显示 “ 立即观看 ”
-