feat: extend Java config, Schema, and DB for selfhosted licensing SDK

This commit is contained in:
2026-05-18 22:20:14 +08:00
parent fbce298f2b
commit 91aabb500c
6 changed files with 132 additions and 7 deletions
+5 -3
View File
@@ -4,11 +4,13 @@
"scenario": "school", "scenario": "school",
"selfhosted": { "selfhosted": {
"baseUrl": "https://license.internal.example/api/v1", "baseUrl": "https://license.internal.example/api/v1",
"tenantKey": "district-west" "tenantKey": "district-west",
"offlineGraceDays": 7,
"heartbeatIntervalHours": 24
}, },
"features": { "features": {
"face": {}, "face": { "selfhostedFeatureKey": "face" },
"expression": {} "expression": { "selfhostedFeatureKey": "expression" }
}, },
"school": { "school": {
"edgeDeviceId": "gate-02" "edgeDeviceId": "gate-02"
@@ -11,4 +11,5 @@ import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public record FeatureMapping( public record FeatureMapping(
@JsonProperty("bitanswerFeatureId") Integer bitanswerFeatureId, @JsonProperty("bitanswerFeatureId") Integer bitanswerFeatureId,
@JsonProperty("bitanswerFeatureName") String bitanswerFeatureName) {} @JsonProperty("bitanswerFeatureName") String bitanswerFeatureName,
@JsonProperty("selfhostedFeatureKey") String selfhostedFeatureKey) {}
@@ -11,4 +11,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public record SelfhostedConfigSection( public record SelfhostedConfigSection(
@JsonProperty("baseUrl") String baseUrl, @JsonProperty("baseUrl") String baseUrl,
@JsonProperty("tenantKey") String tenantKey) {} @JsonProperty("tenantKey") String tenantKey,
@JsonProperty("offlineGraceDays") Integer offlineGraceDays,
@JsonProperty("heartbeatIntervalHours") Integer heartbeatIntervalHours,
@JsonProperty("publicKeyPem") String publicKeyPem) {
public SelfhostedConfigSection {
offlineGraceDays = offlineGraceDays != null ? offlineGraceDays : 7;
heartbeatIntervalHours = heartbeatIntervalHours != null ? heartbeatIntervalHours : 24;
}
}
@@ -17,7 +17,7 @@ import cn.craftlabs.auth.internal.NativeBridge;
*/ */
public final class SelfHostedAuthProvider implements AuthProvider { public final class SelfHostedAuthProvider implements AuthProvider {
static { static {
System.loadLibrary("craftlabs_auth_bitanswer"); System.loadLibrary("craftlabs_auth_core");
} }
private long nativeHandle; private long nativeHandle;
+20 -1
View File
@@ -61,6 +61,24 @@
"tenantKey": { "tenantKey": {
"type": "string", "type": "string",
"description": "可选;租户或调用方标识。" "description": "可选;租户或调用方标识。"
},
"offlineGraceDays": {
"type": "integer",
"minimum": 0,
"maximum": 365,
"default": 7,
"description": "离线宽限期(天),断网后允许继续使用的最大天数。"
},
"heartbeatIntervalHours": {
"type": "integer",
"minimum": 1,
"maximum": 720,
"default": 24,
"description": "心跳间隔(小时)。"
},
"publicKeyPem": {
"type": "string",
"description": "可选;RSA 公钥 PEM。不填则使用 SDK 内嵌公钥。"
} }
} }
}, },
@@ -72,7 +90,8 @@
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"bitanswerFeatureId": { "type": "integer" }, "bitanswerFeatureId": { "type": "integer" },
"bitanswerFeatureName": { "type": "string" } "bitanswerFeatureName": { "type": "string" },
"selfhostedFeatureKey": { "type": "string" }
} }
} }
}, },
@@ -0,0 +1,94 @@
-- V6__selfhosted_licensing.sql
-- 自研授权 SDK 许可证管理表
-- RSA 密钥对管理
CREATE TABLE IF NOT EXISTS platform_license_keys (
id UUID PRIMARY KEY,
key_id VARCHAR(32) NOT NULL UNIQUE,
public_key TEXT NOT NULL,
private_key TEXT NOT NULL,
algorithm VARCHAR(16) NOT NULL DEFAULT 'RS256',
status VARCHAR(16) NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'rotated', 'revoked')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
rotated_at TIMESTAMPTZ
);
-- 许可证策略模板
CREATE TABLE IF NOT EXISTS platform_license_policies (
id UUID PRIMARY KEY,
name VARCHAR(128) NOT NULL,
grant_type VARCHAR(20) NOT NULL DEFAULT 'subscription',
default_validity_days INT,
default_max_devices INT NOT NULL DEFAULT 1,
default_offline_grace_days INT NOT NULL DEFAULT 7,
feature_defaults TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 许可证主表
CREATE TABLE IF NOT EXISTS platform_licenses (
id UUID PRIMARY KEY,
license_id VARCHAR(26) NOT NULL UNIQUE,
tenant_id VARCHAR(64) NOT NULL,
contract_id UUID,
policy_id UUID REFERENCES platform_license_policies(id),
grant_type VARCHAR(20) NOT NULL,
not_before TIMESTAMPTZ NOT NULL,
not_after TIMESTAMPTZ,
offline_grace_days INT NOT NULL DEFAULT 7,
heartbeat_interval_hours INT NOT NULL DEFAULT 24,
max_devices INT NOT NULL DEFAULT 1,
max_concurrent_users INT NOT NULL DEFAULT 0,
max_activations INT NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'revoked', 'expired')),
issued_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
revoked_reason VARCHAR(255),
signed_payload TEXT NOT NULL,
key_id VARCHAR(32) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 许可证特性
CREATE TABLE IF NOT EXISTS platform_license_features (
license_id VARCHAR(26) NOT NULL REFERENCES platform_licenses(license_id) ON DELETE CASCADE,
feature_key VARCHAR(64) NOT NULL,
enabled BOOLEAN NOT NULL,
PRIMARY KEY (license_id, feature_key)
);
-- 终端激活记录
CREATE TABLE IF NOT EXISTS platform_license_activations (
id UUID PRIMARY KEY,
license_id VARCHAR(26) NOT NULL REFERENCES platform_licenses(license_id) ON DELETE CASCADE,
device_hash VARCHAR(128) NOT NULL,
device_info TEXT,
stability_score SMALLINT NOT NULL DEFAULT 0,
server_uuid VARCHAR(64),
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'released', 'deactivated')),
first_seen_at TIMESTAMPTZ NOT NULL,
last_heartbeat TIMESTAMPTZ,
deactivated_at TIMESTAMPTZ,
UNIQUE (license_id, device_hash)
);
-- 心跳审计(可选,视量级决定)
CREATE TABLE IF NOT EXISTS platform_license_heartbeats (
id BIGSERIAL,
license_id VARCHAR(26) NOT NULL,
device_hash VARCHAR(128) NOT NULL,
heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT now(),
online_status BOOLEAN NOT NULL,
response_code VARCHAR(10)
);
CREATE INDEX IF NOT EXISTS idx_heartbeats_license_time
ON platform_license_heartbeats(license_id, heartbeat_at DESC);
-- 索引
CREATE INDEX IF NOT EXISTS idx_licenses_status ON platform_licenses(status);
CREATE INDEX IF NOT EXISTS idx_licenses_tenant ON platform_licenses(tenant_id);
CREATE INDEX IF NOT EXISTS idx_activations_license ON platform_license_activations(license_id);