Extension SDK for the Meituan Open Platform: a multi-tenant wrapper on top of the official
MtOpJavaSDKwith typed business facades, tenant-aware request execution and Spring Boot auto-configuration.
- 1. Project Overview
- 2. Features & Status
- 3. Requirements & Compatibility
- 4. Architecture & Modules
- 5. Installation
- 6. Quick Start
- 7. Configuration
- 8. Core Usage / API
- 9. Testing & Build
- 10. Versioning & Branches
- 11. Contributing & License
meituan-sdk-extension is a Java extension layer on top of the official Meituan
Open Platform SDK (com.sankuai.sjst:MtOpJavaSDK). It lets one server-side
application serve many Meituan developer accounts / stores at the same time:
- Multi-tenant by design — every tenant carries its own
developerId/signKey/appAuthToken; officialMeituanClientinstances are created per tenant key set and cached (ConcurrentHashMap, cache key is adeveloperId:signKey-hashpair so the rawsignKeynever leaks into logs). - Tenant-scoped execution — business calls take a
tenantId, the executor resolves the tenant credentials from a pluggableMeituanTenantConfigStorageand delegates to the official client. - Typed business facades — 14 service interfaces (
MeituanWaimaiService,MeituanRetailService, …) covering the strong-typed Request/Response models extracted from the official SDK (1106 typed methods) (official test packagecorgiTestexcluded by design; coverage is enforced byMeituanApiCoverageTest). - Optional Spring Boot starter — the pairing starter meituan-spring-boot-starter wires config binding, tenant storage, client factory, executor and all business services; this core stays framework-free.
What it is not:
- Not a replacement for the official SDK —
com.meituan.sdk.*types are used underneath. - Not bound to a specific tenant store — the default is in-memory; implement
MeituanTenantConfigStorageto load tenants from a database or config center.
| Area | Status |
|---|---|
Multi-tenant credential model (developerId / signKey per tenant) |
✅ |
| Per-tenant official client creation & caching | ✅ |
| Tenant storage SPI + in-memory + cacheable decorator | ✅ |
Tenant context holder (thread-local tenantId) |
✅ |
| Business facades: catering, daocan (到店餐饮), delivery, distribution, freetry, kemanman, kuailv, live, pay, retail, store, tools, travel, waimai | ✅ |
Optional Spring Boot starter (meituan-spring-boot-starter, one line per Boot 2.3–4.1) |
✅ |
| Exception translation preserving official error codes | ✅ |
| Dependency | Version |
|---|---|
| Java | 1.8+ |
| Spring Boot | 2.7.x (auto-configuration API is Boot 2.7+ compatible) |
| Official SDK | com.sankuai.sjst:MtOpJavaSDK:1.0-20260923 (proprietary, see below) |
| Build | Maven 3.9.16 (./mvnw wrapper included) |
Official SDK availability —
MtOpJavaSDKis distributed by the Meituan Technical Service Cooperation Center (sdk-download) and is not published to Maven Central. A copy of the official jar and an immutable-coordinate POM are vendored underlibs/; CI installs them before building. Local development requires the same one-time installation:mvn install:install-file -Dfile=libs/MtOpJavaSDK-1.0-SNAPSHOT.jar \ -DpomFile=libs/MtOpJavaSDK-1.0-20260923.pom
| Package | Responsibility |
|---|---|
io.github.easy4j.meituan |
framework-free core: config objects, client factory, executor, services |
io.github.easy4j.meituan.client |
Official client factory, tenant-aware MeituanRequestExecutor |
io.github.easy4j.meituan.config |
MeituanConfig (platform) / MeituanTenantConfig (tenant) |
io.github.easy4j.meituan.tenant |
Tenant storage SPI, in-memory impl, cacheable decorator, MeituanTenantContextHolder, loader |
io.github.easy4j.meituan.service |
14 business service interfaces |
io.github.easy4j.meituan.service.impl |
Business service implementations |
io.github.easy4j.meituan.exception |
MeituanJavaException translation |
Call chain:
business service ──> MeituanRequestExecutor ──> MeituanTenantConfigStorage (resolve tenant)
│
└──> MeituanClientFactory (per-tenant cache) ──> com.meituan.sdk.MeituanClient
<dependency>
<groupId>io.github.easy4j</groupId>
<artifactId>meituan-sdk-extension</artifactId>
<version>1.0.x.20260831-SNAPSHOT</version>
</dependency>Spring Boot services should also add the starter
(meituan-spring-boot-starter),
which auto-wires MeituanConfig, MeituanTenantConfigStorage,
MeituanClientFactory, MeituanRequestExecutor and all Meituan*Service
beans with one dependency.
- Add the dependency (above).
- Configure at least one tenant (below).
- Inject a service and call it with the tenant id:
@Service
public class OrderService {
private final MeituanWaimaiService waimaiService;
public OrderService(MeituanWaimaiService waimaiService) {
this.waimaiService = waimaiService;
}
public MeituanResponse<?> queryOrder(String orderId, String tenantId) {
OrderQueryByIdRequest request = new OrderQueryByIdRequest();
// ... fill request
return waimaiService.orderQueryById(request, tenantId);
}
}meituan:
server-url: https://api-open-cater.meituan.com
charset: UTF-8
version: "2"
connect-timeout: 5000
read-timeout: 10000
tenants:
tenant-a:
app-id: app-a
developer-id: 100000
sign-key: your-sign-key
app-auth-token: token-a
business-id: 16
tenant-b:
app-id: app-b
developer-id: 200000
sign-key: another-sign-key
app-auth-token: token-b
business-id: 16Top-level keys are shared client defaults; each tenant carries its own developer credentials and store token.
Call a business service for a specific tenant:
MeituanResponse<?> response = retailService.orderQueryorder(request, "tenant-a");Or use the generic executor directly:
MeituanResponse<Foo> response = executor.execute(request, "tenant-a");
MeituanResponse<Bar> anon = executor.executeWithoutAuth(noAuthRequest);Custom tenant storage (e.g. load from database):
@Bean
MeituanTenantConfigStorage meituanTenantConfigStorage(TenantRepository repository) {
return tenantId -> repository.findByTenantId(tenantId);
}Cacheable storage with TTL for remote/config-center-backed tenants:
// TTL 10 minutes: entry reloads from the loader after expiry
MeituanTenantConfigStorage cached = new CachedMeituanTenantConfigStorage(
tenantId -> repository.findByTenantId(tenantId), Duration.ofMinutes(10));
// Push fresh credentials immediately after an authorization callback
cachedStorage.put("tenant-a", freshConfig); // overwrite cache
cachedStorage.refresh("tenant-a"); // force reload from loader
cachedStorage.evict("tenant-a"); // drop one entryToken lifecycle —
appAuthTokenrefresh is the caller's responsibility: implementMeituanTenantConfigLoaderto return the current token from your store, pick a TTL shorter than the token lifetime, or callput/refreshfrom your token-rotation job. The SDK never refreshes tokens itself.
Meituan callbacks use URL-encoded common parameters and carry
the business JSON in the message field. The SDK provides framework-neutral
parsing, signature verification, message-type resolution, and response models:
MeituanCallbackMessage callback = MeituanCallbackParser.parseForm(requestBody);
if (!MeituanCallbackSigner.verify(signKey, callback.getParameters(), callback.getSign())) {
return MeituanCallbackResponse.failure(-2, "invalid signature");
}
return MeituanBusiness58MessageType.resolve(callback)
.map(messageType -> switch (messageType) {
case OPERATE_DEVICE -> MeituanCallbackResponse.successJson(
handleOperateDevice(callback.getMessage()));
case START_DEVICE_AND_CONSUME_DEAL -> MeituanCallbackResponse.successJson(
handleStartAndConsume(callback.getMessage())); // e.g. {"result":0}
case ORDER_REFUND_INFO_PUSHED -> {
handleRefundNotification(callback.getMessage());
yield MeituanCallbackResponse.success();
}
})
.orElseGet(() -> MeituanCallbackResponse.failure(-1, "unsupported msgType"));MeituanBusiness58MessageType contains the 3 documented business 58 messages;
the enabled-list items “次月扣款查询” and “取消连续包月通知” remain unresolved
because no protocol ID is published. MeituanBusiness59MessageType contains
the 33 business 59 messages whose
msgType is available in the official documentation. It verifies businessId
before resolving. Unknown future types remain available through the raw callback;
the enabled-list item “消费流水变更” remains unresolved because no protocol ID is published.
MeituanBusiness71MessageType contains the documented Store Direct Connection
audit-result callback: capability poi_openapi_msg_push, msgType=7110001.
The four business 71 APIs are declared needAuth=false; MeituanStoreService
selects the tenant's developer credentials while invoking them without an
appAuthToken.
Custom MeituanRequestExecutor implementations must override
executeWithoutAuth(request, tenantId) to preserve tenant isolation; the
default method fails closed instead of silently ignoring tenantId.
The callback utilities cover the transport protocol, not business-side delivery
semantics. Consumers must persist and deduplicate msgId before executing
side effects, and enforce the callback timestamp window required by their
Meituan agreement. Domain payload validation, retry policy, and business actions
remain the application's responsibility.
./mvnw -B clean verify- Unit tests run with JUnit 6 + Mockito (38 tests).
- Live-API debug tests live in
io.github.easy4j.meituan.debug, tagged@Tag("integration")and excluded from normal builds. Fill in your own credentials before enabling them — the committed values are placeholders.
- Current line:
1.0.x—1.0.x.20260831-SNAPSHOT. - CI runs
./mvnw -B clean verifyonfeature/1.0.x(JDK 8).
This wrapper is licensed under Apache License 2.0.
The underlying MtOpJavaSDK remains proprietary to Meituan (三快科技) — you must
obtain it and your developer credentials from the Meituan Open Platform yourself.
Issues and PRs are welcome at
github.com/easy-4-java/meituan-sdk-extension.