# Sundynix 前端接入文档

> 网关地址：`http://{GATEWAY_HOST}:8888`
> 所有接口统一经过网关转发，前端只需对接网关地址

---

## 一、全局约定

### 1.1 统一响应格式

所有接口返回 **HTTP 200**，通过 `code` 区分业务状态：

```json
{
  "code": 200,
  "msg": "success",
  "data": { ... }
}
```

| code | 含义 |
|:----:|------|
| 200 | 成功 |
| 400 | 业务错误（参数错误、逻辑异常等） |
| 401 | 未授权（Token 缺失/过期/无效） |

### 1.2 鉴权机制

需要鉴权的接口必须携带以下请求头：

```
Authorization: Bearer <token>
X-Client-Id: <客户端标识>
```

> [!IMPORTANT]
> - `Authorization` 从登录接口获取
> - `X-Client-Id` 由后台管理端配置的客户端标识（如 `sundynix-admin`、`mini-app`）

### 1.3 Token 自动续期

网关内置**滑动窗口续期**机制。当 Token 剩余有效时间 < 1 小时时，响应头会携带新 Token：

```
X-Refresh-Token: <new_token>
```

前端需要在响应拦截器中处理：

```ts
// axios 响应拦截器
axios.interceptors.response.use((response) => {
  const newToken = response.headers['x-refresh-token']
  if (newToken) {
    // 静默替换本地存储的 Token
    useUserStore().setToken(newToken)
  }
  return response
})
```

### 1.4 CORS 说明

网关已配置跨域，前端无需额外处理。已暴露自定义响应头 `X-Refresh-Token`。

---

## 二、Auth 服务（`/api/auth`）

### 2.1 获取图形验证码

> 🔓 无需鉴权

```
GET /api/auth/captcha
```

**响应**：
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "captchaId": "abc123",
    "captchaImg": "data:image/png;base64,iVBOR..."
  }
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| captchaId | string | 验证码 ID，登录时回传 |
| captchaImg | string | Base64 图片，直接赋给 `<img src>` |

---

### 2.2 账号密码登录

> 🔓 无需鉴权

```
POST /api/auth/login
```

**请求头**：
```
X-Client-Id: sundynix-admin
```

**请求体**：
```json
{
  "account": "admin",
  "password": "123456",
  "captchaId": "abc123",
  "captcha": "5xmn"
}
```

| 字段 | 必填 | 说明 |
|------|:----:|------|
| account | ✅ | 账号 |
| password | ✅ | 密码 |
| captchaId | ✅ | 验证码 ID（从 captcha 接口获取） |
| captcha | ✅ | 用户输入的验证码 |

**响应**：
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "userInfo": {
      "id": "1234567890",
      "name": "张三",
      "account": "admin",
      "nickName": "管理员",
      "phone": "13800138000",
      "avatarId": "file-id-xxx",
      "gender": 1
    }
  }
}
```

---

### 2.3 微信小程序登录

> 🔓 无需鉴权

```
POST /api/auth/miniLogin
```

**请求体**：
```json
{
  "code": "wx_login_code_from_wx.login()",
  "clientId": "mini-app"
}
```

**响应**：同 2.2 的响应格式

---

### 2.4 手机号登录

> 🔓 无需鉴权

```
POST /api/auth/loginByPhone
```

**请求体**：
```json
{
  "code": "phone_code_from_getPhoneNumber",
  "openId": "用户openId",
  "clientId": "mini-app"
}
```

**响应**：同 2.2 的响应格式

---

### 2.5 获取当前用户信息（含角色、菜单）

> 🔒 需要鉴权

```
GET /api/auth/info
```

**响应**：
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "id": "1234567890",
    "name": "张三",
    "account": "admin",
    "nickName": "管理员",
    "phone": "13800138000",
    "avatarId": "file-id-xxx",
    "gender": 1,
    "roles": ["admin", "editor"],
    "menus": [
      {
        "id": "m1",
        "parentId": "0",
        "category": 1,
        "name": "Dashboard",
        "title": "仪表盘",
        "code": "dashboard",
        "path": "/dashboard",
        "permission": "",
        "locale": "menu.dashboard",
        "icon": "icon-dashboard",
        "sort": 1,
        "children": [
          {
            "id": "m2",
            "parentId": "m1",
            "name": "Workplace",
            "title": "工作台",
            "path": "/dashboard/workplace",
            "sort": 1,
            "children": []
          }
        ]
      }
    ],
    "createdAt": 1714000000
  }
}
```

