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 {@Resourceprivate SubjectCategoryService subjectCategoryService;@Overridepublic 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 {@Resourceprivate 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.测试