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.Driver
url :
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.测试