1.技术选型
2.原型设计
1.安装AxureRP
2.进行汉化
3.载入元件库
4.基本设计
3.元数建模
1.安装元数建模软件
2.新建项目
3.新增一个刷题模块主题域
4.新增数据表 subject_category
5.新增关系图,将表拖过来
6.新增题目标签表
7.新增题目信息表
8.新增单选表、多选表、判断题、简答题
9.新增分类、标签、题目关联表
10.关系图预览
4.项目架构分析
1.现有的架构
2.ddd架构
5.mysql采用docker版的主从复制(之前配置过)
1.docker的启动命令
docker run -p 3307 :3306 --name mysql-master \
-v /mysql5.7/mysql-master/log:/var/log/mysql \
-v /mysql5.7/mysql-master/data:/var/lib/mysql \
-v /mysql5.7/mysql-master/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD = ******** \
-d mysql:5.7
2.IDEA测试连接
3.主MySQL的连接信息
4.创建数据库sun_club,然后创建表
6.集成Gitee
1.新建仓库
2.克隆到IDEA
7.新建一个sun-club-subject作为父模块
1.新建父模块
2.删除src目录
3.设置整个项目的编码为utf-8
4.在maven的配置部分指定编译版本为java8
< properties> < java.version> 1.8</ java.version> < maven.compiler.source> 1.8</ maven.compiler.source> < maven.compiler.target> 1.8</ maven.compiler.target> </ properties>
5.在maven的插件配置中指定maven打包的配置
< build> < plugins> < plugin> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-maven-plugin</ artifactId> </ plugin> </ plugins> </ build>
8.新建子模块
1.新建api子模块
2.为子模块的parent部分加上relativePath标签(不要加,只有在需要双继承的时候才需要)
3.在maven的配置部分指定编译版本为java8以及maven的打包插件(每个子模块都要加的)
< properties> < java.version> 1.8</ java.version> < maven.compiler.source> 1.8</ maven.compiler.source> < maven.compiler.target> 1.8</ maven.compiler.target> </ properties> < build> < plugins> < plugin> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-maven-plugin</ artifactId> </ plugin> </ plugins> </ build>
2.新建application子模块
3.跟上面两个一样的常规配置
4.分别新建common、domain、infra、starter模块并进行常规配置
5.各层次目录结构
1.api层
2.starter层
3.infra层
4.domain层
5.common层
6.application层
新建三个子模块并加上常规配置
7.整体结构一览
6.目录结构调整
1.sun-club-subject-api
2.sun-club-starter
3.sun-club-infra
4.sun-club-domain
5.sun-club-common
6.sun-club-application
9.集成SpringBoot
1.编辑sun-club-subject的pom.xml引入SpringBoot2并配置maven仓库
< dependencies> < dependency> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-dependencies</ artifactId> < version> 2.4.2</ version> < type> pom</ type> < scope> import</ scope> </ dependency> </ dependencies> < repositories> < repository> < id> central</ id> < name> aliyun maven</ name> < url> http://maven.aliyun.com/nexus/content/groups/public/</ url> < layout> default</ layout> < releases> < enabled> true</ enabled> </ releases> < snapshots> < enabled> true</ enabled> </ snapshots> </ repository> </ repositories>
2.编辑sun-club-starter的pom.xml引入SpringBoot的starter-web
< dependencies> < dependency> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-starter-web</ artifactId> < version> 2.4.2</ version> </ dependency> </ dependencies>
3.sun-club-starter模块编写启动类SubjectApplication.java
package com. sunxiansheng. subject ; import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
import org. springframework. context. annotation. ComponentScan ;
@SpringBootApplication
@ComponentScan ( "com.sunxiansheng" )
public class SubjectApplication { public static void main ( String [ ] args) { SpringApplication . run ( SubjectApplication . class , args) ; }
}
4.启动测试
5.sun-club-starter模块创建application.yml对项目进行调整
1.文件内容
2.重启测试
10.集成SpringMVC
1.sun-club-application-controller 引入SpringBoot的starter-web
< dependencies> < dependency> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-starter-web</ artifactId> < version> 2.4.2</ version> </ dependency> </ dependencies>
2.编写SubjectController.java
package com. sunxiansheng. subject. application. controller ; import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
public class SubjectController { @GetMapping ( "/test" ) public String test ( ) { return "Hello, World!" ; }
}
3.sun-club-starter引入sun-club-application-controller模块,使其启动时可以找到
< dependency> < groupId> com.sun.club</ groupId> < artifactId> sun-club-application-controller</ artifactId> < version> 1.0-SNAPSHOT</ version> </ dependency>
4.测试访问
11.集成MySQL,Druid,MyBatis
1.sun-club-infra模块添加依赖
< dependencies> < dependency> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-starter-jdbc</ artifactId> < version> 2.4.2</ version> </ dependency> < dependency> < groupId> com.alibaba</ groupId> < artifactId> druid-spring-boot-starter</ artifactId> < version> 1.1.21</ version> </ dependency> < dependency> < groupId> mysql</ groupId> < artifactId> mysql-connector-java</ artifactId> < version> 8.0.22</ version> </ dependency> < dependency> < groupId> com.baomidou</ groupId> < artifactId> mybatis-plus-boot-starter</ artifactId> < version> 3.4.0</ version> </ dependency> </ dependencies>
2.EasyCode插件,生成CRUD
1.安装插件
2.选择表,右键选择EasyCode
3.选择代码生成的位置,和需要的文件
4.查看生成的代码
5.删除与Pageable有关的代码
1.SubjectCategoryDao.java
2.SubjectCategoryService.java
3.SubjectCategoryServiceImpl.java
3.sun-club-starter引入sun-club-infra
< dependency> < groupId> com.sun.club</ groupId> < artifactId> sun-club-infra</ artifactId> < version> 1.0-SNAPSHOT</ version> </ dependency>
4.sun-club-starter启动类配置MapperScan,扫描基础设施层的包
5.sun-club-starter配置数据源和监控
这里没有配置扫描Mapper.xml的原因是:Mapper和Mapper.xml的名字相同并且位于常规位置,MyBatis会自动扫描
spring : datasource : driver-class-name : com.mysql.cj.jdbc.Driverurl : username : password : type : com.alibaba.druid.pool.DruidDataSource druid : initial-size : 20 min-idle : 20 max-active : 100 max-wait : 60000 stat-view-servlet : enabled : true url-pattern : /druid/* login-username : login-password : filter : stat : enabled : true slow-sql-millis : 2000 log-slow-sql : true wall : enabled : true
6.测试
1.sun-club-application-controller 引入sun-club-infra
< dependency> < groupId> com.sun.club</ groupId> < artifactId> sun-club-infra</ artifactId> < version> 1.0-SNAPSHOT</ version> </ dependency>
2.sun-club-application-controller编写SubjectController.java测试
3.启动测试,成功!
7.使用druid对application.yml中的密码进行加密
1.sun-club-infra编写DruidEncryptUtil.java进行加解密
package com. sunxiansheng. subject. infra. basic. utils ; import com. alibaba. druid. filter. config. ConfigTools ; import java. security. NoSuchAlgorithmException ;
import java. security. NoSuchProviderException ;
public class DruidEncryptUtil { private static String publicKey; private static String privateKey; static { try { String [ ] keyPair = ConfigTools . genKeyPair ( 512 ) ; privateKey = keyPair[ 0 ] ; System . out. println ( "privateKey:" + privateKey) ; publicKey = keyPair[ 1 ] ; System . out. println ( "publicKey:" + publicKey) ; } catch ( NoSuchAlgorithmException e) { e. printStackTrace ( ) ; } catch ( NoSuchProviderException e) { e. printStackTrace ( ) ; } } public static String encrypt ( String plainText) throws Exception { String encrypt = ConfigTools . encrypt ( privateKey, plainText) ; System . out. println ( "encrypt:" + encrypt) ; return encrypt; } public static String decrypt ( String encryptText) throws Exception { String decrypt = ConfigTools . decrypt ( publicKey, encryptText) ; System . out. println ( "decrypt:" + decrypt) ; return decrypt; } public static void main ( String [ ] args) throws Exception { String encrypt = encrypt ( "" ) ; System . out. println ( "encrypt:" + encrypt) ; } }
2.sun-club-starter修改application.yml
8.准备apipost测试工具
1.新建目录
2.刷题模块目录
3.再添加一个题目分类的目录
4.添加一个接口
12.分层架构的业务开发演示
1.引入依赖
1.sun-club-common引入lombok和mapstruct,注意lombok必须放到mapstruct前面
< dependency> < groupId> org.projectlombok</ groupId> < artifactId> lombok</ artifactId> < version> 1.18.16</ version> </ dependency> < dependency> < groupId> org.mapstruct</ groupId> < artifactId> mapstruct</ artifactId> < version> 1.4.2.Final</ version> </ dependency> < dependency> < groupId> org.mapstruct</ groupId> < artifactId> mapstruct-processor</ artifactId> < version> 1.4.2.Final</ version> </ dependency>
2.sun-club-infra引入sun-club-common
< dependency> < groupId> com.sun.club</ groupId> < artifactId> sun-club-common</ artifactId> < version> 1.0-SNAPSHOT</ version> </ dependency>
2.sun-club-domain层
1.引入sun-club-infra的依赖
< dependency> < groupId> com.sun.club</ groupId> < artifactId> sun-club-infra</ artifactId> < version> 1.0-SNAPSHOT</ version> </ dependency>
2.创建SubjectCategoryBO.java(只关注业务)
package com. sunxiansheng. subject. domain. entity ; import lombok. Data ;
@Data
public class SubjectCategoryBO { private static final long serialVersionUID = - 66163713173399755L ; private Long id; private String categoryName; private Integer categoryType; private String imageUrl; private Long parentId; }
3.service层
1.SubjectCategoryDomainService.java
package com. sunxiansheng. subject. domain. service ; import com. sunxiansheng. subject. domain. entity. SubjectCategoryBO ;
public interface SubjectCategoryDomainService { void add ( SubjectCategoryBO subjectCategoryBO) ;
}
2.由于需要将BO转换为eneity,所以需要转换器SubjectCategoryConverter.java
package com. sunxiansheng. subject. domain. convert ; import com. sunxiansheng. subject. domain. entity. SubjectCategoryBO ;
import com. sunxiansheng. subject. infra. basic. entity. SubjectCategory ;
import org. mapstruct. Mapper ;
import org. mapstruct. factory. Mappers ;
@Mapper
public interface SubjectCategoryConverter { SubjectCategoryConverter INSTANCE = Mappers . getMapper ( SubjectCategoryConverter . class ) ; SubjectCategory convertBoToSubjectCategory ( SubjectCategoryBO subjectCategoryBO) ;
}
3.SubjectCategoryDomainServiceImpl.java
package com. sunxiansheng. subject. domain. service. impl ; import com. sunxiansheng. subject. domain. convert. SubjectCategoryConverter ;
import com. sunxiansheng. subject. domain. entity. SubjectCategoryBO ;
import com. sunxiansheng. subject. domain. service. SubjectCategoryDomainService ;
import com. sunxiansheng. subject. infra. basic. entity. SubjectCategory ;
import com. sunxiansheng. subject. infra. basic. service. SubjectCategoryService ; import javax. annotation. Resource ;
@Service
public class SubjectCategoryDomainServiceImpl implements SubjectCategoryDomainService { @Resource private SubjectCategoryService subjectCategoryService; @Override public void add ( SubjectCategoryBO subjectCategoryBO) { SubjectCategory subjectCategory = SubjectCategoryConverter . INSTANCE . convertBoToSubjectCategory ( subjectCategoryBO) ; subjectCategoryService. insert ( subjectCategory) ; }
}
3.sun-club-application-controller层
1.引入sun-club-domain的依赖
< dependency> < groupId> com.sun.club</ groupId> < artifactId> sun-club-domain</ artifactId> < version> 1.0-SNAPSHOT</ version> </ dependency>
2.转换器将DTO转换为BO SubjectCategoryDTOConverter.java
package com. sunxiansheng. subject. application. convert ; import com. sunxiansheng. subject. application. dto. SubjectCategoryDTO ;
import com. sunxiansheng. subject. domain. entity. SubjectCategoryBO ;
import org. mapstruct. Mapper ;
import org. mapstruct. factory. Mappers ;
@Mapper
public interface SubjectCategoryDTOConverter { SubjectCategoryDTOConverter INSTANCE = Mappers . getMapper ( SubjectCategoryDTOConverter . class ) ; SubjectCategoryBO convertDTOToSubjectCategory ( SubjectCategoryDTO subjectCategoryDTO) ;
}
3.sun-club-common包中封装统一响应
1.ResultCodeEnum.java
package com. sunxiansheng. subject. common. enums ; import lombok. Getter ;
@Getter
public enum ResultCodeEnum { SUCCESS ( 200 , "成功" ) , FAIL ( 500 , "失败" ) ; public int code; public String desc; ResultCodeEnum ( int code, String desc) { this . code = code; this . desc = desc; } public static ResultCodeEnum getByCode ( int code) { for ( ResultCodeEnum value : values ( ) ) { if ( value. code == code) { return value; } } return null ; }
}
2.Result.java
package com. sunxiansheng. subject. common. eneity ; import com. sunxiansheng. subject. common. enums. ResultCodeEnum ;
import lombok. Data ;
@Data
public class Result < T > { private Boolean success; private Integer code; private String message; private T data; public static Result ok ( ) { Result result = new Result ( ) ; result. setSuccess ( true ) ; result. setCode ( ResultCodeEnum . SUCCESS . getCode ( ) ) ; result. setMessage ( ResultCodeEnum . SUCCESS . getDesc ( ) ) ; return result; } public static < T > Result ok ( T data) { Result result = new Result ( ) ; result. setSuccess ( true ) ; result. setCode ( ResultCodeEnum . SUCCESS . getCode ( ) ) ; result. setMessage ( ResultCodeEnum . SUCCESS . getDesc ( ) ) ; result. setData ( data) ; return result; } public static Result fail ( ) { Result result = new Result ( ) ; result. setSuccess ( false ) ; result. setCode ( ResultCodeEnum . FAIL . getCode ( ) ) ; result. setMessage ( ResultCodeEnum . FAIL . getDesc ( ) ) ; return result; } public static < T > Result fail ( T data) { Result result = new Result ( ) ; result. setSuccess ( false ) ; result. setCode ( ResultCodeEnum . FAIL . getCode ( ) ) ; result. setMessage ( ResultCodeEnum . FAIL . getDesc ( ) ) ; result. setData ( data) ; return result; } }
4.SubjectCategoryController.java
package com. sunxiansheng. subject. application. controller ; import com. alibaba. fastjson. JSON ;
import com. sunxiansheng. subject. application. convert. SubjectCategoryDTOConverter ;
import com. sunxiansheng. subject. application. dto. SubjectCategoryDTO ;
import com. sunxiansheng. subject. common. eneity. Result ;
import com. sunxiansheng. subject. domain. entity. SubjectCategoryBO ;
import com. sunxiansheng. subject. domain. service. SubjectCategoryDomainService ;
import lombok. extern. slf4j. Slf4j ;
import org. springframework. web. bind. annotation. PostMapping ;
import org. springframework. web. bind. annotation. RequestBody ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ; import javax. annotation. Resource ;
@RestController
@RequestMapping ( "/subject/category" )
public class SubjectCategoryController { @Resource private SubjectCategoryDomainService subjectCategoryDomainService; @PostMapping ( "/add" ) public Result < Boolean > add ( @RequestBody SubjectCategoryDTO subjectCategoryDTO) { try { SubjectCategoryBO subjectCategoryBO = SubjectCategoryDTOConverter . INSTANCE . convertDTOToSubjectCategory ( subjectCategoryDTO) ; subjectCategoryDomainService. add ( subjectCategoryBO) ; return Result . ok ( true ) ; } catch ( Exception e) { return Result . fail ( ) ; } }
}
5.测试
4.打印日志
1.sun-club-common引入log4j2和fastjson
< dependency> < groupId> org.springframework.boot</ groupId> < artifactId> spring-boot-starter-log4j2</ artifactId> < version> 2.4.2</ version> </ dependency> < dependency> < groupId> com.alibaba</ groupId> < artifactId> fastjson</ artifactId> < version> 1.2.24</ version> </ dependency>
2.SubjectCategoryController.java打印日志
这里判断是否开启日志的原因是:如果不判断,则即使没开启日志,JSON还是会序列化,影响性能
if ( log. isInfoEnabled ( ) ) { log. info ( "SubjectCategoryController add SubjectCategoryDTO, subjectCategoryDTO:{}" , JSON . toJSONString ( subjectCategoryDTO) ) ; }
3.SubjectCategoryDomainServiceImpl.java
4.SubjectCategoryServiceImpl.java
5.sun-club-starter 引入log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
< configuration status = " INFO" monitorInterval = " 5" > < Properties> < property name = " LOG_PATTERN" value = " %date{HH:mm:ss.SSS} %X{PFTID} [%thread] %-5level %logger{36} - %msg%n" /> < property name = " FILE_PATH" value = " ../log" /> < property name = " FILE_NAME" value = " jcClub.log" /> </ Properties> < appenders> < console name = " Console" target = " SYSTEM_OUT" > < PatternLayout pattern = " ${LOG_PATTERN}" /> < ThresholdFilter level = " info" onMatch = " ACCEPT" onMismatch = " DENY" /> </ console> < File name = " fileLog" fileName = " ${FILE_PATH}/temp.log" append = " false" > < PatternLayout pattern = " ${LOG_PATTERN}" /> </ File> < RollingFile name = " RollingFileInfo" fileName = " ${FILE_PATH}/info.log" filePattern = " ${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz" > < ThresholdFilter level = " info" onMatch = " ACCEPT" onMismatch = " DENY" /> < PatternLayout pattern = " ${LOG_PATTERN}" /> < Policies> < TimeBasedTriggeringPolicy interval = " 1" /> < SizeBasedTriggeringPolicy size = " 10MB" /> </ Policies> < DefaultRolloverStrategy max = " 15" /> </ RollingFile> < RollingFile name = " RollingFileWarn" fileName = " ${FILE_PATH}/warn.log" filePattern = " ${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz" > < ThresholdFilter level = " warn" onMatch = " ACCEPT" onMismatch = " DENY" /> < PatternLayout pattern = " ${LOG_PATTERN}" /> < Policies> < TimeBasedTriggeringPolicy interval = " 1" /> < SizeBasedTriggeringPolicy size = " 10MB" /> </ Policies> < DefaultRolloverStrategy max = " 15" /> </ RollingFile> < RollingFile name = " RollingFileError" fileName = " ${FILE_PATH}/error.log" filePattern = " ${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz" > < ThresholdFilter level = " error" onMatch = " ACCEPT" onMismatch = " DENY" /> < PatternLayout pattern = " ${LOG_PATTERN}" /> < Policies> < TimeBasedTriggeringPolicy interval = " 1" /> < SizeBasedTriggeringPolicy size = " 10MB" /> </ Policies> < DefaultRolloverStrategy max = " 15" /> </ RollingFile> </ appenders> < loggers> < root level = " info" > < appender-ref ref = " Console" /> < appender-ref ref = " RollingFileInfo" /> < appender-ref ref = " RollingFileWarn" /> < appender-ref ref = " RollingFileError" /> < appender-ref ref = " fileLog" /> </ root> </ loggers> </ configuration>
6.sun-club-starter的application.yml配置日志
7.启动会报错
1.报错信息
2.使用Maven Helper查看依赖
3.排除掉springboot-starter-web的log
4.再次测试
5.参数校验
1.使用guava
1.sun-club-common引入依赖
< dependency> < groupId> com.google.guava</ groupId> < artifactId> guava</ artifactId> < version> 19.0</ version> </ dependency>
2.sun-club-application-controller引入公共包
< dependency> < groupId> com.sun.club</ groupId> < artifactId> sun-club-common</ artifactId> < version> 1.0-SNAPSHOT</ version> </ dependency>
3.Preconditions.checkNotNull没有生效,说明guava依赖有问题,clean一下maven,发现报错
4.把所有relativePath全删除,因为并没有双继承,用不上,再次clean,成功!
5.sun-club-application-controller编写SubjectCategoryController.java
Preconditions . checkNotNull ( subjectCategoryDTO. getCategoryType ( ) , "分类类型不能为空" ) ; Preconditions . checkArgument ( ! StringUtils . isBlank ( subjectCategoryDTO. getCategoryName ( ) ) , "分类名称不能为空" ) ;
Preconditions . checkNotNull ( subjectCategoryDTO. getParentId ( ) , "分类父级id不能为空" ) ;
6.端口换成3010
2.测试