--- name: 登录日志功能(前后端) overview: 后端在登录时统一采集客户端信息(IP、IP归属地、浏览器/版本、操作系统/版本、设备类型、原始UA),复用通用 saveobjt 机制写入新表 s_log_login(成功与失败都记录);前端新增只读列表页展示登录日志。表名 s_log_login,权限暂不接入。 design: styleKeywords: - Enterprise - Clean - Audit Table - Teal Accent - Light Theme fontSystem: fontFamily: PingFang SC heading: size: 20px weight: 600 subheading: size: 14px weight: 500 body: size: 13px weight: 400 colorSystem: primary: - "#15998E" - "#173A42" background: - "#F5F7F8" - "#FFFFFF" text: - "#19323A" - "#5B6B70" functional: - "#52C41A" - "#FF4D4F" - "#15998E" todos: - id: add-deps-xdb content: pom.xml 新增 ip2region/uap-java 依赖并下载 ip2region.xdb 到 resources status: completed - id: client-info-utils content: 实现 IpUtils、ClientInfo、IpRegionSearcher、ClientInfoExtractor 采集工具 status: completed dependencies: - add-deps-xdb - id: login-log-service content: 实现 LoginLogService 并在 AuthController/AuthService 登录成功与失败分支串联记录 status: completed dependencies: - client-info-utils - id: migration-table content: 新增 003_create_login_log.sql 建表 s_log_login 及索引 status: completed - id: frontend-log-page content: 用 [skill:antdv-next] 新增路由、侧边栏菜单与登录日志只读列表页(筛选+分页) status: completed dependencies: - migration-table --- ## 用户需求 统一在登录环节采集客户端信息,落地「登录日志」功能,覆盖登录成功与失败(账号不存在、密码错误)两类行为,供后续操作日志与系统审计复用。前端提供只读的登录日志列表页。 ## 产品概述 登录时由后端统一采集客户端环境信息并落库,形成可审计的登录轨迹;管理员可在前端以表格形式分页查看每次登录的客户端画像与结果。 ## 核心功能 - 后端登录流程中采集:IP 地址、IP 归属地(国家/省/市/运营商)、浏览器及版本、操作系统及版本、设备类型、原始 User-Agent。 - 登录成功与失败均写登录日志(表 `s_log_login`),失败记录失败原因,写日志失败不影响登录。 - 前端新增「登录日志」只读列表页:分页展示全部采集字段,成功/失败以标签区分,支持按账号、结果、时间筛选。 - 权限按用户要求暂不做限制(路由不带 moduleCode,登录用户均可访问)。 ## 技术栈 - 后端:Java 21 + Spring Boot 4.1,沿用 `DbUtils` 原生 JDBC 与多机构 `OrgRoutingDataSource`;新增依赖 `org.lionsoul:ip2region`(本地 xdb 解析)、`com.github.ua-parser:uap-java`(User-Agent 解析)。 - 前端:Vue 3 + Vite + antdv-next + Pinia,复用现有 `pageDataApi` 通用分页接口。 ## 实现思路 - 客户端信息全部在后端登录时采集:IP 与 UA 本就是 HTTP 请求头,服务端获取更可靠、不可被前端伪造,也契合规范中「优先通用接口、不新增业务接口」。 - `IpUtils`:按 `X-Real-IP` → `X-Forwarded-For`(取首个有效 IP)→ `request.getRemoteAddr()` 顺序取值,兼容 Nginx 代理。 - `IpRegionSearcher`:以 `@Component` 单例在启动时把 `ip2region.xdb` 读入内存(`Searcher.newWithBuffer(bytes)`),避免每次文件 IO;`search(ip)` 返回 `国家|省|市|运营商`,异常时返回「未知」。 - `ClientInfoExtractor`:`@Component`,注入 `IpRegionSearcher` 与 uap-java `Parser`(单例),`extract(HttpServletRequest)` 解析 UA 得到浏览器/版本、系统/版本、设备类型(依据 `device.family` 映射 Mobile/Tablet/Desktop/Bot/Unknown)。 - `LoginLogService.recordLogin(...)`:在方法内 `OrgContext.setOrgId(orgId)` 后,组装 `s_log_login` 行(`b_id = IdGenerator.nextId()`),以通用保存结构 `{table:'s_log_login', key_field:'b_id', inserts:[row]}` 调用 `DataSaveService.save(...)`(即 `saveobjt` 后端实现),整段 `try/catch` 包裹、失败仅记录日志、`finally` 中 `OrgContext.clear()`,保证登录绝不因写日志失败而中断;机构无效(无数据源)时自然抛错被捕获跳过落库。 - `AuthController.login` 将 `HttpServletRequest` 透传给 `AuthService.login`;在成功(含 `sessionId`)与失败(账号不存在/密码错误)分支均调用 `recordLogin`。 - 前端:新增路由(无 `moduleCode`)+ 侧边栏菜单项(无 `moduleCode` 经 `canModule` 恒为可见)+ 只读 `a-table` 列表页,调用 `pageDataApi({viewName:'s_log_login', orderBy:'b_login_time DESC'})`,分页依据响应 `rows/total`。 ## 性能与可靠性 - ip2region 内存检索为 O(1) 级、uap-java 解析为微秒级,登录非高频操作,无性能瓶颈;不引入每请求文件 IO 或额外 SQL。 - 写日志与登录主流程解耦(try/catch + 独立事务回滚不影响登录),`OrgContext` 严格 `finally` 清理,避免线程上下文污染。 - 不改动 `saveobjt`/通用查询等既有接口与共享组件,向后兼容。 ## 架构设计 ```mermaid flowchart TD A[AuthController.login HttpServletRequest] --> B[AuthService.login] B -->|成功/失败| C[LoginLogService.recordLogin] C --> D[ClientInfoExtractor.extract] D --> E[IpUtils 取IP] D --> F[IpRegionSearcher 归属地] D --> G[uap-java 解析UA] C -->|OrgContext.setOrgId| H[DataSaveService.save 通用saveobjt] H --> I[(s_log_login 机构库)] subgraph 前端 J[LoginLogView] --> K[pageDataApi /data/page] K --> L[(s_log_login)] end ``` ## 目录结构 ``` fms-api/ ├── pom.xml # [MODIFY] 新增 ip2region、uap-java 依赖 ├── src/main/resources/ip2region.xdb # [NEW] 官方 ip2region 二进制库(约11MB,不入库/可 gitignore) ├── src/main/resources/application.yaml # [MODIFY/可选] 如需配置 xdb 路径占位 ├── config/migrations/003_create_login_log.sql # [NEW] 建表 s_log_login + 索引 └── src/main/java/cn/g3soft/fmsapi/ ├── utils/ │ ├── IpUtils.java # [NEW] 从请求头/远程地址提取客户端 IP │ ├── ClientInfo.java # [NEW] 客户端信息载体(ip/location/browser/os/deviceType/ua) │ ├── IpRegionSearcher.java # [NEW] 启动初始化 xdb Searcher,提供 search(ip) │ └── ClientInfoExtractor.java # [NEW] 组合 IpUtils+IpRegionSearcher+uap-java,extract(request) ├── service/ │ ├── LoginLogService.java # [NEW] recordLogin:组装并调用 DataSaveService.save │ └── AuthService.java # [MODIFY] 注入 LoginLogService,login 增 HttpServletRequest 参数并在成功/失败分支记录 └── controller/AuthController.java # [MODIFY] login 透传 HttpServletRequest 给 service fms-vue/ ├── src/services/api.js # [MODIFY] 新增 pageLoginLogApi 包装 pageDataApi ├── src/router/routes.js # [MODIFY] 新增 /system/login-log 路由(无 moduleCode) ├── src/layouts/components/AppSidebar.vue # [MODIFY] systemPages 增加「登录日志」菜单项(无 moduleCode) └── src/views/system/login-log/ └── index.vue # [NEW] 只读列表页:筛选栏 + a-table 分页展示 ``` ## 关键代码结构 ```java // ClientInfo:客户端环境信息载体 public record ClientInfo( String ip, String ipLocation, String browser, String browserVersion, String os, String osVersion, String deviceType, String userAgent ) {} // LoginLogService 写日志入口(成功/失败均调用) void recordLogin(String orgId, String account, Map user, boolean success, String failReason, String sessionId, HttpServletRequest request); ``` ## 设计风格 沿用系统现有的企业级货运物流后台风格:浅色画布、左侧深色(`#173a42`)侧栏、青绿主色(`#15998e`)点缀。登录日志页为「筛选栏 + 只读数据表格」的经典审计列表布局,保持与模块管理页一致的视觉语言与栅格间距,整体克制、清晰、专业。 ## 页面规划(1 屏) - 顶部区块:页标题「登录日志」+ 筛选栏(账号输入框、登录结果下拉「成功/失败」、登录时间范围选择器、查询按钮)。 - 主体区块:antdv `a-table` 只读表格,列含 登录时间、账号、用户姓名、IP、IP 归属地、浏览器(版本)、操作系统(版本)、设备类型、登录结果(标签)、原始 UA(溢出省略,tooltip 全显);底部分页。 - 结果列用 `a-tag` 区分:成功=绿色、失败=红色。 ## 交互与响应式 - 筛选条件变更触发分页查询(`pageDataApi`),加载态用 `a-spin`/表格 `loading`。 - 表格列在窄屏下横向滚动(`scroll.x`),筛选栏在移动端自动换行。 - 行 hover 高亮;UA 等超长文本 `ellipsis` + `tooltip`,避免撑破布局。 ## Agent Extensions ### Skill - **antdv-next** - Purpose: 在实现登录日志只读列表页时,获取 antdv-next 表格(a-table)、筛选控件(a-input / a-select / a-range-picker)、a-tag 等组件的准确 API、事件与插槽用法。 - Expected outcome: 前端列表页使用符合项目自动导入约定的 antdv-next 组件,分页/筛选/标签渲染正确且无 API 误用。