# 科研开放数据接口 — 技术方案 > 依据:同目录 `科研开放数据接口功能需求.md`。对外只读开放 API;**无**前端页面、**无**新表。 --- ## 1. 技术架构 | 项 | 说明 | | --- | --- | | **后端** | RuoYi **v3.9.2**(**springboot2** 分支):JDK 8、Spring MVC、MyBatis | | **数据库** | 只读查询既有表 `biz_pasture`、`biz_yak_asset` | | **鉴权** | 独立 JWT + Servlet Filter;`@Anonymous` 绕过若依登录,Filter 校验 Bearer Token | | **响应** | `OpenApiResponse` + `OpenApiPageResult`(与第三方 farming OpenAPI 同结构) | **代码包**:`baqing-admin` → `com.ruoyi.web.modules.industryservice.research` | 类 | 职责 | | --- | --- | | `ResearchOpenApiController` | HTTP 入口 | | `ResearchOpenApiServiceImpl` | 分页查询 + 脱敏 VO 转换 | | `ResearchOpenApiTokenService` | 签发/校验 JWT | | `ResearchOpenApiAuthFilter` | 除 `/auth/token` 外校验 Token | | `ResearchOpenApiMasking` / `ResearchOpenApiSupport` | 脱敏与响应组装 | --- ## 2. 配置 `application.yml` → `research-open-api`: | 键 | 说明 | | --- | --- | | `enabled` | 是否启用;`false` 时返回 503 | | `app-key` | 申请 Token 用(全局唯一) | | `app-secret` | 申请 Token 用 | | `token-secret` | JWT 签名密钥(**仅服务端**,勿下发) | | `token-expire-minutes` | Token 有效期,默认 120 | | `max-page-size` | 单页上限,默认 200 | **示例:** ```yaml research-open-api: enabled: true app-key: research-open-demo-key app-secret: research-open-demo-secret-change-me token-secret: research-open-api-change-me-in-production token-expire-minutes: 120 max-page-size: 200 ``` --- ## 3. 接口设计 **Base Path**:`/open-api/v1/research` ### 3.1 申请 Token ``` POST /open-api/v1/research/auth/token Content-Type: application/json {"appKey":"research-open-demo-key","appSecret":"research-open-demo-secret-change-me"} ``` **成功响应 `data`:** ```json { "accessToken": "eyJhbGciOiJIUzUxMiJ9...", "tokenType": "Bearer", "expiresIn": 7200 } ``` JWT Claims:`type=research_open_api`;**不含**机构 ID。 ### 3.2 牧场列表 ``` GET /open-api/v1/research/pastures?pageNum=1&pageSize=20&keyword= Authorization: Bearer ``` **`records[]` 元素(ResearchPastureVo):** | 字段 | 类型 | 说明 | | --- | --- | --- | | `pastureId` | long | 本地牧场 ID | | `pastureName` | string | 牧场名称 | | `farmType` | string | 类型 | | `county` / `town` | string | 区县、乡镇 | | `regionSummary` | string | 区县+乡镇摘要 | | `latitude` / `longitude` | number | 约百米精度 | | `floorArea` / `scaleBreeding` / `breedSpecies` | string | 规模类 | | `introduction` | string | 简介 | | `personInCharge` | string | 脱敏负责人 | | `contactPhone` | string | 脱敏手机 | ### 3.3 牦牛资产档案列表 ``` GET /open-api/v1/research/yak-assets?pageNum=1&pageSize=20&assetStatus=1&gender=母&pastureId= Authorization: Bearer ``` **`records[]` 元素(ResearchYakAssetVo):** | 字段 | 类型 | 说明 | | --- | --- | --- | | `subjectCode` | string | 科研主体码,如 `YS00000088` | | `pastureName` / `enclosureName` | string | 牧场、圈舍名称 | | `gender` / `cattleVariety` | string | 性别、品种 | | `ageMonths` | int | 月龄 | | `birthYear` / `birthMonth` | int | 出生年月 | | `assetStatus` | int | 1~4 | | `assetStatusLabel` | string | 正常/死淘/丢失/出栏 | | `entryWeightKg` | decimal | 入栏体重 | | `source` / `breedingMethod` / `entryCycle` | string | 来源、养殖方式、周期 | | `realtimeTemp` / `realtimeSteps` / `envTemp` | number | IoT 快照 | | `fatherSubjectCode` / `motherSubjectCode` | string | 系谱 pseudonym | --- ## 4. 鉴权流程 1. `ResearchOpenApiController` 标注 `@Anonymous` → Spring Security 放行。 2. `ResearchOpenApiAuthFilter` 拦截 `/open-api/v1/research/*`(**排除** `/auth/token`)。 3. 解析 `Authorization: Bearer …`,`ResearchOpenApiTokenService.validateToken` 验签与过期。 4. 失败返回 JSON `{ code:401, message:"..." }`,HTTP 401。 Token 申请:`appKey`/`appSecret` 与配置项精确匹配后签发 JWT(HS512 + `token-secret`)。 --- ## 5. 数据查询 复用既有 Service / Mapper: | 接口 | Service | 表 | 条件 | | --- | --- | --- | --- | | 牧场列表 | `IBizPastureService.selectBizPastureList` | `biz_pasture` | `del_flag=0` | | 档案列表 | `IBizYakAssetService.selectBizYakAssetList` | `biz_yak_asset` | `del_flag=0`;支持 `assetStatus`、`gender`、`pastureId` | 实体 → VO:`ResearchOpenApiSupport.toPastureVo` / `toYakAssetVo`(内部调用 `ResearchOpenApiMasking`)。 --- ## 6. 脱敏实现 | 方法 | 说明 | | --- | --- | | `maskPhone` | 复用 `SubsidyGrantRecordMasking.maskPhone` | | `maskPersonName` | 首字 + `*` | | `regionSummary` | 区县 + 乡镇 | | `roundCoordinate` | 经纬度 3 位小数 | | `subjectCode` | `YS` + 8 位 `id` | | `pseudonymCode` | 系谱编号哈希短码 | --- ## 7. 异常码 | code | HTTP | 场景 | | --- | --- | --- | | 200 | 200 | 成功 | | 401 | 401 | appKey/appSecret 错误、Token 缺失/无效/过期 | | 400 | 200* | pageSize 超限(body.code=400) | | 503 | 503 | `enabled=false` 或未配置凭证 | \* 业务错误仍返回 JSON body,HTTP 状态与 `code` 一致(Filter 401/503;Controller 内 ServiceException 写入 body.code)。 --- ## 8. 调用示例 ```bash # 1. 申请 Token curl -X POST "http://localhost:8010/open-api/v1/research/auth/token" \ -H "Content-Type: application/json" \ -d '{"appKey":"research-open-demo-key","appSecret":"research-open-demo-secret-change-me"}' # 2. 牧场列表 curl "http://localhost:8010/open-api/v1/research/pastures?pageNum=1&pageSize=10" \ -H "Authorization: Bearer " # 3. 牦牛档案 curl "http://localhost:8010/open-api/v1/research/yak-assets?pageNum=1&pageSize=10&assetStatus=1" \ -H "Authorization: Bearer " ``` --- ## 9. 测试 见同目录 `科研开放数据接口测试用例.md`。单元测试: - `ResearchOpenApiMaskingTest` - `ResearchOpenApiTokenServiceTest` - `ResearchOpenApiControllerApiTest` --- ## 10. 需求追溯 | 功能需求 | 技术落点 | | --- | --- | | §5 认证 | **§2** 配置、**§4** Filter + TokenService | | §6.2 牧场 | **§3.2**、**§5** BizPasture | | §6.3 档案 | **§3.3**、**§5** BizYakAsset | | §7 脱敏 | **§6** Masking | | §3 不实现机构区分 | 单一 app-key/secret,JWT 无 clientId | --- ## 11. 修订记录 | 版本 | 日期 | 说明 | | --- | --- | --- | | 1.0 | 2026-07-04 | 初版:Token + 牧场/档案脱敏列表;全局一对凭证 |