2026-01-18 19:44:13 +08:00
|
|
|
|
---
|
|
|
|
|
|
name: ADR-007
|
|
|
|
|
|
description: 查询不同类型ORG登录历史及自动登录功能
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# 查询不同类型ORG登录历史及自动登录功能
|
|
|
|
|
|
|
|
|
|
|
|
## 元数据 (Metadata)
|
|
|
|
|
|
|
|
|
|
|
|
- **决策编号**: ADR-007
|
|
|
|
|
|
- **决策标题**: 查询不同类型ORG登录历史及自动登录功能
|
2026-01-19 10:06:09 +08:00
|
|
|
|
- **状态**: Accepted
|
2026-01-18 19:44:13 +08:00
|
|
|
|
- **日期**: 2026-01-16
|
2026-01-19 10:06:09 +08:00
|
|
|
|
- **最后更新**: 2026-01-18
|
2026-01-18 19:44:13 +08:00
|
|
|
|
- **决策者**: SSOT Architect
|
|
|
|
|
|
- **相关需求**: [REQ-007](../../requirements/REQ-007.md)
|
|
|
|
|
|
|
|
|
|
|
|
## 背景 (Context)
|
|
|
|
|
|
|
|
|
|
|
|
### 问题描述
|
|
|
|
|
|
当前系统支持通过 `DataISfLoginController` 进行 Salesforce 登录操作,包括源ORG和目标ORG的登录。系统已经实现了基本的登录、登出、获取当前登录信息和自动登录功能。但是,当前的 `getCurrentLoginInfo` 接口只能获取当前登录信息,无法查询历史登录记录。而 `autoLogin` 接口虽然支持自动登录,但只能使用最新的登录历史记录。
|
|
|
|
|
|
|
|
|
|
|
|
用户需要:
|
|
|
|
|
|
1. 查询不同类型ORG(如Source、Target、Sandbox、Production等)的成功登录历史记录
|
|
|
|
|
|
2. 基于指定的登录历史记录自动登录到对应的ORG环境
|
|
|
|
|
|
|
|
|
|
|
|
### 当前状态
|
|
|
|
|
|
- `DataISfLoginController` 已存在,包含以下接口:
|
|
|
|
|
|
- `POST /salesforce/login/doLogin` - 执行登录操作
|
|
|
|
|
|
- `POST /salesforce/login/logout` - 执行登出操作
|
|
|
|
|
|
- `GET /salesforce/login/current` - 获取当前登录信息
|
|
|
|
|
|
- `POST /salesforce/login/autoLogin` - 自动登录(使用最新历史)
|
|
|
|
|
|
- `IDataiSfLoginHistoryService` 已存在,包含查询登录历史的方法
|
|
|
|
|
|
- `DataiSfLoginHistory` 实体已存在,包含登录历史的所有字段
|
|
|
|
|
|
|
|
|
|
|
|
### 问题影响
|
|
|
|
|
|
1. 开发人员和测试人员无法快速查询特定类型的登录历史
|
|
|
|
|
|
2. 无法基于指定的历史记录自动登录到不同的ORG环境
|
|
|
|
|
|
3. 需要在多个环境间切换时,需要重复输入登录凭证,效率较低
|
|
|
|
|
|
|
|
|
|
|
|
### 约束条件
|
|
|
|
|
|
- 必须保持与现有代码风格的一致性
|
|
|
|
|
|
- 必须复用现有的 Service 层代码
|
|
|
|
|
|
- 必须确保接口的安全性和性能
|
|
|
|
|
|
- 必须支持多种ORG类型(Source、Target、Sandbox、Production等)
|
|
|
|
|
|
|
|
|
|
|
|
## 决策 (Decision)
|
|
|
|
|
|
|
|
|
|
|
|
### 决策内容
|
2026-01-19 10:06:09 +08:00
|
|
|
|
采用方案 A:在现有的 `DataISfLoginController` 和 `DataiSfLoginHistoryController` 中新增三个接口:
|
2026-01-18 19:44:13 +08:00
|
|
|
|
1. `GET /salesforce/login/history/success/{orgType}` - 查询不同类型org成功登录历史
|
|
|
|
|
|
2. `POST /salesforce/login/autoLoginByHistory` - 根据对应登录成功历史登录不同的org
|
2026-01-19 10:06:09 +08:00
|
|
|
|
3. `GET /auth/history/latest-success-grouped` - 查询所有登录成功历史,按username和instanceUrl分组,每组返回最新一条数据
|
2026-01-18 19:44:13 +08:00
|
|
|
|
|
|
|
|
|
|
### 技术方案
|
|
|
|
|
|
|
|
|
|
|
|
#### 接口1: 查询不同类型org成功登录历史
|
|
|
|
|
|
|
|
|
|
|
|
**接口路径**: `GET /salesforce/login/history/success/{orgType}`
|
|
|
|
|
|
|
|
|
|
|
|
**功能描述**: 查询指定类型的成功登录历史记录
|
|
|
|
|
|
|
|
|
|
|
|
**实现方式**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
@Operation(summary = "查询不同类型ORG成功登录历史")
|
|
|
|
|
|
@GetMapping("/history/success/{orgType}")
|
|
|
|
|
|
public AjaxResult getSuccessLoginHistoryByOrgType(@PathVariable String orgType) {
|
|
|
|
|
|
logger.info("接收到查询登录历史请求,ORG类型: {}", orgType);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
List<DataiSfLoginHistory> historyList = loginHistoryService.selectSuccessLoginHistoryByOrgType(orgType);
|
|
|
|
|
|
return AjaxResult.success("查询成功", historyList);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("查询登录历史请求处理失败: {}", e.getMessage(), e);
|
|
|
|
|
|
return AjaxResult.error("查询失败: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Service 层方法**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
List<DataiSfLoginHistory> selectSuccessLoginHistoryByOrgType(String orgType);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 接口2: 根据对应登录成功历史登录不同的org
|
|
|
|
|
|
|
|
|
|
|
|
**接口路径**: `POST /salesforce/login/autoLoginByHistory`
|
|
|
|
|
|
|
|
|
|
|
|
**功能描述**: 根据指定的登录历史记录自动登录到对应ORG
|
|
|
|
|
|
|
|
|
|
|
|
**实现方式**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
@Operation(summary = "根据登录历史自动登录")
|
|
|
|
|
|
@PostMapping("/autoLoginByHistory")
|
|
|
|
|
|
public AjaxResult autoLoginByHistory(@RequestBody Map<String, Long> request) {
|
|
|
|
|
|
Long historyId = request.get("historyId");
|
|
|
|
|
|
logger.info("接收到根据历史自动登录请求,历史ID: {}", historyId);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
DataiSfLoginHistory loginHistory = loginHistoryService.selectDataiSfLoginHistoryById(historyId);
|
|
|
|
|
|
|
|
|
|
|
|
if (loginHistory == null) {
|
|
|
|
|
|
logger.warn("未找到ID为 {} 的登录历史记录", historyId);
|
|
|
|
|
|
return AjaxResult.error("未找到登录历史记录");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SalesforceLoginResult result = salesforceLoginService.autoLogin(historyHistory.getId());
|
|
|
|
|
|
|
|
|
|
|
|
if (result.isSuccess()) {
|
|
|
|
|
|
logger.info("自动登录成功,用户ID: {}, 组织ID: {}, ORG类型: {}",
|
|
|
|
|
|
result.getUserId(), result.getOrganizationId(), result.getOrgType());
|
|
|
|
|
|
return AjaxResult.success("自动登录成功", result);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logger.warn("自动登录失败,错误码: {}, 错误信息: {}",
|
|
|
|
|
|
result.getErrorCode(), result.getErrorMessage());
|
|
|
|
|
|
return AjaxResult.error(result.getErrorCode(), result.getErrorMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
logger.error("自动登录请求处理失败: {}", e.getMessage(), e);
|
|
|
|
|
|
return AjaxResult.error("自动登录失败: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Service 层方法**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
DataiSfLoginHistory selectDataiSfLoginHistoryById(Long id);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-19 10:06:09 +08:00
|
|
|
|
#### 接口3: 查询所有登录成功历史分组数据
|
|
|
|
|
|
|
|
|
|
|
|
**接口路径**: `GET /auth/history/latest-success-grouped`
|
|
|
|
|
|
|
|
|
|
|
|
**功能描述**: 查询所有登录成功的历史记录,按username和instanceUrl分组,每组返回最新一条数据
|
|
|
|
|
|
|
|
|
|
|
|
**实现方式**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
@Operation(summary = "查询所有登录成功的历史记录,按username和instanceUrl分组,每组返回最新一条")
|
|
|
|
|
|
@PreAuthorize("@ss.hasPermi('auth:history:list')")
|
|
|
|
|
|
@GetMapping("/latest-success-grouped")
|
|
|
|
|
|
public TableDataInfo listLatestSuccessGrouped()
|
|
|
|
|
|
{
|
|
|
|
|
|
List<DataiSfLoginHistory> list = dataiSfLoginHistoryService.selectLatestSuccessLoginHistoryGroupedByUserAndInstance();
|
|
|
|
|
|
List<DataiSfLoginHistoryVo> voList = list.stream().map(DataiSfLoginHistoryVo::objToVo).collect(Collectors.toList());
|
|
|
|
|
|
return getDataTableByPage(voList, voList.size());
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Service 层方法**:
|
|
|
|
|
|
```java
|
|
|
|
|
|
List<DataiSfLoginHistory> selectLatestSuccessLoginHistoryGroupedByUserAndInstance();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**SQL 实现**:
|
|
|
|
|
|
```sql
|
|
|
|
|
|
select * from (
|
|
|
|
|
|
select
|
|
|
|
|
|
<include refid="selectDataiSfLoginHistoryVo"/>,
|
|
|
|
|
|
ROW_NUMBER() OVER (PARTITION BY dslh.username, dslh.instance_url ORDER BY dslh.create_time DESC) as rn
|
|
|
|
|
|
from datai_sf_login_history dslh
|
|
|
|
|
|
where dslh.login_status = 'success'
|
|
|
|
|
|
) t where t.rn = 1
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-18 19:44:13 +08:00
|
|
|
|
### 实施计划
|
|
|
|
|
|
1. 在 `IDataiSfLoginHistoryService` 中添加新方法:
|
|
|
|
|
|
- `selectSuccessLoginHistoryByOrgType(String orgType)`
|
|
|
|
|
|
- `selectDataiSfLoginHistoryById(Long id)`
|
2026-01-19 10:06:09 +08:00
|
|
|
|
- `selectLatestSuccessLoginHistoryGroupedByUserAndInstance()`
|
2026-01-18 19:44:13 +08:00
|
|
|
|
|
|
|
|
|
|
2. 在 `DataiSfLoginHistoryServiceImpl` 中实现新方法
|
|
|
|
|
|
|
2026-01-19 10:06:09 +08:00
|
|
|
|
3. 在 `DataISfLoginController` 中添加两个新接口:
|
|
|
|
|
|
- `GET /salesforce/login/history/success/{orgType}`
|
|
|
|
|
|
- `POST /salesforce/login/autoLoginByHistory`
|
|
|
|
|
|
|
|
|
|
|
|
4. 在 `DataiSfLoginHistoryController` 中添加新接口:
|
|
|
|
|
|
- `GET /auth/history/latest-success-grouped`
|
|
|
|
|
|
|
|
|
|
|
|
5. 在 `DataiSfLoginHistoryMapper.xml` 中添加新的 SQL 查询语句
|
2026-01-18 19:44:13 +08:00
|
|
|
|
|
2026-01-19 10:06:09 +08:00
|
|
|
|
6. 编写单元测试和集成测试
|
2026-01-18 19:44:13 +08:00
|
|
|
|
|
2026-01-19 10:06:09 +08:00
|
|
|
|
7. 更新 API 文档
|
2026-01-18 19:44:13 +08:00
|
|
|
|
|
|
|
|
|
|
### 回滚策略
|
|
|
|
|
|
如果决策失败,可以:
|
|
|
|
|
|
1. 删除新增的两个接口
|
|
|
|
|
|
2. 删除 Service 层新增的方法
|
|
|
|
|
|
3. 回退到之前的代码版本
|
|
|
|
|
|
|
|
|
|
|
|
## 后果 (Consequences)
|
|
|
|
|
|
|
|
|
|
|
|
### 正面影响
|
|
|
|
|
|
1. **提高效率**: 用户可以快速查询登录历史,无需手动记录
|
|
|
|
|
|
2. **多环境支持**: 支持多种ORG类型的快速切换
|
|
|
|
|
|
3. **代码一致性**: 保持与现有代码风格的一致性
|
|
|
|
|
|
4. **开发成本低**: 复用现有代码,开发成本较低
|
|
|
|
|
|
|
|
|
|
|
|
### 负面影响
|
|
|
|
|
|
1. **Controller 膨胀**: Controller 可能会变得过于庞大
|
|
|
|
|
|
2. **维护复杂度**: 需要确保新增接口与现有接口的兼容性
|
|
|
|
|
|
|
|
|
|
|
|
### 风险评估
|
|
|
|
|
|
| 风险 | 概率 | 影响 | 缓解措施 |
|
|
|
|
|
|
|------|------|------|----------|
|
|
|
|
|
|
| 接口性能问题 | 低 | 中 | 添加索引,优化查询语句 |
|
|
|
|
|
|
| 安全性问题 | 低 | 高 | 确保登录历史中的敏感信息加密存储 |
|
|
|
|
|
|
| 兼容性问题 | 低 | 中 | 充分测试,确保与现有功能兼容 |
|
|
|
|
|
|
| 数据库查询性能 | 中 | 中 | 添加 orgType 字段索引 |
|
|
|
|
|
|
|
|
|
|
|
|
### 权衡分析
|
|
|
|
|
|
采用方案 A 的主要权衡是:
|
|
|
|
|
|
- **优点**: 开发成本低,代码一致性好,快速交付
|
|
|
|
|
|
- **缺点**: Controller 可能会变得过于庞大
|
|
|
|
|
|
- **结论**: 考虑到开发成本和代码一致性,以及现有架构的支持,选择方案 A 是合理的
|
|
|
|
|
|
|
|
|
|
|
|
## 合规性验证 (Compliance)
|
|
|
|
|
|
|
|
|
|
|
|
### 验证标准
|
|
|
|
|
|
1. 两个新接口已添加到 `DataISfLoginController`
|
|
|
|
|
|
2. Service 层新方法已实现
|
|
|
|
|
|
3. 接口功能符合需求文档中的验收标准
|
|
|
|
|
|
4. 接口性能满足非功能需求(响应时间 < 500ms)
|
|
|
|
|
|
5. 接口安全性得到保证(敏感信息加密)
|
|
|
|
|
|
6. API 文档已更新
|
|
|
|
|
|
|
|
|
|
|
|
### 验证方法
|
|
|
|
|
|
1. 单元测试:测试 Service 层新方法
|
|
|
|
|
|
2. 集成测试:测试 Controller 层新接口
|
|
|
|
|
|
3. 性能测试:测试接口响应时间
|
|
|
|
|
|
4. 安全测试:测试敏感信息加密
|
|
|
|
|
|
5. 文档审查:检查 API 文档是否完整
|
|
|
|
|
|
|
|
|
|
|
|
### 验证结果
|
|
|
|
|
|
待实施后填写
|
|
|
|
|
|
|
|
|
|
|
|
### 成功指标
|
|
|
|
|
|
| 指标 | 目标值 | 实际值 | 状态 |
|
|
|
|
|
|
|------|--------|--------|------|
|
|
|
|
|
|
| 接口数量 | 2 | 待验证 | 待验证 |
|
|
|
|
|
|
| 接口响应时间 | < 500ms | 待验证 | 待验证 |
|
|
|
|
|
|
| 接口可用性 | > 99.9% | 待验证 | 待验证 |
|
|
|
|
|
|
| 测试覆盖率 | > 80% | 待验证 | 待验证 |
|
|
|
|
|
|
|
|
|
|
|
|
## 相关文档 (Related Documents)
|
|
|
|
|
|
|
|
|
|
|
|
- [需求文档](../../requirements/REQ-007.md)
|
|
|
|
|
|
- [设计文档](../../design/0001-salesforce-cdc-realtime-sync.md)
|
|
|
|
|
|
- [其他 ADR](./0001-salesforce-cdc-sync.md)
|
|
|
|
|
|
- [DataISfLoginController](../../api-docs/auth/DataISfLoginController/) - Salesforce登录控制器接口文档
|
|
|
|
|
|
|
|
|
|
|
|
## 变更历史 (Change History)
|
|
|
|
|
|
|
|
|
|
|
|
| 日期 | 版本 | 变更内容 | 变更人 |
|
|
|
|
|
|
|------|------|----------|--------|
|
|
|
|
|
|
| 2026-01-16 | 1.0.0 | 初始版本 | SSOT Architect |
|