1. Micronaut

https://docs.micronaut.io/latest/guide https://micronaut-projects.github.io/micronaut-security/snapshot/guide/#session

2. 准备环境

使用SDK安装Micronaut基础环境,参考SDK环境文档。

mn create-app dbzdb --lang=groovy

1.x.x 系列我们下载环境包,然后再终端生成项目https://github.com/micronaut-projects/micronaut-core/releases/download/v1.3.7/micronaut-1.3.7.zip
解压到: C:\micronaut
配置环境变量:MICRONAUT_HOME C:\micronaut,添加 bin 目录到 PATH
最新的版本就是1.3.7

2.x.x 版本,我们可以直接再线上生成基础代码 https://micronaut.io/launch/ 官方文档:https://docs.micronaut.io/latest/guide/index.html#quickStart

3.x.x 版本,使用SDK创建。

也可以直接在IDEA上创建最新版本的应用。

4.x.x version, 2023.11.6 https://docs.micronaut.io/4.1.6/guide/index.html

Update micronaut version to 4.2.1 Environments: JDK 17 Gradle 8.4 Groovy 4.0.16

2.1. 创建应用

mn create-app cn.duchaoqun --lang=groovy
mn create-app hello-world --lang=java
mn create-app jigao-risk-assessment --lang=java
mn create-app example.micronaut.micronautguide \
    --features=yaml,data-jdbc,flyway,jdbc-hikari,mysql,graalvm,serialization-jackson,validation \
    --build=gradle \
    --lang=java \
    --test=junit

3. application.yml

3.1.1. 配置 POST 请求中的文件大小

POST 表单中的文件大小限制,注意这里我们使用的文件单位。

micronaut:
  application:
    name: filemanager
  server:
    port: 10021
    multipart:
    max-file-size: '100MB'  # post filesize
    max-request-size: '100MB'  # post filesize

3.1.2. 配置 CORS 限制

cors 当micronaut作为微服务被其他应用系统调用的时候,有一种情况就是不同源的调用,我们需要启动 micronaut 的策略来开放这种请求权限给对方系统。

https://docs.micronaut.io/latest/guide/index.html#cors

micronaut:
  application:
    name: java-api-service
  server:
    port: 8484
    cors:
      enabled: true

3.1.3. 配置数据库

参考文档: https://micronaut-projects.github.io/micronaut-sql/latest/guide/#jdbc

添加引用
// build.gradle

implementation("org.postgresql:postgresql:42.6.0")
implementation("io.micronaut.sql:micronaut-jdbc-tomcat")

配置参数,如果用在生产环境,注意这里面的参数需要优化,例如数据库断开重连。

datasources:
  default:
    url: jdbc:postgresql://127.0.0.1:5432/api?targetServerType=master&tcpKeepAlive=true&autoReconnect=true
    username: postgres
    password: 123456
    driverClassName: org.postgresql.Driver
    jmxEnabled: true
    initialSize: 5
    maxActive: 50
    minIdle: 5
    maxIdle: 25
    maxWait: 10000
    maxAge: 600000
    timeBetweenEvictionRunsMillis: 5000
    minEvictableIdleTimeMillis: 60000
    validationQuery: “SELECT 1”
    validationQueryTimeout: 3
    validationInterval: 15000
    testOnBorrow: true
    testWhileIdle: true
    testOnReturn: false
    jdbcInterceptors: “ConnectionState;StatementCache(max=200)”

dialect: POSTGRES

3.2. Use Two database

MySQL and SQLite

datasources:  
  mysql:  
    driver-class-name: com.mysql.jdbc.Driver  
    url: jdbc:mysql://localhost:3306/jgdb_risk_test  
    username: root  
    password: 123456  
    connectionTimeout: 5000  
    socketTimeout: 10000  
    maxWaitTime: 10000  
    maxConnections: 10  
    minConnections: 1  
    maxConnectionAge: 60000  
    connectionTestQuery: "SELECT 1"  
    validationQuery: "SELECT 1"  
    useCompression: false  
    useKeepAlive: true  
    testOnBorrow: true  
    testOnReturn: false  
    testWhileIdle: true  
    timeBetweenEvictionRunsMillis: 30000  
    minEvictableIdleTimeMillis: 60000  
    numTestsPerEvictionRun: 3  
    minPoolSize: 5  
    maxPoolSize: 20  
    maxLifetime: 1800000  
  sqlite:  
    url: jdbc:sqlite:database.db  
    driverClassName: org.sqlite.JDBC
    // notice here, no username and no password.
    dialect: h2

