diff --git a/src/main/java/com/yunhai/userhome/controller/api/UsedAppController.java b/src/main/java/com/yunhai/userhome/controller/api/UsedAppController.java index ce800f4..42dc15f 100644 --- a/src/main/java/com/yunhai/userhome/controller/api/UsedAppController.java +++ b/src/main/java/com/yunhai/userhome/controller/api/UsedAppController.java @@ -5,7 +5,9 @@ import com.yunhai.platform.utils.model.BoaAppType; import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.userhome.config.Config; import com.yunhai.userhome.entity.UsedApp; +import com.yunhai.userhome.model.AppIconEntryVo; import com.yunhai.userhome.model.SetType; +import com.yunhai.userhome.service.AppIconKeyService; import com.yunhai.userhome.service.AppRemoteService; import com.yunhai.userhome.service.UsedAppService; import org.springframework.beans.factory.annotation.Autowired; @@ -36,6 +38,9 @@ public class UsedAppController { @Autowired private AppRemoteService appRemoteService; + @Autowired + private AppIconKeyService appIconKeyService; + /** * 取当前角色生效的常用应用(BoaApp 详情,按 appIds 顺序)。 * @@ -112,7 +117,7 @@ public class UsedAppController { usedAppService.saveUserId(usedApp); } - /** 按 appIds(逗号分隔)取应用精简映射(id/name/icon/url),在全集中查找。 */ + /** 按 appIds(逗号分隔)取应用精简映射(id/name/icon/url/iconKey/iconColor),在全集中查找。 */ @GetMapping("getAppByIds") public List> getAppByIds(BoaUser authUserWithDept, String appIds) { List> list = new ArrayList<>(); @@ -132,6 +137,10 @@ public class UsedAppController { m.put("name", app.getName()); m.put("icon", app.getIcon()); m.put("url", app.getWebAddr()); + // 矢量图标键(AppIcon 通道;未配置为 null,前端按空走原 img 回退) + AppIconEntryVo iconEntry = appIconKeyService.byId(app.getId()); + m.put("iconKey", iconEntry == null ? null : iconEntry.getIconKey()); + m.put("iconColor", iconEntry == null ? null : iconEntry.getIconColor()); list.add(m); }); } catch (NumberFormatException ignore) { diff --git a/src/main/java/com/yunhai/userhome/controller/api/UserController.java b/src/main/java/com/yunhai/userhome/controller/api/UserController.java index 72d2898..8e3b649 100644 --- a/src/main/java/com/yunhai/userhome/controller/api/UserController.java +++ b/src/main/java/com/yunhai/userhome/controller/api/UserController.java @@ -1,5 +1,7 @@ package com.yunhai.userhome.controller.api; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.yunhai.platform.apistd.BizException; import com.yunhai.platform.openapi.base.OpenApiUserClient; import com.yunhai.platform.openapi.base.YschBaseOpenApi; @@ -11,6 +13,8 @@ import com.yunhai.platform.utils.model.BoaAppType; import com.yunhai.platform.utils.model.BoaRoleInfo; import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.userhome.config.Config; +import com.yunhai.userhome.model.AppIconEntryVo; +import com.yunhai.userhome.service.AppIconKeyService; import com.yunhai.userhome.service.AppRemoteService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,6 +52,12 @@ public class UserController { @Autowired private YschOpenCall openCall; + @Autowired + private AppIconKeyService appIconKeyService; + + @Autowired + private ObjectMapper objectMapper; + /** 当前登录用户信息(CAS 注入)。 */ @GetMapping("GetUserInfo") @@ -72,9 +82,41 @@ public class UserController { * @param userId 指定用户(缺省用登录用户) */ @GetMapping("GetUserApp") - public List getUserApp(BoaUser authUserWithDept, Integer schId, + public JsonNode getUserApp(BoaUser authUserWithDept, Integer schId, @RequestParam(name = "userId", required = false) Integer userId) { - return appRemoteService.getUserApps(authUserWithDept); + return withIconKeys(appRemoteService.getUserApps(authUserWithDept)); + } + + /** + * 应用分组结果上合并注入矢量图标字段(iconKey/iconColor,AppIcon 资产库通道)。
+ * BoaApp 属 BasePlatform SDK 本期不动:SDK 反序列化会丢弃 icon-map 的扩展字段, + * 故序列化前经 JsonNode 平铺注入;icon-map 不可达时按空映射跳过,出参结构与原值完全一致(零回归)。 + */ + private JsonNode withIconKeys(List types) { + JsonNode root = objectMapper.valueToTree(types); + Map iconMap = appIconKeyService.iconMap(); + if (iconMap.isEmpty() || root == null || !root.isArray()) { + return root; + } + for (JsonNode type : root) { + JsonNode apps = type.get("apps"); + if (apps == null || !apps.isArray()) { + continue; + } + for (JsonNode app : apps) { + JsonNode idNode = app.get("id"); + if (idNode == null || !idNode.isNumber()) { + continue; + } + AppIconEntryVo entry = iconMap.get(idNode.asInt()); + if (entry != null) { + ((com.fasterxml.jackson.databind.node.ObjectNode) app) + .put("iconKey", entry.getIconKey()) + .put("iconColor", entry.getIconColor()); + } + } + } + return root; } /** 按应用ID取单个应用(在已拉取全集中查找,免额外跨仓请求)。 */ diff --git a/src/main/java/com/yunhai/userhome/controller/api/UserSettingsController.java b/src/main/java/com/yunhai/userhome/controller/api/UserSettingsController.java index f80b331..74ea814 100644 --- a/src/main/java/com/yunhai/userhome/controller/api/UserSettingsController.java +++ b/src/main/java/com/yunhai/userhome/controller/api/UserSettingsController.java @@ -14,6 +14,8 @@ import com.yunhai.userhome.entity.*; import com.yunhai.userhome.enums.DesktopAppType; import com.yunhai.userhome.enums.LightAppGroup; import com.yunhai.userhome.enums.LinkOpenType; +import com.yunhai.userhome.model.AppIconEntryVo; +import com.yunhai.userhome.service.AppIconKeyService; import com.yunhai.userhome.service.IUserCurrentRoleService; import com.yunhai.userhome.service.IUserSettingsService; import com.yunhai.userhome.service.UsedAppService; @@ -59,6 +61,9 @@ public class UserSettingsController { @Autowired private OpenApiUserClient openApiUserClient; + @Autowired + private AppIconKeyService appIconKeyService; + // ==================== 壁纸 ==================== /** @@ -376,6 +381,10 @@ public class UserSettingsController { map.put("type", DesktopAppType.Bsch.getKey()); map.put("linkType", LinkOpenType.Open.getKey()); map.put("layout", null); + // 矢量图标键(AppIcon 通道;未配置为 null,前端按空走原 img 回退) + AppIconEntryVo iconEntry = appIconKeyService.byId(optionalBoaApp.get().getId()); + map.put("iconKey", iconEntry == null ? null : iconEntry.getIconKey()); + map.put("iconColor", iconEntry == null ? null : iconEntry.getIconColor()); } else if (desktopApp.getAppType() == DesktopAppType.Light) { Optional optionalLightApp = lightAllList.stream() .filter(app -> desktopApp.getAppId().equals(app.getId())).findFirst(); diff --git a/src/main/java/com/yunhai/userhome/model/AppIconEntryVo.java b/src/main/java/com/yunhai/userhome/model/AppIconEntryVo.java new file mode 100644 index 0000000..e53bdd4 --- /dev/null +++ b/src/main/java/com/yunhai/userhome/model/AppIconEntryVo.java @@ -0,0 +1,42 @@ +package com.yunhai.userhome.model; + +/** + * UserBase 应用矢量图标映射条目({@code /oapi/app/icon-map} 出参镜像)。
+ * BoaApp 属 BasePlatform SDK 本期不动,PC 通道的 iconKey/iconColor 经本 VO 独立补拉后 + * 在 GetUserApp / 常用区出参上合并注入(加法式改造,不动既有 oapi 契约)。 + */ +public class AppIconEntryVo { + + /** 应用主键 ID(与 BoaApp.id 同源,按此合并) */ + private Integer id; + + /** 矢量图标键(AppIcon 资产库图标名) */ + private String iconKey; + + /** 矢量图标着色(CSS 色值);null = 端侧默认 */ + private String iconColor; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getIconKey() { + return iconKey; + } + + public void setIconKey(String iconKey) { + this.iconKey = iconKey; + } + + public String getIconColor() { + return iconColor; + } + + public void setIconColor(String iconColor) { + this.iconColor = iconColor; + } +} diff --git a/src/main/java/com/yunhai/userhome/service/AppIconKeyService.java b/src/main/java/com/yunhai/userhome/service/AppIconKeyService.java new file mode 100644 index 0000000..2b41498 --- /dev/null +++ b/src/main/java/com/yunhai/userhome/service/AppIconKeyService.java @@ -0,0 +1,79 @@ +package com.yunhai.userhome.service; + +import com.yunhai.platform.openapi.core.YschOpenCall; +import com.yunhai.userhome.model.AppIconEntryVo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 应用矢量图标映射补拉(AppIcon 资产库 P2/P3 数据通道)。
+ * UserBase 的 {@code GET /oapi/app/icon-map} 返回全量已配置行 [{id, iconKey, iconColor}], + * 本服务拉取后建 id 索引供各出参组装点合并注入(BoaApp 属 SDK 本期不动,矢量字段走本通道)。
+ *
    + *
  • 60 秒本地缓存:多个出参点(GetUserApp/常用区/搜索)一次渲染只打一次跨仓调用;
  • + *
  • 失败降级:返回上次成功值(无则空映射),绝不阻塞主应用列表——空映射下端侧 + * 走原 关键词→URL→首字 回退链,表现与改造前一致(零回归要求);
  • + *
  • 失败后 10 秒内不重试(简单防雪崩)。
  • + *
+ */ +@Service +public class AppIconKeyService { + + private static final Logger log = LoggerFactory.getLogger(AppIconKeyService.class); + + /** 服务目录目标名:userbase(与 UserController.TARGET_USERBASE 同口径) */ + private static final String TARGET_USERBASE = "userbase"; + + private static final String PATH_ICON_MAP = "/oapi/app/icon-map"; + + /** 成功结果缓存时长 */ + private static final long TTL_MS = 60_000L; + + /** 拉取失败后的静默期 */ + private static final long FAIL_BACKOFF_MS = 10_000L; + + @Autowired + private YschOpenCall openCall; + + private volatile Map cached = Collections.emptyMap(); + private volatile long fetchedAt = Long.MIN_VALUE; + + /** + * id → 矢量图标条目 映射(含缓存与降级)。空映射 = 无任何应用配置矢量键或通道不可达。 + */ + public Map iconMap() { + long now = System.currentTimeMillis(); + if (now - fetchedAt < TTL_MS) { + return cached; + } + try { + List list = openCall.getList(TARGET_USERBASE, PATH_ICON_MAP, null, + AppIconEntryVo.class); + Map map = (list == null) ? Collections.emptyMap() + : list.stream() + .filter(v -> v != null && v.getId() != null && v.getIconKey() != null) + .collect(Collectors.toMap(AppIconEntryVo::getId, Function.identity(), (a, b) -> a)); + cached = Collections.unmodifiableMap(map); + fetchedAt = now; + return cached; + } catch (Exception e) { + // 降级:沿用上次值;10 秒内不再重试。应用列表主链路绝不因此报错 + fetchedAt = now - TTL_MS + FAIL_BACKOFF_MS; + log.warn("[appicon] icon-map 拉取失败,按空映射降级:{}", e.toString()); + return cached; + } + } + + /** 按 id 取单条;无配置返回 null。 */ + public AppIconEntryVo byId(Integer appId) { + return (appId == null) ? null : iconMap().get(appId); + } +} diff --git a/src/main/java/com/yunhai/userhome/service/impl/UsedAppServiceImpl.java b/src/main/java/com/yunhai/userhome/service/impl/UsedAppServiceImpl.java index b8255d5..8724bb1 100644 --- a/src/main/java/com/yunhai/userhome/service/impl/UsedAppServiceImpl.java +++ b/src/main/java/com/yunhai/userhome/service/impl/UsedAppServiceImpl.java @@ -4,8 +4,10 @@ import com.yunhai.platform.utils.model.BoaApp; import com.yunhai.platform.utils.model.BoaAppType; import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.userhome.entity.UsedApp; +import com.yunhai.userhome.model.AppIconEntryVo; import com.yunhai.userhome.model.SetType; import com.yunhai.userhome.repository.UsedAppRepository; +import com.yunhai.userhome.service.AppIconKeyService; import com.yunhai.userhome.service.AppRemoteService; import com.yunhai.userhome.service.UsedAppService; import org.springframework.beans.factory.annotation.Autowired; @@ -34,6 +36,9 @@ public class UsedAppServiceImpl implements UsedAppService { @Autowired private AppRemoteService appRemoteService; + @Autowired + private AppIconKeyService appIconKeyService; + @Override public void save(UsedApp usedApp) { Optional model = usedAppRepository @@ -138,6 +143,7 @@ public class UsedAppServiceImpl implements UsedAppService { m.put("name", app.getName()); m.put("icon", app.getIcon()); m.put("url", app.getWebAddr()); + putIconKeys(m, app.getId()); list.add(m); } } catch (NumberFormatException ignore) { @@ -147,6 +153,13 @@ public class UsedAppServiceImpl implements UsedAppService { return list; } + /** 精简映射上补矢量图标键(iconKey/iconColor,AppIcon 通道;未配置时值为 null,前端按空走原回退链)。 */ + private void putIconKeys(Map m, Integer appId) { + AppIconEntryVo entry = appIconKeyService.byId(appId); + m.put("iconKey", entry == null ? null : entry.getIconKey()); + m.put("iconColor", entry == null ? null : entry.getIconColor()); + } + /** BoaAppType 分组平铺为 BoaApp 列表。 */ private List flattenApps(List types) { List apps = new ArrayList<>();