一、创建SpringBoot脚手架项目
1.1、使用Spring Initializr 创建项目
1.2、引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--hutool 工具集-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
1.3、配置 Banner 打印
- 创建Banner 图文件
- 加载banner.txt文件
- 放到resources根目录下,自动加载或使用 spring.banner.location 指定位置
1.4、集成Druid + Mybatis-Plus
- 依赖
```xml
com.alibaba druid-spring-boot-starter
- 配置
```java
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
username: 'root'
password: '123456'
url: 'jdbc:mysql://127.0.0.1:3306/blog?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8'
stat-view-servlet:
enabled: true
login-password: 123456
login-username: admin
url-pattern: /druid/*
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml #mapper.xml文件路径
configuration:
map-underscore-to-camel-case: true #驼峰
配置MyBatisPlus自动填充和分页插件
@MapperScan(basePackages = "cn.hdj.fastboot.modules.**.mapper") //扫描mapper接口
@Configuration
public class MyBatisPlusConfig {
/**
* 分页插件
*
* @return
*/
@Bean
public PaginationInnerInterceptor paginationInterceptor() {
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
paginationInterceptor.setOptimizeJoin(true);
return paginationInterceptor;
}
/**
* 元数据自动填充
*
* @return
*/
@Bean
public MetaObjectHandler metaObjectHandler() {
return new MetaObjectHandler() {
//插入时,填充createTime 和updateTime字段
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
};
}
}
1.5、配置日志
依赖
- 一般不需要手动在添加到 maven 依赖中
- 这些依赖已经包含在 spring-boot-starter-web 中
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
logback-spring.xml 文件配置 ```xml <?xml version=”1.0” encoding=”UTF-8”?>
fast-boot
<!--0. 日志格式和颜色渲染 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName ) [%thread] %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--1. 输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- %d{HH: mm:ss.SSS}——日志输出时间 -->
<!-- %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用 -->
<!-- %-5level——日志级别,并且使用5个字符靠左对齐 -->
<!-- %logger{36}——日志输出者的名字 -->
<!-- %msg——日志消息 -->
<!-- %n——平台的换行符 -->
<!--2. 输出到文档-->
<!-- 2.1 level为 DEBUG 日志,时间滚动输出 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${log.path}/debug.log</file>
<!--日志文档输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档 -->
<fileNamePattern>${log.path}/debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数-->
<maxHistory>7</maxHistory>
</rollingPolicy>
<!-- 此日志文档只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 4. 最终的策略 -->
<!-- 4.1 开发环境:打印控制台-->
<springProfile name="dev"> <!-- 设置指定包下的级别,这样的话 就可以在控制台输出sql语句了 -->
<logger name="org.springframework.web" level="INFO"/>
<logger name="org.mybatis" level="debug"/>
<logger name="cn.hdj" level="debug"/>
</springProfile>
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<!-- 4.2 生产环境:输出到文档-->
<springProfile name="prod">
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="WARN_FILE"/>
</root>
</springProfile>
<a name="K4ofz"></a>
### 1.6、全局异常处理
```java
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 全局异常类中定义的异常都可以被拦截,只是触发条件不一样,如IO异常这种必须抛出异常到
* controller中才可以被拦截,或者在类中用try..catch自己处理
* 绝大部分不需要向上抛出异常即可被拦截,返回前端json数据,如数组下标越界,404 500 400等错误
* 如果自己想要写,按着以下格式增加异常即可
*/
/**
* 启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,
* 都会作用在 被 @RequestMapping 注解的方法上。
*
* @param binder
*/
@InitBinder
public void initWebBinder(WebDataBinder binder) {
}
/**
* 处理自定义异常
*
* @param ex 异常信息
* @return 返回前端异常信息
*/
@ExceptionHandler(BaseException.class)
@ResponseBody
public ResultVO exception(BaseException ex) {
log.error("错误详情:" + ex.getMessage(), ex);
return ResultVO.errorJson(ex.getMessage(), ex.getCode());
}
//省略....
/**
* 系统其它异常
*
* @param e
* @return
*/
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public ResultVO handleException(Exception e) {
log.error("错误详情:" + e.getMessage(), e);
return ResultVO.errorJson(e.getMessage());
}
}
1.7、配置请求响应转换器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 日期格式化
*
* @param registry
*/
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new Formatter<LocalDateTime>() {
@Override
public LocalDateTime parse(String text, Locale locale) {
return DateUtil.parse(text).toTimestamp().toLocalDateTime();
}
@Override
public String print(LocalDateTime object, Locale locale) {
return LocalDateTimeUtil.format(object, DatePattern.NORM_DATETIME_FORMATTER);
}
});
}
/**
* json 消息转换器
*
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
List<MediaType> mediaTypes = new ArrayList<>(converter.getSupportedMediaTypes());
converter.setSupportedMediaTypes(mediaTypes);
mediaTypes.addAll(Arrays.asList(MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.TEXT_XML));
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//前端Long类型精度丢失
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
mapper.registerModule(simpleModule);
//java8 LocalDateTime
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
mapper.registerModule(javaTimeModule);
converter.setObjectMapper(mapper);
converters.add(0, converter);
}
}
1.8、集成 Swagger
依赖
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
配置 ```java @Configuration @EnableSwagger2 public class SwaggerConfig {
@Bean(value = “defaultApi2”) public Docket defaultApi2() {
Contact contact=new Contact("huangjiajian","https://github.com/h-dj","1432517356@qq.com");
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
//.title("swagger-bootstrap-ui-demo RESTful APIs")
.description("# swagger-bootstrap-ui-demo RESTful APIs")
.contact(contact)
.version("1.0")
.build())
//分组名称
.groupName("0.0.1版本")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("cn.hdj.fastboot.modules"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
- swagger2切换swagger3 对应的注解
```java
@Api → @Tag
@ApiIgnore → @Parameter(hidden = true) or @Operation(hidden = true) or @Hidden
@ApiImplicitParam → @Parameter
@ApiImplicitParams → @Parameters
@ApiModel → @Schema
@ApiModelProperty(hidden = true) → @Schema(accessMode = READ_ONLY)
@ApiModelProperty → @Schema
@ApiOperation(value = "foo", notes = "bar") → @Operation(summary = "foo", description = "bar")
@ApiParam → @Parameter
@ApiResponse(code = 404, message = "foo") → @ApiResponse(responseCode = "404", description = "foo")
- 处理错误问题
在springboot 2.6.6会提示documentationPluginsBootstrapper NullPointerException
spring:
mvc:
pathmatch:
matching-strategy: ant-path-matcher
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier
, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier
, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties
, WebEndpointProperties webEndpointProperties
, Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints
, endpointMediaTypes, corsProperties.toCorsConfiguration()
, new EndpointLinksResolver(allEndpoints, basePath)
, shouldRegisterLinksMapping, null);
}
private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
return webEndpointProperties.getDiscovery().isEnabled()
&& (StringUtils.hasText(basePath)
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
1.9、整合 Redis
添加依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
配置
spring:
redis:
redisson:
file: classpath:redisson.yml
singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
password: 'redis@01234560'
subscriptionsPerConnection: 5
clientName: null
address: "redis://127.0.0.1:6379"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 24
connectionPoolSize: 64
database: 0
dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.JsonJacksonCodec> {}
transportMode: "NIO"
缓存配置 ```java @Slf4j @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport {
private RedisSerializer serializer(){
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(ICacheEntity.class);
ObjectMapper objectMapper = new ObjectMapper();
// 将类型序列化到属性json字符串中
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
// 对于找不到匹配属性的时候忽略报错
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 不包含任何属性的bean也不报错
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
serializer.setObjectMapper(objectMapper);
return serializer;
}
/**
* 如使用注解的话需要配置cacheManager
*
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//覆盖默认的序列化
RedisSerializer serializer = serializer();
//默认配置
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration
.defaultCacheConfig()
//设置默认超过期时间是7天
.entryTtl(Duration.ofDays(7))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultCacheConfig)
.build();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//覆盖默认的序列化
RedisSerializer serializer = serializer();
StringRedisSerializer stringRedisSerializer = StringRedisSerializer.UTF_8;
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.setValueSerializer(serializer);
redisTemplate.setDefaultSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Override
public CacheErrorHandler errorHandler() {
// 异常处理,当Redis发生异常时,打印日志,但是程序正常走
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
log.error("Redis occur handleCacheClearError:", e);
}
};
return cacheErrorHandler;
}
}
<a name="NArZX"></a>
## 二、配置 Maven archetype 生成脚手架
<a name="x5Pj5"></a>
### 2.1、添加插件
```java
<!-- 脚手架插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-archetype-plugin</artifactId>
<version>3.2.1</version>
</plugin>
2.2、配置
# 排除打包到脚手架中的文件
excludePatterns=archetype.properties,*.iml,.idea/,logs/,build.sh
# maven脚手架会丢弃.gitignore文件,需要重命名为__gitignore__,脚手架会把__XX__的文件按照配置进行替换
#gitignore=.gitignore
2.3、生成
mvn archetype:create-from-project -Darchetype.properties=archetype.properties
2.4、安装到本地Maven 仓库
# ./target/generated-sources/archetype 目录下执行
mvn install
2.5、生成项目
mvn archetype:generate \
-DarchetypeGroupId=cn.hdj \
-DarchetypeArtifactId=fast-boot-archetype \
-DarchetypeVersion=0.0.1-SNAPSHOT