830 lines
24 KiB
Markdown
830 lines
24 KiB
Markdown
# 用户认证登录API文档
|
||
|
||
## 目录
|
||
1. [用户登录](#用户登录)
|
||
2. [获取用户信息](#获取用户信息)
|
||
3. [获取具有指定权限的用户列表](#获取具有指定权限的用户列表)
|
||
|
||
## 📋 文档说明
|
||
|
||
本文档记录用户认证、登录、用户信息管理等相关 API 接口。
|
||
|
||
**依赖文档**: 请先阅读 [API 文档基础规范.md](./API文档基础规范.md) 了解通用规范。
|
||
|
||
**统一身份认证**: 前端「统一认证登录」按钮的完整业务流程、CAS OAuth2.0 接入说明及排查指南,请参阅 [统一身份认证接入说明.md](../统一身份认证接入说明.md)。
|
||
|
||
---
|
||
|
||
## 🔐 用户认证
|
||
|
||
### 用户登录
|
||
|
||
**接口地址**: `POST /auth/login`
|
||
|
||
**接口描述**: 用户登录认证接口,支持密码登录。请求数据使用 AES+RSA 混合加密传输,确保安全性。
|
||
|
||
**权限要求**: 无需认证(公开接口)
|
||
|
||
**安全机制**:
|
||
|
||
- **数据加密**: 请求参数使用 AES 对称加密
|
||
- **密钥保护**: AES 密钥使用 RSA 非对称加密
|
||
- **自动解密**: 后端自动解密请求数据和响应数据
|
||
|
||
**请求头配置**:
|
||
|
||
```json
|
||
{
|
||
"Content-Type": "application/json;charset=utf-8",
|
||
"clientid": "e5cd7e4891bf95d1d19206ce24a7b32e",
|
||
"encrypt-key": "[RSA加密的AES密钥]",
|
||
"referer": "http://localhost/login?redirect=%252Findex",
|
||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||
"isToken": "false",
|
||
"isEncrypt": "true",
|
||
"repeatSubmit": "false"
|
||
}
|
||
```
|
||
|
||
**关键请求头说明**:
|
||
|
||
| 请求头名 | 必填 | 说明 | 示例值 |
|
||
| ----------- | ---- | --------------------------------- | -------------------------------------------- |
|
||
| clientid | 是 | 客户端 ID,用于身份验证 | "e5cd7e4891bf95d1d19206ce24a7b32e" |
|
||
| encrypt-key | 是 | RSA 加密的 AES 密钥,用于数据解密 | "Q8Td0si9R3EYj3pFe50rzn/8wxo69Y9uR6LE..." |
|
||
| referer | 是 | 来源页面地址,用于请求来源验证 | "http://localhost/login?redirect=%252Findex" |
|
||
|
||
**加密流程说明**:
|
||
|
||
1. 前端生成随机 AES 密钥(32 位字符串)
|
||
2. 使用 AES-ECB-PKCS7 模式加密请求数据
|
||
3. 使用 RSA 公钥加密 AES 密钥
|
||
4. 将加密后的 AES 密钥放入请求头`encrypt-key`
|
||
5. 后端使用 RSA 私钥解密 AES 密钥,再解密请求数据
|
||
|
||
**请求参数**:
|
||
|
||
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
||
| --------- | ------ | ---- | ------------------------------------ | ---------------------------------- |
|
||
| tenantId | string | 是 | 租户 ID,多租户系统标识 | "000000" |
|
||
| username | string | 是 | 用户名,长度 2-30 字符 | "admin" |
|
||
| password | string | 是 | 用户密码,长度 5-30 字符 | "admin123" |
|
||
| clientId | string | 是 | 客户端 ID,用于权限控制 | "e5cd7e4891bf95d1d19206ce24a7b32e" |
|
||
| grantType | string | 是 | 授权类型,目前支持 password | "password" |
|
||
| code | string | 否 | 验证码(当系统开启验证码时必填) | "1234" |
|
||
| uuid | string | 否 | 验证码唯一标识(当需要验证码时必填) | "uuid-string" |
|
||
|
||
**请求示例**:
|
||
|
||
```json
|
||
{
|
||
"tenantId": "000000",
|
||
"username": "admin",
|
||
"password": "admin123",
|
||
"clientId": "e5cd7e4891bf95d1d19206ce24a7b32e",
|
||
"grantType": "password"
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "操作成功",
|
||
"data": {
|
||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxIiwicm5TdHIiOiJ5MlhvbHRnd1Y2YjUzRnRMVzF4SFR1anRSUERjeGdmYyIsImNsaWVudGlkIjoiZTVjZDdlNDg5MWJmOTVkMWQxOTIwNmNlMjRhN2IzMmUiLCJ0ZW5hbnRJZCI6IjAwMDAwMCIsInVzZXJJZCI6MSwidXNlck5hbWUiOiJhZG1pbiIsImRlcHRJZCI6MTAzLCJkZXB0TmFtZSI6IuaZuuiDveaOp-WItuWtpumZoiIsImRlcHRDYXRlZ29yeSI6IiIsIm5pY2tOYW1lIjoi55av54uC55qE54uu5a2QTGkifQ.WI6iY3AgwPz7ceI8WDsVy-GaTXZnU-JKUYUTjhQdoKM",
|
||
"refresh_token": null,
|
||
"expire_in": 604800,
|
||
"refresh_expire_in": null,
|
||
"client_id": "e5cd7e4891bf95d1d19206ce24a7b32e",
|
||
"scope": null,
|
||
"openid": null
|
||
}
|
||
}
|
||
```
|
||
|
||
**实际 Token 解析信息**:
|
||
|
||
| 字段名 | 值 | 说明 |
|
||
| --------- | ----------------- | --------- |
|
||
| userId | 1 | 用户 ID |
|
||
| userName | admin | 用户名 |
|
||
| nickName | 疯狂狮子 Li | 用户昵称 |
|
||
| deptId | 103 | 部门 ID |
|
||
| deptName | 智胜技术开发团队 | 部门名称 |
|
||
| tenantId | 000000 | 租户 ID |
|
||
| clientId | e5cd7e4891bf95... | 客户端 ID |
|
||
| loginType | login | 登录类型 |
|
||
|
||
**Token 有效期**: 7 天 (604800 秒)
|
||
|
||
**响应参数说明**:
|
||
|
||
| 参数名 | 类型 | 说明 |
|
||
| ----------------- | ------ | ------------------------------ |
|
||
| access_token | string | 访问令牌,用于 API 调用认证 |
|
||
| refresh_token | string | 刷新令牌,用于获取新的访问令牌 |
|
||
| expire_in | long | 访问令牌过期时间(秒) |
|
||
| refresh_expire_in | long | 刷新令牌过期时间(秒) |
|
||
| client_id | string | 客户端 ID |
|
||
| scope | string | 权限范围 |
|
||
| openid | string | 第三方登录标识(可为空) |
|
||
|
||
**多种登录方式支持**:
|
||
|
||
系统采用策略模式,支持多种登录方式:
|
||
|
||
1. **密码登录** (`grantType: "password"`)
|
||
|
||
- 使用用户名和密码登录
|
||
- 支持验证码验证(可配置开关)
|
||
- BCrypt 密码加密存储
|
||
|
||
2. **邮箱登录** (`grantType: "email"`)
|
||
|
||
- 使用邮箱和邮箱验证码登录
|
||
- 需要先发送邮箱验证码
|
||
|
||
3. **短信登录** (`grantType: "sms"`)
|
||
|
||
- 使用手机号和短信验证码登录
|
||
- 需要先发送短信验证码
|
||
|
||
4. **小程序登录** (`grantType: "xcx"`)
|
||
- 微信小程序授权登录
|
||
- 需要小程序码和应用 ID
|
||
|
||
**错误码说明**:
|
||
|
||
| 错误码 | 错误信息 | 说明 | 解决方案 |
|
||
| ------ | ---------------------------- | ----------------------- | ---------------------- |
|
||
| 400 | 参数校验失败 | 请求参数格式错误 | 检查参数格式和必填字段 |
|
||
| 400 | 客户端 id 异常 | clientId 不存在或被禁用 | 检查 clientId 配置 |
|
||
| 400 | 授权类型不正确 | grantType 不支持 | 使用支持的授权类型 |
|
||
| 400 | 当前系统没有开启注册功能 | 租户未开启注册 | 联系管理员开启注册功能 |
|
||
| 401 | 用户不存在 | 用户名不存在 | 检查用户名是否正确 |
|
||
| 401 | 用户已被停用 | 用户账号被禁用 | 联系管理员激活账号 |
|
||
| 401 | 密码错误 | 用户密码不正确 | 检查密码是否正确 |
|
||
| 401 | 验证码已过期 | 验证码超时 | 重新获取验证码 |
|
||
| 401 | 验证码错误 | 验证码输入错误 | 检查验证码是否正确 |
|
||
| 403 | 登录失败次数过多,账户已锁定 | 密码错误次数超限 | 等待解锁或联系管理员 |
|
||
| 500 | 租户不存在 | tenantId 无效 | 检查租户配置 |
|
||
|
||
**安全特性**:
|
||
|
||
1. **密码安全**:
|
||
|
||
- BCrypt 哈希加密存储
|
||
- 登录失败次数限制
|
||
- 账户自动锁定机制
|
||
|
||
2. **传输安全**:
|
||
|
||
- AES+RSA 混合加密
|
||
- 防重放攻击保护
|
||
- HTTPS 协议传输
|
||
|
||
3. **会话安全**:
|
||
|
||
- JWT Token 认证
|
||
- Token 自动过期机制
|
||
- 刷新 Token 机制
|
||
|
||
4. **多租户安全**:
|
||
- 租户隔离验证
|
||
- 客户端权限控制
|
||
- 不同设备类型支持
|
||
|
||
**实际使用示例**:
|
||
|
||
```javascript
|
||
// 完整的登录实现示例(AES+RSA加密)
|
||
const axios = require('axios');
|
||
const CryptoJS = require('crypto-js');
|
||
const NodeRSA = require('node-rsa');
|
||
|
||
async function loginWithEncryption(username = 'admin', password = 'admin123') {
|
||
// 配置信息
|
||
const CONFIG = {
|
||
url: 'http://localhost/dev-api/auth/login',
|
||
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||
tenantId: '000000',
|
||
referer: 'http://localhost/login?redirect=%252Findex',
|
||
rsaKey: 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
|
||
};
|
||
|
||
// 生成32位随机AES密钥
|
||
function generateAESKey() {
|
||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||
let result = '';
|
||
for (let i = 0; i < 32; i++) {
|
||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// AES加密
|
||
function aesEncrypt(data, key) {
|
||
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
|
||
return CryptoJS.AES.encrypt(data, keyUtf8, {
|
||
mode: CryptoJS.mode.ECB,
|
||
padding: CryptoJS.pad.Pkcs7,
|
||
}).toString();
|
||
}
|
||
|
||
// RSA加密AES密钥
|
||
function rsaEncrypt(aesKey) {
|
||
const pemKey = `-----BEGIN PUBLIC KEY-----\n${CONFIG.rsaKey}\n-----END PUBLIC KEY-----`;
|
||
const rsaKey = new NodeRSA(pemKey);
|
||
rsaKey.setOptions({ encryptionScheme: 'pkcs1' });
|
||
const aesKeyBase64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(aesKey));
|
||
return rsaKey.encrypt(aesKeyBase64, 'base64');
|
||
}
|
||
|
||
// 生成AES密钥并加密登录数据
|
||
const aesKey = generateAESKey();
|
||
const loginData = {
|
||
tenantId: CONFIG.tenantId,
|
||
username: username,
|
||
password: password,
|
||
clientId: CONFIG.clientId,
|
||
grantType: 'password',
|
||
};
|
||
|
||
const encryptedData = aesEncrypt(JSON.stringify(loginData), aesKey);
|
||
const encryptedKey = rsaEncrypt(aesKey);
|
||
|
||
// 设置请求头
|
||
const headers = {
|
||
'Content-Type': 'application/json;charset=utf-8',
|
||
clientid: CONFIG.clientId,
|
||
'encrypt-key': encryptedKey,
|
||
referer: CONFIG.referer,
|
||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||
isToken: 'false',
|
||
isEncrypt: 'true',
|
||
repeatSubmit: 'false',
|
||
};
|
||
|
||
// 发送登录请求
|
||
const response = await axios.post(CONFIG.url, encryptedData, {
|
||
headers: headers,
|
||
timeout: 10000,
|
||
});
|
||
|
||
if (response.data.code === 200 && response.data.data?.access_token) {
|
||
return response.data.data.access_token;
|
||
} else {
|
||
throw new Error(response.data.msg || 'Login failed');
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
loginWithEncryption()
|
||
.then((token) => {
|
||
console.log('Login successful, token:', token);
|
||
})
|
||
.catch((error) => {
|
||
console.error('Login failed:', error.message);
|
||
});
|
||
```
|
||
|
||
**快速测试脚本**:
|
||
|
||
项目中提供了完整的测试脚本,位于`test/`目录:
|
||
|
||
- `test/custom-login-script.js` - 完整功能版
|
||
- `test/quick-token-getter.js` - 快速获取版
|
||
- `test/登录脚本使用说明.md` - 详细使用文档
|
||
|
||
### 退出登录
|
||
|
||
**接口地址**: `POST /auth/logout`
|
||
|
||
**接口描述**: 用户退出登录,清除服务端会话信息
|
||
|
||
**权限要求**: 需要登录
|
||
|
||
**请求参数**: 无需请求体
|
||
|
||
**请求头**:
|
||
|
||
```json
|
||
{
|
||
"Authorization": "Bearer {access_token}"
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "退出成功",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
**功能说明**:
|
||
|
||
- 清除服务端 Token 会话
|
||
- 关闭 SSE 连接(如果开启)
|
||
- 清理用户登录状态
|
||
|
||
### 获取验证码
|
||
|
||
**接口地址**: `GET /auth/code`
|
||
|
||
**接口描述**: 获取图形验证码,用于登录时的安全验证
|
||
|
||
**权限要求**: 无需认证
|
||
|
||
**请求参数**: 无
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "操作成功",
|
||
"data": {
|
||
"uuid": "d7c2c4c4-4e9d-4a5f-9c1a-2b3c4d5e6f7g",
|
||
"img": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABH..."
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应参数说明**:
|
||
|
||
| 参数名 | 类型 | 说明 |
|
||
| ------ | ------ | ----------------------- |
|
||
| uuid | string | 验证码唯一标识 |
|
||
| img | string | Base64 编码的验证码图片 |
|
||
|
||
**使用流程**:
|
||
|
||
1. 前端调用此接口获取验证码图片和 UUID
|
||
2. 用户输入验证码
|
||
3. 登录时将验证码和 UUID 一起提交
|
||
4. 服务端验证验证码的正确性和有效性
|
||
|
||
---
|
||
|
||
## 👤 用户信息
|
||
|
||
### 获取当前用户信息
|
||
|
||
**接口地址**: `GET /system/user/getInfo`
|
||
|
||
**接口描述**: 获取当前登录用户的详细信息,包括角色权限等
|
||
|
||
**权限要求**: 需要登录
|
||
|
||
**请求参数**: 无
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "操作成功",
|
||
"data": {
|
||
"user": {
|
||
"userId": 1,
|
||
"userName": "admin",
|
||
"nickName": "疯狂狮子Li",
|
||
"email": "admin@example.com",
|
||
"phonenumber": "13800138000",
|
||
"sex": "1",
|
||
"avatar": "1938120323613728770",
|
||
"avatarUrl": "http://example.com/ruoyi/2025/08/31/xxxx.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
|
||
"status": "0",
|
||
"loginIp": "127.0.0.1",
|
||
"loginDate": "2025-01-26 10:30:00",
|
||
"tenantId": "000000",
|
||
"deptId": 103,
|
||
"deptName": "智胜技术开发团队"
|
||
},
|
||
"roles": ["admin", "common"],
|
||
"permissions": ["*:*:*"]
|
||
}
|
||
}
|
||
```
|
||
|
||
**字段说明补充**:
|
||
|
||
- `avatar`:始终返回 `sys_user.avatar` 中存储的 OSS ID(字符串),用于鉴权下载接口 `/resource/oss/download/{ossId}`。
|
||
- `avatarUrl`:系统通过 `TransConstant.OSS_ID_TO_URL` 规则实时翻译得到的可直接访问地址(MinIO 预签名 URL),供无需链路改造的场景快速显示;有效期受 OSS 策略控制。
|
||
|
||
前端如需在带 Token 的受保护组件(如 `ImagePreview`)中展示,请优先使用 `avatar` 并通过统一的认证下载逻辑获取图片,避免直接暴露临时 URL。
|
||
|
||
**超级管理员权限说明**:
|
||
|
||
admin 用户具有系统最高权限`*:*:*`,可以访问所有功能模块和执行所有操作。
|
||
|
||
---
|
||
|
||
## 🏢 租户管理
|
||
|
||
### 获取租户列表
|
||
|
||
**接口地址**: `GET /auth/tenant/list`
|
||
|
||
**接口描述**: 获取系统可用的租户列表,用于登录时选择租户
|
||
|
||
**权限要求**: 无需认证
|
||
|
||
**请求参数**: 无
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "操作成功",
|
||
"data": {
|
||
"tenantEnabled": true,
|
||
"voList": [
|
||
{
|
||
"tenantId": "000000",
|
||
"companyName": "杭州第一技师学院",
|
||
"domain": "hzdyjs.edu.cn",
|
||
"status": "0"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应参数说明**:
|
||
|
||
| 参数名 | 类型 | 说明 |
|
||
| ------------- | ------- | ------------------ |
|
||
| tenantEnabled | boolean | 是否启用多租户功能 |
|
||
| voList | array | 租户列表 |
|
||
|
||
**voList 中每项参数**:
|
||
|
||
| 参数名 | 类型 | 说明 |
|
||
| ----------- | ------ | ------------------- |
|
||
| tenantId | string | 租户 ID |
|
||
| companyName | string | 租户名称 |
|
||
| domain | string | 租户域名 |
|
||
| status | string | 状态(0 正常 1 停用) |
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 📱 移动端登录实现
|
||
|
||
### 移动端特殊配置
|
||
|
||
移动端使用uni-app框架,登录实现与web端保持API兼容,但在实现细节上有所差异:
|
||
|
||
**网络请求配置**:
|
||
```javascript
|
||
// mobile/src/utils/request.js
|
||
const baseURL = 'http://localhost:8080'
|
||
|
||
const request = (options) => {
|
||
return new Promise((resolve, reject) => {
|
||
const token = getToken()
|
||
|
||
uni.request({
|
||
url: baseURL + options.url,
|
||
method: options.method || 'GET',
|
||
data: options.data || {},
|
||
header: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': token,
|
||
'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||
'referer': baseURL,
|
||
...options.header,
|
||
},
|
||
// 响应处理逻辑...
|
||
})
|
||
})
|
||
}
|
||
```
|
||
|
||
**登录API调用**:
|
||
```javascript
|
||
// 移动端登录API
|
||
export const api = {
|
||
login: (data) =>
|
||
request({
|
||
url: '/auth/login',
|
||
method: 'POST',
|
||
data: {
|
||
tenantId: data.tenantId || '000000',
|
||
username: data.username,
|
||
password: data.password,
|
||
clientId: data.clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||
grantType: data.grantType || 'password'
|
||
},
|
||
header: {
|
||
isToken: false,
|
||
isEncrypt: true,
|
||
repeatSubmit: false
|
||
}
|
||
}),
|
||
|
||
getUserInfo: () =>
|
||
request({
|
||
url: '/system/user/getInfo',
|
||
method: 'GET'
|
||
}),
|
||
|
||
// 其他API...
|
||
}
|
||
```
|
||
|
||
### 移动端用户信息缓存
|
||
|
||
**用户信息存储工具**:
|
||
```javascript
|
||
// mobile/src/utils/request.js
|
||
export const userStorage = {
|
||
// 保存登录信息
|
||
saveLoginInfo: (loginResult, userInfo) => {
|
||
try {
|
||
uni.setStorageSync('access_token', loginResult.access_token)
|
||
if (userInfo) {
|
||
uni.setStorageSync('userInfo', JSON.stringify(userInfo))
|
||
}
|
||
uni.setStorageSync('isLoggedIn', true)
|
||
} catch (e) {
|
||
console.error('保存登录信息失败:', e)
|
||
}
|
||
},
|
||
|
||
// 获取用户信息
|
||
getUserInfo: () => {
|
||
try {
|
||
const userInfo = uni.getStorageSync('userInfo')
|
||
return userInfo ? JSON.parse(userInfo) : null
|
||
} catch (e) {
|
||
return null
|
||
}
|
||
},
|
||
|
||
// 检查登录状态
|
||
isLoggedIn: () => {
|
||
try {
|
||
const isLoggedIn = uni.getStorageSync('isLoggedIn')
|
||
const token = uni.getStorageSync('access_token')
|
||
return !!(isLoggedIn && token)
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
},
|
||
|
||
// 清除登录信息
|
||
clearLoginInfo: () => {
|
||
try {
|
||
uni.removeStorageSync('access_token')
|
||
uni.removeStorageSync('userInfo')
|
||
uni.removeStorageSync('isLoggedIn')
|
||
} catch (e) {
|
||
console.error('清除登录信息失败:', e)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 移动端登录流程
|
||
|
||
1. **应用启动** → 检查登录状态 → 未登录自动跳转登录页
|
||
2. **用户登录** → 调用`/auth/login`API → 获取access_token
|
||
3. **获取用户信息** → 调用`/system/user/getInfo`API → 获取用户详情
|
||
4. **缓存信息** → 使用`userStorage.saveLoginInfo()`缓存登录信息
|
||
5. **页面访问** → 所有页面自动检查登录状态
|
||
6. **退出登录** → 清除本地缓存 → 跳转登录页
|
||
|
||
### 移动端页面入口配置
|
||
|
||
**pages.json配置**:
|
||
```json
|
||
{
|
||
"pages": [
|
||
{
|
||
"path": "pages/login/index",
|
||
"style": {
|
||
"navigationBarTitleText": "登录",
|
||
"navigationStyle": "custom",
|
||
"navigationBarBackgroundColor": "transparent",
|
||
"backgroundColor": "transparent"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 移动端登录状态验证
|
||
|
||
**全局登录验证**:
|
||
```javascript
|
||
// 在每个需要登录的页面中
|
||
onLoad() {
|
||
this.checkLoginStatus();
|
||
},
|
||
|
||
methods: {
|
||
checkLoginStatus() {
|
||
if (!userStorage.isLoggedIn()) {
|
||
uni.reLaunch({
|
||
url: '/pages/login/index'
|
||
});
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 移动端与web端差异
|
||
|
||
| 特性 | Web端 | 移动端 |
|
||
|------|-------|--------|
|
||
| 框架 | Vue 3 + TypeScript | Vue 2 + uni-app |
|
||
| 存储 | localStorage | uni.storage |
|
||
| 网络请求 | axios | uni.request |
|
||
| 路由跳转 | router.push | uni.navigateTo/reLaunch |
|
||
| UI组件 | Element Plus | uview-ui |
|
||
| 加密方式 | AES+RSA | 支持,但简化实现 |
|
||
|
||
### 移动端登录页面特性
|
||
|
||
- ✅ **现代化UI**: 渐变背景 + 毛玻璃效果
|
||
- ✅ **租户选择**: 支持多租户选择(与web端一致)
|
||
- ✅ **表单验证**: 完整的输入验证和错误提示
|
||
- ✅ **响应式设计**: 适配不同屏幕尺寸
|
||
- ✅ **无障碍访问**: 支持键盘导航和屏幕阅读器
|
||
- ✅ **安全存储**: 使用uni.storage安全存储用户信息
|
||
|
||
---
|
||
|
||
## 📞 联系方式
|
||
|
||
如有认证相关问题,请联系开发团队或查看项目仓库的 Issues。
|
||
|
||
---
|
||
|
||
**📝 维护**: 用户认证登录 API 文档
|
||
**🔄 更新记录**:
|
||
- 2025-11-28:`/system/user/getInfo` 增加 `avatarUrl` 字段,并明确 `avatar` 返回 OSS ID
|
||
- 2025-01-03:文档初始化
|
||
**📁 文件**: 用户认证登录 API.md
|
||
|
||
## 获取具有指定权限的用户列表
|
||
|
||
### 基本信息
|
||
- **接口URL**: `/system/user/list-by-permission`
|
||
- **请求方式**: GET
|
||
- **接口描述**: 获取具有指定权限的用户列表
|
||
- **权限标识**: `system:user:list`
|
||
|
||
### 请求参数
|
||
#### Query参数
|
||
| 参数名 | 类型 | 是否必须 | 说明 | 示例值 |
|
||
|--------|------|----------|------|--------|
|
||
| permission | string | 是 | 权限标识符 | hq:hallAudit:list |
|
||
|
||
### 响应参数
|
||
```typescript
|
||
{
|
||
code: number; // 状态码
|
||
msg: string; // 提示信息
|
||
rows: {
|
||
userId: string; // 用户ID
|
||
userName: string; // 用户名
|
||
nickName: string; // 昵称
|
||
dept: {
|
||
deptId: string; // 部门ID
|
||
deptName: string; // 部门名称
|
||
};
|
||
avatar: string; // 头像OSS ID
|
||
avatarUrl?: string; // 头像访问URL(可选,预签名)
|
||
}[];
|
||
total: number; // 总记录数
|
||
}
|
||
```
|
||
|
||
### 响应示例
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "操作成功",
|
||
"rows": [
|
||
{
|
||
"userId": "1",
|
||
"userName": "admin",
|
||
"nickName": "管理员",
|
||
"dept": {
|
||
"deptId": "1",
|
||
"deptName": "后勤部"
|
||
},
|
||
"avatar": "1938120323613728770",
|
||
"avatarUrl": "http://example.com/avatar.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&..."
|
||
}
|
||
],
|
||
"total": 1
|
||
}
|
||
```
|
||
|
||
### 错误码
|
||
| 错误码 | 说明 |
|
||
|--------|------|
|
||
| 401 | 未登录或登录超时 |
|
||
| 403 | 没有权限 |
|
||
| 500 | 服务器内部错误 |
|
||
|
||
### 注意事项
|
||
1. 该接口需要用户登录后才能访问
|
||
2. 返回的用户信息已过滤敏感字段
|
||
3. 建议前端对结果进行缓存,避免频繁请求
|
||
4. 返回结果按照用户名(`user_name`)升序排序
|
||
|
||
### 使用示例
|
||
```typescript
|
||
// 前端调用示例
|
||
import { listUsersByPermission } from '@/api/system/user';
|
||
|
||
async function getAuditors() {
|
||
try {
|
||
const res = await listUsersByPermission('hq:hallAudit:list');
|
||
if (res.code === 200) {
|
||
return res.rows;
|
||
}
|
||
} catch (error) {
|
||
console.error('获取审核人员列表失败:', error);
|
||
return [];
|
||
}
|
||
}
|
||
```
|
||
|
||
## 根据部门获取用户列表
|
||
|
||
### 基本信息
|
||
- **接口URL**: `/system/user/list/dept/{deptId}`
|
||
- **请求方式**: GET
|
||
- **接口描述**: 根据部门ID获取该部门下所有启用状态的用户,用于部门人员选择器
|
||
- **权限标识**: `system:user:list`
|
||
|
||
### 路径参数
|
||
| 参数名 | 类型 | 是否必须 | 说明 | 示例值 |
|
||
|--------|------|----------|------|--------|
|
||
| deptId | number | 是 | 部门ID | 1001 |
|
||
|
||
### 响应参数
|
||
```typescript
|
||
{
|
||
code: number;
|
||
msg: string;
|
||
data: {
|
||
userId: number;
|
||
userName: string;
|
||
nickName: string;
|
||
phonenumber: string;
|
||
}[];
|
||
}
|
||
```
|
||
|
||
### 注意事项
|
||
1. 只返回未删除且状态为正常的用户
|
||
2. 返回结果按照用户名(`user_name`)升序排序,便于快速定位
|
||
|
||
## 批量获取用户基础信息
|
||
|
||
### 基本信息
|
||
- **接口URL**: `/system/user/optionselect`
|
||
- **请求方式**: GET
|
||
- **接口描述**: 根据用户ID集合(可选)和部门ID(可选)批量获取基础用户信息
|
||
- **权限标识**: `system:user:query`
|
||
|
||
### Query参数
|
||
| 参数名 | 类型 | 是否必须 | 说明 | 示例值 |
|
||
|--------|------|----------|------|--------|
|
||
| userIds | string | 否 | 逗号分隔的用户ID集合 | 1,2,3 |
|
||
| deptId | number | 否 | 指定部门ID时仅返回该部门用户 | 1001 |
|
||
|
||
### 响应参数
|
||
```typescript
|
||
{
|
||
code: number;
|
||
msg: string;
|
||
data: {
|
||
userId: number;
|
||
userName: string;
|
||
nickName: string;
|
||
phonenumber: string;
|
||
}[];
|
||
}
|
||
```
|
||
|
||
### 注意事项
|
||
1. 用户ID集合为空时返回有权限范围内的全部用户
|
||
2. 返回结果默认按照用户名(`user_name`)升序排序,保持前端下拉展示一致性
|