> [!TIP]
> - `roles` 是角色 code 数组，用于前端权限判断
> - `menus` 是树形结构，已按 `sort` 排序，前端直接渲染侧边栏
> - `createdAt` 是 Unix 时间戳（秒）

---

### 2.6 更新个人信息

> 🔒 需要鉴权

```
POST /api/auth/update
```

**请求体**（所有字段可选，只传需要修改的）：
```json
{
  "name": "李四",
  "nickName": "小李",
  "phone": "13900139000",
  "avatarId": "new-file-id"
}
```

**响应**：
```json
{ "code": 200, "msg": "success" }
```

---

## 三、System 服务（`/api/sys`）

> [!IMPORTANT]
> 以下所有接口均需要鉴权（`Authorization: Bearer <token>`）

---

### 3.1 客户端管理

#### 创建客户端

```
POST /api/sys/client/create
```

```json
{
  "clientId": "sundynix-admin",
  "name": "Web管理端",
  "grantType": "password",
  "additionalInfo": "",
  "activeTimeout": 7200
}
```

| 字段 | 必填 | 说明 |
|------|:----:|------|
| clientId | ✅ | 客户端唯一标识 |
| name | ✅ | 客户端名称 |
| grantType | ❌ | 授权类型 |
| additionalInfo | ❌ | 附加信息 |
| activeTimeout | ❌ | Token 有效期（秒），默认 7200 |

#### 更新客户端

```
POST /api/sys/client/update
```

```json
{
  "id": "客户端记录ID",
  "name": "新名称",
  "activeTimeout": 3600
}
```

#### 删除客户端

```
POST /api/sys/client/delete
```

```json
{ "ids": ["id1", "id2"] }
```

#### 客户端列表

```
POST /api/sys/client/list
```

```json
{
  "current": 1,
  "pageSize": 10,
  "name": ""
}
```

**响应**：
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "list": [
      {
        "id": "xxx",
        "clientId": "sundynix-admin",
        "name": "Web管理端",
        "grantType": "password",
        "additionalInfo": "",
        "activeTimeout": 7200
      }
    ],
    "total": 1,
    "current": 1,
    "size": 10
  }
}
```

---

### 3.2 角色管理

#### 创建角色

```
POST /api/sys/role/create
```

```json
{
  "name": "管理员",
  "code": "admin",
  "sort": 1,
  "menuIds": ["menu-id-1", "menu-id-2"]
}
```

#### 更新角色

```
POST /api/sys/role/update
```

```json
{
  "id": "角色ID",
  "name": "超级管理员",
  "code": "super-admin",
  "sort": 0,
  "menuIds": ["menu-id-1", "menu-id-2", "menu-id-3"]
}
```

> [!NOTE]
> `menuIds` 是**全量替换**，传什么就关联什么。不传或空数组则清除所有菜单关联。

#### 删除角色

```
POST /api/sys/role/delete
```

```json
{ "ids": ["role-id-1"] }
```

#### 角色列表

```
POST /api/sys/role/list
```

```json
{
  "current": 1,
  "pageSize": 10,
  "name": ""
}
```

**响应**：
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "list": [
      {
        "id": "xxx",
        "name": "管理员",
        "code": "admin",
        "sort": 1,
        "menuIds": ["m1", "m2"]
      }
    ],
    "total": 1
  }
}
```

---

### 3.3 菜单管理

#### 创建菜单

```
POST /api/sys/menu/create
```

```json
{
  "parentId": "0",
  "category": 1,
  "name": "Dashboard",
  "title": "仪表盘",
  "code": "dashboard",
  "path": "/dashboard",
  "permission": "",
  "locale": "menu.dashboard",
  "icon": "icon-dashboard",
  "sort": 1
}
```

| 字段 | 说明 |
|------|------|
| parentId | 父级ID，顶级菜单传 `"0"` 或空字符串 |
| category | 类型：1=菜单 2=按钮/权限 |
| name | 路由 name（英文，如 `Dashboard`） |
| title | 显示标题（中文，如 `仪表盘`） |
| code | 权限标识（如 `dashboard`） |
| path | 路由路径（如 `/dashboard`） |
| permission | 操作权限标识 |
| locale | 国际化 key |
| icon | 图标名称 |
| sort | 排序值，越小越靠前 |

