Files
workspace/code/g3soft-libs/docs/content/api/getting-started.md
T
2026-10-12 22:25:41 +08:00

201 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` 基本能直接告诉你哪个参数不对。