# 后端项目搭建

# 建立Gradle 项目

# 主项目

项目用 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# system子项目

# IDEA 新建子模块

建立子模块

# 子模块的build.gradle

plugins {
    id 'org.springframework.boot'
}
processResources {
    from('src/main/java') {
        include '**/*.xml'
    }
}
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
}
test {
    useJUnitPlatform()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 配置Log4j2环境

Log4j2 环境对应三个状态

  • 生产环境(proc)不对Console进行,级别定为info
  • 开发环境(dev),对Console进行,级别定为debug
  • 测试环境(test),对Console进行,级别定为debug

# 生产环境 log4j2-prod.xml

<?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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 开发与测试环境 log4j2-dev.xml

<?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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# application.yml

# application.yml 主环境

spring:
  jackson:
    date-format: yyyy/MM/dd HH:mm:ss
    default-property-inclusion: NON_EMPTY
  profiles:
    active: prod
  main:
    banner-mode: off
1
2
3
4
5
6
7
8

# application-proc.yml 主环境

logging:
  config: classpath:log4j2-prod.xml
1
2

# application-dev.yml 主环境

logging:
  config: classpath:log4j2-dev.xml
1
2

# 基础环境单元测试

一切基于测试进行构建

# 建立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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 建立Controller类

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";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 对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")));

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 配置knife4j

# builder.gradle 配置

在主项目 builder.gradle 不同的区域加入以下信息

buildscript {
  ext {
      knife4jVersion = '3.0.3'
  }
}
subprojects {
    dependencies {
       implementation "com.github.xiaoymin:knife4j-spring-boot-starter:${knife4jVersion}"
    }
}
1
2
3
4
5
6
7
8
9
10

# application.yml 配置

knife4j 3.0.3不兼容spring boot 2.6 系列

spring:
  mvc:
    pathmatch:
      # knife4j 使用 spring boot 2.5.7以前的模式
      matching-strategy: ant_path_matcher
1
2
3
4
5

# 配置文档分组

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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# knife4j 单元测试

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")));

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 配置数据库环境

# builder.gradle 加入依赖

在主项目 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"
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# application.yml 配置

# 主配置

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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

# application-prod.yml 配置

dev、test 配置与prod类似,可以在不同的环境下使用不同的数据库

spring:
  datasource:
    dynamic:
      datasource:
        master:
          username: xxxx
          password: xxxxxx
          url: jdbc:postgresql://postgres-server:5432/xxxx?currentSchema=public&stringtype=unspecified
1
2
3
4
5
6
7
8

# 设置Mapper搜索包

@MapperScan(value = {"com.jingmin.system.dao"})
public class SystemApplication {
    public static void main(String[] args) {
        SpringApplication.run(SystemApplication.class, args);
    }
}
1
2
3
4
5
6

# 生成数据代码

生成数据代码

# 测试

# 建立一个数据表
-- 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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

从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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
# 测试类
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"))));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 输出格式


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());
        }
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92