#### 更新菜单

```
POST /api/sys/menu/update
```

```json
{
  "id": "菜单ID",
  "title": "新标题",
  "sort": 2
}
```

#### 删除菜单

```
POST /api/sys/menu/delete
```

```json
{ "ids": ["menu-id-1"] }
```

> [!WARNING]
> 删除菜单会同步清除该菜单与所有角色的关联关系

#### 菜单列表（树形）

```
GET /api/sys/menu/list
```

无参数。返回完整的树形菜单结构。

**响应**：
```json
{
  "code": 200,
  "msg": "success",
  "data": [
    {
      "id": "m1",
      "parentId": "0",
      "category": 1,
      "name": "System",
      "title": "系统管理",
      "code": "system",
      "path": "/system",
      "icon": "icon-settings",
      "sort": 99,
      "children": [
        {
          "id": "m2",
          "parentId": "m1",
          "name": "User",
          "title": "用户管理",
          "path": "/system/user",
          "sort": 1,
          "children": []
        }
      ]
    }
  ]
}
```

#### 根据角色获取菜单

```
POST /api/sys/menu/byRole
```

```json
{ "id": "角色ID" }
```

**响应**：同菜单列表格式，但只包含该角色关联的菜单

---

### 3.4 用户管理（后台管理）

#### 用户列表

```
POST /api/sys/user/list
```

```json
{
  "current": 1,
  "pageSize": 10,
  "name": "",
  "account": ""
}
```

**响应**：
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "list": [
      {
        "id": "xxx",
        "name": "张三",
        "account": "admin",
        "nickName": "管理员",
        "phone": "13800138000",
        "gender": 1,
        "createdAt": 1714000000
      }
    ],
    "total": 1
  }
}
```

#### 创建用户

```
POST /api/sys/user/create
```

```json
{
  "name": "张三",
  "account": "zhangsan",
  "password": "123456",
  "phone": "13800138000",
  "nickName": "小张",
  "roleIds": ["role-id-1", "role-id-2"]
}
```

| 字段 | 必填 | 说明 |
|------|:----:|------|
| name | ✅ | 姓名 |
| account | ✅ | 账号（唯一） |
| password | ✅ | 密码 |
| phone | ❌ | 手机号 |
| nickName | ❌ | 昵称 |
| roleIds | ❌ | 关联角色ID数组 |

#### 更新用户

```
POST /api/sys/user/update
```

```json
{
  "id": "用户ID",
  "name": "李四",
  "phone": "13900139000",
  "roleIds": ["role-id-1"]
}
```

#### 删除用户

```
POST /api/sys/user/delete
```

```json
{ "ids": ["user-id-1", "user-id-2"] }
```

#### 重置密码

```
POST /api/sys/user/resetPassword
```

```json
{
  "id": "用户ID",
  "password": "newpassword123"
}
```

---

### 3.5 字典管理

#### 创建字典

```
POST /api/sys/dict/create
```

```json
{
  "type": "gender",
  "label": "男",
  "value": "1",
  "sort": 1,
  "desc": "性别-男"
}
```

#### 更新字典

```
POST /api/sys/dict/update
```

```json
{
  "id": "字典ID",
  "label": "女",
  "value": "2"
}
```

#### 删除字典

```
POST /api/sys/dict/delete
```

```json
{ "ids": ["dict-id-1"] }
```

#### 字典列表

```
POST /api/sys/dict/list
```

```json
{
  "current": 1,
  "pageSize": 10,
  "type": "gender"
}
```

**响应**：
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "list": [
      {
        "id": "xxx",
        "type": "gender",
        "label": "男",
        "value": "1",
        "sort": 1,
        "desc": "性别-男"
      }
    ],
    "total": 2
  }
}
```

---

### 3.6 操作日志

#### 日志列表

```
POST /api/sys/log/list
```

```json
{
  "current": 1,
  "pageSize": 20,
  "method": "POST",
  "path": "/api/auth/login",
  "status": 200
}
```

