`,授权码后边紧跟“<>” 示例:`JNLTGZSE******`**<123> | |
- **featureId** - [IN] 登录授权所需要包含的特征项ID。
@@ -73,6 +74,7 @@ lic:///tmp,lic:///data,bit://127.0.0.1:8273
```
+
- **pApplicationData** - [IN] 产品识别码,在Bitanswer SDK头文件里。
- **pHandle** - [OUT] 通过Login函数返回的上下文句柄。
- **mode** - [IN] 登录模式,按位操作。
@@ -154,6 +156,7 @@ BIT_STATUS Bit_LoginByToken(
BIT_UCHAR *pApplicationData,
BIT_HANDLE *pHandle)
```
+
帐号授权的登录接口。仅支持两种认证方式,这里是使用比特授权云平台自己产生的access_token进行认证。
### 参数
@@ -182,6 +185,7 @@ BIT_STATUS Bit_LoginByTokenEx(
BIT_UCHAR *pApplicationData,
BIT_HANDLE *pHandle)
```
+
帐号授权的登录接口。仅支持两种认证方式,这里是使用OIDC协议产生的id_token认证。
### 参数
@@ -213,6 +217,7 @@ BIT_STATUS Bit_LoginByPassword(
BIT_UCHAR *pApplicationData,
BIT_HANDLE *pHandle);
```
+
通过用户名和密码登录帐号授权。
### 参数
@@ -231,6 +236,7 @@ BIT_STATUS Bit_LoginByPassword(
BIT_STATUS Bit_Logout (
BIT_HANDLE handle)
```
+
此函数用于释放上下文句柄,退出登录状态,与Login相关接口一一对应。
### 参数
@@ -247,6 +253,7 @@ BIT_STATUS Bit_Revoke (
BIT_CHAR *pRevocationInfo,
BIT_UINT32 *pRevocationInfoSize)
```
+
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。根据输入参数的不同,本函数可用于在线或离线迁出。
### 参数
@@ -264,6 +271,7 @@ BIT_STATUS Bit_RemoveSn (
BIT_PCSTR szSN,
BIT_UCHAR *pApplicationData)
```
+
删除指定授权码在本机的授权数据。
### 参数
@@ -278,6 +286,7 @@ BIT_STATUS Bit_Heartbeat(
BIT_HANDLE handle,
BIT_UINT32 *pReconnectsNum)
```
+
手动心跳,可以无限次调用,10s只会触发一次。
注:当自动心跳停止后,调用该接口可以尝试恢复自动心跳。
@@ -298,6 +307,7 @@ BIT_STATUS Bit_SessionControl(
BIT_CHAR *pValue,
BIT_UINT32 *pValueLen)
```
+
控制或设置session的信息。具体使用场景可参考《浏览器并发控制》文档。
### 参数
@@ -325,6 +335,7 @@ BIT_STATUS Bit_SetSessionState (
BIT_UINT32 state,
BIT_VOID *pReserved)
```
+
设置客户端的状态为空闲状态或繁忙状态或激活状态。
### 参数
@@ -358,7 +369,6 @@ if (用户正在操作) {
## 激活升级
-
### Bit_UpdateOnline
```c
@@ -367,6 +377,7 @@ BIT_STATUS Bit_UpdateOnline (
BIT_PCSTR szSN,
BIT_UCHAR *pApplicationData)
```
+
此函数用于与授权服务器在线连接,自动完成本地授权的升级操作。本函数需要进行网络连接。
### 参数
@@ -395,6 +406,7 @@ BIT_STATUS Bit_GetRequestInfo (
BIT_CHAR *pRequestInfo,
BIT_UINT32 *pRequestInfoSize)
```
+
获取当前运行环境的升级请求码,用于发起本地授权激活及升级请求。如果第一次调用返回错误码260,说明传入的pRequestInfoSize太小,可传入返回的pRequestInfoSize再重新调用一次。
### 参数
@@ -442,6 +454,7 @@ BIT_STATUS Bit_ApplyUpdateInfo(
BIT_CHAR *pReceipt,
BIT_UINT32 *pReceiptSize)
```
+
应用升级码完成本地授权激活或升级。本函数必须在获取请求码的同一环境下执行。
### 参数
@@ -475,6 +488,7 @@ BIT_STATUS Bit_ApplyUpdateInfoEx(
BIT_CHAR *pReceipt,
BIT_UINT32 *pReceiptSize);
```
+
应用升级码完成远程集团授权激活或升级。一般不建议使用。
### 参数
@@ -495,6 +509,7 @@ BIT_STATUS Bit_GetUpdateInfo (
BIT_CHAR *pUpdateInfo,
BIT_UINT32 *pUpdateInfoSize)
```
+
使用请求码与授权服务器进行连接,获取升级码。本函数需要进行网络连接。
### 参数
@@ -508,7 +523,6 @@ BIT_STATUS Bit_GetUpdateInfo (
## 特征项操作
-
### Bit_BatchBegin
```c
@@ -516,6 +530,7 @@ BIT_STATUS Bit_BatchBegin(
BIT_HANDLE handle,
BIT_UINT32 mode)
```
+
开启批量Query模式,调用此API后调用的所有Query相关API,全部由Bit_BatchEnd接口批量提交连接集团服务。
### 参数
@@ -538,6 +553,7 @@ BIT_STATUS Bit_BatchEnd(
BIT_UINT32 *pResultList,
BIT_UINT32 *pResultListSize)
```
+
批量提交Query请求。
### 参数
@@ -577,6 +593,7 @@ BIT_STATUS Bit_QueryFeature (
BIT_UINT32 featureId,
BIT_UINT32 *pCapacity)
```
+
开发商可以对软件的某个功能进行单独授权,当程序运行该模块时,可以通过该接口检查是否可用。
对于单机授权:只检查特征项是否可用。
@@ -610,6 +627,7 @@ BIT_STATUS Bit_ReleaseFeature (
BIT_UINT32 featureId,
BIT_UINT32 *pCapacity)
```
+
释放所占用的用户数,该函数和Bit_QueryFeature一一对应。
### 参数
@@ -643,6 +661,7 @@ BIT_STATUS Bit_QueryFeatureEx (
BIT_UINT32 *pCapacity,
BIT_PCSTR xmlScope)
```
+
集团授权专用,该接口支持占用指定用户数,支持通过特征项版本进行检查。
### 参数
@@ -702,6 +721,7 @@ BIT_STATUS Bit_ReleaseFeatureEx (
BIT_UINT32 *pCapacity,
BIT_PCSTR xmlScope)
```
+
集团授权专用,释放指定的用户数。该接口与Bit_QueryFeatureEx对应。
### 参数
@@ -740,6 +760,7 @@ BIT_STATUS Bit_QueryFeatureEx2 (
BIT_PCSTR xmlScope,
BIT_TICKET *pTicket)
```
+
可以使用“特征项名称 + 特征项版本”进行用户数占用,支持队列模式。
对于单机授权:只检查特征项是否可用。
@@ -796,6 +817,7 @@ BIT_STATUS Bit_ReleaseFeatureEx2 (
BIT_TICKET ticket,
BIT_UINT32 consumed)
```
+
释放ticket所占用的用户数。该函数和Bit_QueryFeatureEx2一一对应。
### 参数
@@ -811,6 +833,7 @@ if (status == BIT_SUCCESS) {
// 释放ticket占用的用户数
}
```
+
**特征项Query相关接口比较**
@@ -830,6 +853,7 @@ BIT_STATUS Bit_GetFeatureInfo2 (
BIT_PCSTR xmlScope,
BIT_INT32 *pExpired)
```
+
检查特征项是否存在,不会占用授权码或特征项的用户数,获取特征项的剩余有效期。
### 参数
@@ -845,6 +869,7 @@ BIT_STATUS Bit_GetFeatureInfo2 (
```
+
- **pExpired** - [OUT] 返回该特征项的剩余有效期,正数代表剩余有效期,负数代表过期天数,36500代表永久有效。
### Bit_GetFeatureInfoEx2
@@ -857,6 +882,7 @@ BIT_STATUS Bit_GetFeatureInfoEx2(
BIT_CHAR *pFeatureInfo,
BIT_UINT32 *pFeatureInfoSize);
```
+
获取指定feature的信息,以XML格式返回。
### 参数
@@ -872,6 +898,7 @@ BIT_STATUS Bit_GetFeatureInfoEx2(
```
+
- **pFeatureInfo** - [OUT] feature信息存储区地址。
- **pFeatureInfoSize** - [IN/OUT] 会话存储区大小。
@@ -884,6 +911,7 @@ BIT_STATUS Bit_GetTicketInfo(
BIT_CHAR *pXmlInfo,
BIT_UINT32 *pSize)
```
+
获取ticket信息。
### 参数
@@ -946,6 +974,7 @@ BIT_STATUS Bit_ConvertFeature(
BIT_UINT32 para4,
BIT_UINT32 *pResult);
```
+
使用“算法”类型的特征项对输入参数进行变换操作,得到唯一对应的4字节结果。
### 参数
@@ -981,6 +1010,7 @@ BIT_STATUS Bit_EncryptFeature(
BIT_VOID *pCipherBuffer,
BIT_UINT32 dataBufferSize);
```
+
使用“密钥”类型的特征项对输入的明文进行加密,返回密文结果。
### 参数
@@ -1013,6 +1043,7 @@ BIT_STATUS Bit_DecryptFeature(
BIT_VOID *pPlainBuffer,
BIT_UINT32 dataBufferSize);
```
+
使用“密钥”类型的特征项对输入的密文进行解密,返回明文结果。
### 参数
@@ -1043,6 +1074,7 @@ BIT_STATUS Bit_ReadFeature(
BIT_UINT32 featureId,
BIT_UINT32 *pFeatureValue);
```
+
读取特征项的数据内容,可用于“只读”和“读写”特征类型。
### 参数
@@ -1059,6 +1091,7 @@ BIT_STATUS Bit_WriteFeature(
BIT_UINT32 featureId,
BIT_UINT32 featureValue);
```
+
更新“读写”类型的特征项的数据内容。
### 参数
@@ -1069,7 +1102,6 @@ BIT_STATUS Bit_WriteFeature(
## 配置项操作
-
### Bit_GetDataItem
```c
@@ -1079,6 +1111,7 @@ BIT_STATUS Bit_GetDataItem (
BIT_VOID *pDataItemValue,
BIT_UINT32 *pDataItemValueSize)
```
+
读取指定的配置项数据。
### 参数
@@ -1097,6 +1130,7 @@ BIT_STATUS Bit_SetDataItem (
BIT_VOID *pDataItemValue,
BIT_UINT32 dataItemValueSize)
```
+
创建或更新配置项。如果相同名称的配置项存在,则会更新其中的数据;否则将添加新的授权码配置项。
### 参数
@@ -1113,6 +1147,7 @@ BIT_STATUS Bit_GetDataItemNum (
BIT_HANDLE handle,
BIT_UINT32 *pNum)
```
+
此函数用于获取可访问配置项的数量,一般用于配置项的枚举操作。
### 参数
@@ -1129,6 +1164,7 @@ BIT_STATUS Bit_GetDataItemName (
BIT_CHAR *pDataItemName,
BIT_UINT32 *pDataItemNameSize)
```
+
根据配置项索引获取其名称,一般用于配置项的枚举操作。
### 参数
@@ -1145,6 +1181,7 @@ BIT_STATUS Bit_RemoveDataItem (
BIT_HANDLE handle,
BIT_PCSTR szDataItemName)
```
+
删除指定的配置项。该操作无法删除通过控制台设置的产品配置项或模版配置项。
### 参数
@@ -1154,7 +1191,6 @@ BIT_STATUS Bit_RemoveDataItem (
## 信息获取
-
### Bit_GetSessionInfo
```c
@@ -1164,6 +1200,7 @@ BIT_STATUS Bit_GetSessionInfo (
BIT_CHAR *pSessionInfo,
BIT_UINT32 *pSessionInfoSize)
```
+
获取当前会话信息,以字符串形式返回。根据获取的内容不同,返回结果可能是XML格式或非XML格式。返回数据中的日期项已根据客户端的本地时区进行调整。如果Login时未指定SN,返回串为当前系统所有可用SN的综合结果。
当函数返回BIT_ERR_BUFFER_SMALL时,pSessionInfoSize保存的是实际数据的大小。
@@ -1242,6 +1279,7 @@ BIT_STATUS Bit_GetInfo (
BIT_CHAR *pInfo,
BIT_UINT32 *pInfoSize)
```
+
获取本地授权信息。
如果要获取集团授权的信息,则szSN输入“`@bit://ip:port`”,会获取集团授权信息(仅支持type=BIT_INFO_SN)。
@@ -1281,6 +1319,7 @@ BIT_STATUS Bit_GetServerInfo(
BIT_CHAR *pServerInfo,
BIT_UINT32 *pServerInfoSize);
```
+
获取集团服务的License信息,数据以XML格式返回。调用此函数前客户端不需要执行登录操作。
### 参数
@@ -1321,6 +1360,7 @@ BIT_STATUS Bit_GetServerInfo(
……
```
+
1. 指定分组(group)查询
仅支持BIT_SERVER_INFO_SN_USERS、BIT_SERVER_INFO_FEATURE_USERS、BIT_SERVER_INFO_FEATURE_USERS_EX三种类型,通过name限定待查询的分组。
@@ -1332,6 +1372,7 @@ BIT_STATUS Bit_GetServerInfo(
……
```
+
> 说明:通过Bit_SetCustomInfo的CUSTOM_GROUP_NAME(0xA)设置分组。
### 返回XML数据说明
@@ -1360,6 +1401,7 @@ XML返回示例:
BIT_STATUS Bit_GetVersion (
BIT_UINT32 *pVersion)
```
+
获取客户端安全库版本号。
### 参数
@@ -1373,6 +1415,7 @@ BIT_STATUS Bit_GetNextHandle(
BIT_HANDLE handle,
BIT_HANDLE *pNextHandle)
```
+
用来遍历当前进程内的handle。
### 参数
@@ -1398,6 +1441,7 @@ BIT_STATUS Bit_TestBitService (
BIT_UINT32 featureId,
BIT_UCHAR *pApplicationData)
```
+
测试集团授权的特征项是否可用,不会占用授权码或特征项的用户数。
### 参数
@@ -1421,6 +1465,7 @@ BIT_STATUS Bit_GetProductPath(
BIT_CHAR *pPath,
BIT_UINT32 lenPath);
```
+
获取授权存储目录。
### 参数
@@ -1434,6 +1479,7 @@ BIT_STATUS Bit_GetProductPath(
```c
BIT_STATUS Bit_GetLastError();
```
+
获取上一个API调用返回的错误码。
### 参数
@@ -1442,7 +1488,6 @@ BIT_STATUS Bit_GetLastError();
## 属性设置
-
### Bit_SetAttr
```c
@@ -1451,6 +1496,7 @@ BIT_STATUS Bit_SetAttr (
BIT_UINT32 type,
BIT_VOID *pValue)
```
+
设置全局配置或当前会话的配置。
### 参数
@@ -1516,6 +1562,7 @@ BIT_STATUS Bit_SetProxy (
BIT_PCSTR szUserID,
BIT_PCSTR szPassword)
```
+
设置代理服务的地址和端口。
### 参数
@@ -1540,6 +1587,7 @@ BIT_STATUS Bit_SetCustomInfo (
BIT_VOID *pInfoData,
BIT_UINT32 infoDataSize)
```
+
设置客户端运行自定义信息,需要在程序的最开始调用。
### 参数
@@ -1589,6 +1637,7 @@ if (status == BIT_SUCCESS) {
BIT_STATUS Bit_SetRootPath (
BIT_PCSTR szPath)
```
+
设置授权文件的存储路径。
### 参数
@@ -1604,6 +1653,7 @@ BIT_STATUS Bit_SetLocalServer (
BIT_UINT32 nPort,
BIT_UINT32 nTimeoutSecondes)
```
+
设置集团服务的地址和端口。
### 参数
@@ -1615,7 +1665,6 @@ BIT_STATUS Bit_SetLocalServer (
## 借出操作
-
### Bit_GetBorrowRequest
```c
@@ -1626,6 +1675,7 @@ BIT_STATUS Bit_GetBorrowRequest(
BIT_CHAR *pRequestInfo,
BIT_UINT32 *pRequestInfoSize)
```
+
获取借出请求串,用来离线借出。
### 参数
@@ -1647,6 +1697,7 @@ BIT_STATUS Bit_GetBorrowFeatureRequest(
BIT_CHAR *pRequestInfo,
BIT_UINT32 *pRequestInfoSize)
```
+
获取借出请求串,该接口支持指定特征项借出。
### 参数
@@ -1684,6 +1735,7 @@ BIT_STATUS Bit_CheckOutSnEx(
BIT_UCHAR *pApplicationData,
BIT_UINT32 nDurationDays);
```
+
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
### 参数
@@ -1721,6 +1773,7 @@ BIT_STATUS Bit_CheckOut(
BIT_UCHAR *pApplicationData,
BIT_UINT32 nDurationDays)
```
+
从授权服务器在线借出授权码或者特征项。
### 参数
@@ -1740,6 +1793,7 @@ type:借出类型。目前仅支持ws,表示从外网借出(针对云授
sn:指定借出的SN
-->
```
+
- **pFeatureList** xml结构,指定借出的特征项列表,如果不传则借出整个SN。
```xml
@@ -1749,6 +1803,7 @@ sn:指定借出的SN
```
+
当feature指定了借出时间,则会覆盖nDurationDays该时间,没有指定,则借出nDurationDays天。
借出feature时,如果指定了版本,则直接大于等于借出版本的特征项,只借一个,如果没有指定版本,则该特征项的所有版本都借出一个。
@@ -1792,6 +1847,7 @@ BIT_STATUS Bit_CheckOutFeatures(
BIT_UINT32 nFeatureInList,
BIT_UINT32 nDurationDays);
```
+
从集团授权服务器借出一组特征项,这些特征项必须包含在同一个授权码中。被借出的集团授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给集团服务器。
### 参数
@@ -1831,6 +1887,7 @@ BIT_STATUS Bit_CheckIn(
BIT_UINT32 featureId,
BIT_UCHAR *pApplicationData)
```
+
提前返还从集团授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
### 参数
@@ -1848,6 +1905,7 @@ BIT_STATUS Bit_CheckInEx(
BIT_PCSTR szScope,
BIT_UCHAR *pApplicationData)
```
+
提前返还从授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
### 参数
@@ -1861,6 +1919,7 @@ BIT_STATUS Bit_CheckInEx(
ws
```
+
用于指定借出的参数,格式:wsxxxx,type设定为ws时表示从云授权服务器借出和归还。
- **pApplicationData** - [IN] 产品识别码。记录在接口定义文件中,与产品一一对应。
@@ -1872,9 +1931,11 @@ BIT_STATUS Bit_ApplyBorrowInfo(
BIT_UCHAR *pApplicationData,
BIT_PCSTR pBorrowInfo)
```
+
客户端应用离线借出串,Bit_ApplyUpdateInfo可以兼容该接口。
### 参数
- **pApplicationData** - [IN] 产品识别码。记录在接口定义文件中,与产品一一对应。
- **pBorrowInfo** - [IN] 借出请求串。
+
diff --git a/docs/java.md b/docs/java.md
index dc14a45..91a45af 100644
--- a/docs/java.md
+++ b/docs/java.md
@@ -1,12 +1,11 @@
# 比特授权云 · Java 语言接口(离线摘录)
-> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:
+> 本文档由官网页面离线摘录并整理排版,便于本地检索。官方地址:[https://doc.bitanswer.cn/docs/client-api/java-interface-v2/](https://doc.bitanswer.cn/docs/client-api/java-interface-v2/)
---
## 构造方法详细信息
-
### BitAnswer
```java
@@ -16,7 +15,6 @@ public BitAnswer(String url, String sn, LoginMode mode)
## 认证
-
### login / loginEx
```java
@@ -30,6 +28,7 @@ void loginEx (String url,
String xmlScope,
LoginMode mode)
```
+
授权登录。初始化运行环境,获取操作句柄。必须在除升级函数之外的其它操作前执行。根据登录模式的不同可能需要连接授权服务器。
Login 等价于 LoginEx(featureId=0);当需要登录包含指定特征项的授权时才需要调用LoginEx。
@@ -75,6 +74,7 @@ Login 等价于 LoginEx(featureId=0);当需要登录包含指定特征项
void loginByToken (String url,
String token)
```
+
帐号授权的登录接口。仅支持两种认证方式,这里是使用比特授权云平台自己产生的access_token进行认证。
### 参数
@@ -95,6 +95,7 @@ void loginByTokenEx (String url,
String idpGuid,
String grantType)
```
+
帐号授权的登录接口。仅支持两种认证方式,这里是使用OIDC协议产生的id_token认证。
### 参数
@@ -118,6 +119,7 @@ void loginByPassword (String url,
String account,
String password)
```
+
通过用户名和密码登录帐号授权。
### 参数
@@ -137,6 +139,7 @@ void loginByPassword (String url,
```java
void logout()
```
+
此函数用于释放上下文句柄,退出登录状态,与Login相关接口一一对应。
### 参数
@@ -152,6 +155,7 @@ void logout()
```java
String revoke (String sn)
```
+
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。根据输入参数的不同,本函数可用于在线或离线迁出。
### 参数
@@ -168,6 +172,7 @@ String revoke (String sn)
void revokeOnline (String url,
String sn)
```
+
从客户端迁出已激活的浮动授权码。授权码迁出后,可以用于其它的客户端。本函数需要进行网络连接。
### 参数
@@ -184,6 +189,7 @@ void revokeOnline (String url,
```java
void removeSn (String sn)
```
+
删除指定授权码在本机的授权数据。
### 参数
@@ -199,6 +205,7 @@ void removeSn (String sn)
```java
int heartbeat()
```
+
手动心跳,可以无限次调用,10s只会触发一次。
注:当自动心跳停止后,调用该接口可以尝试恢复自动心跳。
@@ -218,6 +225,7 @@ String sessionControl (String url,
String sessionId,
SessionCtlType type)
```
+
控制或设置session的信息。具体使用场景可参考《浏览器并发控制》文档。
### 参数
@@ -243,6 +251,7 @@ String sessionControl (String url,
void setSessionState (int state,
byte[] pReserved)
```
+
设置客户端的状态为空闲状态或繁忙状态或激活状态。
### 参数
@@ -264,13 +273,13 @@ void setSessionState (int state,
## 激活升级
-
### updateOnline
```java
void updateOnline (String url,
String sn)
```
+
此函数用于与授权服务器在线连接,自动完成本地授权的升级操作。本函数需要进行网络连接。
### 参数
@@ -288,6 +297,7 @@ void updateOnline (String url,
String getRequestInfo (String sn,
BindingType type)
```
+
获取当前运行环境的升级请求码,用于发起本地授权激活及升级请求。
### 参数
@@ -310,6 +320,7 @@ String getRequestInfo (String sn,
```java
String applyUpdateInfo (String updateInfo)
```
+
应用升级码完成本地授权激活或升级。本函数必须在获取请求码的同一环境下执行。
### 参数
@@ -327,6 +338,7 @@ String getUpdateInfo (String url,
String sn,
String requestInfo)
```
+
使用请求码与授权服务器进行连接,获取升级码。本函数需要进行网络连接。
### 参数
@@ -341,12 +353,12 @@ String getUpdateInfo (String url,
## 特征项操作
-
### batchBegin
```java
void batchBegin (BatchMode mode)
```
+
开启批量Query模式,调用此API后调用的所有Query相关API,全部由Bit_batchEnd接口批量提交连接集团服务。
### 参数
@@ -365,6 +377,7 @@ void batchBegin (BatchMode mode)
```java
int[] batchEnd()
```
+
批量提交Query请求。
### 参数
@@ -380,6 +393,7 @@ int[] batchEnd()
```java
int queryFeature (int featureId)
```
+
开发商可以对软件的某个功能进行单独授权,当程序运行该模块时,可以通过该接口检查是否可用。
### 参数
@@ -395,6 +409,7 @@ int queryFeature (int featureId)
```java
int releaseFeature (int featureId)
```
+
释放所占用的用户数,该函数和Bit_QueryFeature一一对应。
### 参数
@@ -413,6 +428,7 @@ int queryFeatureEx (int featureId,
int required,
String scope)
```
+
集团授权专用,该接口支持占用指定用户数,支持通过特征项版本进行检查。
### 参数
@@ -439,6 +455,7 @@ int releaseFeatureEx (int featureId,
int consumed,
String scope)
```
+
集团授权专用,释放指定的用户数。该接口与Bit_QueryFeatureEx对应。
### 参数
@@ -459,6 +476,7 @@ long queryFeatureEx2 (String featureName,
int required,
String scope)
```
+
可以使用“特征项名称 + 特征项版本”进行用户数占用,支持队列模式。
### 参数
@@ -486,6 +504,7 @@ long queryFeatureEx2 (String featureName,
void releaseFeatureEx2 (byte[] ticket,
int consumed)
```
+
释放ticket所占用的用户数。该函数和Bit_QueryFeatureEx2一一对应。
### 参数
@@ -503,6 +522,7 @@ void releaseFeatureEx2 (byte[] ticket,
int getFeatureInfo2 (String featureName,
String scope)
```
+
检查特征项是否存在,不会占用授权码或特征项的用户数,获取特征项的剩余有效期。
### 参数
@@ -520,6 +540,7 @@ int getFeatureInfo2 (String featureName,
String getFeatureInfoEx2 (String featureName,
String scope)
```
+
获取指定feature的信息,以XML格式返回。
### 参数
@@ -544,6 +565,7 @@ String getFeatureInfoEx2 (String featureName,
String getTicketInfo (byte[] ticket,
int type)
```
+
获取ticket信息。
### 参数
@@ -568,6 +590,7 @@ int convertFeature (int featureId,
int para3,
int para4)
```
+
使用“算法”类型的特征项对输入参数进行变换操作,得到唯一对应的4字节结果。
### 参数
@@ -588,6 +611,7 @@ int convertFeature (int featureId,
byte[] encryptFeature (int featureId,
byte[] pPlainBuffer)
```
+
使用“密钥”类型的特征项对输入的明文进行加密,返回密文结果。
### 参数
@@ -605,6 +629,7 @@ byte[] encryptFeature (int featureId,
byte[] decryptFeature (int featureId,
byte[] pCipherBuffer)
```
+
使用“密钥”类型的特征项对输入的密文进行解密,返回明文结果。
### 参数
@@ -621,6 +646,7 @@ byte[] decryptFeature (int featureId,
```java
int readFeature (int featureId)
```
+
读取特征项的数据内容,可用于“只读”和“读写”特征类型。
### 参数
@@ -637,6 +663,7 @@ int readFeature (int featureId)
void writeFeature (int featureId,
int featureValue)
```
+
更新“读写”类型的特征项的数据内容。
### 参数
@@ -650,12 +677,12 @@ void writeFeature (int featureId,
## 配置项操作
-
### getDataItem
```java
byte[] getDataItem (String dataItemName)
```
+
读取指定的配置项数据。
### 参数
@@ -672,6 +699,7 @@ byte[] getDataItem (String dataItemName)
void setDataItem (String dataItemName,
byte[] dataItemValue)
```
+
创建或更新配置项。如果相同名称的配置项存在,则会更新其中的数据;否则将添加新的授权码配置项。
### 参数
@@ -688,6 +716,7 @@ void setDataItem (String dataItemName,
```java
int getDataItemNum()
```
+
此函数用于获取可访问配置项的数量,一般用于配置项的枚举操作。
### 参数
@@ -703,6 +732,7 @@ int getDataItemNum()
```java
String getDataItemName (int index)
```
+
根据配置项索引获取其名称,一般用于配置项的枚举操作。
### 参数
@@ -718,6 +748,7 @@ String getDataItemName (int index)
```java
void removeDataItem (String dataItemName)
```
+
删除指定的配置项。该操作无法删除通过控制台设置的产品配置项或模版配置项。
### 参数
@@ -730,12 +761,12 @@ void removeDataItem (String dataItemName)
## 信息获取
-
### getSessionInfo
```java
String getSessionInfo (SessionType type)
```
+
获取当前会话信息,以字符串形式返回。根据获取的内容不同,返回结果可能是XML格式或非XML格式。返回数据中的日期项已根据客户端的本地时区进行调整。如果Login时未指定SN,返回串为当前系统所有可用SN的综合结果。
### 参数
@@ -771,6 +802,7 @@ String getSessionInfo (SessionType type)
String getInfo (String sn,
InfoType type)
```
+
获取本地授权信息。
### 参数
@@ -800,6 +832,7 @@ String getServerInfo (String url,
String scope,
ServerInfoType type)
```
+
获取集团服务的license信息,数据以XML格式返回。调用此函数前客户端不需要执行登录操作。
### 参数
@@ -824,6 +857,7 @@ String getServerInfo (String url,
```java
int getVersion()
```
+
获取客户端安全库版本号。
### 参数
@@ -841,6 +875,7 @@ void testBitService (String url,
String sn,
int featureId)
```
+
测试集团授权的特征项是否可用,不会占用授权码或特征项的用户数。
### 参数
@@ -858,6 +893,7 @@ void testBitService (String url,
```java
String getProductPath()
```
+
获取授权存储目录。
### 参数
@@ -873,6 +909,7 @@ String getProductPath()
```java
int getLastError()
```
+
获取上一个API调用返回的错误码。
### 参数
@@ -889,13 +926,13 @@ int getLastError()
## 属性设置
-
### setAttr
```java
void setAttr (int type,
byte[] pValue)
```
+
设置全局配置或当前会话的配置。
### 参数
@@ -924,6 +961,7 @@ void setProxy (String hostName,
String userId,
String password)
```
+
设置代理服务的地址和端口。
### 参数
@@ -943,6 +981,7 @@ void setProxy (String hostName,
void setCustomInfo (int infoId,
String infoData)
```
+
设置客户端运行自定义信息,需要在程序的最开始调用。infoId定义详见CustomInfo类型。
### 参数
@@ -958,6 +997,7 @@ void setCustomInfo (int infoId,
void setCustomInfo (int infoId,
int infoData)
```
+
设置客户端运行自定义信息,需要在程序的最开始调用。infoId定义详见CustomInfo类型。
### 参数
@@ -980,6 +1020,7 @@ void setCustomInfo (int infoId,
```java
void setRootPath (String rootPath)
```
+
设置授权文件的存储路径。
### 参数
@@ -997,6 +1038,7 @@ void setLocalServer (String hostName,
int port,
int timeout)
```
+
设置集团服务的地址和端口。
### 参数
@@ -1011,13 +1053,13 @@ void setLocalServer (String hostName,
## 借出操作
-
### getBorrowRequest
```java
String getBorrowRequest (String sn,
int durationDay)
```
+
获取借出请求串,用来离线借出。
### 参数
@@ -1035,6 +1077,7 @@ String getBorrowRequest (String sn,
String getBorrowFeatureRequest (int durationDay,
String borrowScope)
```
+
获取借出请求串,该接口支持指定特征项借出。
### 参数
@@ -1056,6 +1099,7 @@ String getBorrowFeatureRequest (int durationDay,
void checkOutSn (String url,
int featureId)
```
+
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
### 参数
@@ -1075,6 +1119,7 @@ void checkOutSnEx (String url,
String xmlScope,
int durationDays)
```
+
从授权服务器在线借出一个完整的授权码,以允许客户端服务器单独使用。被借出的授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给服务器。
### 参数
@@ -1096,6 +1141,7 @@ void checkOut (String url,
String featureList,
int durationDays)
```
+
从授权服务器在线借出授权码或者特征项。
### 参数
@@ -1113,6 +1159,7 @@ void checkOut (String url,
type:借出类型。目前仅支持ws,表示从外网借出(针对云授权和集团授权)
sn:指定借出的SN
```
+
- **featureList** - [IN] 特征项列表,指定借出的特征项列表,如果不传则借出整个SN。示例:
```xml
@@ -1125,6 +1172,7 @@ sn:指定借出的SN
当feature指定了借出时间,则会覆盖nDurationDays该时间,没有指定,则借出nDurationDays天。
借出feature时,如果指定了版本,则直接大于等于借出版本的特征项,只借一个,如果没有指定版本,则该特征项的所有版本都借出一个。
```
+
- **durationDays** - [IN] 借出有效期,单位为天,需小于等于授权码允许的最大借出天数。
### 返回
@@ -1138,6 +1186,7 @@ void checkOutFeatures (String url,
int[] featureList,
int durationDays)
```
+
从集团授权服务器借出一组特征项,这些特征项必须包含在同一个授权码中。被借出的集团授权码必须具有可借出属性,并在客户端成功借出后减少一个可用用户数。被借出的用户数在到期后将自动返还给集团服务器。
### 参数
@@ -1156,6 +1205,7 @@ void checkOutFeatures (String url,
void checkIn (String url,
int featureId)
```
+
提前返还从集团授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
### 参数
@@ -1174,6 +1224,7 @@ void checkInEx (String url,
int featureId,
String xmlScope)
```
+
提前返还从授权服务器借出的授权。要提前返还授权,该授权码必须具有允许提前返还属性。
### 参数
@@ -1191,6 +1242,7 @@ void checkInEx (String url,
```java
void ApplyBorrowInfo (string borrowInfo)
```
+
客户端应用离线借出串,ApplyUpdateInfo可以兼容该接口。
### 参数
@@ -1199,4 +1251,4 @@ void ApplyBorrowInfo (string borrowInfo)
### 返回
-无
+无
\ No newline at end of file
diff --git a/examples/config/floating.bitanswer.json b/examples/config/floating.bitanswer.json
new file mode 100644
index 0000000..ebcefc3
--- /dev/null
+++ b/examples/config/floating.bitanswer.json
@@ -0,0 +1,17 @@
+{
+ "schemaVersion": 1,
+ "provider": "bitanswer",
+ "scenario": "floating",
+ "bitanswer": {
+ "url": "https://cloud.bitanswer.example/e3",
+ "loginMode": "REMOTE"
+ },
+ "features": {
+ "face": { "bitanswerFeatureId": 301 }
+ },
+ "floating": {
+ "projectId": "migrant-flow-prj-2026-001",
+ "projectName": "某市流动人口人像核验",
+ "contractRef": "PO-2026-8848"
+ }
+}
diff --git a/examples/config/school.bitanswer.json b/examples/config/school.bitanswer.json
new file mode 100644
index 0000000..647e33a
--- /dev/null
+++ b/examples/config/school.bitanswer.json
@@ -0,0 +1,18 @@
+{
+ "schemaVersion": 1,
+ "provider": "bitanswer",
+ "scenario": "school",
+ "bitanswer": {
+ "url": "https://cloud.bitanswer.example/e3",
+ "loginMode": "AUTO",
+ "sn": ""
+ },
+ "features": {
+ "face": { "bitanswerFeatureId": 201 },
+ "expression": { "bitanswerFeatureId": 202 }
+ },
+ "school": {
+ "edgeDeviceId": "classroom-gate-01",
+ "tenantId": "school-district-demo"
+ }
+}
diff --git a/examples/config/school.selfhosted.json b/examples/config/school.selfhosted.json
new file mode 100644
index 0000000..f27cc74
--- /dev/null
+++ b/examples/config/school.selfhosted.json
@@ -0,0 +1,16 @@
+{
+ "schemaVersion": 1,
+ "provider": "selfhosted",
+ "scenario": "school",
+ "selfhosted": {
+ "baseUrl": "https://license.internal.example/api/v1",
+ "tenantKey": "district-west"
+ },
+ "features": {
+ "face": {},
+ "expression": {}
+ },
+ "school": {
+ "edgeDeviceId": "gate-02"
+ }
+}
diff --git a/examples/config/wharf.bitanswer.json b/examples/config/wharf.bitanswer.json
new file mode 100644
index 0000000..8d3bada
--- /dev/null
+++ b/examples/config/wharf.bitanswer.json
@@ -0,0 +1,21 @@
+{
+ "schemaVersion": 1,
+ "provider": "bitanswer",
+ "scenario": "wharf",
+ "bitanswer": {
+ "url": "bit://license.example.com:8273",
+ "loginMode": "AUTO",
+ "rootPath": "/var/lib/craftlabs/bitanswer"
+ },
+ "features": {
+ "container_number_detect": { "bitanswerFeatureId": 101 },
+ "container_number_recognize": { "bitanswerFeatureId": 102 },
+ "iso_detect": { "bitanswerFeatureId": 103 },
+ "iso_recognize": { "bitanswerFeatureId": 104 }
+ },
+ "wharf": {
+ "topology": "group",
+ "groupServiceUrl": "bit://license.example.com:8273",
+ "notes": "边设备集中连集团服务;特征项与控制台产品一致后替换 id。"
+ }
+}
diff --git a/examples/java/ExampleApp.java b/examples/java/ExampleApp.java
index cf867ea..84935c8 100644
--- a/examples/java/ExampleApp.java
+++ b/examples/java/ExampleApp.java
@@ -1,9 +1,14 @@
import cn.craftlabs.auth.AuthProvider;
import cn.craftlabs.auth.AuthResult;
import cn.craftlabs.auth.bitanswer.BitAnswerProvider;
+import cn.craftlabs.auth.config.AuthConfig;
+import cn.craftlabs.auth.config.AuthConfigException;
+import cn.craftlabs.auth.config.AuthConfigs;
/**
- * 演示:通过 {@link BitAnswerProvider} 完成初始化与许可校验。
+ * 演示:校验 {@code config_json} 后初始化并完成许可校验。
+ *
+ * 编译时需将 {@code craftlabs-auth-core} 与 Jackson 置于 classpath(与 Maven 模块依赖一致)。
*
*
版权所有 © 广州创飞人工智能技术有限公司
*
@@ -11,8 +16,30 @@ import cn.craftlabs.auth.bitanswer.BitAnswerProvider;
*/
public class ExampleApp {
public static void main(String[] args) {
+ String json =
+ """
+ {
+ "schemaVersion": 1,
+ "provider": "bitanswer",
+ "scenario": "school",
+ "bitanswer": { "url": "https://example.invalid/bitanswer", "loginMode": "AUTO" },
+ "features": {
+ "face": { "bitanswerFeatureId": 201 },
+ "expression": { "bitanswerFeatureId": 202 }
+ },
+ "school": { "edgeDeviceId": "demo-edge-01", "tenantId": "demo-tenant" }
+ }
+ """;
+ try {
+ AuthConfig cfg = AuthConfigs.parse(json);
+ System.out.println("scenario=" + cfg.scenario() + ", face id=" + cfg.bitanswerFeatureId("face"));
+ } catch (AuthConfigException e) {
+ System.err.println("config invalid: " + e.getMessage());
+ return;
+ }
+
try (AuthProvider p = new BitAnswerProvider()) {
- AuthResult r = p.initialize("{}");
+ AuthResult r = p.initialize(json);
System.out.println("init: " + r.isSuccess() + " " + r.getMessage());
r = p.checkLicense();
System.out.println("check: " + r.isSuccess() + " " + r.getMessage());
diff --git a/java/RELEASING.md b/java/RELEASING.md
new file mode 100644
index 0000000..1fda366
--- /dev/null
+++ b/java/RELEASING.md
@@ -0,0 +1,93 @@
+# CraftLabs 授权 SDK — 发布与完整性
+
+对齐架构文档 [SYSTEM_ARCHITECTURE §9.8](../docs/engineering/SYSTEM_ARCHITECTURE.md):官方渠道、**SHA-256 清单**、**GPG 签名(建议)**、`java` 与 `native` **同 Git tag**。
+
+## 1. 发布前检查
+
+- [ ] `mvn -f java/pom.xml verify` 通过(JDK 17+)。
+- [ ] `native` 已在各目标平台完成构建,且与本次 **同一 tag** 一并交付。
+- [ ] `CHANGELOG`(或发布说明)写明 **比特 SDK / 运行时** 兼容版本。
+
+## 2. 生成 SHA256SUMS(必做)
+
+在仓库根目录执行(会先 `mvn -DskipTests package`):
+
+```bash
+chmod +x scripts/sdk-release-checksums.sh
+./scripts/sdk-release-checksums.sh --output dist/sdk-release
+```
+
+已构建过时跳过 Maven:
+
+```bash
+./scripts/sdk-release-checksums.sh --no-mvn --output dist/sdk-release
+```
+
+把 **本机构建出的 Native** 一并写入清单(路径相对于仓库根,便于客户校验):
+
+```bash
+./scripts/sdk-release-checksums.sh --output dist/sdk-release --native-path "$(pwd)/native/build"
+```
+
+含完整测试的构建后再生成清单:
+
+```bash
+./scripts/sdk-release-checksums.sh --verify --output dist/sdk-release
+```
+
+输出:
+
+- `dist/sdk-release/SHA256SUMS` — 每行:`哈希 相对路径`
+- `dist/sdk-release/RELEASE-MANIFEST.txt` — 提交 SHA、UTC 时间
+
+**客户校验**(在克隆/解压后的仓库根或同目录结构下):
+
+```bash
+sha256sum -c dist/sdk-release/SHA256SUMS
+```
+
+macOS:
+
+```bash
+shasum -a 256 -c dist/sdk-release/SHA256SUMS
+```
+
+对 `SHA256SUMS` 本身做 **分离签名**(本机已配置 GPG):
+
+```bash
+SIGN=1 ./scripts/sdk-release-checksums.sh --no-mvn --output dist/sdk-release
+```
+
+生成 `SHA256SUMS.asc`;客户使用公布的公钥:`gpg --verify SHA256SUMS.asc SHA256SUMS`。
+
+## 3. Maven JAR 的 GPG 签名(强烈建议)
+
+父 POM 已配置 `maven-gpg-plugin`,**默认跳过**(`gpg.skip=true`),不影响日常 `verify`。
+
+发布前在已导入私钥的机器上:
+
+```bash
+gpg --version # 确认可用
+mvn -f java/pom.xml -Prelease-sign verify
+```
+
+各 **可发布模块**(`craftlabs-auth-core`、`craftlabs-auth-bitanswer`、`craftlabs-auth-selfhosted`)的 `target/*.jar` 旁会出现 **`.asc`**。`craftlabs-auth-tests` 模块固定 **不签名**。
+
+若无私钥或未就绪,可只发 **SHA256SUMS**;待密钥就绪后再打开 `-Prelease-sign`。
+
+### CI / 无人值守(可选)
+
+在构建机配置 `MAVEN_GPG_PASSPHRASE` 等环境变量,或使用 `gpg-agent`;勿将私钥提交仓库。GitHub Actions 可用 `GPG_PRIVATE_KEY` secret + [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) 导入后再执行 `mvn -Prelease-sign verify`。
+
+## 4. GitHub Release 建议资产
+
+每个 **tag** 上传:
+
+1. 三个 **release JAR**(及对应 **.asc**,若已签名)
+2. 各平台 **Native** 压缩包
+3. **`SHA256SUMS`** 与 **`SHA256SUMS.asc`**
+4. 固定页面公布 **GPG 公钥指纹** 与下载说明
+
+---
+
+版权所有 © 广州创飞人工智能技术有限公司(以项目实际声明为准)。
diff --git a/java/craftlabs-auth-core/pom.xml b/java/craftlabs-auth-core/pom.xml
index bfb4ed8..37b0cd2 100644
--- a/java/craftlabs-auth-core/pom.xml
+++ b/java/craftlabs-auth-core/pom.xml
@@ -13,4 +13,21 @@
craftlabs-auth-core
CraftLabs Auth — core API
jar
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ com.networknt
+ json-schema-validator
+ test
+
+
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/AuthProvider.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/AuthProvider.java
index ead73af..9898b74 100644
--- a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/AuthProvider.java
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/AuthProvider.java
@@ -3,6 +3,9 @@ package cn.craftlabs.auth;
/**
* 授权能力的统一契约:初始化、激活、校验许可、查询特性与释放等生命周期方法。
*
+ *
{@link #initialize(String)} 的 JSON 建议使用 {@link cn.craftlabs.auth.config.AuthConfigs#parse(String)}
+ * 先校验后再传入,格式见仓库 {@code schemas/craftlabs-auth-config.schema.json} 与 {@code examples/config/}。
+ *
*
实现类负责加载对应 native 或远端适配器;调用方应在不再使用时调用 {@link #close()} 释放底层资源。
*
*
版权所有 © 广州创飞人工智能技术有限公司
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfig.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfig.java
new file mode 100644
index 0000000..013d87f
--- /dev/null
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfig.java
@@ -0,0 +1,47 @@
+package cn.craftlabs.auth.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * {@link cn.craftlabs.auth.AuthProvider#initialize(String)} 所用 JSON 的配置模型,与 {@code
+ * schemas/craftlabs-auth-config.schema.json} 对齐。
+ *
+ *
版权所有 © 广州创飞人工智能技术有限公司
+ *
+ * @author huangping@craftlabs.cn
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record AuthConfig(
+ @JsonProperty("schemaVersion") int schemaVersion,
+ @JsonProperty("provider") String provider,
+ @JsonProperty("scenario") String scenario,
+ @JsonProperty("bitanswer") BitanswerConfigSection bitanswer,
+ @JsonProperty("selfhosted") SelfhostedConfigSection selfhosted,
+ @JsonProperty("features") Map features,
+ @JsonProperty("wharf") WharfScenarioSection wharf,
+ @JsonProperty("school") SchoolScenarioSection school,
+ @JsonProperty("floating") FloatingScenarioSection floating) {
+
+ public AuthConfig {
+ features = features == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(features));
+ provider = Objects.requireNonNullElse(provider, "");
+ scenario = Objects.requireNonNullElse(scenario, "");
+ }
+
+ /** 逻辑特性键对应的比特特征 id;未配置或非数值映射时返回 {@code null}。 */
+ public Integer bitanswerFeatureId(String logicalFeatureKey) {
+ FeatureMapping m = features.get(logicalFeatureKey);
+ return m != null ? m.bitanswerFeatureId() : null;
+ }
+
+ /** 逻辑特性键对应的比特特征名称;未配置时返回 {@code null}。 */
+ public String bitanswerFeatureName(String logicalFeatureKey) {
+ FeatureMapping m = features.get(logicalFeatureKey);
+ return m != null ? m.bitanswerFeatureName() : null;
+ }
+}
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfigException.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfigException.java
new file mode 100644
index 0000000..27e5ff6
--- /dev/null
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfigException.java
@@ -0,0 +1,19 @@
+package cn.craftlabs.auth.config;
+
+/**
+ * {@link AuthConfigs#parse(String)} 或校验失败时抛出。
+ *
+ * 版权所有 © 广州创飞人工智能技术有限公司
+ *
+ * @author huangping@craftlabs.cn
+ */
+public final class AuthConfigException extends Exception {
+
+ public AuthConfigException(String message) {
+ super(message);
+ }
+
+ public AuthConfigException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfigs.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfigs.java
new file mode 100644
index 0000000..e0653aa
--- /dev/null
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/AuthConfigs.java
@@ -0,0 +1,111 @@
+package cn.craftlabs.auth.config;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * 解析并校验 {@link cn.craftlabs.auth.AuthProvider#initialize(String)} 的 JSON 配置。
+ *
+ *
版权所有 © 广州创飞人工智能技术有限公司
+ *
+ * @author huangping@craftlabs.cn
+ */
+public final class AuthConfigs {
+
+ /** 与 {@code schemas/craftlabs-auth-config.schema.json} 中 {@code schemaVersion} 一致。 */
+ public static final int SCHEMA_VERSION = 1;
+
+ private static final ObjectMapper MAPPER =
+ new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ private AuthConfigs() {}
+
+ /** 解析 JSON 并执行 {@link #validate(AuthConfig)}。 */
+ public static AuthConfig parse(String json) throws AuthConfigException {
+ if (json == null || json.isBlank()) {
+ throw new AuthConfigException("config JSON is null or blank");
+ }
+ try {
+ AuthConfig cfg = MAPPER.readValue(json, AuthConfig.class);
+ validate(cfg);
+ return cfg;
+ } catch (JsonProcessingException e) {
+ String msg = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage();
+ throw new AuthConfigException("Invalid config JSON: " + msg, e);
+ }
+ }
+
+ /** 将配置写回 JSON(便于日志脱敏后落盘或调试)。 */
+ public static String toJson(AuthConfig config) throws AuthConfigException {
+ try {
+ return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(config);
+ } catch (JsonProcessingException e) {
+ throw new AuthConfigException("Failed to serialize config", e);
+ }
+ }
+
+ /**
+ * 校验必填组合;通过后可安全交给 native / 供应商实现。
+ *
+ * @throws AuthConfigException 校验失败,消息为多条原因拼接
+ */
+ public static void validate(AuthConfig c) throws AuthConfigException {
+ List err = new ArrayList<>();
+
+ if (c.schemaVersion() != SCHEMA_VERSION) {
+ err.add("schemaVersion must be " + SCHEMA_VERSION);
+ }
+
+ String provider = norm(c.provider());
+ if (provider.isEmpty()) {
+ err.add("provider is required");
+ } else if (!provider.equals("bitanswer") && !provider.equals("selfhosted")) {
+ err.add("provider must be bitanswer or selfhosted");
+ }
+
+ String scenario = norm(c.scenario());
+ if (scenario.isEmpty()) {
+ err.add("scenario is required");
+ } else if (!scenario.equals("wharf") && !scenario.equals("school") && !scenario.equals("floating")) {
+ err.add("scenario must be wharf, school, or floating");
+ }
+
+ if (provider.equals("bitanswer")) {
+ if (c.bitanswer() == null) {
+ err.add("bitanswer section is required when provider=bitanswer");
+ } else if (isBlank(c.bitanswer().url())) {
+ err.add("bitanswer.url is required and must be non-blank");
+ }
+ }
+
+ if (provider.equals("selfhosted")) {
+ if (c.selfhosted() == null) {
+ err.add("selfhosted section is required when provider=selfhosted");
+ } else if (isBlank(c.selfhosted().baseUrl())) {
+ err.add("selfhosted.baseUrl is required and must be non-blank");
+ }
+ }
+
+ if (scenario.equals("floating")) {
+ if (c.floating() == null || isBlank(c.floating().projectId())) {
+ err.add("floating.projectId is required when scenario=floating");
+ }
+ }
+
+ if (!err.isEmpty()) {
+ throw new AuthConfigException(String.join("; ", err));
+ }
+ }
+
+ private static String norm(String s) {
+ return s == null ? "" : s.trim().toLowerCase(Locale.ROOT);
+ }
+
+ private static boolean isBlank(String s) {
+ return s == null || s.trim().isEmpty();
+ }
+}
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/BitanswerConfigSection.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/BitanswerConfigSection.java
new file mode 100644
index 0000000..2995d28
--- /dev/null
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/BitanswerConfigSection.java
@@ -0,0 +1,17 @@
+package cn.craftlabs.auth.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * {@code config_json} 中与比特安索客户端相关的字段子集。
+ *
+ * @author huangping@craftlabs.cn
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record BitanswerConfigSection(
+ @JsonProperty("url") String url,
+ @JsonProperty("loginMode") String loginMode,
+ @JsonProperty("rootPath") String rootPath,
+ @JsonProperty("sn") String sn,
+ @JsonProperty("applicationData") String applicationData) {}
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/FeatureMapping.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/FeatureMapping.java
new file mode 100644
index 0000000..893d0ab
--- /dev/null
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/FeatureMapping.java
@@ -0,0 +1,14 @@
+package cn.craftlabs.auth.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * 逻辑特性键(如 {@code face})到比特特征项的映射;至少填 id 或 name 之一即可在实现层选用。
+ *
+ * @author huangping@craftlabs.cn
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record FeatureMapping(
+ @JsonProperty("bitanswerFeatureId") Integer bitanswerFeatureId,
+ @JsonProperty("bitanswerFeatureName") String bitanswerFeatureName) {}
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/FloatingScenarioSection.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/FloatingScenarioSection.java
new file mode 100644
index 0000000..a06f2a8
--- /dev/null
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/FloatingScenarioSection.java
@@ -0,0 +1,11 @@
+package cn.craftlabs.auth.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/** 流动人口按项目授权场景补充字段。 */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record FloatingScenarioSection(
+ @JsonProperty("projectId") String projectId,
+ @JsonProperty("projectName") String projectName,
+ @JsonProperty("contractRef") String contractRef) {}
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/SchoolScenarioSection.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/SchoolScenarioSection.java
new file mode 100644
index 0000000..db5616b
--- /dev/null
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/SchoolScenarioSection.java
@@ -0,0 +1,10 @@
+package cn.craftlabs.auth.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/** 学校按边设备运营场景补充字段。 */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record SchoolScenarioSection(
+ @JsonProperty("edgeDeviceId") String edgeDeviceId,
+ @JsonProperty("tenantId") String tenantId) {}
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/SelfhostedConfigSection.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/SelfhostedConfigSection.java
new file mode 100644
index 0000000..52b1ee4
--- /dev/null
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/SelfhostedConfigSection.java
@@ -0,0 +1,14 @@
+package cn.craftlabs.auth.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * {@code provider=selfhosted} 时的 HTTP 后端参数。
+ *
+ * @author huangping@craftlabs.cn
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record SelfhostedConfigSection(
+ @JsonProperty("baseUrl") String baseUrl,
+ @JsonProperty("tenantKey") String tenantKey) {}
diff --git a/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/WharfScenarioSection.java b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/WharfScenarioSection.java
new file mode 100644
index 0000000..7a0a454
--- /dev/null
+++ b/java/craftlabs-auth-core/src/main/java/cn/craftlabs/auth/config/WharfScenarioSection.java
@@ -0,0 +1,11 @@
+package cn.craftlabs.auth.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/** 码头 / 集中授权场景补充字段。 */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record WharfScenarioSection(
+ @JsonProperty("topology") String topology,
+ @JsonProperty("groupServiceUrl") String groupServiceUrl,
+ @JsonProperty("notes") String notes) {}
diff --git a/java/craftlabs-auth-core/src/test/java/cn/craftlabs/auth/config/AuthConfigsTest.java b/java/craftlabs-auth-core/src/test/java/cn/craftlabs/auth/config/AuthConfigsTest.java
new file mode 100644
index 0000000..c25d020
--- /dev/null
+++ b/java/craftlabs-auth-core/src/test/java/cn/craftlabs/auth/config/AuthConfigsTest.java
@@ -0,0 +1,78 @@
+package cn.craftlabs.auth.config;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.junit.jupiter.api.Test;
+
+class AuthConfigsTest {
+
+ @Test
+ void parseWharfExampleFromClasspath() throws Exception {
+ String json = readClasspath("examples/config/wharf.bitanswer.json");
+ AuthConfig c = AuthConfigs.parse(json);
+ assertEquals(1, c.schemaVersion());
+ assertEquals("bitanswer", c.provider());
+ assertEquals("wharf", c.scenario());
+ assertNotNull(c.bitanswer());
+ assertTrue(c.bitanswer().url().startsWith("bit://"));
+ assertEquals(101, c.bitanswerFeatureId("container_number_detect"));
+ assertEquals("group", c.wharf().topology());
+ }
+
+ @Test
+ void parseSchoolExample() throws Exception {
+ String json = readClasspath("examples/config/school.bitanswer.json");
+ AuthConfig c = AuthConfigs.parse(json);
+ assertEquals("school", c.scenario());
+ assertEquals(201, c.bitanswerFeatureId("face"));
+ assertEquals("classroom-gate-01", c.school().edgeDeviceId());
+ }
+
+ @Test
+ void parseFloatingExample_requiresProjectId() throws Exception {
+ String json = readClasspath("examples/config/floating.bitanswer.json");
+ AuthConfig c = AuthConfigs.parse(json);
+ assertEquals("floating", c.scenario());
+ assertEquals("migrant-flow-prj-2026-001", c.floating().projectId());
+ }
+
+ @Test
+ void validateFails_whenFloatingWithoutProject() {
+ String json =
+ """
+ {"schemaVersion":1,"provider":"bitanswer","scenario":"floating",\
+ "bitanswer":{"url":"http://x"},"floating":{}}\
+ """;
+ AuthConfigException ex = assertThrows(AuthConfigException.class, () -> AuthConfigs.parse(json));
+ assertTrue(ex.getMessage().contains("projectId"));
+ }
+
+ @Test
+ void validateFails_whenBitanswerWithoutUrl() {
+ String json =
+ """
+ {"schemaVersion":1,"provider":"bitanswer","scenario":"school",\
+ "bitanswer":{"url":""}}\
+ """;
+ assertThrows(AuthConfigException.class, () -> AuthConfigs.parse(json));
+ }
+
+ @Test
+ void roundTrip_toJson() throws Exception {
+ String json = readClasspath("examples/config/school.bitanswer.json");
+ AuthConfig c = AuthConfigs.parse(json);
+ String out = AuthConfigs.toJson(c);
+ AuthConfig again = AuthConfigs.parse(out);
+ assertEquals(c.scenario(), again.scenario());
+ assertEquals(c.bitanswerFeatureId("expression"), again.bitanswerFeatureId("expression"));
+ }
+
+ private static String readClasspath(String path) throws Exception {
+ try (InputStream in = AuthConfigsTest.class.getClassLoader().getResourceAsStream(path)) {
+ assertNotNull(in, "missing classpath resource: " + path);
+ return new String(in.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ }
+}
diff --git a/java/craftlabs-auth-core/src/test/java/cn/craftlabs/auth/config/ExamplesConfigSchemaValidationTest.java b/java/craftlabs-auth-core/src/test/java/cn/craftlabs/auth/config/ExamplesConfigSchemaValidationTest.java
new file mode 100644
index 0000000..42b7d8a
--- /dev/null
+++ b/java/craftlabs-auth-core/src/test/java/cn/craftlabs/auth/config/ExamplesConfigSchemaValidationTest.java
@@ -0,0 +1,69 @@
+package cn.craftlabs.auth.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SpecVersion;
+import com.networknt.schema.ValidationMessage;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * BP-10 / I5:{@code examples/config/*.json} 与仓库根 {@code schemas/craftlabs-auth-config.schema.json} 一致。
+ */
+class ExamplesConfigSchemaValidationTest {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ @Test
+ void allExampleConfigsValidateAgainstSchema() throws Exception {
+ Path repoRoot = findRepoRoot();
+ Path schemaPath = repoRoot.resolve("schemas/craftlabs-auth-config.schema.json");
+ Path examplesDir = repoRoot.resolve("examples/config");
+ assertTrue(Files.isRegularFile(schemaPath), "schema missing: " + schemaPath);
+ assertTrue(Files.isDirectory(examplesDir), "examples dir missing: " + examplesDir);
+
+ JsonNode schemaNode = MAPPER.readTree(schemaPath.toFile());
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
+ JsonSchema schema = factory.getSchema(schemaNode);
+
+ try (Stream stream = Files.list(examplesDir)) {
+ stream.filter(p -> p.toString().endsWith(".json")).forEach(jsonFile -> {
+ try {
+ JsonNode instance = MAPPER.readTree(jsonFile.toFile());
+ Set errors = schema.validate(instance);
+ assertTrue(
+ errors.isEmpty(),
+ () -> jsonFile.getFileName() + ": " + errors);
+ } catch (IOException e) {
+ throw new AssertionError(jsonFile.toString(), e);
+ }
+ });
+ }
+ }
+
+ /**
+ * 自 {@code user.dir} 向上查找含 {@code schemas/craftlabs-auth-config.schema.json} 的目录(兼容模块目录或仓库根执行)。
+ */
+ static Path findRepoRoot() {
+ Path p = Path.of(System.getProperty("user.dir", ".")).toAbsolutePath().normalize();
+ for (int i = 0; i < 8 && p != null; i++) {
+ Path candidate = p.resolve("schemas/craftlabs-auth-config.schema.json");
+ if (Files.isRegularFile(candidate)) {
+ return p;
+ }
+ p = p.getParent();
+ }
+ throw new IllegalStateException(
+ "Could not find repo root (schemas/craftlabs-auth-config.schema.json) from user.dir="
+ + System.getProperty("user.dir"));
+ }
+}
diff --git a/java/craftlabs-auth-core/src/test/resources/examples/config/floating.bitanswer.json b/java/craftlabs-auth-core/src/test/resources/examples/config/floating.bitanswer.json
new file mode 100644
index 0000000..ebcefc3
--- /dev/null
+++ b/java/craftlabs-auth-core/src/test/resources/examples/config/floating.bitanswer.json
@@ -0,0 +1,17 @@
+{
+ "schemaVersion": 1,
+ "provider": "bitanswer",
+ "scenario": "floating",
+ "bitanswer": {
+ "url": "https://cloud.bitanswer.example/e3",
+ "loginMode": "REMOTE"
+ },
+ "features": {
+ "face": { "bitanswerFeatureId": 301 }
+ },
+ "floating": {
+ "projectId": "migrant-flow-prj-2026-001",
+ "projectName": "某市流动人口人像核验",
+ "contractRef": "PO-2026-8848"
+ }
+}
diff --git a/java/craftlabs-auth-core/src/test/resources/examples/config/school.bitanswer.json b/java/craftlabs-auth-core/src/test/resources/examples/config/school.bitanswer.json
new file mode 100644
index 0000000..647e33a
--- /dev/null
+++ b/java/craftlabs-auth-core/src/test/resources/examples/config/school.bitanswer.json
@@ -0,0 +1,18 @@
+{
+ "schemaVersion": 1,
+ "provider": "bitanswer",
+ "scenario": "school",
+ "bitanswer": {
+ "url": "https://cloud.bitanswer.example/e3",
+ "loginMode": "AUTO",
+ "sn": ""
+ },
+ "features": {
+ "face": { "bitanswerFeatureId": 201 },
+ "expression": { "bitanswerFeatureId": 202 }
+ },
+ "school": {
+ "edgeDeviceId": "classroom-gate-01",
+ "tenantId": "school-district-demo"
+ }
+}
diff --git a/java/craftlabs-auth-core/src/test/resources/examples/config/wharf.bitanswer.json b/java/craftlabs-auth-core/src/test/resources/examples/config/wharf.bitanswer.json
new file mode 100644
index 0000000..8d3bada
--- /dev/null
+++ b/java/craftlabs-auth-core/src/test/resources/examples/config/wharf.bitanswer.json
@@ -0,0 +1,21 @@
+{
+ "schemaVersion": 1,
+ "provider": "bitanswer",
+ "scenario": "wharf",
+ "bitanswer": {
+ "url": "bit://license.example.com:8273",
+ "loginMode": "AUTO",
+ "rootPath": "/var/lib/craftlabs/bitanswer"
+ },
+ "features": {
+ "container_number_detect": { "bitanswerFeatureId": 101 },
+ "container_number_recognize": { "bitanswerFeatureId": 102 },
+ "iso_detect": { "bitanswerFeatureId": 103 },
+ "iso_recognize": { "bitanswerFeatureId": 104 }
+ },
+ "wharf": {
+ "topology": "group",
+ "groupServiceUrl": "bit://license.example.com:8273",
+ "notes": "边设备集中连集团服务;特征项与控制台产品一致后替换 id。"
+ }
+}
diff --git a/java/craftlabs-auth-tests/pom.xml b/java/craftlabs-auth-tests/pom.xml
index b0a58fc..94385fc 100644
--- a/java/craftlabs-auth-tests/pom.xml
+++ b/java/craftlabs-auth-tests/pom.xml
@@ -28,6 +28,8 @@
+
+ true
${project.basedir}/../../native/build
diff --git a/java/pom.xml b/java/pom.xml
index 0da01be..ddd2932 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -21,6 +21,10 @@
UTF-8
17
5.10.2
+ 2.17.2
+ 1.5.7
+
+ true
@@ -36,6 +40,17 @@
${junit.version}
test
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ com.networknt
+ json-schema-validator
+ ${json-schema-validator.version}
+ test
+
@@ -52,7 +67,39 @@
maven-surefire-plugin
3.2.5
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.2.7
+
+
+
+
+
+ release-sign
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+
+
+ sign-release-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
diff --git a/native/include/craftlabs_auth.h b/native/include/craftlabs_auth.h
index 9c1801b..5403a1f 100644
--- a/native/include/craftlabs_auth.h
+++ b/native/include/craftlabs_auth.h
@@ -13,6 +13,8 @@
* FFI / Python(ctypes、cffi)说明
* ----------------------------------
* - 所有 `const char*` 均为 **UTF-8** 编码、以 `\0` 结尾。
+ * - `config_json`:UTF-8 JSON,语义与仓库 `schemas/craftlabs-auth-config.schema.json` 及 `examples/config/`
+ * 一致;Java 侧可先经 `cn.craftlabs.auth.config.AuthConfigs` 校验后再序列化传入。
* - `AuthHandle`:不透明指针,仅由本库函数产生与销毁(`auth_initialize` / `auth_destroy`)。
* - `AuthResult`、`LicenseInfo` 使用 `stdint` 定宽字段,避免 `time_t`/`int` 宽度随平台变化导致 Python 侧 Structure 布局错误。
* - `AuthResult.message`:指向 **由本库管理的静态或内部只读缓冲区**,调用方不得 `free`;在任意后续对本库的再次调用之后视为可能失效(与其它 FFI 语言惯例一致)。
diff --git a/schemas/craftlabs-auth-config.schema.json b/schemas/craftlabs-auth-config.schema.json
new file mode 100644
index 0000000..5ba7735
--- /dev/null
+++ b/schemas/craftlabs-auth-config.schema.json
@@ -0,0 +1,164 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://craftlabs.cn/schemas/craftlabs-auth-config.json",
+ "title": "CraftLabs Auth — initialize(config_json)",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["schemaVersion", "provider", "scenario"],
+ "properties": {
+ "schemaVersion": {
+ "type": "integer",
+ "const": 1,
+ "description": "配置格式版本;与解析器 AuthConfigs.SCHEMA_VERSION 对齐。"
+ },
+ "provider": {
+ "type": "string",
+ "enum": ["bitanswer", "selfhosted"],
+ "description": "后端实现:比特安索 native 或自研 HTTP。"
+ },
+ "scenario": {
+ "type": "string",
+ "enum": ["wharf", "school", "floating"],
+ "description": "业务场景:码头集中授权、学校按边设备、流动人口按项目。"
+ },
+ "bitanswer": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "provider=bitanswer 时由校验器要求非空且含 url。",
+ "properties": {
+ "url": {
+ "type": "string",
+ "description": "Bit_Login szURL:云 http(s)://、集团 bit://、本地 lic:// 等,参见比特文档。"
+ },
+ "loginMode": {
+ "type": "string",
+ "description": "与 Bitanswer Java LoginMode 名称一致,如 AUTO、REMOTE、LOCAL。"
+ },
+ "rootPath": {
+ "type": "string",
+ "description": "可选;对应 Bit_SetRootPath。"
+ },
+ "sn": {
+ "type": "string",
+ "description": "可选;若激活前即固定 SN 可放此处,否则走 activate(licenseKey)。"
+ },
+ "applicationData": {
+ "type": "string",
+ "description": "随产品绑定的 application_data,若不由库内编译常量承担。"
+ }
+ }
+ },
+ "selfhosted": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "provider=selfhosted 时由校验器要求非空且含 baseUrl。",
+ "properties": {
+ "baseUrl": {
+ "type": "string",
+ "format": "uri",
+ "description": "自研授权服务根 URL。"
+ },
+ "tenantKey": {
+ "type": "string",
+ "description": "可选;租户或调用方标识。"
+ }
+ }
+ },
+ "features": {
+ "type": "object",
+ "description": "逻辑特性键 → 比特特征项 id 或名称,供 hasFeature 映射。",
+ "additionalProperties": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "bitanswerFeatureId": { "type": "integer" },
+ "bitanswerFeatureName": { "type": "string" }
+ }
+ }
+ },
+ "wharf": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "scenario=wharf 时建议填写。",
+ "properties": {
+ "topology": {
+ "type": "string",
+ "enum": ["cloud", "group", "local_float"],
+ "description": "云授权、集团授权或单机浮动等拓扑提示。"
+ },
+ "groupServiceUrl": {
+ "type": "string",
+ "description": "topology=group 时 bit://host:port。"
+ },
+ "notes": { "type": "string" }
+ }
+ },
+ "school": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "scenario=school 时建议填写。",
+ "properties": {
+ "edgeDeviceId": {
+ "type": "string",
+ "description": "边设备实例标识,用于运营侧对账;不参与比特 API 则仅作遥测/日志。"
+ },
+ "tenantId": { "type": "string" }
+ }
+ },
+ "floating": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "scenario=floating 时校验器要求 projectId。",
+ "properties": {
+ "projectId": {
+ "type": "string",
+ "minLength": 1,
+ "description": "项目维度授权锚点。"
+ },
+ "projectName": { "type": "string" },
+ "contractRef": { "type": "string", "description": "合同或订单引用,可选。" }
+ }
+ }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "provider": { "const": "bitanswer" } } },
+ "then": {
+ "required": ["bitanswer"],
+ "properties": {
+ "bitanswer": {
+ "type": "object",
+ "required": ["url"],
+ "properties": { "url": { "type": "string", "minLength": 1 } }
+ }
+ }
+ }
+ },
+ {
+ "if": { "properties": { "provider": { "const": "selfhosted" } } },
+ "then": {
+ "required": ["selfhosted"],
+ "properties": {
+ "selfhosted": {
+ "type": "object",
+ "required": ["baseUrl"],
+ "properties": { "baseUrl": { "type": "string", "minLength": 1 } }
+ }
+ }
+ }
+ },
+ {
+ "if": { "properties": { "scenario": { "const": "floating" } } },
+ "then": {
+ "required": ["floating"],
+ "properties": {
+ "floating": {
+ "type": "object",
+ "required": ["projectId"],
+ "properties": { "projectId": { "type": "string", "minLength": 1 } }
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/scripts/sdk-release-checksums.sh b/scripts/sdk-release-checksums.sh
new file mode 100755
index 0000000..f2719e9
--- /dev/null
+++ b/scripts/sdk-release-checksums.sh
@@ -0,0 +1,126 @@
+#!/usr/bin/env bash
+# 生成对外发布用 SHA256SUMS(相对仓库根路径,便于客户执行 sha256sum -c)。
+# 用法见 java/RELEASING.md
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+OUTPUT="${ROOT}/dist/sdk-release"
+RUN_MVN="package"
+SKIP_TESTS="-DskipTests"
+NATIVE_PATH=""
+SIGN="${SIGN:-0}"
+
+usage() {
+ sed -n '2,20p' "$0" | head -5
+ echo "用法: $0 [--output DIR] [--verify] [--native-path DIR] [--no-mvn]"
+ echo " --output DIR 输出目录(默认 dist/sdk-release)"
+ echo " --verify 执行 mvn verify(默认为 package -DskipTests)"
+ echo " --native-path DIR 额外哈希该目录下 .so/.dylib/.dll"
+ echo " --no-mvn 不执行 Maven(假定已 package)"
+ echo "环境变量 SIGN=1 且已配置 gpg 时,生成 SHA256SUMS.asc"
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --output)
+ OUTPUT="$2"
+ shift 2
+ ;;
+ --verify)
+ RUN_MVN="verify"
+ SKIP_TESTS=""
+ shift
+ ;;
+ --native-path)
+ NATIVE_PATH="$2"
+ shift 2
+ ;;
+ --no-mvn)
+ NO_MVN=1
+ shift
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo "未知参数: $1" >&2
+ usage >&2
+ exit 1
+ ;;
+ esac
+done
+
+mkdir -p "$OUTPUT"
+
+if [[ -z "${NO_MVN:-}" ]]; then
+ if [[ -n "$SKIP_TESTS" ]]; then
+ (cd "$ROOT/java" && mvn -q $SKIP_TESTS "$RUN_MVN")
+ else
+ (cd "$ROOT/java" && mvn -q "$RUN_MVN")
+ fi
+fi
+
+SUMFILE="${OUTPUT}/SHA256SUMS"
+: >"$SUMFILE"
+
+add_hash() {
+ local file="$1"
+ local rel="$2"
+ local h
+ if command -v sha256sum >/dev/null 2>&1; then
+ h=$(sha256sum "$file" | awk '{print $1}')
+ else
+ h=$(shasum -a 256 "$file" | awk '{print $1}')
+ fi
+ echo "$h $rel" >>"$SUMFILE"
+}
+
+for mod in craftlabs-auth-core craftlabs-auth-bitanswer craftlabs-auth-selfhosted; do
+ dir="$ROOT/java/$mod/target"
+ [[ -d "$dir" ]] || {
+ echo "缺少目录: $dir(请先 mvn package)" >&2
+ exit 1
+ }
+ shopt -s nullglob
+ for f in "$dir"/*.jar; do
+ bn=$(basename "$f")
+ [[ "$bn" == *-sources.jar ]] && continue
+ [[ "$bn" == *-javadoc.jar ]] && continue
+ add_hash "$f" "java/$mod/target/$bn"
+ done
+ shopt -u nullglob
+done
+
+if [[ -f "$ROOT/schemas/craftlabs-auth-config.schema.json" ]]; then
+ add_hash "$ROOT/schemas/craftlabs-auth-config.schema.json" "schemas/craftlabs-auth-config.schema.json"
+fi
+
+if [[ -n "$NATIVE_PATH" ]]; then
+ [[ -d "$NATIVE_PATH" ]] || {
+ echo "无效 --native-path: $NATIVE_PATH" >&2
+ exit 1
+ }
+ find "$NATIVE_PATH" -type f \( -name '*.so' -o -name '*.dylib' -o -name '*.dll' \) | while IFS= read -r nf; do
+ rel="${nf#$ROOT/}"
+ add_hash "$nf" "$rel"
+ done
+fi
+
+{
+ echo "repo=$(basename "$ROOT")"
+ git -C "$ROOT" rev-parse HEAD 2>/dev/null || echo "git=n/a"
+ date -u +"%Y-%m-%dT%H:%M:%SZ"
+} >"${OUTPUT}/RELEASE-MANIFEST.txt"
+
+echo "已写入: $SUMFILE"
+wc -l "$SUMFILE"
+
+if [[ "$SIGN" == "1" ]] && command -v gpg >/dev/null 2>&1; then
+ gpg --batch --yes --armor --detach-sign --output "${SUMFILE}.asc" "$SUMFILE"
+ echo "已写入: ${SUMFILE}.asc"
+fi
+
+echo ""
+echo "校验(在仓库根目录执行):"
+echo " cd \"$ROOT\" && sha256sum -c \"${SUMFILE#$ROOT/}\""