数据库搭建
创建数据库wunian_edu
,创建多张数据库表并插入一些数据。建表SQL如下:
CREATE TABLE `edu_chapter` (
`id` char(19) NOT NULL COMMENT '章节ID',
`course_id` char(19) NOT NULL COMMENT '课程ID',
`title` varchar(50) NOT NULL COMMENT '章节名称',
`sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '显示排序',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_course_id` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程';
CREATE TABLE `edu_course` (
`id` char(19) NOT NULL COMMENT '课程ID',
`teacher_id` char(19) NOT NULL COMMENT '课程讲师ID',
`subject_id` char(19) NOT NULL COMMENT '课程专业ID',
`subject_parent_id` char(19) NOT NULL COMMENT '课程专业父级ID',
`title` varchar(50) NOT NULL COMMENT '课程标题',
`price` decimal(10,4) unsigned NOT NULL DEFAULT '0.0000' COMMENT '课程销售价格,设置为0则可免费观看',
`lesson_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '总课时',
`cover` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '课程封面图片路径',
`buy_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '销售数量',
`view_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '浏览数量',
`version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁',
`status` varchar(10) NOT NULL DEFAULT 'Draft' COMMENT '课程状态 Draft未发布 Normal已发布',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_title` (`title`),
KEY `idx_subject_id` (`subject_id`),
KEY `idx_teacher_id` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程';
CREATE TABLE `edu_course_description` (
`id` char(19) NOT NULL COMMENT '课程ID',
`description` text COMMENT '课程简介',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程简介';
CREATE TABLE `edu_subject` (
`id` char(19) NOT NULL COMMENT '课程类别ID',
`title` varchar(10) NOT NULL COMMENT '类别名称',
`parent_id` char(19) NOT NULL DEFAULT '0' COMMENT '父ID',
`sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程科目';
CREATE TABLE `edu_teacher` (
`id` char(19) NOT NULL COMMENT '讲师ID',
`name` varchar(20) NOT NULL COMMENT '讲师姓名',
`intro` varchar(255) NOT NULL COMMENT '讲师资历,一句话说明讲师',
`career` text COMMENT '讲师简介',
`level` int(10) unsigned NOT NULL COMMENT '头衔 1高级讲师 2首席讲师',
`avatar` varchar(255) DEFAULT NULL COMMENT '讲师头像',
`sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='讲师';
CREATE TABLE `edu_video` (
`id` char(19) NOT NULL COMMENT '视频ID',
`course_id` char(19) NOT NULL COMMENT '课程ID',
`chapter_id` char(19) NOT NULL COMMENT '章节ID',
`title` varchar(50) NOT NULL COMMENT '节点名称',
`video_source_id` varchar(100) DEFAULT NULL COMMENT '云端视频资源',
`video_original_name` varchar(100) DEFAULT NULL COMMENT '原始文件名称',
`sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段',
`play_count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '播放次数',
`is_free` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否可以试听:0收费 1免费',
`duration` float NOT NULL DEFAULT '0' COMMENT '视频时长(秒)',
`status` varchar(20) NOT NULL DEFAULT '' COMMENT '视频状态',
`size` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '视频源文件大小(字节)',
`version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_course_id` (`course_id`),
KEY `idx_chapter_id` (`chapter_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程视频';
创建项目
使用IDEA创建SpringBoot项目,项目创建完成后,项目结构如下图 所示。
完善代码架构的编写
使用MyBatis-Plus代码自动生成工具来生成基本的CRUD业务代码,只需要一个配置方法,基本业务代码全部搞定。
自动生成代码配置类如下:
//自动生成代码
public class CodeGenerator {
public static void main(String[] args) {
//模块名
String moduleName="edu";
//1、 代码生成器
AutoGenerator mpg = new AutoGenerator();
//规则的配置
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath=System.getProperty("user.dir"); //获取当前项目的路径,这里指的是wunian_edu_api
gc.setOutputDir(projectPath+"/wunian-edu-edu/src/main/java");//输出目录
gc.setAuthor("wunian"); //配置文档注释@Author
gc.setOpen(false);//生成代码后不打开文件夹
gc.setFileOverride(false);//不覆盖之前生成的文件
gc.setServiceName("%sService");//把Service接口的首字母I去掉 IUserService ->UserService
gc.setIdType(IdType.ID_WORKER_STR);//主键策略
gc.setDateType(DateType.ONLY_DATE);//日期类型
gc.setSwagger2(true);//自动开启Swagger配置
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/wunian_"+moduleName+"?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(moduleName);
pc.setParent("com.wunian");
pc.setController("controller");//controller层包名
pc.setService("service");//service层包名
pc.setEntity("pojo");//实体类层包名
pc.setMapper("mapper");//dao层的包名
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
//要生成哪张表对应的类(重要,以后要改配置就改这里)
strategy.setInclude(moduleName+"_\\w*");//所有edu开头的表都自动生成
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表生成到实体类的策略,下划线转驼峰
strategy.setTablePrefix(pc.getModuleName()+"_");//表前缀 edu这个前缀不生成在类中 edu_teacher->Teacher
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库列名生成到实体类属性的策略,下划线转驼峰
strategy.setEntityLombokModel(true);//自动生成lombok注解
strategy.setLogicDeleteFieldName("is_deleted");//配置逻辑删除字段
strategy.setEntityBooleanColumnRemoveIsPrefix(true);//去掉布尔值的is_前缀
//自动填充策略 创建时间和更新时间
TableFill gmt_create=new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmt_modified=new TableFill("gmt_modified",FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills=new ArrayList<>();
tableFills.add(gmt_create);
tableFills.add(gmt_modified);
strategy.setTableFillList(tableFills);
//乐观锁
strategy.setVersionFieldName("version");//配置乐观锁字段
strategy.setRestControllerStyle(true);//restful风格的 api
strategy.setControllerMappingHyphenStyle(true);//使用_连接驼峰 /user/hello_world
mpg.setStrategy(strategy);
//2、执行代码生成器
mpg.execute();
}
}
配置Swagger进行测试
在edu模块中添加Swagger配置类,代码如下:
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket webApiConfig(){
//过滤掉 admin 下的请求
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}
@Bean
public Docket adminApiConfig(){
//只选取 /admin 下的请求
return new Docket(DocumentationType.SWAGGER_2)
.groupName("adminApi")
.apiInfo(adminApiInfo())
.select()
.paths(Predicates.and(PathSelectors.regex("/admin/.*")))
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("网站-课程中心API文档")
.description("本文档描述了课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("Coding", "http://icodingedu.com", "24736743@qq.com"))
.build();
}
private ApiInfo adminApiInfo(){
return new ApiInfoBuilder()
.title("后台管理系统-课程中心API文档")
.description("本文档描述了后台管理系统课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("Coding", "http://icodingedu.com", "24736743@qq.com"))
.build();
}
}
注意编写后端接口时,Swagger注释应该一并写好,尽量不要返工,一次性搞定接口文档的问题最好。
封装接口统一返回数据格式
真正的微服务是提供给多端使用的,项目中我们会将响应的数据封装成JSON返回,一般会将所有接口的数据格式统一,这样可以使前端对数据的操作更一致、轻松。
一般情况下,统一返回数据格式并不固定,只要能描述清楚返回的数据状态以及要返回的具体数据就行。但是一般会包含状态码、返回消息、数据这些内容。
例如,我们的系统要求返回的基本数据格式如下:
列表
{ "success": true, "code": 20000, "message": "成功", "data": { "items": [ { "id": "1", "name": "coding", "intro": "coding老师有点帅" } ] }
}
分页
{ "success": true, "code": 20000, "message": "成功", "data": { "total": 17, "rows": [ { "id": "1", "name": "coding", "intro": "coding老师有点帅" } ] } }
无返回数据
{ "success": true, "code": 20000, "message": "成功", "data": {} }
失败
{ "success": false, "code": 20001, "message": "失败", "data": {} }
因此,我们可以定义统一返回结果格式为:
{
"success": 布尔, //响应是否成功
"code": 数字, //响应码
"message": 字符串, //返回消息
"data": HashMap //返回数据,放在键值对中
}
在common模块中定义VO对象R,代码如下:
//无论什么接口,返回值永远是R
@Data
@ApiModel(value = "全局的统一返回结果")
public class R {
@ApiModelProperty(value = "是否成功")
private Boolean success;
@ApiModelProperty(value = "返回状态码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private Map<String,Object> data=new HashMap<>();
public R(){}
//ok
public static R ok(){
R r=new R();
r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
r.setCode(ResultCodeEnum.SUCCESS.getCode());
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
return r;
}
//error
public static R error(){
R r=new R();
r.setSuccess(ResultCodeEnum.UNKNOW_REASON.getSuccess());
r.setCode(ResultCodeEnum.UNKNOW_REASON.getCode());
r.setMessage(ResultCodeEnum.UNKNOW_REASON.getMessage());
return r;
}
//自定义错误码
public static R setResult(ResultCodeEnum resultCodeEnum){
R r=new R();
r.setSuccess(resultCodeEnum.getSuccess());
r.setCode(resultCodeEnum.getCode());
r.setMessage(resultCodeEnum.getMessage());
return r;
}
//以下方法用于链式编程
public R success(Boolean success){
this.setSuccess(success);
return this;
}
public R message(String message){
this.setMessage(message);
return this;
}
public R code(Integer code){
this.setCode(code);
return this;
}
public R data(String key,Object value){
this.data.put(key, value);
return this;
}
public R data(Map<String,Object> map){
this.setData(map);
return this;
}
}
分页查询和条件查询
service继承IService,查询字段可以单独定义为一个Query类,在service实现类中使用QueryWrapper对象来实现动态SQL,在实现类中可以使用baseMapper.selectPage(pageParam,queryWrapper)
方法来实现分页查询和条件查询。
TeacherQuery类代码如下:
@ApiModel(value = "Teacher查询对象",description = "讲师查询对象封装")
@Data
public class TeacherQuery implements Serializable {
private static final long serializableUID=1L;
@ApiModelProperty(value = "讲师名称,模糊查询")
private String name;
@ApiModelProperty(value = "头衔 1 高级讲师 2 顶级讲师")
private Integer level;
@ApiModelProperty(value = "入驻时间开始")
private String begin;//String 类型,前端传递的数据不需要做类型转换
@ApiModelProperty(value = "入驻时间结束")
private String end;
}
TeacherServiceImpl类代码如下:
@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements TeacherService {
//MP是让我们可以通过面向对象的方式编写sql
//pageParam 分页 teacherQuery 条件查询
@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);
}
}