Skip to content

metamessage/mm-spring-boot

Repository files navigation

mm-spring-boot

為 Spring Boot 應用提供完整的 MetaMessage 協議支持。

MetaMessage 是一種結構化數據交換協議,自描述、自約束、自示例,實現了無損數據交換。本項目讓 Spring Boot 應用能以零配置接入 MetaMessage 生態,並提供類型安全的泛型 HTTP 客戶端。


目錄


特性一覽

  • 組合映射註解 —— @MMGet/@MMPost/@MMPut/@MMDelete/@MMPatch 自動設置 consumes/produces
  • @MMBody / @MMQuery —— @MMBody 替代 @RequestBody 解碼請求體,@MMQuery?data=<hex> 解碼查詢參數
  • Schema 自動推斷 —— 無需顯式 @MMSchema,從 @MMBody/@MMQuery 參數自動推斷 OPTIONS 樣例類型
  • 零配置自動裝配 —— 引入 starter 即生效,可通過 metamessage.* 屬性按需關閉各模塊
  • OPTIONS Schema 發現 —— OPTIONS 請求返回結構體樣例 + Schema-Md5 校驗頭
  • 泛型 HTTP 客戶端 —— mmGet/mmPost/mmPut/mmDelete/mmPatch reified 函數,自動 OPTIONS 預檢 + Schema-Md5 緩存
  • 完整示例 —— 內存 CRUD 演示服務端 + 客戶端全流程

項目結構

mm-spring-boot/
├── settings.gradle.kts              # 根 settings,聲明三個子模塊
├── build.gradle.kts                 # 根 build,統一 group/version/倉庫
├── mm-spring-boot-starter/          # 服務端 starter
│   ├── build.gradle.kts
│   └── src/main/kotlin/io/github/metamessage/spring/boot/
│       ├── codec/                   # MetaMessageCodec + 異常封裝
│       ├── converter/               # wire + JSONC HttpMessageConverter
│       ├── binder/                  # @MMBody/@MMQuery + ArgumentResolver + ConverterFactory
│       ├── web/                     # @MMGet/@MMPost/@MMPut/@MMDelete/@MMPatch
│       ├── autoconfigure/           # @AutoConfiguration + Properties
│       └── schema/                  # @MMSchema + OPTIONS 攔截器(支持自動推斷)
├── mm-client/                       # 泛型 HTTP 客戶端
│   ├── build.gradle.kts
│   └── src/main/kotlin/io/github/metamessage/client/
│       ├── MMClient.kt              # 核心 doRequest<REQ, RESP>
│       └── MMClients.kt             # mmGet/mmPost/... 便捷函數
└── examples/                        # 可運行的示例應用
    ├── build.gradle.kts
    └── src/main/kotlin/com/example/
        ├── Application.kt           # Spring Boot 入口
        ├── model/Models.kt          # @MM 標註的數據模型
        ├── controller/UserController.kt  # 完整 CRUD 端點
        └── ClientDemo.kt            # 客戶端演示

快速開始

1. 添加 JitPack 倉庫與依賴

// Gradle Kotlin DSL
repositories {
    mavenCentral()
    maven { url = uri("https://jitpack.io") }
}

2. 引入 starter(服務端)

dependencies {
    implementation("com.github.metamessage:mm-spring-boot-starter:v0.2.9")
}

3. 引入客戶端(可選)

dependencies {
    implementation("com.github.metamessage:mm-client:v0.2.9")
}

就這樣!


服務端使用

定義數據模型

@field:MM 註解聲明字段元信息,ValueType 指定類型:

import io.github.metamessage.MM
import io.github.metamessage.ir.ValueType

data class User @JvmOverloads constructor(
    @field:MM(desc = "用戶ID", type = ValueType.I64) var id: Long = 0L,
    @field:MM(desc = "用戶名稱", type = ValueType.STR, min = "1", max = "50") var name: String = "",
    @field:MM(desc = "電子郵箱", type = ValueType.EMAIL, allowEmpty = true) var email: String = "",
    @field:MM(desc = "年齡", type = ValueType.U8, min = "0", max = "150", allowEmpty = true) var age: Int = 0,
    @field:MM(desc = "是否激活", type = ValueType.BOOL) var active: Boolean = false
)