3.3. 配置SSL

使用pkcs12文件生成Tomcat使用的jks文件:
keytool -importkeystore -srckeystore C:\tomcat.p12 -srcstoretype pkcs12 -destkeystore C:\tomcat.jks

micronaut:

  application:

    name: aoye

  server:

    port: 8484

  ssl:

    enabled: true

    key-store:

      path: file:C:\Users\ducha\Desktop\file.rexen.com.cn.p12

      password: 123456

      type: PKCS12

pkcs12文件参考文档:
http://blog.szwyll.com/archives/1171
https://blog.freessl.cn/ssl-cert-format-introduce/

micronaut配置https:
https://docs.micronaut.io/latest/guide/index.html#https

4. Controller

Web的访问路径:
@Controller(“/process”)
class Process {

处理GET请求。
@Get(value = “/test”)
def test() {

@Inject
DataSource dataSource

4.1. @PathVariable

从URL中取到的参数

@Get("/getFile/{filename}")  
def getFile(@PathVariable String filename) {  
	return new File(base_file_url + filename)  
}

4.1.1. consumes

默认接收一个JSON字符串,放到content里面,在代码里面手动解析,产出的也是JSON

@Post(value = “/v1/putContract”, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)

def putContract(@Body String content) {}

设置允许接受的类型,默认的是“application/json”

@Post(value = “/callback”, consumes = MediaType.APPLICATION_FORM_URLENCODED)

def callback(String signature, String timestamp, String content) {

    logger.info(“signature:” + signature)

    logger.info(“timestamp:” + timestamp)

    logger.info(“content:” + content)

   return “success”

}

4.1.2. produces

设置返回想响应类型
@Get(value = “/detail”, produces = MediaType.TEXT_JSON) // 返回JSON数据
@Get(value = “/downloadContract”, produces = ‘application/pdf’) // 返回PDF文档byte[]

返回xlsx类型文件
@Produces(value = “application/vnd.ms-excel”)
@Get(value = “/testAccount”)
def testAccount(String type) {
InputStream is = new ByteArrayInputStream(outputStream.toByteArray())
  return new StreamedFile(is, new MediaType(“application/vnd.ms-excel”)).attach(“member\.xlsx”)
}

通过代码控制 Response,具体的 contentType 是什么字符,请参考其他文档。
@Get(”/“)
public HttpResponse sample(final HttpHeaders headers) {
// Simple object to be returned from this method either
// as XML or JSON, based on the HTTP Accept header.
final Message message = new Message(“Micronaut is awesome”);

// Check if HTTP Accept header is “application/xml”.

if (headerAcceptXml(headers)) {

    // Encode messages as XML.

    final String xml = encodeAsXml(message);

    // Return response and set content type

    // to “application/xml”.

    return HttpResponse.ok(xml)

            .contentType(MediaType.APPLICATION_XML_TYPE);

}

// Default response as JSON.

return HttpResponse.ok(message);

最常用的情况就是我们直接返回一个Map对象,它会自动转换成对应的JSON String,接受处理的时候要注意值的类型,是Integer,而不是String。
return [‘status’:200, ‘code’:0]

4.1.3. @Post

4.1.3.1. application/x-www-form-urlencoded

@QueryValue 无论是Get或者Post或者其他请求,获取URL中的查询参数

   @Post(value = “/testPost”, consumes = MediaType.APPLICATION_FORM_URLENCODED)

   def callback(@QueryValue String signature, @QueryValue String timestamp, String content) {

        log.info(“signature:” + signature)

        log.info(“timestamp:” + timestamp)

        log.info(“content:” + content)

       return “success”

   }

4.1.3.2. multipart/form-data

   @Post(value = “/testPost”, consumes = MediaType.MULTIPART_FORM_DATA)

   def callback(@QueryValue String signature, @QueryValue String timestamp, String content) {

        log.info(“signature:” + signature)

        log.info(“timestamp:” + timestamp)

        log.info(“content:” + content)

       return [“signature”: content]

   }

4.1.4. 获取application.yml中的配置

在yml配置文件中定义号我们需要的配置信息。
config:
    baseURL: https://www.duchaoqun.cn

// 注入到我们需要的类中。  
@Controller("/test")  
class GetPdf {  
    // 再这里就可以注入配置文件中的信息  
    @Value('${config.baseURL}')  
    private String baseURL

4.1.5. HttpResponse

return Mono._from_(uploadPublisher)  
        .map(success -> {  
            if (success) {  
                return HttpResponse._ok_(resOk.toJSONString()).contentType(MediaType._APPLICATION_JSON_);  
            } else {  
                return HttpResponse.<String>_status_(_BAD_REQUEST_)  
                        .body(resFail.toJSONString());  
            }  
        });

4.2. 定时任务

在Controler中设置定时任务,cron表达式。

@Scheduled(cron = “0 2 16 * * ?“)
void execute(){
    // code
}

4.3. RxHttpClient

4.3.1. 阻塞方式

// 创建一个客户端
@Client(“http://www.nmc.cn”)
@Inject
RxHttpClient httpClient

@Get(value = “/test6”, produces = MediaType.TEXT_PLAIN)

def test6() {

    // 使用阻塞方式访问,这里直接会返回内容

    String result = httpClient.toBlocking().retrieve(“/f/rest/real/54161”)

    // 使用阿里巴巴的 fastjson 包解析数据

    println(JSON.parse(result))

    return result

}

4.3.2. 非阻塞方式

import io.micronaut.http.client.RxHttpClient
import static io.micronaut.http.HttpRequest.GET

@Client(“http://www.nmc.cn”)

@Inject

RxHttpClient httpClient

@Get(value = “/test6”, produces = MediaType.TEXT_PLAIN)

def test6() {

    // 非阻塞方式,线程并不会一直在这里等待,需要做同步处理。

    return httpClient.retrieve(GET(“/f/rest/real/54161”)).firstElement()

}

4.4. 使用静态页面

// https://mvnrepository.com/artifact/io.micronaut.views/micronaut-views-core

implementation ‘io.micronaut.views:micronaut-views-core’

// application.yml

micronaut:

  application:

    name: aoye

  router:

   static-resources:

      main:

        paths: classpath:views

        mapping: /main/**

// Controller
 
@Controller('main')
 
class MainController {
 
   @View("index.html")
 
   @Get("/")
 
    HttpResponse index() {
 
       return HttpResponse.ok()
 
   }
 
}

5. Logback

首先配置日志规则:src/main/resources/logback.xml

<configuration>  
     <!-- 负责写日志的组件,两个必要的属性-->   <!-- name: appender的名称-->   <!-- class: appender全限定名称-->   
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">  
        <withJansi>false</withJansi>  
         <!-- 格式化日志 -->   
<encoder>  
            <pattern>%cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n  
            </pattern>  
        </encoder>  
    </appender>  
  
     <!-- 将日志写入文件 -->   
<appender name="httpAccessLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">  
         <!-- 是否追加日志 -->   
<append>true</append>  
         <!-- 写入文件位置 -->   
<file>access.log</file>  
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
             <!-- daily rollover -->   
<fileNamePattern>access-%d{yyyy-MM-dd}.log  
            </fileNamePattern>  
            <maxHistory>180</maxHistory>  
        </rollingPolicy>  
         <!-- 格式化日志 -->   
<encoder>  
            <charset>UTF-8</charset>  
            <pattern>%cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n  
            </pattern>  
        </encoder>  
        <immediateFlush>true</immediateFlush>  
    </appender>  
  
    <root level="INFO">  
        <appender-ref ref="STDOUT"/>  
        <appender-ref ref="httpAccessLogAppender"/>  
    </root>  
</configuration>

代码中使用日志:

private static Logger logger = LoggerFactory.getLogger(Process.class);

5.1. Resources文件

使用resources文件夹中的文件
ClassPathResourceLoader loader = new ResourceResolver().getLoader(ClassPathResourceLoader.class).get()
InputStream template = loader.getResourceAsStream(‘template.docx’).get()

5.2. 打包发布

./gradlew assemble

6. Mac 下如果如下下面提示,说明这个文件的格式不对

env: bash\r: No such file or directory

7. 解决:把文件改成unix格式即可。

vim gradlew

:set fileformat=unix

:wq

8. jwt

https://guides.micronaut.io/latest/micronaut-security-jwt-gradle-groovy.html