feat(appicon): 应用图标键下发出栏(YschAppIcons 10-04 轮 P2 后端)
build / build-push (push) Successful in 13m48s

- AppIconKeyService/AppIconEntryVo(新):icon_key/icon_color 资产库键值读取
- UserController:getUserApp 返回体加图标键注入(withIconKeys)
- UserSettingsController:用户设置应用列表注入 iconKey/iconColor(未配置 null 走原 img 回退)
- UsedAppController/UsedAppServiceImpl:常用应用列表同口径注入
依据:根目录《AppIcon资产库落地-执行报告-20261004.md》P2 数据侧/管理端
This commit is contained in:
chenxing
2026-10-06 20:21:04 +08:00
parent 564403b5ca
commit b621c959cf
6 changed files with 197 additions and 3 deletions
@@ -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 数据通道)。<br>
* UserBase 的 {@code GET /oapi/app/icon-map} 返回全量已配置行 [{id, iconKey, iconColor}],
* 本服务拉取后建 id 索引供各出参组装点合并注入(BoaApp 属 SDK 本期不动,矢量字段走本通道)。<br>
* <ul>
* <li>60 秒本地缓存:多个出参点(GetUserApp/常用区/搜索)一次渲染只打一次跨仓调用;</li>
* <li>失败降级:返回上次成功值(无则空映射),绝不阻塞主应用列表——空映射下端侧
* 走原 关键词→URL→首字 回退链,表现与改造前一致(零回归要求);</li>
* <li>失败后 10 秒内不重试(简单防雪崩)。</li>
* </ul>
*/
@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<Integer, AppIconEntryVo> cached = Collections.emptyMap();
private volatile long fetchedAt = Long.MIN_VALUE;
/**
* id → 矢量图标条目 映射(含缓存与降级)。空映射 = 无任何应用配置矢量键或通道不可达。
*/
public Map<Integer, AppIconEntryVo> iconMap() {
long now = System.currentTimeMillis();
if (now - fetchedAt < TTL_MS) {
return cached;
}
try {
List<AppIconEntryVo> list = openCall.getList(TARGET_USERBASE, PATH_ICON_MAP, null,
AppIconEntryVo.class);
Map<Integer, AppIconEntryVo> 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);
}
}
@@ -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<UsedApp> 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<String, Object> 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<BoaApp> flattenApps(List<BoaAppType> types) {
List<BoaApp> apps = new ArrayList<>();