258 lines
9.8 KiB
Markdown
258 lines
9.8 KiB
Markdown
---
|
||
name: ADR-007
|
||
description: 查询不同类型ORG登录历史及自动登录功能
|
||
---
|
||
|
||
# 查询不同类型ORG登录历史及自动登录功能
|
||
|
||
## 元数据 (Metadata)
|
||
|
||
- **决策编号**: ADR-007
|
||
- **决策标题**: 查询不同类型ORG登录历史及自动登录功能
|
||
- **状态**: Accepted
|
||
- **日期**: 2026-01-16
|
||
- **最后更新**: 2026-01-18
|
||
- **决策者**: 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)
|
||
|
||
### 决策内容
|
||
采用方案 A:在现有的 `DataISfLoginController` 和 `DataiSfLoginHistoryController` 中新增三个接口:
|
||
1. `GET /salesforce/login/history/success/{orgType}` - 查询不同类型org成功登录历史
|
||
2. `POST /salesforce/login/autoLoginByHistory` - 根据对应登录成功历史登录不同的org
|
||
3. `GET /auth/history/latest-success-grouped` - 查询所有登录成功历史,按username和instanceUrl分组,每组返回最新一条数据
|
||
|
||
### 技术方案
|
||
|
||
#### 接口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);
|
||
```
|
||
|
||
#### 接口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
|
||
```
|
||
|
||
### 实施计划
|
||
1. 在 `IDataiSfLoginHistoryService` 中添加新方法:
|
||
- `selectSuccessLoginHistoryByOrgType(String orgType)`
|
||
- `selectDataiSfLoginHistoryById(Long id)`
|
||
- `selectLatestSuccessLoginHistoryGroupedByUserAndInstance()`
|
||
|
||
2. 在 `DataiSfLoginHistoryServiceImpl` 中实现新方法
|
||
|
||
3. 在 `DataISfLoginController` 中添加两个新接口:
|
||
- `GET /salesforce/login/history/success/{orgType}`
|
||
- `POST /salesforce/login/autoLoginByHistory`
|
||
|
||
4. 在 `DataiSfLoginHistoryController` 中添加新接口:
|
||
- `GET /auth/history/latest-success-grouped`
|
||
|
||
5. 在 `DataiSfLoginHistoryMapper.xml` 中添加新的 SQL 查询语句
|
||
|
||
6. 编写单元测试和集成测试
|
||
|
||
7. 更新 API 文档
|
||
|
||
### 回滚策略
|
||
如果决策失败,可以:
|
||
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 |
|