編寫控制器

使用 @MMGet/@MMPost 等組合註解自動設置 Content-Type,@MMBody/@MMQuery 聲明參數綁定方式,Schema 自動推斷無需 @MMSchema

import io.github.metamessage.spring.boot.binder.MMBody
import io.github.metamessage.spring.boot.binder.MMQuery
import io.github.metamessage.spring.boot.web.MMGet
import io.github.metamessage.spring.boot.web.MMPost
import io.github.metamessage.spring.boot.web.MMPut
import io.github.metamessage.spring.boot.web.MMDelete

@RestController
@RequestMapping("/api/v1")
class UserController {

    // GET:@MMQuery 從 ?data=<hex> 解碼為 SearchRequest
    @MMGet(["/user/search"])
    fun search(@MMQuery req: SearchRequest): APIResponse {
        val user = users.find {
            it.name.contains(req.name) && it.age in req.minAge..req.maxAge
        } ?: throw NoSuchElementException("user not found")
        return APIResponse(code = 200, message = "ok", data = user)
    }

    // POST:@MMBody 解碼請求體為 CreateUserRequest
    @MMPost(["/user/create"])
    fun create(@MMBody req: CreateUserRequest): APIResponse {
        val user = User(id = nextId++, name = req.name, email = req.email, age = req.age)
        users.add(user)
        return APIResponse(code = 200, message = "user created", data = user)
    }

    // PUT:path variable + body
    @MMPut(["/user/update/{id}"])
    fun update(@PathVariable id: Long, @MMBody req: UpdateUserRequest): APIResponse {
        // ...
    }

    // DELETE:無請求體
    @MMDelete(["/user/delete/{id}"])
    fun delete(@PathVariable id: Long): APIResponse {
        // ...
    }
}
註解 作用
@MMGet(["/path"]) GET 端點,自動 produces = ["application/metamessage"]
@MMPost(["/path"]) POST 端點,自動 consumes + produces
@MMPut(["/path"]) PUT 端點,自動 consumes + produces
@MMDelete(["/path"]) DELETE 端點,自動 produces
@MMBody 請求體參數,由 ArgumentResolver 解碼
@MMQuery 查詢參數,從 ?data=<hex> 解碼
@MMSchema(type = X::class) 可選,顯式指定 OPTIONS 樣例類型(覆蓋自動推斷)

OPTIONS Schema 發現

攔截器自動響應 OPTIONS 請求,返回請求類型的樣例 + Schema-Md5 校驗頭。Schema 類型按以下優先級推斷:

  1. 顯式 @MMSchema(type = X::class) —— 優先級最高
  2. @MMQuery 參數類型 —— 自動推斷
  3. @MMBody 參數類型 —— 自動推斷
  4. 無上述註解 —— 不返回樣例
OPTIONS /api/v1/user/create
→ 200 OK
  Content-Type: application/metamessage
  Schema-Md5: <md5-hex>
  Access-Control-Max-Age: 86400
  Allow: GET,HEAD,POST,PUT,DELETE,OPTIONS,PATCH
  Body: <MetaMessage 編碼的請求類型樣例>

客戶端可緩存 Schema-Md5 並在後續請求中攜帶,供服務端校驗。


客戶端使用

mm-client 提供對標 mm-web-go 的泛型 HTTP 客戶端,每次請求前自動發送 OPTIONS 預檢並緩存 Schema-Md5

初始化

import io.github.metamessage.client.MMClient

MMClient.setDefaultClient("http://localhost:8090", debug = true)

發送請求

使用 reified 泛型函數,類型安全,無需傳 Class 對象:

import io.github.metamessage.client.mmGet
import io.github.metamessage.client.mmPost
import io.github.metamessage.client.mmPut
import io.github.metamessage.client.mmDelete

// POST:body 以 wire 二進制作為請求體
val resp: APIResponse = mmPost<CreateUserRequest, APIResponse>(
    "/api/v1/user/create",
    CreateUserRequest(name = "David", email = "david@example.com", age = 28)
)