**响应**：
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "list": [
      {
        "id": "xxx",
        "clientId": "sundynix-admin",
        "ip": "192.168.1.100",
        "method": "POST",
        "path": "/api/auth/login",
        "status": 200,
        "latency": 52000000,
        "agent": "Mozilla/5.0...",
        "errorMessage": "",
        "body": "{\"account\":\"admin\"...}",
        "resp": "{\"code\":200...}",
        "userId": "user-id-xxx",
        "createdAt": 1714000000
      }
    ],
    "total": 100
  }
}
```

> [!NOTE]
> `latency` 单位是纳秒，前端显示时需转换：`(latency / 1000000).toFixed(0) + 'ms'`

#### 删除日志

```
POST /api/sys/log/delete
```

```json
{ "ids": ["log-id-1", "log-id-2"] }
```

---

## 四、前端接入参考

### 4.1 Axios 封装建议

```ts
import axios from 'axios'

const request = axios.create({
  baseURL: 'http://192.168.100.4:8888', // 网关地址
  timeout: 10000,
})

// 请求拦截器
request.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  config.headers['X-Client-Id'] = 'sundynix-admin'  // 你的客户端标识
  return config
})

// 响应拦截器
request.interceptors.response.use((response) => {
  // Token 自动续期
  const newToken = response.headers['x-refresh-token']
  if (newToken) {
    localStorage.setItem('token', newToken)
  }

  const { code, msg, data } = response.data
  if (code === 401) {
    // 跳转登录
    window.location.href = '/login'
    return Promise.reject(new Error(msg))
  }
  if (code !== 200) {
    return Promise.reject(new Error(msg))
  }
  return data
})

export default request
```

### 4.2 API 分组

```ts
// api/auth.ts
export const authApi = {
  captcha: () => request.get('/api/auth/captcha'),
  login: (data: LoginReq) => request.post('/api/auth/login', data),
  getUserInfo: () => request.get('/api/auth/info'),
  updateUser: (data: UpdateUserReq) => request.post('/api/auth/update', data),
}

// api/system.ts
export const clientApi = {
  list: (data: PageReq) => request.post('/api/sys/client/list', data),
  create: (data: ClientReq) => request.post('/api/sys/client/create', data),
  update: (data: ClientUpdateReq) => request.post('/api/sys/client/update', data),
  delete: (ids: string[]) => request.post('/api/sys/client/delete', { ids }),
}

export const roleApi = {
  list: (data: PageReq) => request.post('/api/sys/role/list', data),
  create: (data: RoleReq) => request.post('/api/sys/role/create', data),
  update: (data: RoleUpdateReq) => request.post('/api/sys/role/update', data),
  delete: (ids: string[]) => request.post('/api/sys/role/delete', { ids }),
}

export const menuApi = {
  list: () => request.get('/api/sys/menu/list'),
  byRole: (id: string) => request.post('/api/sys/menu/byRole', { id }),
  create: (data: MenuReq) => request.post('/api/sys/menu/create', data),
  update: (data: MenuUpdateReq) => request.post('/api/sys/menu/update', data),
  delete: (ids: string[]) => request.post('/api/sys/menu/delete', { ids }),
}

export const userApi = {
  list: (data: UserListReq) => request.post('/api/sys/user/list', data),
  create: (data: UserCreateReq) => request.post('/api/sys/user/create', data),
  update: (data: UserUpdateReq) => request.post('/api/sys/user/update', data),
  delete: (ids: string[]) => request.post('/api/sys/user/delete', { ids }),
  resetPassword: (data: ResetPasswordReq) => request.post('/api/sys/user/resetPassword', data),
}

export const dictApi = {
  list: (data: DictListReq) => request.post('/api/sys/dict/list', data),
  create: (data: DictReq) => request.post('/api/sys/dict/create', data),
  update: (data: DictUpdateReq) => request.post('/api/sys/dict/update', data),
  delete: (ids: string[]) => request.post('/api/sys/dict/delete', { ids }),
}

export const logApi = {
  list: (data: LogListReq) => request.post('/api/sys/log/list', data),
  delete: (ids: string[]) => request.post('/api/sys/log/delete', { ids }),
}
```

### 4.3 TypeScript 类型定义

```ts
// ========== Auth ==========
interface LoginReq {
  account: string
  password: string
  captchaId: string
  captcha: string
}

