# Mokee Gateway 系统接入完整指南
> 版本:2026-06-13 | 适用于所有需要通过 Mokee Gateway 统一登录的业务系统
---
## 目录
1. [架构概览](#一架构概览)
2. [接入流程](#二接入流程)
3. [前端接入(完整代码)](#三前端接入完整代码)
4. [后端接入(完整代码)](#四后端接入完整代码)
5. [API 接口参考(完整请求/响应结构)](#五api-接口参考)
6. [数据库初始化](#六数据库初始化)
7. [创建管理员账号](#七创建网关管理员账号)
8. [创建管理员账号](#七创建网关管理员账号)
9. [UserChip Widget 一键接入](#八usership-widget-一键接入)
10. [对接测试清单](#九对接测试清单)
---
## 一、架构概览
```
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 业务前端 │────▶│ Mokee Gateway │────▶│ 业务后端 │
│ (Vue/React) │ │ gw.server.zgitm.com │ │ (任意语言) │
│ │ │ │ │ │
│ 携带 Token │ │ 验证 Token │ │ 从请求头读取: │
│ + X-System-Code │ 识别系统编码 │ │ X-User-Id │
│ │ │ 转发请求 │ │ X-User-Name │
│ │ │ 注入用户信息头 │ │ X-System-Code │
└──────────────┘ └──────────────────┘ └──────────────────┘
```
### 流程
1. 用户访问业务前端 → 无 Token → 跳转统一登录页
2. 登录成功 → 获得 JWT Token → 回跳到业务前端
3. 业务前端所有 API 请求 → 网关 (携带 Authorization + X-System-Code)
4. 网关验证 Token → 注入用户信息请求头 → 转发到业务后端
5. 业务后端从请求头读取用户信息 → 执行业务逻辑
### 三条关键规则
**规则一:前端只调网关** — 业务前端 baseURL 配置为网关地址,不能直接调业务后端。
**规则二:后端必须开跨域 (CORS)** — 业务后端需要允许来自网关域名的跨域请求。
**规则三:业务后端不再做登录/鉴权** — Token 验证由网关完成,业务后端只需从请求头读取用户信息。
---
## 二、接入流程
1. 在网关管理后台注册业务系统 → 获得 `systemCode` 和 `docToken`
2. 在网关数据库执行初始化 SQL → 创建菜单和角色
3. 前端接入:配置 Axios 实例 + 路由守卫
4. 后端接入:配置 CORS + UserContext 中间件
5. 创建管理员账号 → 登录管理后台维护本系统数据
6. 对接测试 → 完成验收
---
## 三、前端接入(完整代码)
### 3.1 安装依赖
```bash
npm install axios
```
### 3.2 环境变量
```env
VITE_GATEWAY_URL = https://gw.server.zgitm.com
VITE_LOGIN_URL = https://web.gw.zgitm.com/login
VITE_SYSTEM_CODE = YOUR_SYSTEM_CODE
```
| 变量 | 说明 | 从哪里获取 |
|------|------|-----------|
| `VITE_GATEWAY_URL` | 网关 API 地址 | 固定值,见附录地址表 |
| `VITE_LOGIN_URL` | 统一登录页地址 | 固定值,见附录地址表 |
| `VITE_SYSTEM_CODE` | 你的业务系统编码 | 在网关管理后台注册系统后获得(联系平台管理员) |
### 3.3 Axios 实例 (src/utils/request.ts)
```typescript
import axios from 'axios'
import { ElMessage } from 'element-plus'
const SYSTEM_CODE = import.meta.env.VITE_SYSTEM_CODE
const request = axios.create({
baseURL: import.meta.env.VITE_GATEWAY_URL,
timeout: 30000,
})
// 从 Cookie 读取 Token(登录后网关写入的跨域 Cookie)
function getToken(): string | null {
const match = document.cookie.match(/(?:^|;\s*)token=([^;]*)/)
return match ? decodeURIComponent(match[1]) : null
}
// 请求拦截器:携带 Token 和系统编码
request.interceptors.request.use(config => {
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
// 关键:网关根据此头识别业务系统
config.headers['X-System-Code'] = SYSTEM_CODE
return config
})
// 响应拦截器:401 自动跳转登录
request.interceptors.response.use(
res => res.data.code === 200 ? res.data : Promise.reject(res.data),
err => {
if (err.response?.status === 401) {
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?systemCode=${SYSTEM_CODE}`
}
return Promise.reject(err)
}
)
export default request
```
### 3.4 路由守卫 (src/router/index.ts)
```typescript
import { createRouter, createWebHistory } from 'vue-router'
const SYSTEM_CODE = import.meta.env.VITE_SYSTEM_CODE
const LOGIN_URL = import.meta.env.VITE_LOGIN_URL
const router = createRouter({
history: createWebHistory(),
routes: [ /* 你的路由 */ ],
})
router.beforeEach((to, _from, next) => {
const token = document.cookie.match(/(?:^|;\s*)token=([^;]*)/)?.[1]
if (!token && to.path !== '/login') {
// 无 Token → 跳统一登录页
window.location.href = `${LOGIN_URL}?systemCode=${SYSTEM_CODE}`
return
}
next()
})
export default router
```
### 3.5 登录成功回调处理
用户在统一登录页登录成功后,网关将 Token 写入 **Cookie**(domain: `.zgitm.com`,跨子域共享),然后跳回系统注册时填写的 `frontUrl`(即你的业务前端首页地址)。业务前端无需自行实现登录接口,只需从 Cookie 中读取 Token 即可。
```typescript
// 从 Cookie 读取 Token
function getToken(): string | null {
const match = document.cookie.match(/(?:^|;\s*)token=([^;]*)/)
return match ? decodeURIComponent(match[1]) : null
}
// App.vue onMounted 或 main.ts
const token = getToken()
if (token) {
// 用户已登录,正常渲染应用
// 如需获取用户信息,调用网关 API:
const res = await request.get('/zgapi/v1/auth/user/info')
console.log('当前用户:', res.data)
} else {
// 无 Token → 跳统一登录页
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?systemCode=${SYSTEM_CODE}`
}
```
#### 多系统场景说明
一个用户可能绑定了多个业务系统。跳转登录页时在 URL 中带上 `systemCode` 参数,登录页会自动选择对应系统:
```
https://web.gw.zgitm.com/login?systemCode=YOUR_SYSTEM_CODE
```
这就是为什么路由守卫和 Axios 拦截器中跳转登录时都要拼 `?systemCode=${SYSTEM_CODE}`——网关登录页根据这个参数知道用户要从哪个系统登录,登录成功后直接回跳到该系统的 `frontUrl`。
> **💡 Token 存储说明**:Token 存在 Cookie 中(而非 localStorage),因为 Cookie 支持跨子域共享(`.zgitm.com`),统一登录、管理后台、文件服务等不同子站都能读取到同一个 Token。Cookie 有效期 2 小时,与 Token 过期时间一致。
### 3.6 按钮权限控制(v-permission 指令)
用户信息接口(`/zgapi/v1/auth/user/info`)会返回 `permissions` 字段(按钮权限标识列表),前端据此控制按钮的显示/隐藏。你需要注册一个 `v-permission` 自定义指令:
**src/directives/permission.ts:**
```typescript
import type { Directive } from 'vue'
// 从 Cookie 读取 Token,解析出 payload 中的 permissions
// 或从 Vuex/Pinia store 中读取(取决于你的状态管理方案)
function getPermissions(): string[] {
const token = document.cookie.match(/(?:^|;\s*)token=([^;]*)/)?.[1]
if (!token) return []
try {
// JWT payload 在中间那段(Base64)
const payload = JSON.parse(atob(token.split('.')[1]))
return payload.permissions ?? []
} catch {
return []
}
}
export const permission: Directive = {
mounted(el, binding) {
const permissions = getPermissions()
const required = binding.value as string
// 如果用户没有该权限,隐藏按钮
if (!permissions.includes(required)) {
el.parentNode?.removeChild(el)
}
},
}
```
**src/main.ts 中全局注册:**
```typescript
import { createApp } from 'vue'
import App from './App.vue'
import { permission } from './directives/permission'
const app = createApp(App)
app.directive('permission', permission)
app.mount('#app')
```
**业务代码中使用:**
```html
新增用户
编辑
删除
```
> **原理**:后端返回的 `permissions` 就是你在 `sys_menu` 表里配置的 `permission` 字段值(menu_type=3 的按钮记录)。它们通过角色绑定到用户,登录时返回。前端 `v-permission` 指令检查当前用户是否有该权限,没有就从 DOM 中移除元素。
---
## 四、后端接入(完整代码)
### 4.1 网关转发时注入的请求头
| 请求头名称 | 数据类型 | 说明 |
|-----------|---------|------|
| `X-User-Id` | String | 当前登录用户的唯一ID |
| `X-User-Name` | String | 用户真实姓名 |
| `X-User-Account` | String | 用户登录账号 |
| `X-User-Email` | String | 用户邮箱 |
| `X-User-Post` | String | 用户岗位 |
| `X-User-Roles` | JSON | 用户角色列表 `[{roleId, roleName, roleCode}]` |
| `X-System-Id` | String | 当前业务系统的数据库ID |
| `X-System-Code` | String | 当前业务系统的唯一编码 |
| `X-System-Name` | String | 当前业务系统的显示名称 |
> **重要**:这些请求头由网关自动注入,业务后端可以信任这些值。不需要再验证 Token 或查询用户信息。
### 4.2 Java — UserContext 工具类
```java
// ====== UserContext.java ======
import java.util.*;
public class UserContext {
private static final ThreadLocal