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