SpringBoot SpringBoot项目建立 新建项目,勾选依赖项,如:
Developer Tools中的Lombok(通过注释简化java开发)
Web中的Spring Web(相当于SpringMVC)
SQL中的Mybatis Framework(Mybatis的框架)和MySQL Driver(MySQL的驱动)
数据库设计 通过Navicat创造新的MySQL数据库,并设计表格的名称,类型,以便于后端拿数据
SpringBoot各级设计 配置类编写 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 spring: application: name: novel server: port: 80 --- spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/novel?useUnicode=true&characterEncoding=utf-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC username: root password: Tec@258011 --- novel: cors: allow-origins: - http://localhost:1024 - http://localhost:8888 - http://localhost:8080 --- mybatis-plus: mapper-locations: classpath:/mapper/*.xml mybatis: configuration: mapUnderscoreToCamelCase=true: spring: jackson: generator: write_numbers_as_strings: true
Dao层编写 数据访问对象,负责封装对数据库的CRUD操作,与数据库进行交互,一般是mapper写接口,xml文件写sql语句的形式。
包括entity层(实体层) 编写以及Mapper层 编写
entity层(实体层) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @TableName("home_book") public class HomeBook implements Serializable { private static final long serialVersionUID = 1L ; @TableId(value = "id", type = IdType.AUTO) private Long id; private Integer type; private Integer sort; private Long bookId; private LocalDateTime createTime; private LocalDateTime updateTime; }
Mapper层 不要忘了在启动类下@MapperScan(“com.tec.vuepractice.dao.mapper”),这样在包扫描的时候就不会漏掉mapper层
1 2 3 public interface HomeBookMapper extends BaseMapper <HomeBook> {}
Service层编写+Impl(实现层) 更加关注业务逻辑,是业务处理层,将manager组合过的操作和业务逻辑组合在一起,再封装成业务操作。
service层 1 2 3 4 5 6 7 8 9 public interface HomeService { RestResp<List<HomeBookRespDto>> listHomeBooks () ; }
serviceImpl层 1 2 3 4 5 6 7 8 9 10 11 @Service @RequiredArgsConstructor public class HomeServiceImpl implements HomeService { private final HomeBookCacheManager homeBookCacheManager; @Override public RestResp<List<HomeBookRespDto>> listHomeBooks () { return RestResp.ok(homeBookCacheManager.listHomeBooks()); } }
Controller层编写 主要负责接受前台的数据和请求,并且在底层处理完之后把结果返回回去,一般不能写业务逻辑在这一层,因为第一造成了不可复用,第二以后的维护困难,第三这一层没有上层,如果给用户返回了奇怪的错误信息将会非常丑陋。
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 @RestController @RequestMapping(ApiRouterConsts.API_FRONT_HOME_URL_PREFIX) @RequiredArgsConstructor public class HomeController { private final HomeService homeService; @GetMapping("books") public RestResp<List<HomeBookRespDto>> listHomeBooks () { return homeService.listHomeBooks(); } @GetMapping("content/{chapterId}") public RestResp<BookContentAboutRespDto> getBookContentAbout (@PathVariable("chapterId") Long chapterId) { log.info("查询方法调用之前,参数id={}" , chapterId); return bookService.getBookContentAbout(chapterId); }
Dto层编写 传输对象,一般是把数据库表封装成对象,表的各个字段就是该对象的各个变量
包括req层 (Http 请求数据封装)resp层 (Http 响应数据封装)
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 @Data @NoArgsConstructor @AllArgsConstructor @Builder public class BookInfoRespDto { @Schema(description = "小说ID") private Long id; @Schema(description = "类别ID") private Long categoryId; @Schema(description = "类别名") private String categoryName; @Schema(description = "小说封面地址") private String picUrl; @Schema(description = "小说名") private String bookName; @Schema(description = "作家id") private Long authorId; @Schema(description = "作家名") private String authorName; @Schema(description = "书籍描述") private String bookDesc; @Schema(description = "书籍状态;0-连载中 1-已完结") private Integer bookStatus; @Schema(description = "点击量") private Long visitCount; @Schema(description = "总字数") private Integer wordCount; @Schema(description = "评论数") private Integer commentCount; @Schema(description = "首章节ID") private Long firstChapterId; @Schema(description = "最新章节ID") private Long lastChapterId; @Schema(description = "最新章节名") private String lastChapterName; @Schema(description = "最新章节更新时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime updateTime; }
manager层编写 负责将Dao层中的数据库操作组合复用,主要是一些缓存方案,中间件的处理,以及对第三方平台封装的层。
cache层编写 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 @Component @RequiredArgsConstructor public class HomeBookCacheManager { private final HomeBookMapper homeBookMapper; private final BookInfoMapper bookInfoMapper; public List<HomeBookRespDto> listHomeBooks () { QueryWrapper<HomeBook> queryWrapper = new QueryWrapper <>(); queryWrapper.orderByAsc(DatabaseConsts.CommonColumnEnum.SORT.getName()); List<HomeBook> homeBooks = homeBookMapper.selectList(queryWrapper); if (!CollectionUtils.isEmpty(homeBooks)) { List<Long> bookIds = homeBooks.stream() .map(HomeBook::getBookId) .toList(); QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper <>(); bookInfoQueryWrapper.in(DatabaseConsts.CommonColumnEnum.ID.getName(), bookIds); List<BookInfo> bookInfos = bookInfoMapper.selectList(bookInfoQueryWrapper); if (!CollectionUtils.isEmpty(bookInfos)) { Map<Long, BookInfo> bookInfoMap = bookInfos.stream() .collect(Collectors.toMap(BookInfo::getId, Function.identity())); return homeBooks.stream().map(v -> { BookInfo bookInfo = bookInfoMap.get(v.getBookId()); HomeBookRespDto bookRespDto = new HomeBookRespDto (); bookRespDto.setType(v.getType()); bookRespDto.setBookId(v.getBookId()); bookRespDto.setBookName(bookInfo.getBookName()); bookRespDto.setPicUrl(bookInfo.getPicUrl()); bookRespDto.setAuthorName(bookInfo.getAuthorName()); bookRespDto.setBookDesc(bookInfo.getBookDesc()); return bookRespDto; }).toList(); } } return Collections.emptyList(); } }
一般的业务的写法 (自己写的,对以后的业务编写挺有参考意义的,感动QAQ)
上面的manager层编写业务的写法主要是在使用缓存 时使用
一般在写业务逻辑的时候还是在service层下的Impl层 比较常用,下面是该业务的常规写法
mapper层 先准备好需要的mapper接口
HomeBookMapper 1 2 public interface HomeBookMapper extends BaseMapper <HomeBook> {}
BookInfoMapper 1 2 3 public interface BookInfoMapper extends BaseMapper <BookInfo> {}
service层 然后在service层下定义好方法
1 2 3 4 public interface HomeService extends IService <HomeBook> { List<HomeBookRespDto> listHomeBooks () ; }
impl层 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 @Service @RequiredArgsConstructor public class HomeServiceImpl extends ServiceImpl <HomeBookMapper, HomeBook> implements HomeService { @Resource private final HomeBookMapper homeBookMapper; @Resource private final BookInfoMapper bookInfoMapper; @Override public List<HomeBookRespDto> listHomeBooks () { QueryWrapper<HomeBook> queryWrapper = new QueryWrapper <>(); queryWrapper.orderByAsc(DatabaseConsts.CommonColumnEnum.SORT.getName()); List<HomeBook> homeBooks = homeBookMapper.selectList(queryWrapper); if (!CollectionUtils.isEmpty(homeBooks)) { List<Long> bookIds = homeBooks.stream() .map(HomeBook::getBookId) .toList(); QueryWrapper<BookInfo> bookInfoQueryWrapper = new QueryWrapper <>(); bookInfoQueryWrapper.in(DatabaseConsts.CommonColumnEnum.ID.getName(), bookIds); List<BookInfo> bookInfos = bookInfoMapper.selectList(bookInfoQueryWrapper); if (!CollectionUtils.isEmpty(bookInfos)) { Map<Long, BookInfo> bookInfoMap = bookInfos.stream() .collect(Collectors.toMap(BookInfo::getId, Function.identity())); return homeBooks.stream().map(v -> { BookInfo bookInfo = bookInfoMap.get(v.getBookId()); HomeBookRespDto bookRespDto = new HomeBookRespDto (); bookRespDto.setType(v.getType()); bookRespDto.setBookId(v.getBookId()); bookRespDto.setBookName(bookInfo.getBookName()); bookRespDto.setPicUrl(bookInfo.getPicUrl()); bookRespDto.setAuthorName(bookInfo.getAuthorName()); bookRespDto.setBookDesc(bookInfo.getBookDesc()); return bookRespDto; }).toList(); } } return Collections.emptyList(); } }
衍生QueryWrapper的知识点 orderByAsc:实现递增排序
orderByDesc:实现递减排序
衍生BaseMapper接口CRUD的知识点 引入:extends
BaseMapper
selectlist:根据 entity 条件,查询全部记录 selectOne:根据 entity 条件,查询一条记录
衍生Service接口CRUD 引入implements
IService(T:实体层)
衍生的工具类 CollectionUtils.isEmpty(list): false
CollectionUtils.isNotEmpty(list): true
上面的!CollectionUtils.isEmpty(bookInfos)其实相当于第二种,也同时是第一种判断为flase后相反(!)为true
衍生的final的作用 在这里将 HomeBookMapper 声明为 final 主要是出于两个原因: 1、线程安全:当我们将一个对象声明为 final 时,它的引用就不能再指向其他对象,也就是说这个对象是不可变的,这可以避免多线程访问时发生冲突。在Spring中,@Autowired 自动注入的属性默认是可变的,将其声明为 final 可以使其不可变,从而提高线程安全性。 2、提高可读性:使用 final 关键字来声明变量,可以让人们更加明确地知道这个变量不会被修改,也可以让编译器进行更多的优化,提高代码的性能。此外,通过将类中的属性声明为 final,可以让读者更容易地看出这个属性是类的“特征”,不会在运行过程中被修改。 总的来说,将 HomeBookMapper 声明为 final 可以提高代码的可读性和线程安全性。
衍生的stream的知识点: .stream是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
.stream.map(方法):其中的方法获取了小说ID,而map是一种键值对存储结构,用于存储一组不重复的键和对应的值,例如此处的ID键和它所对应的值
.toList():将数组转化为List
衍生的Lambda表达式的知识点: Lambda表达式:(方法参数) ->{方法实现} (单个参数可以省略括号)
lambda遍历List集合
集合的遍历,采用lambda表达式会更简洁:
1 2 3 4 5 6 7 8 9 10 11 12 13 List features = Arrays.asList("Lambdas" , "Default Method" , "Stream API" , "Date and Time API" );for (String feature : features) { System.out.println(feature); } List features = Arrays.asList("Lambdas" , "Default Method" , "Stream API" , "Date and Time API" );features.forEach(n -> System.out.println(n)); features.forEach(System.out::println);
衍生的toMap(BookInfo::getId, Function.identity())解释: Function.identity() 是一个静态方法,它返回一个函数,这个函数接收一个参数并返回其自身。也就是说,它返回一个标识函数,将输入对象直接返回,不做任何处理。 在这个例子中,Function.identity() 方法用作 toMap() 方法的第二个参数。toMap() 方法期望一个将键映射到值的函数,所以使用 Function.identity() 将对象自身作为键的映射值。因此,对于一个 BookInfo 对象 bookInfo,bookInfo.getId() 作为键,bookInfo 自身作为值。 简而言之,这里使用 Function.identity() 是为了将 BookInfo 对象本身作为值,方便后续从 Map 中获取对象并使用其属性。
stream流(List bookIds = homeBooks.stream())\
在 Java8 之前,我们通常是通过 for 循环或者 Iterator 迭代来重新排序合并数据,又或者通过重新定义 Collections.sorts 的 Comparator 方法来实现,这两种方式对于大数据量系统来说,效率并不是很理想。Stream 的聚合操作 与数据库 SQL 的聚合操作 sorted、filter、map 等类似。我们在应用层就可以高效地实现类似数据库 SQL 的 聚合操作了,而在数据操作方面,Stream 不仅可以通过串行的方式实现数据操作,还可以通过并行的方式处理大批量数据,提高数据 的处理效率
总结的方法: 1、List类型的数据库查找方法
主要是return部分
1 2 3 4 5 6 7 8 9 10 11 12 @Override public List<BookInfoRespDto> listHomeCategory () { QueryWrapper<BookInfo>CategoryQueryWrapper = new QueryWrapper <>(); return bookInfoMapper.selectList(CategoryQueryWrapper).stream() .map(v -> BookInfoRespDto.builder() .id(v.getId()) .categoryId(v.getCategoryId()) .categoryName(v.getCategoryName()) .bookDesc(v.getBookDesc()) .bookName(v.getBookName()) .build()).toList(); }
2、Dto类型的数据库查找方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public BookChapterRespDto getChapter (Long chapterId) { QueryWrapper<BookChapter>chapterQueryWrapper=new QueryWrapper <>(); chapterQueryWrapper.eq("id" , chapterId); BookChapter bookChapter = bookChapterMapper.selectOne(chapterQueryWrapper); return BookChapterRespDto.builder() .id(chapterId) .bookId(bookChapter.getBookId()) .chapterNum(bookChapter.getChapterNum()) .chapterName(bookChapter.getChapterName()) .chapterWordCount(bookChapter.getWordCount()) .chapterUpdateTime(bookChapter.getUpdateTime()) .build(); }
controller层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @RestController @RequestMapping(ApiRouterConsts.API_FRONT_HOME_URL_PREFIX) @RequiredArgsConstructor public class HomeController { private final HomeBookMapper homeBookMapper; private final BookInfoMapper bookInfoMapper; private final HomeService homeService; @GetMapping("books") public RestResp<List<HomeBookRespDto>> listHomeBooks () { return RestResp.ok(homeService.listHomeBooks()); } }
core层编写 config层 MybatisPlusConfig 卡了我两个下午QAQ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Configuration public class MybatisPlusConfig { @Primary @Bean("db1SqlSessionFactory") public SqlSessionFactory db1SqlSessionFactory (DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean b1 = new MybatisSqlSessionFactoryBean (); System.out.println("dataSourceLyz" +dataSource.toString()); b1.setDataSource(dataSource); b1.setMapperLocations(new PathMatchingResourcePatternResolver ().getResources("classpath:mapper/*.xml" )); return b1.getObject(); } }
CorsProperties 解决跨域问题
1 2 3 @ConfigurationProperties(prefix = "novel.cors") public record CorsProperties (List<String> allowOrigins) {}
CorsConfig 解决跨域问题
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 @Configuration @EnableConfigurationProperties(CorsProperties.class) @RequiredArgsConstructor public class CorsConfig { private final CorsProperties corsProperties; @Bean public CorsFilter corsFilter () { CorsConfiguration config = new CorsConfiguration (); for (String allowOrigin : corsProperties.allowOrigins()) { config.addAllowedOrigin(allowOrigin); } config.addAllowedHeader("*" ); config.addAllowedMethod("*" ); config.setAllowCredentials(true ); UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource (); configurationSource.registerCorsConfiguration("/**" , config); return new CorsFilter (configurationSource); } }
common层 comstant层(通用常量层) 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 @Getter @AllArgsConstructor public enum ErrorCodeEnum { OK("00000" , "一切 ok" ), USER_ERROR("A0001" , "用户端错误" ), USER_REGISTER_ERROR("A0100" , "用户注册错误" ), USER_NO_AGREE_PRIVATE_ERROR("A0101" , "用户未同意隐私协议" ), USER_REGISTER_AREA_LIMIT_ERROR("A0102" , "注册国家或地区受限" ), USER_VERIFY_CODE_ERROR("A0240" , "用户验证码错误" ), USER_NAME_EXIST("A0111" , "用户名已存在" ), USER_ACCOUNT_NOT_EXIST("A0201" , "用户账号不存在" ), USER_PASSWORD_ERROR("A0210" , "用户密码错误" ), USER_REQUEST_PARAM_ERROR("A0400" , "用户请求参数错误" ), USER_LOGIN_EXPIRED("A0230" , "用户登录已过期" ), USER_UN_AUTH("A0301" , "访问未授权" ), USER_REQ_EXCEPTION("A0500" , "用户请求服务异常" ), USER_REQ_MANY("A0501" , "请求超出限制" ), USER_COMMENT("A2000" , "用户评论异常" ), USER_COMMENTED("A2001" , "用户已发表评论" ), AUTHOR_PUBLISH("A3000" , "作家发布异常" ), AUTHOR_BOOK_NAME_EXIST("A3001" , "小说名已存在" ), USER_UPLOAD_FILE_ERROR("A0700" , "用户上传文件异常" ), USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701" , "用户上传文件类型不匹配" ), SYSTEM_ERROR("B0001" , "系统执行出错" ), SYSTEM_TIMEOUT_ERROR("B0100" , "系统执行超时" ), THIRD_SERVICE_ERROR("C0001" , "调用第三方服务出错" ), MIDDLEWARE_SERVICE_ERROR("C0100" , "中间件服务出错" ); private final String code; private final String message; }
resp层 Http Rest 响应工具及数据格式封装
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 @Getter public class RestResp <T> { @Schema(description = "错误码,00000-没有错误") private String code; @Schema(description = "响应消息") private String message; @Schema(description = "响应数据") private T data; private RestResp () { this .code = ErrorCodeEnum.OK.getCode(); this .message = ErrorCodeEnum.OK.getMessage(); } private RestResp (ErrorCodeEnum errorCode) { this .code = errorCode.getCode(); this .message = errorCode.getMessage(); } private RestResp (T data) { this (); this .data = data; } public static RestResp<Void> ok () { return new RestResp <>(); } public static <T> RestResp<T> ok (T data) { return new RestResp <>(data); } public static RestResp<Void> fail (ErrorCodeEnum errorCode) { return new RestResp <>(errorCode); } public static RestResp<Void> error () { return new RestResp <>(ErrorCodeEnum.SYSTEM_ERROR); } public boolean isOk () { return Objects.equals(this .code, ErrorCodeEnum.OK.getCode()); } }
req层 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 @Getter public class RestResp <T> { private String code; private String message; private T data; private RestResp () { this .code = ErrorCodeEnum.OK.getCode(); this .message = ErrorCodeEnum.OK.getMessage(); } private RestResp (ErrorCodeEnum errorCode) { this .code = errorCode.getCode(); this .message = errorCode.getMessage(); } private RestResp (T data) { this (); this .data = data; } public static RestResp<Void> ok () { return new RestResp <>(); } public static <T> RestResp<T> ok (T data) { return new RestResp <>(data); } public static RestResp<Void> fail (ErrorCodeEnum errorCode) { return new RestResp <>(errorCode); } public static RestResp<Void> error () { return new RestResp <>(ErrorCodeEnum.SYSTEM_ERROR); } public boolean isOk () { return Objects.equals(this .code, ErrorCodeEnum.OK.getCode()); } }
启动类 1 2 3 4 5 6 7 8 9 10 11 12 @Slf4j @SpringBootApplication @MapperScan("com.tec.vuepractice.dao.mapper") @ServletComponentScan @EnableTransactionManagement public class VuePracticeApplication { public static void main (String[] args) { SpringApplication.run(VuePracticeApplication.class, args); log.info("项目启动成功!!!" ); } }