- 添加认证相关API文档(登录认证、验证码) - 添加权限管理API文档(菜单管理、角色管理) - 添加系统管理API文档(图片管理、文件管理、登录日志) - 添加用户管理API文档 - 完善项目API文档结构,提升开发体验
1017 lines
27 KiB
Markdown
1017 lines
27 KiB
Markdown
# 登录日志API
|
||
|
||
## 概述
|
||
|
||
登录日志模块用于记录和管理系统用户的登录行为,包括登录成功、登录失败、退出登录等操作的详细记录。提供日志查询、统计分析和安全监控功能,是系统安全审计的重要组成部分。
|
||
|
||
## 权限说明
|
||
|
||
登录日志接口需要相应的权限才能访问:
|
||
|
||
| 操作 | 权限码 | 说明 |
|
||
|------|--------|------|
|
||
| 查询登录日志 | `system:loginlog:list` | 查看登录日志权限 |
|
||
| 删除登录日志 | `system:loginlog:remove` | 删除登录日志权限 |
|
||
| 导出登录日志 | `system:loginlog:export` | 导出登录日志权限 |
|
||
|
||
## 接口列表
|
||
|
||
### 1. 分页查询登录日志
|
||
|
||
**接口地址**: `GET /coder/sysLoginLog/listPage`
|
||
|
||
**接口描述**: 分页查询系统登录日志列表
|
||
|
||
**是否需要认证**: 是
|
||
|
||
**权限要求**: `system:loginlog:list`
|
||
|
||
**请求头**:
|
||
```
|
||
Authorization: Bearer your-token-value
|
||
```
|
||
|
||
**请求参数**:
|
||
|
||
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|
||
|--------|------|------|------|------|
|
||
| pageNo | Integer | 否 | 页码 | 1 |
|
||
| pageSize | Integer | 否 | 每页大小 | 10 |
|
||
| loginName | String | 否 | 登录账号 | admin |
|
||
| loginStatus | String | 否 | 登录状态 | 0 |
|
||
| clientType | String | 否 | 客户端类型 | WEB |
|
||
| loginIp | String | 否 | 登录IP | 127.0.0.1 |
|
||
| loginAddress | String | 否 | 登录地址 | 本地登录 |
|
||
| browser | String | 否 | 浏览器类型 | Chrome |
|
||
| os | String | 否 | 操作系统 | Windows 10 |
|
||
| beginTime | String | 否 | 开始时间 | 2024-01-01 |
|
||
| endTime | String | 否 | 结束时间 | 2024-12-31 |
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"status": 200,
|
||
"msg": "SUCCESS",
|
||
"data": {
|
||
"records": [
|
||
{
|
||
"infoId": 1,
|
||
"loginName": "admin",
|
||
"userName": "管理员",
|
||
"userId": 1,
|
||
"loginStatus": "0",
|
||
"clientType": "WEB",
|
||
"deviceName": "Windows PC",
|
||
"loginIp": "127.0.0.1",
|
||
"loginAddress": "本地登录",
|
||
"browser": "Chrome 120.0.0.0",
|
||
"os": "Windows 10",
|
||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
"loginTime": "2024-07-05 10:00:00",
|
||
"logoutTime": "2024-07-05 11:00:00",
|
||
"sessionDuration": 3600,
|
||
"failureReason": "",
|
||
"remark": "登录成功",
|
||
"createBy": "system",
|
||
"createTime": "2024-07-05 10:00:00",
|
||
"updateBy": "system",
|
||
"updateTime": "2024-07-05 11:00:00"
|
||
}
|
||
],
|
||
"total": 1,
|
||
"size": 10,
|
||
"current": 1,
|
||
"pages": 1
|
||
},
|
||
"traceId": "trace-123456"
|
||
}
|
||
```
|
||
|
||
**调用示例**:
|
||
|
||
```bash
|
||
curl -X GET \
|
||
"http://localhost:18099/coder/sysLoginLog/listPage?pageNo=1&pageSize=10&loginName=admin&loginStatus=0" \
|
||
-H "Authorization: Bearer your-token-value"
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 查询所有登录日志
|
||
|
||
**接口地址**: `GET /coder/sysLoginLog/list`
|
||
|
||
**接口描述**: 查询所有系统登录日志(不分页)
|
||
|
||
**是否需要认证**: 是
|
||
|
||
**权限要求**: `system:loginlog:list`
|
||
|
||
**请求参数**: 同分页查询(除pageNo、pageSize外)
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"status": 200,
|
||
"msg": "SUCCESS",
|
||
"data": [
|
||
{
|
||
"infoId": 1,
|
||
"loginName": "admin",
|
||
"userName": "管理员",
|
||
"userId": 1,
|
||
"loginStatus": "0",
|
||
"clientType": "WEB",
|
||
"deviceName": "Windows PC",
|
||
"loginIp": "127.0.0.1",
|
||
"loginAddress": "本地登录",
|
||
"browser": "Chrome 120.0.0.0",
|
||
"os": "Windows 10",
|
||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
"loginTime": "2024-07-05 10:00:00",
|
||
"logoutTime": "2024-07-05 11:00:00",
|
||
"sessionDuration": 3600,
|
||
"failureReason": "",
|
||
"remark": "登录成功",
|
||
"createBy": "system",
|
||
"createTime": "2024-07-05 10:00:00",
|
||
"updateBy": "system",
|
||
"updateTime": "2024-07-05 11:00:00"
|
||
}
|
||
],
|
||
"traceId": "trace-123456"
|
||
}
|
||
```
|
||
|
||
**调用示例**:
|
||
|
||
```bash
|
||
curl -X GET \
|
||
"http://localhost:18099/coder/sysLoginLog/list?loginStatus=1&beginTime=2024-07-01" \
|
||
-H "Authorization: Bearer your-token-value"
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 根据ID查询登录日志
|
||
|
||
**接口地址**: `GET /coder/sysLoginLog/getById/{id}`
|
||
|
||
**接口描述**: 根据日志ID查询登录日志详细信息
|
||
|
||
**是否需要认证**: 是
|
||
|
||
**权限要求**: `system:loginlog:list`
|
||
|
||
**路径参数**:
|
||
|
||
| 参数名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | Long | 是 | 日志ID |
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"status": 200,
|
||
"msg": "SUCCESS",
|
||
"data": {
|
||
"infoId": 1,
|
||
"loginName": "admin",
|
||
"userName": "管理员",
|
||
"userId": 1,
|
||
"loginStatus": "0",
|
||
"clientType": "WEB",
|
||
"deviceName": "Windows PC",
|
||
"loginIp": "127.0.0.1",
|
||
"loginAddress": "本地登录",
|
||
"browser": "Chrome 120.0.0.0",
|
||
"os": "Windows 10",
|
||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
"loginTime": "2024-07-05 10:00:00",
|
||
"logoutTime": "2024-07-05 11:00:00",
|
||
"sessionDuration": 3600,
|
||
"failureReason": "",
|
||
"remark": "登录成功",
|
||
"createBy": "system",
|
||
"createTime": "2024-07-05 10:00:00",
|
||
"updateBy": "system",
|
||
"updateTime": "2024-07-05 11:00:00"
|
||
},
|
||
"traceId": "trace-123456"
|
||
}
|
||
```
|
||
|
||
**调用示例**:
|
||
|
||
```bash
|
||
curl -X GET \
|
||
http://localhost:18099/coder/sysLoginLog/getById/1 \
|
||
-H "Authorization: Bearer your-token-value"
|
||
```
|
||
|
||
---
|
||
|
||
### 4. 新增登录日志
|
||
|
||
**接口地址**: `POST /coder/sysLoginLog/add`
|
||
|
||
**接口描述**: 新增系统登录日志记录
|
||
|
||
**是否需要认证**: 是
|
||
|
||
**权限要求**: `system:loginlog:add`
|
||
|
||
**请求参数**:
|
||
|
||
```json
|
||
{
|
||
"loginName": "admin",
|
||
"userName": "管理员",
|
||
"userId": 1,
|
||
"loginStatus": "0",
|
||
"clientType": "WEB",
|
||
"deviceName": "Windows PC",
|
||
"loginIp": "127.0.0.1",
|
||
"loginAddress": "本地登录",
|
||
"browser": "Chrome 120.0.0.0",
|
||
"os": "Windows 10",
|
||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
"loginTime": "2024-07-05 10:00:00",
|
||
"failureReason": "",
|
||
"remark": "登录成功"
|
||
}
|
||
```
|
||
|
||
**请求参数说明**:
|
||
|
||
| 参数名 | 类型 | 必填 | 说明 | 校验规则 |
|
||
|--------|------|------|------|----------|
|
||
| loginName | String | 是 | 登录账号 | 不能为空 |
|
||
| userName | String | 否 | 用户姓名 | 可为空 |
|
||
| userId | Long | 否 | 用户ID | 有效的用户ID |
|
||
| loginStatus | String | 是 | 登录状态 | 0-成功 1-失败 |
|
||
| clientType | String | 是 | 客户端类型 | WEB, MOBILE, API |
|
||
| deviceName | String | 否 | 设备名称 | 可为空 |
|
||
| loginIp | String | 是 | 登录IP | 有效的IP地址 |
|
||
| loginAddress | String | 否 | 登录地址 | 可为空 |
|
||
| browser | String | 否 | 浏览器信息 | 可为空 |
|
||
| os | String | 否 | 操作系统 | 可为空 |
|
||
| userAgent | String | 否 | 用户代理 | 可为空 |
|
||
| loginTime | String | 是 | 登录时间 | 时间格式 |
|
||
| failureReason | String | 否 | 失败原因 | 登录失败时必填 |
|
||
| remark | String | 否 | 备注信息 | 最长500字符 |
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"status": 200,
|
||
"msg": "SUCCESS",
|
||
"data": "新增成功",
|
||
"traceId": "trace-123456"
|
||
}
|
||
```
|
||
|
||
**调用示例**:
|
||
|
||
```bash
|
||
curl -X POST \
|
||
http://localhost:18099/coder/sysLoginLog/add \
|
||
-H "Content-Type: application/json" \
|
||
-H "Authorization: Bearer your-token-value" \
|
||
-d '{
|
||
"loginName": "admin",
|
||
"userName": "管理员",
|
||
"userId": 1,
|
||
"loginStatus": "0",
|
||
"clientType": "WEB",
|
||
"loginIp": "127.0.0.1",
|
||
"loginTime": "2024-07-05 10:00:00",
|
||
"remark": "登录成功"
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
### 5. 修改登录日志
|
||
|
||
**接口地址**: `POST /coder/sysLoginLog/update`
|
||
|
||
**接口描述**: 修改系统登录日志信息(通常用于更新退出时间)
|
||
|
||
**是否需要认证**: 是
|
||
|
||
**权限要求**: `system:loginlog:edit`
|
||
|
||
**请求参数**:
|
||
|
||
```json
|
||
{
|
||
"infoId": 1,
|
||
"loginName": "admin",
|
||
"userName": "管理员",
|
||
"userId": 1,
|
||
"loginStatus": "0",
|
||
"clientType": "WEB",
|
||
"deviceName": "Windows PC",
|
||
"loginIp": "127.0.0.1",
|
||
"loginAddress": "本地登录",
|
||
"browser": "Chrome 120.0.0.0",
|
||
"os": "Windows 10",
|
||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
"loginTime": "2024-07-05 10:00:00",
|
||
"logoutTime": "2024-07-05 11:00:00",
|
||
"sessionDuration": 3600,
|
||
"failureReason": "",
|
||
"remark": "正常退出"
|
||
}
|
||
```
|
||
|
||
**请求参数说明**:
|
||
|
||
| 参数名 | 类型 | 必填 | 说明 | 校验规则 |
|
||
|--------|------|------|------|----------|
|
||
| infoId | Long | 是 | 日志ID | 必须是有效的日志ID |
|
||
| logoutTime | String | 否 | 退出时间 | 时间格式 |
|
||
| sessionDuration | Integer | 否 | 会话时长(秒) | 大于等于0 |
|
||
| 其他参数 | - | - | 同新增日志 | - |
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"status": 200,
|
||
"msg": "SUCCESS",
|
||
"data": "修改成功",
|
||
"traceId": "trace-123456"
|
||
}
|
||
```
|
||
|
||
**调用示例**:
|
||
|
||
```bash
|
||
curl -X POST \
|
||
http://localhost:18099/coder/sysLoginLog/update \
|
||
-H "Content-Type: application/json" \
|
||
-H "Authorization: Bearer your-token-value" \
|
||
-d '{
|
||
"infoId": 1,
|
||
"logoutTime": "2024-07-05 11:00:00",
|
||
"sessionDuration": 3600,
|
||
"remark": "正常退出"
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
### 6. 删除登录日志
|
||
|
||
**接口地址**: `POST /coder/sysLoginLog/deleteById/{id}`
|
||
|
||
**接口描述**: 根据ID删除登录日志
|
||
|
||
**是否需要认证**: 是
|
||
|
||
**权限要求**: `system:loginlog:remove`
|
||
|
||
**路径参数**:
|
||
|
||
| 参数名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| id | Long | 是 | 日志ID |
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"status": 200,
|
||
"msg": "SUCCESS",
|
||
"data": "删除成功",
|
||
"traceId": "trace-123456"
|
||
}
|
||
```
|
||
|
||
**调用示例**:
|
||
|
||
```bash
|
||
curl -X POST \
|
||
http://localhost:18099/coder/sysLoginLog/deleteById/1 \
|
||
-H "Authorization: Bearer your-token-value"
|
||
```
|
||
|
||
---
|
||
|
||
### 7. 批量删除登录日志
|
||
|
||
**接口地址**: `POST /coder/sysLoginLog/batchDelete`
|
||
|
||
**接口描述**: 批量删除登录日志
|
||
|
||
**是否需要认证**: 是
|
||
|
||
**权限要求**: `system:loginlog:remove`
|
||
|
||
**请求参数**:
|
||
|
||
```json
|
||
{
|
||
"ids": [1, 2, 3]
|
||
}
|
||
```
|
||
|
||
**请求参数说明**:
|
||
|
||
| 参数名 | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| ids | Long[] | 是 | 日志ID数组 |
|
||
|
||
**响应示例**:
|
||
|
||
```json
|
||
{
|
||
"status": 200,
|
||
"msg": "SUCCESS",
|
||
"data": "删除成功",
|
||
"traceId": "trace-123456"
|
||
}
|
||
```
|
||
|
||
**调用示例**:
|
||
|
||
```bash
|
||
curl -X POST \
|
||
http://localhost:18099/coder/sysLoginLog/batchDelete \
|
||
-H "Content-Type: application/json" \
|
||
-H "Authorization: Bearer your-token-value" \
|
||
-d '{
|
||
"ids": [1, 2, 3]
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
## 登录日志字段说明
|
||
|
||
### 基础字段
|
||
|
||
| 字段名 | 类型 | 说明 | 示例 |
|
||
|--------|------|------|------|
|
||
| infoId | Long | 日志ID | 1 |
|
||
| loginName | String | 登录账号 | admin |
|
||
| userName | String | 用户姓名 | 管理员 |
|
||
| userId | Long | 用户ID | 1 |
|
||
|
||
### 状态字段
|
||
|
||
| 字段名 | 类型 | 说明 | 示例 |
|
||
|--------|------|------|------|
|
||
| loginStatus | String | 登录状态 | 0-成功 1-失败 |
|
||
| clientType | String | 客户端类型 | WEB, MOBILE, API |
|
||
| deviceName | String | 设备名称 | Windows PC |
|
||
|
||
### 网络字段
|
||
|
||
| 字段名 | 类型 | 说明 | 示例 |
|
||
|--------|------|------|------|
|
||
| loginIp | String | 登录IP地址 | 127.0.0.1 |
|
||
| loginAddress | String | 登录地理位置 | 本地登录 |
|
||
| userAgent | String | 用户代理字符串 | Mozilla/5.0... |
|
||
|
||
### 环境字段
|
||
|
||
| 字段名 | 类型 | 说明 | 示例 |
|
||
|--------|------|------|------|
|
||
| browser | String | 浏览器信息 | Chrome 120.0.0.0 |
|
||
| os | String | 操作系统 | Windows 10 |
|
||
|
||
### 时间字段
|
||
|
||
| 字段名 | 类型 | 说明 | 示例 |
|
||
|--------|------|------|------|
|
||
| loginTime | String | 登录时间 | 2024-07-05 10:00:00 |
|
||
| logoutTime | String | 退出时间 | 2024-07-05 11:00:00 |
|
||
| sessionDuration | Integer | 会话时长(秒) | 3600 |
|
||
|
||
### 其他字段
|
||
|
||
| 字段名 | 类型 | 说明 | 示例 |
|
||
|--------|------|------|------|
|
||
| failureReason | String | 失败原因 | 密码错误 |
|
||
| remark | String | 备注信息 | 登录成功 |
|
||
|
||
---
|
||
|
||
## 数据字典
|
||
|
||
### 登录状态 (loginStatus)
|
||
|
||
| 值 | 说明 |
|
||
|----|------|
|
||
| 0 | 登录成功 |
|
||
| 1 | 登录失败 |
|
||
|
||
### 客户端类型 (clientType)
|
||
|
||
| 值 | 说明 |
|
||
|----|------|
|
||
| WEB | 网页端 |
|
||
| MOBILE | 移动端 |
|
||
| API | 接口调用 |
|
||
| DESKTOP | 桌面应用 |
|
||
| WECHAT | 微信小程序 |
|
||
|
||
### 常见失败原因
|
||
|
||
| 失败原因 | 说明 |
|
||
|----------|------|
|
||
| 用户不存在 | 登录账号不存在 |
|
||
| 密码错误 | 登录密码不正确 |
|
||
| 验证码错误 | 验证码输入错误 |
|
||
| 账号被禁用 | 用户账号被禁用 |
|
||
| 账号被锁定 | 用户账号被锁定 |
|
||
| IP被限制 | 登录IP被限制 |
|
||
| 设备被限制 | 登录设备被限制 |
|
||
| 会话过期 | 用户会话已过期 |
|
||
|
||
---
|
||
|
||
## 日志记录机制
|
||
|
||
### 1. 自动记录
|
||
|
||
```java
|
||
@Component
|
||
@Slf4j
|
||
public class LoginLogService {
|
||
|
||
/**
|
||
* 记录登录成功日志
|
||
*/
|
||
public void recordLoginSuccess(String loginName, HttpServletRequest request) {
|
||
try {
|
||
SysLoginLog loginLog = new SysLoginLog();
|
||
|
||
// 基础信息
|
||
loginLog.setLoginName(loginName);
|
||
loginLog.setUserId(getCurrentUserId(loginName));
|
||
loginLog.setUserName(getCurrentUserName(loginName));
|
||
loginLog.setLoginStatus("0");
|
||
loginLog.setLoginTime(LocalDateTime.now());
|
||
|
||
// 网络信息
|
||
loginLog.setLoginIp(getClientIP(request));
|
||
loginLog.setLoginAddress(getAddressByIP(loginLog.getLoginIp()));
|
||
|
||
// 客户端信息
|
||
loginLog.setClientType(getClientType(request));
|
||
loginLog.setUserAgent(request.getHeader("User-Agent"));
|
||
loginLog.setBrowser(getBrowserInfo(request));
|
||
loginLog.setOs(getOSInfo(request));
|
||
loginLog.setDeviceName(getDeviceName(request));
|
||
|
||
// 其他信息
|
||
loginLog.setRemark("登录成功");
|
||
loginLog.setCreateBy("system");
|
||
loginLog.setCreateTime(LocalDateTime.now());
|
||
|
||
// 保存日志
|
||
sysLoginLogMapper.insert(loginLog);
|
||
|
||
} catch (Exception e) {
|
||
log.error("记录登录成功日志失败", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 记录登录失败日志
|
||
*/
|
||
public void recordLoginFailure(String loginName, String failureReason, HttpServletRequest request) {
|
||
try {
|
||
SysLoginLog loginLog = new SysLoginLog();
|
||
|
||
// 基础信息
|
||
loginLog.setLoginName(loginName);
|
||
loginLog.setLoginStatus("1");
|
||
loginLog.setLoginTime(LocalDateTime.now());
|
||
loginLog.setFailureReason(failureReason);
|
||
|
||
// 网络信息
|
||
loginLog.setLoginIp(getClientIP(request));
|
||
loginLog.setLoginAddress(getAddressByIP(loginLog.getLoginIp()));
|
||
|
||
// 客户端信息
|
||
loginLog.setClientType(getClientType(request));
|
||
loginLog.setUserAgent(request.getHeader("User-Agent"));
|
||
loginLog.setBrowser(getBrowserInfo(request));
|
||
loginLog.setOs(getOSInfo(request));
|
||
loginLog.setDeviceName(getDeviceName(request));
|
||
|
||
// 其他信息
|
||
loginLog.setRemark("登录失败:" + failureReason);
|
||
loginLog.setCreateBy("system");
|
||
loginLog.setCreateTime(LocalDateTime.now());
|
||
|
||
// 保存日志
|
||
sysLoginLogMapper.insert(loginLog);
|
||
|
||
} catch (Exception e) {
|
||
log.error("记录登录失败日志失败", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 记录退出登录日志
|
||
*/
|
||
public void recordLogout(String loginName, LocalDateTime loginTime) {
|
||
try {
|
||
// 查找对应的登录日志
|
||
SysLoginLog loginLog = sysLoginLogMapper.selectOne(
|
||
new LambdaQueryWrapper<SysLoginLog>()
|
||
.eq(SysLoginLog::getLoginName, loginName)
|
||
.eq(SysLoginLog::getLoginTime, loginTime)
|
||
.eq(SysLoginLog::getLoginStatus, "0")
|
||
.isNull(SysLoginLog::getLogoutTime)
|
||
.last("ORDER BY create_time DESC LIMIT 1")
|
||
);
|
||
|
||
if (loginLog != null) {
|
||
LocalDateTime logoutTime = LocalDateTime.now();
|
||
long sessionDuration = Duration.between(loginLog.getLoginTime(), logoutTime).getSeconds();
|
||
|
||
loginLog.setLogoutTime(logoutTime);
|
||
loginLog.setSessionDuration((int) sessionDuration);
|
||
loginLog.setRemark("正常退出");
|
||
loginLog.setUpdateBy("system");
|
||
loginLog.setUpdateTime(LocalDateTime.now());
|
||
|
||
sysLoginLogMapper.updateById(loginLog);
|
||
}
|
||
|
||
} catch (Exception e) {
|
||
log.error("记录退出登录日志失败", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取客户端IP
|
||
*/
|
||
private String getClientIP(HttpServletRequest request) {
|
||
String ip = request.getHeader("X-Forwarded-For");
|
||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||
ip = request.getHeader("Proxy-Client-IP");
|
||
}
|
||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||
}
|
||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||
}
|
||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||
}
|
||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||
ip = request.getRemoteAddr();
|
||
}
|
||
return ip;
|
||
}
|
||
|
||
/**
|
||
* 解析浏览器信息
|
||
*/
|
||
private String getBrowserInfo(HttpServletRequest request) {
|
||
String userAgent = request.getHeader("User-Agent");
|
||
if (userAgent == null) {
|
||
return "Unknown";
|
||
}
|
||
|
||
if (userAgent.contains("Chrome")) {
|
||
return "Chrome";
|
||
} else if (userAgent.contains("Firefox")) {
|
||
return "Firefox";
|
||
} else if (userAgent.contains("Safari")) {
|
||
return "Safari";
|
||
} else if (userAgent.contains("Edge")) {
|
||
return "Edge";
|
||
} else if (userAgent.contains("IE")) {
|
||
return "Internet Explorer";
|
||
} else {
|
||
return "Other";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解析操作系统信息
|
||
*/
|
||
private String getOSInfo(HttpServletRequest request) {
|
||
String userAgent = request.getHeader("User-Agent");
|
||
if (userAgent == null) {
|
||
return "Unknown";
|
||
}
|
||
|
||
if (userAgent.contains("Windows NT 10.0")) {
|
||
return "Windows 10";
|
||
} else if (userAgent.contains("Windows NT 6.3")) {
|
||
return "Windows 8.1";
|
||
} else if (userAgent.contains("Windows NT 6.2")) {
|
||
return "Windows 8";
|
||
} else if (userAgent.contains("Windows NT 6.1")) {
|
||
return "Windows 7";
|
||
} else if (userAgent.contains("Mac OS X")) {
|
||
return "Mac OS";
|
||
} else if (userAgent.contains("Linux")) {
|
||
return "Linux";
|
||
} else if (userAgent.contains("Android")) {
|
||
return "Android";
|
||
} else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) {
|
||
return "iOS";
|
||
} else {
|
||
return "Other";
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 异步处理
|
||
|
||
```java
|
||
@Service
|
||
public class AsyncLoginLogService {
|
||
|
||
@Async("taskExecutor")
|
||
public void recordLoginLogAsync(SysLoginLog loginLog) {
|
||
try {
|
||
// 异步记录登录日志
|
||
sysLoginLogMapper.insert(loginLog);
|
||
|
||
// 更新用户登录信息
|
||
updateUserLoginInfo(loginLog);
|
||
|
||
// 检查异常登录
|
||
checkAbnormalLogin(loginLog);
|
||
|
||
} catch (Exception e) {
|
||
log.error("异步记录登录日志失败", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查异常登录
|
||
*/
|
||
private void checkAbnormalLogin(SysLoginLog loginLog) {
|
||
// 检查IP异常
|
||
if (isAbnormalIP(loginLog.getLoginIp(), loginLog.getLoginName())) {
|
||
sendSecurityAlert("检测到异常IP登录", loginLog);
|
||
}
|
||
|
||
// 检查设备异常
|
||
if (isAbnormalDevice(loginLog.getUserAgent(), loginLog.getLoginName())) {
|
||
sendSecurityAlert("检测到异常设备登录", loginLog);
|
||
}
|
||
|
||
// 检查时间异常
|
||
if (isAbnormalTime(loginLog.getLoginTime(), loginLog.getLoginName())) {
|
||
sendSecurityAlert("检测到异常时间登录", loginLog);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 统计分析功能
|
||
|
||
### 1. 登录统计
|
||
|
||
```java
|
||
/**
|
||
* 登录统计服务
|
||
*/
|
||
@Service
|
||
public class LoginStatisticsService {
|
||
|
||
/**
|
||
* 获取登录成功率统计
|
||
*/
|
||
public LoginSuccessRateVO getLoginSuccessRate(String beginTime, String endTime) {
|
||
// 查询总登录次数
|
||
long totalCount = sysLoginLogMapper.selectCount(
|
||
new LambdaQueryWrapper<SysLoginLog>()
|
||
.between(SysLoginLog::getLoginTime, beginTime, endTime)
|
||
);
|
||
|
||
// 查询成功登录次数
|
||
long successCount = sysLoginLogMapper.selectCount(
|
||
new LambdaQueryWrapper<SysLoginLog>()
|
||
.eq(SysLoginLog::getLoginStatus, "0")
|
||
.between(SysLoginLog::getLoginTime, beginTime, endTime)
|
||
);
|
||
|
||
// 计算成功率
|
||
double successRate = totalCount > 0 ? (double) successCount / totalCount * 100 : 0;
|
||
|
||
return LoginSuccessRateVO.builder()
|
||
.totalCount(totalCount)
|
||
.successCount(successCount)
|
||
.failureCount(totalCount - successCount)
|
||
.successRate(successRate)
|
||
.build();
|
||
}
|
||
|
||
/**
|
||
* 获取每日登录统计
|
||
*/
|
||
public List<DailyLoginStatVO> getDailyLoginStat(String beginTime, String endTime) {
|
||
return sysLoginLogMapper.selectDailyLoginStat(beginTime, endTime);
|
||
}
|
||
|
||
/**
|
||
* 获取客户端类型统计
|
||
*/
|
||
public List<ClientTypeStatVO> getClientTypeStat(String beginTime, String endTime) {
|
||
return sysLoginLogMapper.selectClientTypeStat(beginTime, endTime);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 安全监控
|
||
|
||
```java
|
||
/**
|
||
* 安全监控服务
|
||
*/
|
||
@Service
|
||
public class SecurityMonitorService {
|
||
|
||
/**
|
||
* 检测暴力破解
|
||
*/
|
||
public void detectBruteForce() {
|
||
// 查询5分钟内失败次数超过5次的IP
|
||
List<String> suspiciousIPs = sysLoginLogMapper.selectSuspiciousIPs();
|
||
|
||
for (String ip : suspiciousIPs) {
|
||
// 加入黑名单
|
||
addToBlacklist(ip);
|
||
|
||
// 发送告警
|
||
sendSecurityAlert("检测到暴力破解攻击", ip);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检测异地登录
|
||
*/
|
||
public void detectRemoteLogin() {
|
||
// 查询用户最近登录地址
|
||
List<SysLoginLog> recentLogins = sysLoginLogMapper.selectRecentLogins();
|
||
|
||
for (SysLoginLog loginLog : recentLogins) {
|
||
String lastLoginAddress = getLastLoginAddress(loginLog.getLoginName());
|
||
|
||
if (!loginLog.getLoginAddress().equals(lastLoginAddress)) {
|
||
// 发送异地登录通知
|
||
sendRemoteLoginNotification(loginLog);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 错误码说明
|
||
|
||
| 错误码 | 错误信息 | 说明 |
|
||
|--------|----------|------|
|
||
| 400 | 登录账号不能为空 | 登录账号为空 |
|
||
| 400 | 登录状态不能为空 | 登录状态为空 |
|
||
| 400 | 登录时间不能为空 | 登录时间为空 |
|
||
| 400 | 登录IP不能为空 | 登录IP为空 |
|
||
| 400 | 日志不存在 | 日志ID不存在 |
|
||
| 400 | 时间格式错误 | 时间格式不正确 |
|
||
| 401 | 当前会话未登录 | 未登录或Token无效 |
|
||
| 403 | 权限不足 | 没有相应的操作权限 |
|
||
| 500 | 日志记录失败 | 日志保存失败 |
|
||
| 500 | 系统异常 | 服务器内部错误 |
|
||
|
||
---
|
||
|
||
## 日志清理策略
|
||
|
||
### 1. 定时清理
|
||
|
||
```java
|
||
@Component
|
||
public class LoginLogCleanupTask {
|
||
|
||
/**
|
||
* 每天凌晨2点清理30天前的登录日志
|
||
*/
|
||
@Scheduled(cron = "0 0 2 * * ?")
|
||
public void cleanupOldLogs() {
|
||
try {
|
||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(30);
|
||
|
||
int deletedCount = sysLoginLogMapper.delete(
|
||
new LambdaQueryWrapper<SysLoginLog>()
|
||
.lt(SysLoginLog::getCreateTime, cutoffTime)
|
||
);
|
||
|
||
log.info("清理登录日志完成,删除{}条记录", deletedCount);
|
||
|
||
} catch (Exception e) {
|
||
log.error("清理登录日志失败", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 归档登录日志
|
||
*/
|
||
@Scheduled(cron = "0 0 1 1 * ?") // 每月1号凌晨1点执行
|
||
public void archiveLogs() {
|
||
try {
|
||
LocalDateTime lastMonth = LocalDateTime.now().minusMonths(1);
|
||
|
||
// 归档上个月的日志到历史表
|
||
sysLoginLogMapper.archiveLogsToHistory(lastMonth);
|
||
|
||
log.info("归档登录日志完成");
|
||
|
||
} catch (Exception e) {
|
||
log.error("归档登录日志失败", e);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 日志配置
|
||
|
||
```yaml
|
||
# application.yml
|
||
logging:
|
||
login-log:
|
||
# 是否启用登录日志
|
||
enabled: true
|
||
# 日志保留天数
|
||
retention-days: 90
|
||
# 是否记录成功登录
|
||
log-success: true
|
||
# 是否记录失败登录
|
||
log-failure: true
|
||
# 是否异步记录
|
||
async: true
|
||
# 是否启用IP地址解析
|
||
resolve-address: true
|
||
# 是否启用安全监控
|
||
security-monitor: true
|
||
```
|
||
|
||
---
|
||
|
||
## 使用建议
|
||
|
||
### 1. 日志管理
|
||
|
||
- **合理保留期**: 根据业务需要设置日志保留期
|
||
- **定期清理**: 定期清理过期日志释放存储空间
|
||
- **分表存储**: 大量日志可考虑按月分表存储
|
||
- **归档备份**: 重要日志定期归档备份
|
||
|
||
### 2. 安全监控
|
||
|
||
- **实时监控**: 实时监控异常登录行为
|
||
- **告警机制**: 建立完善的安全告警机制
|
||
- **自动响应**: 对可疑行为自动采取防护措施
|
||
- **定期分析**: 定期分析登录日志发现安全趋势
|
||
|
||
### 3. 性能优化
|
||
|
||
- **异步记录**: 使用异步方式记录日志避免影响登录性能
|
||
- **批量处理**: 批量处理日志数据提高效率
|
||
- **索引优化**: 为查询字段建立合适索引
|
||
- **分页查询**: 大数据量查询使用分页
|
||
|
||
### 4. 隐私保护
|
||
|
||
- **敏感信息**: 不记录密码等敏感信息
|
||
- **数据脱敏**: 对部分信息进行脱敏处理
|
||
- **访问控制**: 严格控制日志访问权限
|
||
- **合规要求**: 遵守相关数据保护法规
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
|
||
1. **日志完整性**: 确保登录日志记录的完整性和准确性
|
||
2. **性能影响**: 日志记录不应影响用户登录体验
|
||
3. **存储空间**: 注意日志存储空间的管理和清理
|
||
4. **安全防护**: 防止日志被恶意篡改或删除
|
||
5. **隐私保护**: 不记录用户密码等敏感信息
|
||
6. **异常处理**: 日志记录失败不应影响正常业务
|
||
7. **监控告警**: 建立异常登录的监控告警机制
|
||
8. **数据备份**: 重要日志数据需要备份
|
||
9. **合规要求**: 遵守相关法律法规对日志的要求
|
||
10. **访问审计**: 对日志访问行为也要进行审计 |