package com.smppw.analysis.domain.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import com.smppw.analysis.domain.dao.FundInformationDao; import com.smppw.analysis.domain.dao.IndexesProfileDao; import com.smppw.analysis.domain.dao.RongzhiIndexNavDao; import com.smppw.analysis.domain.dataobject.FundSimilarDo; import com.smppw.analysis.domain.dataobject.MonetaryFundProfitDO; import com.smppw.analysis.domain.dto.info.FundSimilarParams; import com.smppw.analysis.domain.event.SaveCacheEvent; import com.smppw.analysis.domain.gateway.CacheFactory; import com.smppw.analysis.domain.gateway.CacheGateway; import com.smppw.analysis.domain.service.BaseInfoService; import com.smppw.analysis.infrastructure.config.AnalysisProperty; import com.smppw.analysis.infrastructure.consts.RedisConst; import com.smppw.common.cache.CaffeineLocalCache; import com.smppw.common.pojo.IStrategy; import com.smppw.common.pojo.enums.Frequency; import com.smppw.common.pojo.enums.Indicator; import com.smppw.common.pojo.enums.strategy.Strategy; import com.smppw.constants.SecType; import com.smppw.utils.StrategyHandleUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.lang.NonNull; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @Service public class BaseInfoServiceImpl implements BaseInfoService, ApplicationContextAware { public static final String HF = "HF"; public static final String MF = "MF"; public static final String CF = "CF"; public static final String FA = "FA"; public static final String CI = "CI"; public static final String CO = "CO"; public static final String PL = "PL"; public static final String PO = "PO"; public static final String IN = "IN"; public static final String AP = "AP"; private static final Map INDEX_EXIST = MapUtil.newConcurrentHashMap(); private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final CacheGateway cacheGateway; private final IndexesProfileDao indexesProfileDao; private final RongzhiIndexNavDao rongzhiIndexNavDao; private final FundInformationDao fundInformationDao; private ApplicationContext applicationContext; public BaseInfoServiceImpl(AnalysisProperty property, CacheFactory factory, IndexesProfileDao indexesProfileDao, RongzhiIndexNavDao rongzhiIndexNavDao, FundInformationDao fundInformationDao) { this.cacheGateway = factory.getCacheGateway(property.getCacheType()); this.indexesProfileDao = indexesProfileDao; this.rongzhiIndexNavDao = rongzhiIndexNavDao; this.fundInformationDao = fundInformationDao; } @Override public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public String getLatestRankRat() { // todo 排名期 return "2023-07"; } @Override public String getSecType(String secId) { if (secId == null) { return null; } if (secId.startsWith(HF)) { return SecType.PRIVATELY_OFFERED_FUND; } else if (secId.startsWith(MF)) { return SecType.PUBLICLY_OFFERED_FUNDS; } else if (secId.startsWith(CF)) { return SecType.PRIVATE_FUND; } else if (secId.startsWith(FA)) { return SecType.FACTOR; } else if (secId.startsWith(CI)) { return SecType.UDF_INDEX; } else if (secId.startsWith(CO)) { return SecType.COMPANY; } else if (secId.startsWith(PL)) { return SecType.MANAGER; } else if (secId.startsWith(PO)) { return SecType.COMBINATION; } else if (StrUtil.isNumeric(secId)) { if (Strategy.isStrategy(secId)) { return SecType.STRATEGY; } } else if (secId.startsWith(IN)) { List thirdIndexes = CaffeineLocalCache.getThirdIndexes(); if (thirdIndexes != null && thirdIndexes.contains(secId)) { return SecType.THIRD_INDEX_FUND; } List riskOfFreeIdList = CaffeineLocalCache.getRiskOfFreeId(); if (riskOfFreeIdList != null && riskOfFreeIdList.contains(secId)) { return SecType.RISK_OF_FREE; } Boolean isExist = INDEX_EXIST.get(secId); if (isExist == null) { isExist = rongzhiIndexNavDao.isExist(secId); INDEX_EXIST.put(secId, isExist); } if (isExist) { return SecType.INDEX_FUND; } else { return SecType.RONGZHI_INDEX; } } else if (secId.startsWith(AP)) { return SecType.ADVISORY_POOL_CURVE; } return null; } @Override public Map> getTypeSecMap(List secIdList) { Map> secIdTypeMap = new HashMap<>(10); for (String secId : secIdList) { String secIdType = getSecType(secId); if (secIdTypeMap.containsKey(secIdType)) { List list = secIdTypeMap.get(secIdType); list.add(secId); } else { List list = new ArrayList<>(); list.add(secId); secIdTypeMap.put(secIdType, list); } } return secIdTypeMap; } @Override public Map querySecsType(List secIdList) { if (CollUtil.isEmpty(secIdList)) { return MapUtil.newHashMap(8); } return secIdList.stream().collect(Collectors.toMap(e -> e, this::getSecType)); } @Override public Frequency getNavFrequency(String secId) { Frequency frequency; String fundType = this.getSecType(secId); if (SecType.MANAGER.equals(fundType) || SecType.COMPANY.equals(fundType)) { frequency = Frequency.Monthly; } else if (SecType.PRIVATELY_OFFERED_FUND.equals(fundType) || SecType.PUBLICLY_OFFERED_FUNDS.equals(fundType) || SecType.INDEX_FUND.equals(fundType) || SecType.RONGZHI_INDEX.equals(fundType)) { String freq = this.fundInformationDao.getNavFrequencyByFundId(secId); if ("天".equals(freq)) { frequency = Frequency.Daily; } else if ("周".equals(freq)) { frequency = Frequency.Weekly; } else { frequency = Frequency.Monthly; } } else { frequency = Frequency.Daily; } return frequency; } @Override public Map querySecName(List allSecIdList) { if (CollUtil.isEmpty(allSecIdList)) { return MapUtil.empty(); } int size = allSecIdList.size(); String key = RedisConst.INFO_NAME; Map hget = this.cacheGateway.hget(key); if (MapUtil.isEmpty(hget)) { hget = MapUtil.empty(); } Map> redisSecMap = allSecIdList.stream().collect(Collectors.groupingBy(hget::containsKey)); List redisSecIds = redisSecMap.getOrDefault(Boolean.TRUE, ListUtil.empty()); List noRedisSecIds = redisSecMap.getOrDefault(Boolean.FALSE, ListUtil.empty()); Map secNameMap = MapUtil.newHashMap(size, false); if (CollUtil.isNotEmpty(noRedisSecIds)) { Map> typeSecMap = this.getTypeSecMap(noRedisSecIds); // 市场基金 List marketFundIds = ListUtil.of(SecType.PRIVATELY_OFFERED_FUND, SecType.PUBLICLY_OFFERED_FUNDS); this.loadNameMap(secNameMap, typeSecMap, marketFundIds, this.fundInformationDao::getMarketFundIdNameMap); // 市场指数 List marketIndexIds = ListUtil.of(SecType.INDEX_FUND, SecType.RONGZHI_INDEX, SecType.THIRD_INDEX_FUND); this.loadNameMap(secNameMap, typeSecMap, marketIndexIds, this.indexesProfileDao::getFundIdNameMap); // 推送事件,存缓存 SaveCacheEvent> event = new SaveCacheEvent<>(key, secNameMap, t -> { this.cacheGateway.hset(key, t); return this.cacheGateway.expire(key, 1, TimeUnit.DAYS); }); this.applicationContext.publishEvent(event); } // 解决乱序问题 Map result = MapUtil.newHashMap(size, true); for (String secId : allSecIdList) { String name = redisSecIds.contains(secId) ? MapUtil.getStr(hget, secId) : MapUtil.getStr(secNameMap, secId); result.put(secId, name); } return result; } @Override public List queryMonetaryFund(String fundId) { return this.fundInformationDao.queryMonetaryFund(fundId); } @Override public List> getFundRank(String rankDate, String fundId, List indexIds, Indicator indicator) { return this.fundInformationDao.getFundRank(rankDate, fundId, indexIds, indicator); } @Override public List getFundSimilarList(FundSimilarParams params) { if (StrUtil.isBlank(params.getThreshold()) || !NumberUtil.isNumber(params.getThreshold())) { logger.warn(String.format("相关性阈值 %s 设置错误,提供默认值:%s", params.getThreshold(), FundSimilarParams.DEFAULT_THRESHOLD)); params.setThreshold(FundSimilarParams.DEFAULT_THRESHOLD); } IStrategy strategy = StrategyHandleUtils.getStrategy(params.getStrategy()); String rankDate = this.getLatestRankRat(); List tempList = ListUtil.list(true); if (params.getCalcType() == 2) { Map req = MapUtil.builder().put("strategy", strategy.getStrategyId()) .put("tableName", "rz_hfdb_core.nav").put("trustId", params.getTrustId()).put("rankDate", rankDate).build(); tempList.addAll(this.fundInformationDao.getSameCompanyFundIds(req)); } else { Map req = MapUtil.builder().put("strategy", strategy.getStrategyId()).put("rankNum", 50) .put("tableName", "rz_hfdb_core.fund_indicator_ranking").put("fundId", params.getRefId()).put("rankDate", rankDate).build(); tempList.addAll(this.fundInformationDao.getSameStrategyFundIds(req)); } List dataList = ListUtil.list(true); // 过滤当前基金 dataList.addAll(tempList.stream().filter(e -> !params.getRefId().equals(e.getFundId())).collect(Collectors.toList())); return dataList; } /** * 把指定类型的标的的名称映射查询出来 * * @param secNameMap 保存的map * @param typeSecMap 指定类型对应的标的 * @param types 指定类型列表 * @param function 查询操作封装 */ private void loadNameMap(Map secNameMap, Map> typeSecMap, List types, Function, Map> function) { List refIds = ListUtil.list(true); for (String type : types) { CollUtil.addAllIfNotContains(refIds, typeSecMap.getOrDefault(type, ListUtil.empty())); } if (CollUtil.isNotEmpty(refIds)) { secNameMap.putAll(function.apply(refIds)); } } }