做个基于springboot的项目(用springboot2.6)
项目用 gradle 7 进行构建,在Idea 进行图形化操作,今天小编就来说说关于做个基于springboot的项目?下面更多详细答案一起来看看吧!
做个基于springboot的项目
主项目项目用 gradle 7 进行构建,在Idea 进行图形化操作。
IDEA 中建立项目建立之后删除主文件夹下的 src 目录。
主项目的 build.gradle
buildscript {
//定义扩展属性(可选)
ext {
springBootVersion = "2.6.2"
ALI_REPOSITORY_URL = 'https://maven.aliyun.com/repository/public'
}
repositories {
mavenCentral()
maven { url "https://repo.spring.io/plugins-release" }
}
dependencies {
classpath("org.Springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
allprojects {
//修改项目属性(可选)
group 'com.jingmin'
version '1.0-SNAPSHOT'
//应用插件
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'io.spring.dependency-management'
// JVM 版本号要求
sourceCompatibility = 11
targetCompatibility = 11
// java编译的时候缺省状态下会因为中文字符而失败
[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'
// 全局设置依赖配置
configurations {
providedRuntime
}
// 定义仓库
repositories {
maven {
url ALI_REPOSITORY_URL
}
maven { url 'https://mvnrepository.com/' }
mavenLocal()
mavenCentral()
}
dependencies {
modules {
module("org.springframework.boot:spring-boot-starter-logging") {
replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
}
module("org.springframework.boot:spring-boot-starter-tomcat") {
replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use undertow instead of tomcat")
}
}
}
}
subprojects {
apply plugin: 'java'
dependencies {
implementation("org.springframework.boot:spring-boot-starter-log4j2")
implementation("org.springframework.boot:spring-boot-starter-undertow")
// lombok bean类
testCompileOnly 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
// 测试类
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// 使用 WebTestClient 测试需要
testImplementation 'org.springframework.boot:spring-boot-starter-webflux'
}
configurations {
all*.exclude module: 'spring-boot-starter-tomcat'
all*.exclude module: 'HikariCP'
all*.exclude module: 'junit-vintage-engine'
}
// 关掉bootRepackage任务
// bootRepackage.enabled=false
}
plugins {
id 'org.springframework.boot'
}
processResources {
from('src/main/java') {
include '**/*.xml'
}
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
}
test {
useJUnitPlatform()
}
Log4j2 环境对应三个状态
- 生产环境(proc)不对Console进行,级别定为info
- 开发环境(dev),对Console进行,级别定为debug
- 测试环境(test),对Console进行,级别定为debug
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<RollingFile name="RollingFileInfo" fileName="./logs/info.log"
filePattern="logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<!--只接受INFO级别的日志,其余的全部拒绝处理-->
<ThresholdFilter level="INFO"/>
<!-- <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>-->
</Filters>
<PatternLayout
pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</appenders>
<loggers>
<root level="info">
<appender-ref ref="RollingFileInfo"/>
</root>
</loggers>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss.SSS}] %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
</appenders>
<loggers>
<root level="debug">
<appender-ref ref="Console"/>
</root>
<logger name="com.jingmin.management" level="debug" additivity="false">
<appender-ref ref="Console"/>
</logger>
</loggers>
</configuration>
spring:
jackson:
date-format: yyyy/MM/dd HH:mm:ss
default-property-inclusion: NON_EMPTY
profiles:
active: prod
main:
banner-mode: off
logging:
config: classpath:log4j2-prod.xml
logging:
config: classpath:log4j2-dev.xml
一切基于测试进行构建
建立Application主类
package com.jingmin.system;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SystemApplication {
public static void main(String[] args) {
SpringApplication.run(SystemApplication.class, args);
}
}
package com.jingmin.system.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/")
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
}
测试使用WebTextClient,测试要包含 testImplementation 'org.springframework.boot:spring-boot-starter-webflux'库, 这个测试用于测试环境是否正常。
package com.jingmin.system.controller;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import java.util.Arrays;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = {com.jingmin.system.SystemApplication.class})
@AutoConfigureMockMvc
@Slf4j
@DisplayName("基础测试")
class IndexControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
@DisplayName("home page")
void testIndex() {
String url = "/";
this.webTestClient
.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk().expectBody(String.class)
.consumeWith(response ->
assertThat(response.getResponseBody(), containsString("index")));
}
}
在主项目 builder.gradle 不同的区域加入以下信息
buildscript {
ext {
knife4jVersion = '3.0.3'
}
}
subprojects {
dependencies {
implementation "com.github.xiaoymin:knife4j-spring-boot-starter:${knife4jVersion}"
}
}
knife4j 3.0.3不兼容spring boot 2.6 系列
spring:
mvc:
pathmatch:
# knife4j 使用 spring boot 2.5.7以前的模式
matching-strategy: ant_path_matcher
package com.jingmin.system.configuration;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@Slf4j
@EnableSwagger2
public class WebMvcConfiguration implements WebMvcConfigurer {
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("静敏工作室Api文档")
.description("静敏工作室Api文档")
.termsOfServiceUrl("http://localhost:8080/")
.version("1.0")
.build();
}
@Bean
public Docket defaultApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.jingmin.management"))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}
}
package com.jingmin.system.controller;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import java.util.Arrays;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {com.jingmin.system.SystemApplication.class})
@AutoConfigureMockMvc
@Slf4j
@DisplayName("基础测试")
class IndexControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
@DisplayName("Knife4j Document")
void testKnife4jDocument() {
String url = "/doc.html";
this.webTestClient
.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk().expectBody(String.class)
.consumeWith(response ->
assertThat("包含Knife4j-vue",response.getResponseBody(), containsString("knife4j-vue")));
}
}
在主项目 builder.gradle 不同的区域加入以下信息
buildscript {
//定义扩展属性(可选)
ext {
mybatisPlusVersion = '3.4.3.4'
dynamicDatasourceVersion = '3.4.1'
druidVersion = '1.2.8'
}
}
subprojects {
dependencies {
implementation "com.alibaba:druid-spring-boot-starter:${druidVersion}"
implementation "com.baomidou:mybatis-plus-boot-starter:${mybatisPlusVersion}"
implementation "com.baomidou:dynamic-datasource-spring-boot-starter:${dynamicDatasourceVersion}"
implementation "org.postgresql:postgresql"
}
}
spring:
autoconfigure:
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
datasource:
dynamic:
primary: master
datasource:
master:
driver-class-name: org.postgresql.Driver
platform: POSTGRESQL
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 20
min-idle: 1
max-active: 50
#配置获取连接等待超时的时间
max-wait: 60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
#配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
#测试连接
validation-query: SELECT 'x'
#申请连接的时候检测,建议配置为true,不影响性能,并且保证安全性
test-while-idle: false
#获取连接时执行检测,建议关闭,影响性能
test-on-borrow: false
#归还连接时执行检测,建议关闭,影响性能
test-on-return: false
#是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭
pool-prepared-statements: false
#开启poolPreparedStatements后生效
max-pool-prepared-statement-per-connection-size: 20
#配置扩展插件,常用的插件有=>stat:监控统计 log4j:日志 wall:防御sql注入
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connection-properties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
# Druid WebStatFilter配置
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: '*.gif,*.png,*.jpg,*.html,*.js,*.css,*.ico,/druid/*'
# Druid StatViewServlet配置
# stat-view-servlet:
# enabled: true
# url-pattern: /druid/*
# reset-enable: true
# login-username: admin
# login-password: admin
# 配置日志输出
filter:
stat:
log-slow-sql: true
slf4j:
enabled: true
statement-executable-sql-log-enable: false
statement-sql-pretty-format: false
statement-create-after-log-enabled: false
statement-close-after-log-enabled: false
result-set-open-after-log-enabled: false
result-set-close-after-log-enabled: false
statement-log-enabled: false
mybatis-plus:
mapper-locations: classpath*:/mapper/*.xml
# type-enums-package: com.jingmin.system.enums,com.jingmin.product.enums
configuration:
default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
map-underscore-to-camel-case: on
cache-enabled: true
local-cache-scope: statement
log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: false
db-config:
table-underline: true
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
dev、test 配置与prod类似,可以在不同的环境下使用不同的数据库
spring:
datasource:
dynamic:
datasource:
master:
username: xxxx
password: xxxxxx
url: jdbc:postgresql://postgres-server:5432/xxxx?currentSchema=public&stringtype=unspecified
@MapperScan(value = {"com.jingmin.system.dao"})
public class SystemApplication {
public static void main(String[] args) {
SpringApplication.run(SystemApplication.class, args);
}
}
-- auto-generated definition
create table auth_user
(
id bigint not null
primary key,
department_code varchar(40) default ''::character varying not null,
username varchar(50) not null,
password varchar(100) not null,
email varchar(80) not null,
nickname varchar(255) default ''::character varying not null,
mobile_phone varchar(16) default ''::character varying not null,
gender smallint not null,
enabled boolean default false not null,
flag smallint default 0 not null,
create_time timestamp default CURRENT_TIMESTAMP not null,
update_time timestamp default CURRENT_TIMESTAMP not null
);
comment on table auth_user is '用户';
comment on column auth_user.id is '用户编号';
comment on column auth_user.department_code is '部门编号';
comment on column auth_user.username is '用户名';
comment on column auth_user.password is '密码';
comment on column auth_user.email is '电子邮件';
comment on column auth_user.nickname is '昵称';
comment on column auth_user.mobile_phone is '手机号';
comment on column auth_user.gender is '性别';
comment on column auth_user.enabled is '有效用户';
comment on column auth_user.flag is '删除标志';
comment on column auth_user.create_time is '建立时间';
comment on column auth_user.update_time is '更新时间';
alter table auth_user
owner to management;
create unique index auth_user_username_unique_key
on auth_user (username);
create unique index auth_user_mobile_phone_unique_key
on auth_user (mobile_phone);
create unique index auth_user_email_unique_key
on auth_user (email);
从Controller层进行测试,建立一个UserController,最简单的获取列表
#controller 类
@RestController
@RequestMapping("/user")
@Api(value = "用户", tags = {"用户管理接口"})
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/list")
@ApiOperationSupport(order = 1, author = "jing.min@163.com")
@ApiOperation(value = "01、获取所有用用户")
public List<User> list() {
return userService.list();
}
}
package com.jingmin.system.controller;
import com.jingmin.system.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {com.jingmin.system.SystemApplication.class})
@AutoConfigureMockMvc
@Slf4j
@DisplayName("用户接口")
class UserControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
@DisplayName("User List")
void testUserList() {
String url = "/user/list";
EntityExchangeResult<List<User>> response = this.webTestClient
.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody(new ParameterizedTypeReference<List<User>>() {
}).returnResult();
List<User> result = response.getResponseBody();
assertThat(result, hasItem(hasProperty("username", equalTo("admin"))));
}
}
package com.jingmin.management.configuration;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Configuration
//@EnableWebMvc
@Slf4j
@EnableSwagger2
public class WebMvcConfiguration implements WebMvcConfigurer {
@Value("${spring.jackson.date-format:yyyy/MM/dd HH:mm:ss}")
private String dateTimePattern;
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
builder.serializerByType(Long.class,new LongSerializer());
builder.deserializerByType(Long.class, new LongDeserializer());
builder.serializerByType(LocalDateTime.class, localDateTimeSerializer());
builder.deserializerByType(LocalDateTime.class, localDateTimeDeserializer());
builder.serializerByType(LocalDate.class, localDateSerializer());
builder.deserializerByType(LocalDate.class, localDateDeserializer());
};
}
@Bean
public LocalDateTimeSerializer localDateTimeSerializer() {
return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimePattern));
}
@Bean
public LocalDateTimeDeserializer localDateTimeDeserializer() {
return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimePattern));
}
@Bean
public LocalDateSerializer localDateSerializer() {
String pattern = dateTimePattern;
if (pattern.indexOf(' ') > 0) {
pattern = pattern.substring(0, pattern.indexOf(' '));
}
return new LocalDateSerializer(DateTimeFormatter.ofPattern(pattern));
}
@Bean
public LocalDateDeserializer localDateDeserializer() {
String pattern = dateTimePattern;
if (pattern.indexOf(' ') > 0) {
pattern = pattern.substring(0, pattern.indexOf(' '));
}
return new LocalDateDeserializer(DateTimeFormatter.ofPattern(pattern));
}
public class LongSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value.toString());
}
}
public class LongDeserializer extends JsonDeserializer<Long> {
@Override
public Long deserialize(JsonParser p, DeserializationContext deserializationContext)
throws IOException {
return Long.parseLong(p.getValueAsString());
}
}
}
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com