写在前面

谷粒学苑刚开始是从班助那边了解到的,学完springboot就可以去做了,但是我打开的时候写着的是微服务项目,我就有点迷糊了,我还没学spring cloud呢咋就能做了捏,所以后面我就先去做了瑞吉外卖这个项目,做完之后我才发现我这个顺序应该是对的,因为瑞吉外卖真的是spring boot最好的实战项目了,也很基础很入门,很适合刚刚接触整个项目流程的小白做。而这个谷粒学苑我大致看了一下,不同于瑞吉的最大区别是全栈开发,前端后端都有,真的像一个完完全全的项目,所以可能学起来会比较吃力而且周期也会比较长。但应该来说是收获满满的。我打算按照视频那样,以天数更新blog,做完几天的内容我就跟进这个笔记直到所有代码都完成。不是说我记了几天就是只做了那几天哈哈哈,可能我现实中的好几天成果才是我记录的一天而已,我也有学校的事情,各种别的事情要做,视频里的都是一天都在做这个项目二三十天就完成了。应该来说是很长的一个项目,争取暑假之前把它敲完(虽然我暑假还有数据结构要学,要做一下取舍…)加油吧!o( ̄▽ ̄)ブ

第一、二天(项目搭建以及讲师模块)

环境搭建

配置项

狠狠的吐槽:启动项目都花了我一天时间,都是版本惹得祸,记录一下,我换了老师给的仓库,以后记得换回来。springboot版本选择:2.2.1.RELEASE
还有数据库常见的问题,新版mysql我已经不知道踩了多少坑了,记录一下.新版必须加上useSSL=false!!!
springboot中application.properties的配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 指定端口
server.port=8001

# 服务名
spring.application.name=service-edu

# 环境设置dev、test、prod
spring.profiles.active=dev

# mysql连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8&useSSL=false
spring.datasource.username=root
spring.datasource.password=74922423zjn

#前端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

代码生成器

记录一下代码生成器的使用,以后可以直接套用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.atguigu.demo;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

/**
* @author
* @since 2023/12/13
*/
public class CodeGenerator {

@Test
public void run() {

// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();

// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("D:\\Codeidea\\guli_parent\\guli_parent\\service\\service_edu" + "/src/main/java");
gc.setAuthor("zjn");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.ID_WORKER_STR); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式

mpg.setGlobalConfig(gc);

// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8&useSSL=false");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("74922423zjn");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);

// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("eduservice"); //模块名
pc.setParent("com.atguigu");
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);

// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("edu_teacher");
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

mpg.setStrategy(strategy);


// 6、执行
mpg.execute();
}
}

讲师模块

讲师类(entity包)

没什么好说的,代码生成器生成的,注意的是逻辑删除(即不是真的从库中删除而是根据0或1来判断)
@TableField(fill = FieldFill.INSERT)每次自动写入添加时间(mp yyds!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* <p>
* 讲师
* </p>
*
* @author zjn
* @since 2023-05-25
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="EduTeacher对象", description="讲师")
public class EduTeacher implements Serializable {

private static final long serialVersionUID = 1L;

@ApiModelProperty(value = "讲师ID")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;

@ApiModelProperty(value = "讲师姓名")
private String name;

@ApiModelProperty(value = "讲师简介")
private String intro;

@ApiModelProperty(value = "讲师资历,一句话说明讲师")
private String career;

@ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
private Integer level;

@ApiModelProperty(value = "讲师头像")
private String avatar;

@ApiModelProperty(value = "排序")
private Integer sort;

@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
@TableLogic
private Boolean isDeleted;

@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;

@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}

mapper&&service包

一般来说很多方法需要写在service包下,然后在controller中调用,但现在代码量少,先全部写在controller中后面再调整
有些sql MybaitPlus实现不了就需要在mapper中自己写sql语句,后面写了再添加

controller包

业务层肯定都是细节
重点:多条件组合查询(动态sql)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package com.atguigu.eduservice.controller;


import com.atguigu.commonutils.R;
import com.atguigu.eduservice.entity.EduTeacher;
import com.atguigu.eduservice.entity.vo.TeacherQuery;
import com.atguigu.eduservice.service.EduTeacherService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;

/**
* <p>
* 讲师 前端控制器
* </p>
*
* @author zjn
* @since 2023-05-25
*/
@Api("讲师管理")
@RestController
@RequestMapping("/eduservice/teacher")
public class EduTeacherController {
@Autowired
private EduTeacherService teacherService;

//1查询讲师表所有数据
@ApiOperation("所有讲师列表")
@GetMapping("/findAll")
public R findAllTeacher() {
List<EduTeacher> list = teacherService.list(null);
return R.ok().data("items", list);
}

//2逻辑删除讲师方法
@ApiOperation("逻辑删除讲师")
@DeleteMapping("{id}")//id值需要通过路径进行传递(localhost:8001/edu/delete/1?status=0)问号前面的是路径
public R removeTeacher(@ApiParam(name = "id", value = "讲师ID", required = true) @PathVariable String id) {//@PathVariable获取路径中的id值
boolean flag = teacherService.removeById(id); //required表示这个参数是必填项
/* if (flag) {
return R.ok();
} else {
return R.error();
}*/
return flag ? R.ok() : R.error();
}

//3分页查询讲师的方法
//current 当前页
//limit 每页记录条数
@ApiOperation("分页查询讲师")
@GetMapping("pageTeacher/{current}/{limit}")
public R pageListTeacher(@PathVariable long current,
@PathVariable long limit) {
//创建Page对象
Page<EduTeacher> pageTeacher = new Page<>(current, limit);
//调用方法实现分页
//调用方法时候,底层封装,把分页所有数据都封装到pageTeacher对象里面
IPage<EduTeacher> page = teacherService.page(pageTeacher, null);

long total = pageTeacher.getTotal();//总记录数
List<EduTeacher> records = pageTeacher.getRecords();//数据List集合

/* Map map =new HashMap();
map.put("total",total);
map.put("records",records);
return R.ok().data(map);*/

return R.ok().data("total", total).data("records", records);

}


//4条件查询且分页的方法
@ApiOperation("多条件分页查询")
@PostMapping("pageTeacherCondition/{current}/{limit}")
public R pageTeacherCondition(@PathVariable long current, @PathVariable long limit,
@RequestBody(required = false) TeacherQuery teacherQuery) {//@RequestBody传json数据required = false参数值可以为空
//创建一个Page对象
Page<EduTeacher> pageTeacher = new Page<>(current, limit);
//构建条件
QueryWrapper<EduTeacher> queryWrapper = new QueryWrapper<>();

//多条件组合查询(动态sql)
//判断条件是否为空,如果不为空(前端调用了此条件)就拼接条件
String name = teacherQuery.getName();
Integer level = teacherQuery.getLevel();
String begin = teacherQuery.getBegin();
String end = teacherQuery.getEnd();
if (!StringUtils.isEmpty(name)) {
//构建条件,column传入的是数据库的字段名称,不是类的属性值
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_modified", end);
}

//调用方法实现条件查询分页
teacherService.page(pageTeacher, queryWrapper);
//获取查询的总记录数
long total = pageTeacher.getTotal();
//获取数据的List集合
List<EduTeacher> records = pageTeacher.getRecords();
return R.ok().data("total", total).data("records", records);
}

//添加讲师接口的方法
@ApiOperation("添加讲师")
@PostMapping("addTeacher")
public R addTeacher(@RequestBody EduTeacher eduTeacher) {
boolean flag = teacherService.save(eduTeacher);
/* if(flag){
return R.ok();
}else {
return R.error();
}*/
return flag ? R.ok() : R.error();
}

//根据讲师id进行查询
@ApiOperation("根据id查询讲师")
@GetMapping("getTeacher/{id}")
public R getTeacher(@PathVariable String id) {
EduTeacher eduTeacher = teacherService.getById(id);
return R.ok().data("teacher", eduTeacher);
}

//讲师修改功能
@ApiOperation("修改讲师信息")
@PutMapping("updateTeacher")
public R updateTeacher(@RequestBody EduTeacher eduTeacher) {
boolean flag = teacherService.updateById(eduTeacher);
return flag ? R.ok() : R.error();
}
}

配置类(config)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.atguigu.eduservice.config;

import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.atguigu.eduservice.mapper")
public class EduConfig {
/**
* 逻辑删除插件
*/
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}

/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}

Swagger(Api接口文档)

使用之前必须添加swagger依赖

1
2
3
4
5
6
7

<!--swagger 我这里写的是一般的依赖项,项目中版本被父类管理了-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>

接着就是配置类,里面的内容根据实际可修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.atguigu.servicebase;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration//配置类
@EnableSwagger2//swagger注解
public class SwaggerConfig {

@Bean
public Docket webApiConfig() {

return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}

private ApiInfo webApiInfo() {
return new ApiInfoBuilder()
.title("网站-课程中心API文档")
.description("本文档描述了课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("SunnyHerry", "http://atguigu.com", "1585592275@qq.com"))
.build();
}
}

其它功能

自动写入时间

mp YYDS!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.atguigu.servicebase.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component//配置类注解
public class MYMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {//这里的fieldName传入的是类的属性名称,不是数据库的字段名称
this.setFieldValByName("gmtCreate", new Date(), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}

@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtModified", new Date(), metaObject);

}
}

全局异常处理

第三天会有自定义异常处理以及特定异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.atguigu.servicebase.exceptionhandler;

import com.atguigu.commonutils.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice//这个注解内置了@ResponseBody,可以返回数据给前端(有时间感觉得回去学学spring切面编程)
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
//@ResponseBody
public R error(Exception e){
e.printStackTrace();
return R.error().message("执行了全局异常处理..");
}
}

前后端统一返回类(R)

首先定义一个静态状态码接口

1
2
3
4
5
6
package com.atguigu.commonutils;

public interface ResultCode {
public static Integer SUCCESS = 20000;//成功
public static Integer ERROR = 20001;//失败
}

然后就是R类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.atguigu.commonutils;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;

@Data
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<String, Object>();

//把构造方法私有化
private R() {
}

//链式编程,return this就可以使用链式编程

//成功的静态方法
public static R ok() {
R r = new R();
r.setSuccess(true);
r.setCode(ResultCode.SUCCESS);
r.setMessage("成功");
return r;
}

//失败的静态方法
public static R error(){
R r=new R();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage("失败");
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;
}
}

一二天总结

还是有很多点不足的地方,虽然就在刚开始的时候启动类报错找了很久,但是后面还是比较顺利的。总体来说刚开始的项目搭建和瑞吉比较大的区别就是整个项目的模块、包、类分的特别多。我不知道实际开发中是不是分着这么多,刚开始挺乱的,又要分清楚每个模块的含义还要记住哪些包放在哪个位置,还有很多maven要导不同的依赖还要关注依赖的引用关系,防止重复。可能就是对整个项目的架构还不太熟悉吧,讲师类基本上就是和瑞吉大概一致,特点就是用Swagger测试,还挺好用的。整理完笔记之后更清晰了,下几天好像就是学前端了,妈啊前端都要自己手敲了,真就全栈呗哈哈哈。下次见!