// GET:body 編碼為 hex 作為 ?data=<hex> 查詢參數,服務端 @MMQuery 自動解碼
val search: APIResponse = mmGet<SearchRequest, APIResponse>(
    "/api/v1/user/search",
    SearchRequest(name = "Alice", minAge = 20, maxAge = 40)
)

// GET:無請求體
val list: ListUsersResponse = mmGet<Unit, ListUsersResponse>("/api/v1/users")

// PUT / DELETE 同理
val updated: APIResponse = mmPut<UpdateUserRequest, APIResponse>(
    "/api/v1/user/update/1",
    UpdateUserRequest(name = "Alice Updated")
)

val deleted: APIResponse = mmDelete<Unit, APIResponse>("/api/v1/user/delete/3")

高級用法:直接使用 MMClient

val client = MMClient("http://localhost:8090", debug = true)
val resp: APIResponse = client.doRequest(
    "POST",
    "/api/v1/user/create",
    CreateUserRequest(name = "David"),
    APIResponse::class.java
)

配置項

所有配置項前綴為 metamessage,均支持 matchIfMissing(不配置時默認啟用):

metamessage:
  enabled: true                    # 總開關,false 則整個 starter 不生效

示例應用

examples 模塊提供一個完整的 Spring Boot 應用,演示:

端點 方法 說明
/api/v1/users GET 列出所有用戶(wire)
/api/v1/user/search GET 按名稱+年齡範圍搜索用戶(wire)
/api/v1/user/{id} GET 獲取單個用戶(wire)
/api/v1/user/create POST 創建用戶(wire 請求體)
/api/v1/user/update/{id} PUT 更新用戶(wire)
/api/v1/user/delete/{id} DELETE 刪除用戶(wire)
/api/v1/health GET 健康檢查

運行示例

# 啟動示例應用
./gradlew :examples:bootRun --args="--server.port=8090"

啟動後 ClientDemo 會自動運行,依次演示 create → list → get → search → update → delete → health 全流程,並打印結果:

[ClientDemo] base URL: http://localhost:8090
[ClientDemo] create -> code=200 message=user created data=User(id=4, name=David, ...)
[ClientDemo] list -> total=4
  - id=1 name=Alice email=alice@example.com age=30
  - id=2 name=Bob email=bob@example.com age=25
  ...
[ClientDemo] get -> code=200 message=ok data=User(id=1, name=Alice, ...)
[ClientDemo] search -> code=200 message=ok data=User(id=1, name=Alice, ...)
[ClientDemo] update -> code=200 message=user updated data=User(id=1, name=Alice Updated, ...)
[ClientDemo] delete -> code=200 message=user deleted data=User(id=3, ...)
[ClientDemo] health -> status=ok

技術棧

組件 版本
Kotlin 1.9.22
Spring Boot 3.2.5
JDK 17
MetaMessage 核心庫 com.github.metamessage:metamessage:v0.2.9(JitPack)
構建工具 Gradle 8.7(多模塊,Kotlin DSL)

構建與測試

# 編譯全部模塊
./gradlew build

# 運行單元測試
./gradlew test

# 打包(跳過測試)
./gradlew build -x test

測試覆蓋

  • MetaMessageCodecTest —— wire 編解碼、JSONC 編解碼 round-trip
  • MetaMessageHttpMessageConverterTest —— wire 轉換器讀寫
  • MetaMessageJsoncHttpMessageConverterTest —— JSONC 轉換器讀寫
  • MetaMessageConverterTest —— ConverterFactory 字符串轉對象
  • MMClientTest —— 用 MockRestServiceServer 驗證 OPTIONS 預檢、Schema-Md5 緩存、wire 請求/響應、GET 查詢參數

License

本項目跟隨 metamessage 生態的開源許可。

About

MetaMessage 是一種結構化數據交換協議,自描述、自約束、自示例,實現了無損數據交換。本項目讓 Spring Boot 應用能以零配置接入 MetaMessage 生態,並提供類型安全的泛型 HTTP 客戶端。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages