LegalDatabase 域覆盖 12 家法律数据库供应商。这是整个集成库里能力差异最大的域:8 家厂商只有 SSO 跳转入口,根本没有公开检索 API;4 家厂商提供 API 检索。抽象层用功能标志枚举声明能力差异,并通过内置适配器 SsoRedirectLegalDatabaseProvider 把 SSO 跳转委托给共享的 SsoRedirect 域。
本页是完整手册:从能力标志到适配器、从 SSO-only 厂商到 API 厂商、从 Search/AccessUrl 决策到厂商能力矩阵。
为什么需要适配器模式
标准三层模式假设所有厂商实现同一套完整接口。法律数据库的现实是:
- 威科先行、法信、聚法案例、把手案例、见微、律商联讯、秘塔、无讼这 8 家只有 SSO 跳转入口,没有公开检索 API;
- 元典、得理法搜、合享专利库这 3 家提供完整 API 检索;
- 北大法宝的能力是动态的——配了
ApiToken才支持检索,否则只有跳转。
强行让 SSO 厂商实现 SearchAsync 会产生一堆空实现,调用方也无法判断哪些方法真的可用。适配器模式把”能力声明”和”能力实现”分离:
- 厂商通过
Capabilities属性声明自己支持哪些操作(功能标志); - 调用方在使用前用
HasFlag检查标志位; - 不支持的操作直接抛
NotSupportedException,而不是返回空结果假装成功。
功能标志枚举
[Flags] 枚举,用位运算组合:
[Flags]public enum LegalDatabaseCapabilities{ None = 0, AccessUrl = 1, // SSO 跳转:生成带认证参数的访问链接 Search = 2, // API 检索:关键词搜索法律文献 DocumentDetail = 4, // API 文档详情:获取单篇文档全文 All = AccessUrl | Search | DocumentDetail,}AccessUrl 是所有 12 家厂商都支持的基础能力;Search 和 DocumentDetail 只有 API 厂商才支持。
统一接口
public interface ILegalDatabaseProvider{ string ProviderName { get; } LegalDatabaseCapabilities Capabilities { get; } // 功能标志,调用方据此判断可用性
// AccessUrl 能力——所有厂商都支持 Task<LegalDatabaseAccessUrl> GenerateAccessUrlAsync( string userId, string? redirectUrl = null, CancellationToken ct = default);
// Search 能力——仅 API 厂商支持 Task<LegalSearchResult> SearchAsync( string keyword, LegalSearchType searchType = LegalSearchType.All, int pageIndex = 1, int pageSize = 20, CancellationToken ct = default);
// DocumentDetail 能力——仅 API 厂商支持 Task<LegalDocument> GetDocumentAsync(string documentId, CancellationToken ct = default);}注意:和走 Result<T> 的电子签域不同,LegalDatabase 的接口直接返回模型对象(LegalDatabaseAccessUrl / LegalSearchResult / LegalDocument)。失败时抛 LegalDatabaseException,把供应商错误码和消息封装在内。
SsoRedirectLegalDatabaseProvider 适配器
对只支持 SSO 跳转的厂商,抽象包内置了 SsoRedirectLegalDatabaseProvider。它实现 ILegalDatabaseProvider,把 GenerateAccessUrlAsync 委托给共享层的 SsoRedirectProvider,其余两个方法直接抛异常:
public class SsoRedirectLegalDatabaseProvider : ILegalDatabaseProvider, IDisposable{ private readonly SsoRedirectProvider _inner;
public LegalDatabaseCapabilities Capabilities => LegalDatabaseCapabilities.AccessUrl;
public async Task<LegalDatabaseAccessUrl> GenerateAccessUrlAsync( string userId, string? redirectUrl = null, CancellationToken ct = default) { var result = await _inner.GenerateAccessUrlAsync(userId, redirectUrl, ct); return new LegalDatabaseAccessUrl { Url = result.Url, ExpiresAt = result.ExpiresAt }; }
public Task<LegalSearchResult> SearchAsync(...) => throw new NotSupportedException($"供应商 [{_inner.ProviderName}] 不支持 API 检索,仅支持 SSO 跳转访问");
public Task<LegalDocument> GetDocumentAsync(...) => throw new NotSupportedException($"供应商 [{_inner.ProviderName}] 不支持获取文档详情,仅支持 SSO 跳转访问");}这个适配器委托给共享的 SsoRedirect 域,而不是重新实现跳转逻辑。SsoRedirectProvider 支持 UrlTemplate 模式(拼接 URL)和 API 重定向模式(捕获 3xx Location),适配器的构造函数按需注入对应的 HttpClient。
多数 SSO 厂商直接继承这个适配器,零代码即可接入。
12 家厂商能力矩阵
| 厂商 | 中文 | Provider ID | AccessUrl | Search | DocumentDetail | 接入方式 |
|---|---|---|---|---|---|---|
| WkInfo | 威科先行 | WkInfo | ✓ | — | — | SsoRedirect 配置注册 |
| Faxin | 法信 | Faxin | ✓ | — | — | 继承适配器 |
| Jufaanli | 聚法案例 | Jufaanli | ✓ | — | — | 继承适配器 |
| Wusong | 无讼 | Wusong | ✓ | — | — | SsoRedirect 配置注册 |
| Metalaw | 秘塔 | Metalaw | ✓ | — | — | 继承适配器 |
| Lawsdata | 把手案例 | Lawsdata | ✓ | — | — | 继承适配器 |
| Jianwei | 见微 | Jianwei | ✓ | — | — | 继承适配器 |
| LexisNexis | 律商联讯 | LexisNexis | ✓ | — | — | 继承适配器 |
| Pkulaw | 北大法宝 | Pkulaw | ✓ | 动态 | 动态 | 自有实现 |
| Yuandian | 元典 | Yuandian | ✓ | ✓ | ✓ | 自有实现 |
| Delilegal | 得理法搜 | Delilegal | ✓ | ✓ | ✓ | 自有实现 |
| Incopat | 合享专利库 | Incopat | ✓ | ✓ | ✓ | 自有实现 |
8 家纯 SSO 厂商(WkInfo / Faxin / Jufaanli / Wusong / Metalaw / Lawsdata / Jianwei / LexisNexis)只支持 AccessUrl,通过继承适配器或配置注册接入。
北大法宝的能力是动态的:配置了 ApiToken 时额外支持 Search 和 DocumentDetail,否则只有 AccessUrl。它的 Capabilities 属性在运行时读取配置决定。
3 家 API 厂商(Yuandian / Delilegal / Incopat)完整实现三个方法。
LegalSearchType 检索类型
SearchAsync 的 searchType 参数限定检索范围:
| 值 | 名称 | 说明 |
|---|---|---|
| 0 | All | 全部类型(默认) |
| 1 | Law | 法律法规 |
| 2 | Case | 司法案例 |
| 3 | Patent | 专利(合享专利库专长) |
| 4 | Journal | 法学期刊 |
不同 API 厂商对检索类型的支持粒度不同,部分类型可能并入 All 返回。
错误处理
LegalDatabaseException 继承 IntegrationException,domain 固定为 "LegalDatabase":
public class LegalDatabaseException : IntegrationException{ public string ProviderName => Provider ?? string.Empty; public new string? ErrorCode => base.ErrorCode; public new string? ProviderMessage => base.ProviderMessage;
public LegalDatabaseException(string providerName, string? errorCode, string? providerMessage) : base(domain: "LegalDatabase", message: $"[{providerName}] {errorCode}: {providerMessage}", ...) { }}抽象层还定义了 LegalDatabaseResult<T>(含 IsSuccess / Data / ErrorCode / ErrorMessage 与 Success / Fail 工厂),但接口方法直接返回模型对象而非 Result——这是为未来 Result 化预留的类型,当前失败统一走异常路径。
Search vs AccessUrl:如何决策
| 场景 | 用什么 | 谁可用 |
|---|---|---|
| 用户点击后直接跳到厂商平台查阅 | GenerateAccessUrlAsync | 全部 12 家 |
| 在应用内嵌入检索框,返回结构化结果列表 | SearchAsync | 仅 4 家 API 厂商 |
| 点击检索结果查看单篇文档全文 | GetDocumentAsync | 仅 4 家 API 厂商 |
| 多厂商统一检索入口 | 先 HasFlag(Search) 筛选可用厂商 | API 厂商子集 |
注册
单厂商
// ① 威科先行(纯 SSO,UrlTemplate 模式)builder.Services.AddBitzsoftWkInfoLegalDatabase(builder.Configuration.GetSection("LegalDatabase:WkInfo"));
// ② 元典(API 检索,自有实现)builder.Services.AddBitzsoftYuandianLegalDatabase(builder.Configuration.GetSection("LegalDatabase:Yuandian"));
// ③ 通用 SSO 跳转注册(任意 SsoRedirect 模式厂商,按配置注册)builder.Services.AddBitzsoftSsoRedirectLegalDatabase(options =>{ options.ProviderName = "威科先行"; options.SsoUrlTemplate = "https://www.wkinfo.com.cn/sso?uid={userId}&token={token}&t={timestamp}"; options.Token = "your-sso-token";});全量聚合
// 注意:聚合方法名是 AddBitzsoftLegalDatabase,不带 All 后缀builder.Services.AddBitzsoftLegalDatabase(builder.Configuration, "LegalDatabase");按配置节存在性自动注册:
{ "LegalDatabase": { "WkInfo": { "ProviderName": "威科先行", "SsoUrlTemplate": "...", "Token": "..." }, "Yuandian": { "ApiBaseUrl": "...", "ApiKey": "..." }, "Pkulaw": { "BaseUrl": "...", "ApiToken": "..." } }}消费
AccessUrl(所有厂商)
public class LegalAccessService(ILegalDatabaseProvider db){ public async Task<string> GetAccessUrlAsync(string userId) { var url = await db.GenerateAccessUrlAsync(userId); return url.Url; // 带认证参数,直接返回给前端跳转 }}Search(仅 API 厂商)
public class LegalSearchService(ILegalDatabaseProvider db){ public async Task<IReadOnlyList<LegalDocument>> SearchAsync(string keyword) { // ① 检查能力标志,避免 NotSupportedException if (!db.Capabilities.HasFlag(LegalDatabaseCapabilities.Search)) throw new NotSupportedException($"{db.ProviderName} 不支持 API 检索");
var result = await db.SearchAsync(keyword, LegalSearchType.All, pageIndex: 1, pageSize: 20); return result.Items; }}多厂商路由
public class LegalRouter(IIntegrationProviderResolver<ILegalDatabaseProvider> providers){ public async Task<LegalSearchResult> SearchAsync(string source, string keyword) { var db = providers.GetRequired(source); // "Yuandian" / "Delilegal" / "Pkulaw" ... if (!db.Capabilities.HasFlag(LegalDatabaseCapabilities.Search)) throw new NotSupportedException($"{source} 不支持检索"); return await db.SearchAsync(keyword); }}