201 lines
7.2 KiB
Markdown
201 lines
7.2 KiB
Markdown
---
|
||
title: 快速开始
|
||
description: 接口约定、鉴权与联调步骤
|
||
pageClass: g3-api
|
||
---
|
||
|
||
# 快速开始
|
||
|
||
后端提供的是一套**元数据驱动的通用数据接口**:前端不拼业务 SQL,只声明「查哪张视图 / 存哪张表 + 条件 + 字段」,由后端按 `s_module` / `s_field` 里的配置生成参数化 SQL 并执行。除登录外,所有接口共用同一套鉴权、机构数据源路由与统一响应格式。
|
||
|
||
## 基础约定
|
||
|
||
<G3ApiParams variant="bare" :title="null">
|
||
|
||
| 项 | 约定 |
|
||
|---|---|
|
||
| 基础路径 | `/api`(`server.servlet.context-path`),例如 `http://localhost:8088/api/data/page` |
|
||
| 方法 | **全部是 `POST`**;唯一的 `GET` 是文件静态访问 `/api/file/static/{fileName}` |
|
||
| 请求体 | `application/json`,参数是**一个对象或数组**,不是表单 |
|
||
| 编码 | UTF-8 |
|
||
| HTTP 状态码 | **始终是 200** —— 成败一律看响应体里的 `code`,不要用 HTTP 状态码判断 |
|
||
|
||
</G3ApiParams>
|
||
|
||
最后一条最容易踩:鉴权失败、参数错误、系统异常**都不会**返回 4xx / 5xx,而是 200 + `code != 0`。所以不要依赖 `axios` 的错误拦截分支来处理业务失败。
|
||
|
||
## 统一响应格式
|
||
|
||
所有接口返回同一个外壳:
|
||
|
||
<G3ApiPayload title="响应" :code="0">
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "操作成功",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
</G3ApiPayload>
|
||
|
||
<G3ApiParams variant="bare" title="响应字段">
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `code` | `0` 成功,非 0 见下表 |
|
||
| `message` | 可直接展示给用户的文案;出错时是原因 |
|
||
| `data` | 成功时的载荷;失败时一般为 `null` |
|
||
|
||
</G3ApiParams>
|
||
|
||
## 错误码
|
||
|
||
<G3ApiParams variant="bare" :title="null">
|
||
|
||
| code | 含义 | 触发场景 | 前端应对 |
|
||
|---|---|---|---|
|
||
| `0` | 成功 | —— | —— |
|
||
| `401` | 未登录 / 凭证失效 | 缺少或无效的 `Authorization`;账号密码错误 | 清 token,回登录页 |
|
||
| `40101` | 账号已在其他设备登录 | 同一账号在别处登录,本端会话被顶 | 清 token,提示后回登录页 |
|
||
| `1000` | 业务错误(默认) | 参数缺失或类型不对、标识符非法、只读 SQL 校验不通过、目标表没有可识别字段等 | 直接用 `message` 提示 |
|
||
| `2001` | 保存失败 | 写入时的数据库错误,`data` 带 `table` / `action` / `index` / `cause` | 提示 `message`;`data.index` 可定位到第几行 |
|
||
| `2002` | 保存前置条件不成立 | 请求声明的 guard 条件不满足,`data` 带 `table` / `action` / `index` | 直接用 `message`(那是写条件的人给的业务文案) |
|
||
| `2003` | 并发编辑冲突 | `$original` 原值或 `b_row_version` 不匹配,`data` 带 `fields` | 提示后**重新拉取该行**,标出 `fields` 里的字段 |
|
||
| `500` | 系统异常 | 未预期的异常 | 提示「系统异常,请稍后重试」 |
|
||
|
||
</G3ApiParams>
|
||
|
||
参数类错误都是 `1000`,`message` 形如 `view_name 不能为空`、`page_no 必须是大于 0 的整数`、`参数类型不正确`、`非法数据库标识符: xxx` —— 通常已经说清是哪个参数错了。
|
||
|
||
## 鉴权
|
||
|
||
### 登录
|
||
|
||
<G3ApiPayload title="请求">
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8088/api/auth/login \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"orgid":"G3HD","userid":"admin","password":"******"}'
|
||
```
|
||
|
||
</G3ApiPayload>
|
||
|
||
<G3ApiPayload title="响应" :code="0">
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "操作成功",
|
||
"data": {
|
||
"token": "eyJhbGciOiJIUzI1NiJ9...",
|
||
"orgid": "G3HD",
|
||
"user": { "id": "g3soft", "account": "g3soft", "name": "G3Soft" }
|
||
}
|
||
}
|
||
```
|
||
|
||
</G3ApiPayload>
|
||
|
||
<G3ApiParams>
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `orgid` | string | 是 | 机构码,决定连哪个机构的数据库 |
|
||
| `userid` | string | 是 | 账号;字符串业务键方案下 `b_user.b_id` 就是账号本身 |
|
||
| `password` | string | | 与库里存的密码比对 |
|
||
|
||
</G3ApiParams>
|
||
|
||
机构码或账号为空 → `1000`(`机构码不能为空` / `用户ID不能为空`);账号、密码、机构任一对不上 → 统一 `401` + `机构码、账号或密码错误`(刻意不区分是哪一个错)。
|
||
|
||
### 带上凭证
|
||
|
||
除 `/api/auth/login` 与 `OPTIONS` 预检外,**所有接口都要带**:
|
||
|
||
<G3ApiPayload title="请求头">
|
||
|
||
```
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
</G3ApiPayload>
|
||
|
||
> **机构码不用在业务接口里传。** token 里带着 `orgId`,服务端据此把请求路由到该机构自己的数据库,前端只管带 token。
|
||
>
|
||
> token 默认 24 小时过期(`erp.auth.jwt-expiration`)。同一账号再次登录会让旧 token 失效并返回 `40101`。
|
||
|
||
## 最小可跑示例
|
||
|
||
<G3ApiPayload title="示例代码">
|
||
|
||
```js
|
||
const api = axios.create({ baseURL: '/api' })
|
||
|
||
api.interceptors.request.use((config) => {
|
||
const token = localStorage.getItem('g3erp:token')
|
||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||
return config
|
||
})
|
||
|
||
// HTTP 200 也可能是业务失败,所以要在这里统一判 code
|
||
api.interceptors.response.use((res) => {
|
||
const { code, message } = res.data
|
||
if (code === 401 || code === 40101) {
|
||
localStorage.removeItem('g3erp:token')
|
||
location.href = '/login'
|
||
}
|
||
return res.data
|
||
})
|
||
|
||
// 分页查询
|
||
const { data } = await api.post('/data/page', {
|
||
view_name: 'v_b_othercompany',
|
||
search_columns: ['b_id', 'b_name'],
|
||
search_condition: 'b_canuse = 1',
|
||
order_by: 'b_id ASC',
|
||
page_no: 1,
|
||
page_size: 20,
|
||
})
|
||
// data.rows / data.total / data.page_no / data.page_size
|
||
```
|
||
|
||
</G3ApiPayload>
|
||
|
||
## 一个必须知道的序列化约定
|
||
|
||
**所有长整型(`Long`)都以 JSON 字符串输出**(后端 `JacksonConfig` 给 `Long` / `long` 注册了 `ToStringSerializer`):
|
||
|
||
<G3ApiPayload title="响应" :code="0">
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "操作成功",
|
||
"data": "1873456789012345678"
|
||
}
|
||
```
|
||
|
||
</G3ApiPayload>
|
||
|
||
原因是雪花 ID 是 64 位,超过 JS 的 `Number.MAX_SAFE_INTEGER`(2⁵³−1),按数字解析会**静默丢精度**。所以 `data.nextid`、行里的各种 id 一律当 `string` 处理,不要 `Number()` / `parseInt()`,也不要参与算术。
|
||
|
||
## 接口一览
|
||
|
||
- **数据查询** —— [数据查询](/api/data):`/data/describe`、`/data/loaddata`、`/data/page`、`/data/loaddatabysql`、`/data/nextid`、`/data/nextcode`
|
||
- **数据写入** —— [数据写入](/api/save):`/data/saveobjt`、`/data/delete`
|
||
- **鉴权** —— 本页:`/auth/login`
|
||
- **文件** —— 暂无专页:`/file/config`、`/file/upload`、`/file/upload-credential`、`/file/save-record`、`/file/delete`、`GET /file/static/{fileName}`
|
||
- **导出** —— 暂无专页:`/export/list`、`/export/count`、`/export/totals`(**返回文件流,不是 `ApiResponse`**)
|
||
|
||
> `/file/static/{fileName}` 同样受鉴权过滤(只有 `/auth/login` 被放行),所以不能用 `<img src="...">` 直接访问,需要按 blob 取回再用 `URL.createObjectURL`。
|
||
|
||
## 联调步骤
|
||
|
||
1. 起后端:`cd server && ./mvnw spring-boot:run`(默认 8088);前端 dev server 的 `/api` 代理到它。
|
||
2. 调 `/api/auth/login` 拿 token,存进 `localStorage`(键名沿用组件库示例里的 `g3erp:token`)。
|
||
3. 用 `/api/data/describe` 看目标表或视图有哪些列,再调 `/api/data/page` 取数据。
|
||
4. 拿到 `code != 0` 先查上面的错误码表;`1000` 的 `message` 基本能直接告诉你哪个参数不对。
|