feat(密钥管理): 列表按用户聚合三字段列 + 代理配置服务端上报

- Java:后台列表一行一用户(货源查询密钥/外观专利密钥/代理设置),行级状态三项全通过才算通过;检测/清空改为按用户;搜索仅按用户名(去 UID);新增代理检测(经用户代理请求自家域名)与代理掩码(隐去账密)
- 后台前端:三字段列改版 + 行级状态筛选 + 用户名筛选
- 桌面前端:登录加载密钥时补报本地代理(只填空缺不覆盖)、保存/清空代理实时上报
This commit is contained in:
2026-09-13 10:47:19 +08:00
parent 82a782550e
commit b7d4d325e0
14 changed files with 665 additions and 226 deletions
@@ -2,10 +2,13 @@ package com.nanri.aiimage.modules.usersecret.service;
import com.nanri.aiimage.common.security.ShopCredentialCryptoService;
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
import com.nanri.aiimage.modules.usersecret.client.JikipProxyClient;
import com.nanri.aiimage.modules.usersecret.mapper.UserApiSecretMapper;
import com.nanri.aiimage.modules.usersecret.model.dto.AdminUserSecretQuery;
import com.nanri.aiimage.modules.usersecret.model.dto.UserApiSecretMigrateRequest;
import com.nanri.aiimage.modules.usersecret.model.entity.UserApiSecretEntity;
import com.nanri.aiimage.modules.usersecret.model.vo.AdminUserSecretModuleVo;
import com.nanri.aiimage.modules.usersecret.model.vo.UserApiSecretBundleVo;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@@ -154,6 +157,102 @@ class UserApiSecretServiceTest {
verify(mapper).delete(any());
}
@Test
void bundleExcludesProxyModule() {
UserApiSecretService service = newService();
when(mapper.selectOne(any())).thenReturn(null);
UserApiSecretBundleVo bundle = service.bundle(7L);
assertThat(bundle.getItems()).extracting(item -> item.getModuleKey())
.containsExactlyInAnyOrder("appearance-patent", "similar-asin");
assertThat(bundle.getRequiredModules()).doesNotContain("proxy");
}
@Test
void saveRejectsInvalidProxyUrl() {
UserApiSecretService service = newService();
org.assertj.core.api.Assertions.assertThatThrownBy(() -> service.save(7L, "proxy", "1.2.3.4:8080"))
.isInstanceOf(com.nanri.aiimage.common.exception.BusinessException.class)
.hasMessageContaining("代理地址格式不正确");
}
@Test
void summarizeRequiresAllThreePassed() {
UserApiSecretService service = newService();
assertThat(service.summarizeRowStatus(List.of(
moduleVo(true, "passed"), moduleVo(true, "passed"), moduleVo(true, "passed")))
).isEqualTo("passed");
assertThat(service.summarizeRowStatus(List.of(
moduleVo(true, "passed"), moduleVo(false, "unknown"), moduleVo(true, "passed")))
).isEqualTo("incomplete");
assertThat(service.summarizeRowStatus(List.of(
moduleVo(true, "failed"), moduleVo(false, "unknown"), moduleVo(true, "passed")))
).isEqualTo("failed");
assertThat(service.summarizeRowStatus(List.of(
moduleVo(true, "passed"), moduleVo(true, "error"), moduleVo(true, "passed")))
).isEqualTo("error");
assertThat(service.summarizeRowStatus(List.of(
moduleVo(true, "passed"), moduleVo(true, "unknown"), moduleVo(true, "passed")))
).isEqualTo("unknown");
}
@Test
void adminPageAggregatesOneRowPerUserWithProxyMasked() {
UserApiSecretService service = newService();
when(mapper.selectList(any())).thenReturn(List.of(
row(1L, "similar-asin", "enc:sk-sa-1234", "passed"),
row(1L, "appearance-patent", "enc:sk-ap-5678", "passed"),
row(1L, "proxy", "enc:http://user:pass@1.2.3.4:8080", "failed")));
AdminUserEntity user = new AdminUserEntity();
user.setId(1L);
user.setUsername("张三");
when(adminUserMapper.selectById(1L)).thenReturn(user);
var page = service.adminPage(new AdminUserSecretQuery());
assertThat(page.getItems()).hasSize(1);
var rowVo = page.getItems().get(0);
assertThat(rowVo.getUsername()).isEqualTo("张三");
assertThat(rowVo.getStatus()).isEqualTo("failed");
assertThat(rowVo.getSimilarAsin().getMasked()).isEqualTo("sk-s****1234");
assertThat(rowVo.getProxy().getExists()).isTrue();
assertThat(rowVo.getProxy().getMasked()).isEqualTo("http://***@1.2.3.4:8080");
}
@Test
void adminClearByUserDeletesAllRowsOfUser() {
UserApiSecretService service = newService();
service.adminClearByUser(7L);
verify(mapper).delete(any());
}
private AdminUserSecretModuleVo moduleVo(boolean exists, String status) {
AdminUserSecretModuleVo vo = new AdminUserSecretModuleVo();
vo.setExists(exists);
vo.setCheckStatus(status);
return vo;
}
private UserApiSecretEntity row(Long userId, String moduleKey, String cipher, String status) {
UserApiSecretEntity entity = new UserApiSecretEntity();
entity.setId((long) (Math.random() * 100000));
entity.setUserId(userId);
entity.setModuleKey(moduleKey);
entity.setSecretValue(cipher);
entity.setCheckStatus(status);
entity.setUpdatedAt(java.time.LocalDateTime.now());
return entity;
}
private UserApiSecretMigrateRequest.Item item(String moduleKey, String value) {
UserApiSecretMigrateRequest.Item item = new UserApiSecretMigrateRequest.Item();
item.setModuleKey(moduleKey);