interface LoginResp {
  token: string
  userInfo: UserInfo
}

interface UserInfo {
  id: string
  name: string
  account: string
  nickName: string
  phone: string
  avatarId: string
  gender: number      // 0=未知 1=男 2=女
  roles?: string[]    // 角色 code 列表（仅 /info 接口返回）
  menus?: MenuItem[]  // 菜单树（仅 /info 接口返回）
  createdAt?: number  // Unix 时间戳（秒）
}

interface MenuItem {
  id: string
  parentId: string
  category: number   // 1=菜单 2=按钮
  name: string       // 路由名（英文）
  title: string      // 显示标题（中文）
  code: string
  path: string
  permission: string
  locale: string
  icon: string
  sort: number
  children: MenuItem[]
}

// ========== System ==========
interface ClientInfo {
  id: string
  clientId: string
  name: string
  grantType: string
  additionalInfo: string
  activeTimeout: number // Token有效期（秒）
}

interface RoleInfo {
  id: string
  name: string
  code: string
  sort: number
  menuIds: string[]
}

interface DictInfo {
  id: string
  type: string
  label: string
  value: string
  sort: number
  desc: string
}

interface OperationRecordInfo {
  id: string
  clientId: string
  ip: string
  method: string
  path: string
  status: number
  latency: number   // 纳秒
  agent: string
  errorMessage: string
  body: string
  resp: string
  userId: string
  createdAt: number // Unix 时间戳（秒）
}

// ========== 通用 ==========
interface PageReq {
  current?: number
  pageSize?: number
}

interface PageResp<T> {
  list: T[]
  total: number
}
```

---

## 五、接口速查表

| 模块 | 方法 | 路径 | 鉴权 | 说明 |
|------|:----:|------|:----:|------|
| **Auth** | GET | `/api/auth/captcha` | ❌ | 获取验证码 |
| | POST | `/api/auth/login` | ❌ | 账号密码登录 |
| | POST | `/api/auth/miniLogin` | ❌ | 小程序登录 |
| | POST | `/api/auth/loginByPhone` | ❌ | 手机号登录 |
| | GET | `/api/auth/info` | ✅ | 获取用户信息+角色+菜单 |
| | POST | `/api/auth/update` | ✅ | 更新个人信息 |
| **客户端** | POST | `/api/sys/client/create` | ✅ | 创建客户端 |
| | POST | `/api/sys/client/update` | ✅ | 更新客户端 |
| | POST | `/api/sys/client/delete` | ✅ | 删除客户端 |
| | POST | `/api/sys/client/list` | ✅ | 客户端列表 |
| **角色** | POST | `/api/sys/role/create` | ✅ | 创建角色 |
| | POST | `/api/sys/role/update` | ✅ | 更新角色 |
| | POST | `/api/sys/role/delete` | ✅ | 删除角色 |
| | POST | `/api/sys/role/list` | ✅ | 角色列表 |
| **菜单** | POST | `/api/sys/menu/create` | ✅ | 创建菜单 |
| | POST | `/api/sys/menu/update` | ✅ | 更新菜单 |
| | POST | `/api/sys/menu/delete` | ✅ | 删除菜单 |
| | GET | `/api/sys/menu/list` | ✅ | 菜单列表(树形) |
| | POST | `/api/sys/menu/byRole` | ✅ | 按角色查菜单 |
| **用户** | POST | `/api/sys/user/list` | ✅ | 用户列表 |
| | POST | `/api/sys/user/create` | ✅ | 创建用户 |
| | POST | `/api/sys/user/update` | ✅ | 更新用户 |
| | POST | `/api/sys/user/delete` | ✅ | 删除用户 |
| | POST | `/api/sys/user/resetPassword` | ✅ | 重置密码 |
| **字典** | POST | `/api/sys/dict/create` | ✅ | 创建字典 |
| | POST | `/api/sys/dict/update` | ✅ | 更新字典 |
| | POST | `/api/sys/dict/delete` | ✅ | 删除字典 |
| | POST | `/api/sys/dict/list` | ✅ | 字典列表 |
| **日志** | POST | `/api/sys/log/list` | ✅ | 操作日志列表 |
| | POST | `/api/sys/log/delete` | ✅ | 删除日志 |
