feat: use tencent cos for uploads and add aslan stats
This commit is contained in:
parent
52658d5d65
commit
24e1af1841
@ -289,6 +289,7 @@
|
|||||||
|
|
||||||
<tencentcloud-cls-logback-appender.version>1.0.4</tencentcloud-cls-logback-appender.version>
|
<tencentcloud-cls-logback-appender.version>1.0.4</tencentcloud-cls-logback-appender.version>
|
||||||
<tencentcloud.version>3.1.877</tencentcloud.version>
|
<tencentcloud.version>3.1.877</tencentcloud.version>
|
||||||
|
<cos_api.version>5.6.151</cos_api.version>
|
||||||
<opentelemetry-logback.version>1.9.2-alpha</opentelemetry-logback.version>
|
<opentelemetry-logback.version>1.9.2-alpha</opentelemetry-logback.version>
|
||||||
<protobuf-java.version>3.5.1</protobuf-java.version>
|
<protobuf-java.version>3.5.1</protobuf-java.version>
|
||||||
<easyexcel.version>3.1.3</easyexcel.version>
|
<easyexcel.version>3.1.3</easyexcel.version>
|
||||||
|
|||||||
@ -36,3 +36,13 @@ management:
|
|||||||
enabled: true
|
enabled: true
|
||||||
health:
|
health:
|
||||||
show-details: always
|
show-details: always
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
ignorePaths:
|
||||||
|
- /console/account/login
|
||||||
|
- /console/actuator/**
|
||||||
|
- /console/datav/active/user-country-code
|
||||||
|
- /console/datav/online/user/count
|
||||||
|
- /console/datav/online/room/count
|
||||||
|
- /console/datav/aslan/region-country/statistics
|
||||||
|
- /console/user/base/info/im/sig
|
||||||
|
|||||||
@ -2,7 +2,11 @@ package com.red.circle.external.inner.endpoint.oss.api;
|
|||||||
|
|
||||||
import com.red.circle.framework.dto.ResultResponse;
|
import com.red.circle.framework.dto.ResultResponse;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* oss 服务api.
|
* oss 服务api.
|
||||||
@ -19,6 +23,13 @@ public interface OssServiceClientApi {
|
|||||||
@GetMapping("/sts")
|
@GetMapping("/sts")
|
||||||
ResultResponse<Object> sts();
|
ResultResponse<Object> sts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件.
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
|
ResultResponse<String> upload(@RequestPart("file") MultipartFile file,
|
||||||
|
@RequestParam("key") String key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除文件.
|
* 移除文件.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -2,15 +2,21 @@ package com.red.circle.console.adapter.app.party3rd;//package com.sugartime.app.
|
|||||||
|
|
||||||
import com.red.circle.external.inner.endpoint.oss.OssServiceClient;
|
import com.red.circle.external.inner.endpoint.oss.OssServiceClient;
|
||||||
import com.red.circle.framework.core.asserts.ResponseAssert;
|
import com.red.circle.framework.core.asserts.ResponseAssert;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阿里云服务.
|
* 对象存储服务.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -21,11 +27,25 @@ public class AliYunRestController {
|
|||||||
private final OssServiceClient ossServiceClient;
|
private final OssServiceClient ossServiceClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阿里云sts.
|
* 对象存储上传配置.
|
||||||
*/
|
*/
|
||||||
@GetMapping("/oss/sts")
|
@GetMapping("/oss/sts")
|
||||||
public Object sts() {
|
public Object sts() {
|
||||||
return ResponseAssert.requiredSuccess(ossServiceClient.sts());
|
return ResponseAssert.requiredSuccess(ossServiceClient.sts());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件.
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/oss/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
|
public Map<String, Object> upload(@RequestPart("file") MultipartFile file,
|
||||||
|
@RequestParam("key") String key) {
|
||||||
|
String url = ResponseAssert.requiredSuccess(ossServiceClient.upload(file, key));
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("name", key);
|
||||||
|
result.put("url", url);
|
||||||
|
result.put("res", Map.of("status", 200));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
package com.red.circle.console.adapter.app.user;
|
package com.red.circle.console.adapter.app.user;
|
||||||
|
|
||||||
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||||
|
import com.red.circle.console.app.dto.clienobject.count.aslan.AslanRegionCountryStatisticsCO;
|
||||||
|
import com.red.circle.console.app.service.app.count.AslanRegionCountryStatisticsService;
|
||||||
import com.red.circle.console.app.service.app.user.DatavService;
|
import com.red.circle.console.app.service.app.user.DatavService;
|
||||||
import com.red.circle.framework.web.controller.BaseController;
|
import com.red.circle.framework.web.controller.BaseController;
|
||||||
import com.red.circle.other.inner.model.cmd.user.OnlineCountQryCmd;
|
import com.red.circle.other.inner.model.cmd.user.OnlineCountQryCmd;
|
||||||
import com.red.circle.other.inner.model.dto.user.ActiveUserCountryCodeDTO;
|
import com.red.circle.other.inner.model.dto.user.ActiveUserCountryCodeDTO;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -25,6 +29,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
public class DatavRestController extends BaseController {
|
public class DatavRestController extends BaseController {
|
||||||
|
|
||||||
private final DatavService userExpandService;
|
private final DatavService userExpandService;
|
||||||
|
private final AslanRegionCountryStatisticsService aslanRegionCountryStatisticsService;
|
||||||
|
|
||||||
@GetMapping("/active/user-country-code")
|
@GetMapping("/active/user-country-code")
|
||||||
public List<ActiveUserCountryCodeDTO> latestActiveUserCountryCode() {
|
public List<ActiveUserCountryCodeDTO> latestActiveUserCountryCode() {
|
||||||
@ -41,4 +46,11 @@ public class DatavRestController extends BaseController {
|
|||||||
return Objects.toString(userExpandService.count(sysOrigin));
|
return Objects.toString(userExpandService.count(sysOrigin));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/aslan/region-country/statistics")
|
||||||
|
public List<AslanRegionCountryStatisticsCO> aslanRegionCountryStatistics(
|
||||||
|
@RequestParam(value = "date", required = false)
|
||||||
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
|
||||||
|
return aslanRegionCountryStatisticsService.listRegionCountryStatistics(date);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,367 @@
|
|||||||
|
package com.red.circle.console.app.service.app.count;
|
||||||
|
|
||||||
|
import static org.springframework.data.mongodb.core.aggregation.Aggregation.group;
|
||||||
|
import static org.springframework.data.mongodb.core.aggregation.Aggregation.match;
|
||||||
|
import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
|
||||||
|
import static org.springframework.data.mongodb.core.aggregation.Aggregation.project;
|
||||||
|
|
||||||
|
import com.mongodb.BasicDBObject;
|
||||||
|
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||||
|
import com.red.circle.console.app.dto.clienobject.count.aslan.AslanCountryStatisticsCO;
|
||||||
|
import com.red.circle.console.app.dto.clienobject.count.aslan.AslanRegionCountryStatisticsCO;
|
||||||
|
import com.red.circle.console.app.service.app.sys.SysCountryCodeService;
|
||||||
|
import com.red.circle.console.infra.database.rds.dao.count.AslanRegionCountryStatisticsDAO;
|
||||||
|
import com.red.circle.console.infra.database.rds.dto.count.CountryAmountMetricDTO;
|
||||||
|
import com.red.circle.console.infra.database.rds.dto.count.CountryLongMetricDTO;
|
||||||
|
import com.red.circle.console.infra.database.rds.dto.count.UserCountryMetricDTO;
|
||||||
|
import com.red.circle.framework.core.asserts.ResponseAssert;
|
||||||
|
import com.red.circle.other.inner.endpoint.user.region.RegionConfigClient;
|
||||||
|
import com.red.circle.other.inner.model.cmd.user.SysRegionConfigQryCmd;
|
||||||
|
import com.red.circle.other.inner.model.dto.sys.SysCountryCodeDTO;
|
||||||
|
import com.red.circle.other.inner.model.dto.user.reigon.RegionConfigDTO;
|
||||||
|
import com.red.circle.tool.core.collection.CollectionUtils;
|
||||||
|
import com.red.circle.tool.core.text.StringUtils;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||||
|
import org.springframework.data.mongodb.core.query.Criteria;
|
||||||
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aslan 区域国家统计.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AslanRegionCountryStatisticsServiceImpl implements AslanRegionCountryStatisticsService {
|
||||||
|
|
||||||
|
private static final String DAILY_ACTIVE_COLLECTION = "user_daily_active_log";
|
||||||
|
private static final String TEAM_MEMBER_COLLECTION = "team_member";
|
||||||
|
private static final String BANK_BALANCE_COLLECTION = "user_bank_balance";
|
||||||
|
private static final Pattern COUNTRY_CODE_PATTERN = Pattern.compile("[A-Za-z0-9_]+");
|
||||||
|
private static final ZoneId ASIA_SHANGHAI = ZoneId.of("Asia/Shanghai");
|
||||||
|
private static final DateTimeFormatter DATE_NUMBER_FORMATTER = DateTimeFormatter.BASIC_ISO_DATE;
|
||||||
|
private static final int USER_ID_CHUNK_SIZE = 1000;
|
||||||
|
|
||||||
|
private final MongoTemplate mongoTemplate;
|
||||||
|
private final RegionConfigClient regionConfigClient;
|
||||||
|
private final SysCountryCodeService sysCountryCodeService;
|
||||||
|
private final AslanRegionCountryStatisticsDAO statisticsDAO;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AslanRegionCountryStatisticsCO> listRegionCountryStatistics(LocalDate date) {
|
||||||
|
LocalDate statDate = Objects.requireNonNullElseGet(date,
|
||||||
|
() -> LocalDate.now(ASIA_SHANGHAI));
|
||||||
|
String sysOrigin = SysOriginPlatformEnum.ATYOU.name();
|
||||||
|
var startTime = statDate.atStartOfDay();
|
||||||
|
var endTime = statDate.plusDays(1).atStartOfDay();
|
||||||
|
Integer dateNumber = Integer.parseInt(statDate.format(DATE_NUMBER_FORMATTER));
|
||||||
|
|
||||||
|
List<RegionConfigDTO> regions = listRegionConfigs(sysOrigin);
|
||||||
|
if (CollectionUtils.isEmpty(regions)) {
|
||||||
|
return CollectionUtils.newArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, SysCountryCodeDTO> countryMap = mapCountryCode();
|
||||||
|
Map<String, BigDecimal> rechargeMap = mapAmount(
|
||||||
|
statisticsDAO.listDailyRechargeByCountry(sysOrigin, dateNumber, startTime, endTime));
|
||||||
|
Map<String, Long> registerMap = mapLong(
|
||||||
|
statisticsDAO.listDailyRegisterByCountry(sysOrigin, startTime, endTime));
|
||||||
|
Map<String, Long> goldBalanceMap = mapLong(statisticsDAO.listGoldBalanceByCountry(sysOrigin));
|
||||||
|
Map<String, Long> dailyActiveMap = mapDailyActive(sysOrigin, statDate);
|
||||||
|
Map<String, BigDecimal> hostSalaryBalanceMap = mapHostSalaryBalance(sysOrigin);
|
||||||
|
|
||||||
|
return regions.stream()
|
||||||
|
.sorted(Comparator.comparing(RegionConfigDTO::getCreateTime,
|
||||||
|
Comparator.nullsLast(Comparator.naturalOrder())).reversed())
|
||||||
|
.map(region -> buildRegionStatistics(statDate, region, countryMap, rechargeMap,
|
||||||
|
registerMap, goldBalanceMap, dailyActiveMap, hostSalaryBalanceMap))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RegionConfigDTO> listRegionConfigs(String sysOrigin) {
|
||||||
|
SysRegionConfigQryCmd query = new SysRegionConfigQryCmd();
|
||||||
|
query.setSysOrigin(sysOrigin);
|
||||||
|
return Optional.ofNullable(ResponseAssert.requiredSuccess(regionConfigClient.listByCondition(query)))
|
||||||
|
.orElseGet(CollectionUtils::newArrayList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, SysCountryCodeDTO> mapCountryCode() {
|
||||||
|
List<SysCountryCodeDTO> countries = sysCountryCodeService.listContent();
|
||||||
|
if (CollectionUtils.isEmpty(countries)) {
|
||||||
|
return CollectionUtils.newHashMap();
|
||||||
|
}
|
||||||
|
return countries.stream()
|
||||||
|
.filter(country -> StringUtils.isNotBlank(country.getAlphaTwo()))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
country -> normalizeCountryCode(country.getAlphaTwo()),
|
||||||
|
Function.identity(),
|
||||||
|
(left, right) -> left));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AslanRegionCountryStatisticsCO buildRegionStatistics(
|
||||||
|
LocalDate statDate,
|
||||||
|
RegionConfigDTO region,
|
||||||
|
Map<String, SysCountryCodeDTO> countryMap,
|
||||||
|
Map<String, BigDecimal> rechargeMap,
|
||||||
|
Map<String, Long> registerMap,
|
||||||
|
Map<String, Long> goldBalanceMap,
|
||||||
|
Map<String, Long> dailyActiveMap,
|
||||||
|
Map<String, BigDecimal> hostSalaryBalanceMap) {
|
||||||
|
|
||||||
|
List<AslanCountryStatisticsCO> countries = parseCountryCodes(region.getCountryCodes()).stream()
|
||||||
|
.map(countryCode -> buildCountryStatistics(countryCode, countryMap, rechargeMap,
|
||||||
|
registerMap, goldBalanceMap, dailyActiveMap, hostSalaryBalanceMap))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
BigDecimal regionRecharge = countries.stream()
|
||||||
|
.map(AslanCountryStatisticsCO::getDailyRecharge)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
Long regionActive = countries.stream().mapToLong(AslanCountryStatisticsCO::getDailyActive).sum();
|
||||||
|
Long regionRegister = countries.stream().mapToLong(AslanCountryStatisticsCO::getDailyRegister)
|
||||||
|
.sum();
|
||||||
|
Long regionGoldBalance = countries.stream().mapToLong(AslanCountryStatisticsCO::getGoldBalance)
|
||||||
|
.sum();
|
||||||
|
BigDecimal regionHostSalaryBalance = countries.stream()
|
||||||
|
.map(AslanCountryStatisticsCO::getHostSalaryBalance)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|
||||||
|
return new AslanRegionCountryStatisticsCO()
|
||||||
|
.setDate(statDate.toString())
|
||||||
|
.setRegionId(region.getId())
|
||||||
|
.setRegionCode(region.getRegionCode())
|
||||||
|
.setRegionName(region.getRegionName())
|
||||||
|
.setDailyRecharge(regionRecharge)
|
||||||
|
.setDailyActive(regionActive)
|
||||||
|
.setDailyRegister(regionRegister)
|
||||||
|
.setGoldBalance(regionGoldBalance)
|
||||||
|
.setHostSalaryBalance(regionHostSalaryBalance)
|
||||||
|
.setCountries(countries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AslanCountryStatisticsCO buildCountryStatistics(
|
||||||
|
String countryCode,
|
||||||
|
Map<String, SysCountryCodeDTO> countryMap,
|
||||||
|
Map<String, BigDecimal> rechargeMap,
|
||||||
|
Map<String, Long> registerMap,
|
||||||
|
Map<String, Long> goldBalanceMap,
|
||||||
|
Map<String, Long> dailyActiveMap,
|
||||||
|
Map<String, BigDecimal> hostSalaryBalanceMap) {
|
||||||
|
|
||||||
|
SysCountryCodeDTO country = countryMap.get(countryCode);
|
||||||
|
return new AslanCountryStatisticsCO()
|
||||||
|
.setCountryCode(countryCode)
|
||||||
|
.setCountryName(Optional.ofNullable(country)
|
||||||
|
.map(SysCountryCodeDTO::getCountryName)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.orElse(countryCode))
|
||||||
|
.setDailyRecharge(rechargeMap.getOrDefault(countryCode, BigDecimal.ZERO))
|
||||||
|
.setDailyActive(dailyActiveMap.getOrDefault(countryCode, 0L))
|
||||||
|
.setDailyRegister(registerMap.getOrDefault(countryCode, 0L))
|
||||||
|
.setGoldBalance(goldBalanceMap.getOrDefault(countryCode, 0L))
|
||||||
|
.setHostSalaryBalance(hostSalaryBalanceMap.getOrDefault(countryCode, BigDecimal.ZERO));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, BigDecimal> mapAmount(List<CountryAmountMetricDTO> metrics) {
|
||||||
|
if (CollectionUtils.isEmpty(metrics)) {
|
||||||
|
return CollectionUtils.newHashMap();
|
||||||
|
}
|
||||||
|
return metrics.stream()
|
||||||
|
.filter(metric -> StringUtils.isNotBlank(metric.getCountryCode()))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
metric -> normalizeCountryCode(metric.getCountryCode()),
|
||||||
|
metric -> Optional.ofNullable(metric.getAmount()).orElse(BigDecimal.ZERO),
|
||||||
|
BigDecimal::add));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Long> mapLong(List<CountryLongMetricDTO> metrics) {
|
||||||
|
if (CollectionUtils.isEmpty(metrics)) {
|
||||||
|
return CollectionUtils.newHashMap();
|
||||||
|
}
|
||||||
|
return metrics.stream()
|
||||||
|
.filter(metric -> StringUtils.isNotBlank(metric.getCountryCode()))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
metric -> normalizeCountryCode(metric.getCountryCode()),
|
||||||
|
metric -> Optional.ofNullable(metric.getQuantity()).orElse(0L),
|
||||||
|
Long::sum));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Long> mapDailyActive(String sysOrigin, LocalDate statDate) {
|
||||||
|
var aggregation = newAggregation(
|
||||||
|
match(Criteria.where("activeDate").is(statDate.toString()).and("sysOrigin").is(sysOrigin)),
|
||||||
|
group("registerCountryCode").count().as("quantity"),
|
||||||
|
project("quantity").and("_id").as("countryCode")
|
||||||
|
);
|
||||||
|
|
||||||
|
var results = mongoTemplate.aggregate(aggregation, DAILY_ACTIVE_COLLECTION, BasicDBObject.class)
|
||||||
|
.getMappedResults();
|
||||||
|
if (CollectionUtils.isEmpty(results)) {
|
||||||
|
return CollectionUtils.newHashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.stream()
|
||||||
|
.filter(result -> StringUtils.isNotBlank(result.getString("countryCode")))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
result -> normalizeCountryCode(result.getString("countryCode")),
|
||||||
|
result -> Optional.ofNullable(result.get("quantity"))
|
||||||
|
.map(Number.class::cast)
|
||||||
|
.map(Number::longValue)
|
||||||
|
.orElse(0L),
|
||||||
|
Long::sum));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, BigDecimal> mapHostSalaryBalance(String sysOrigin) {
|
||||||
|
Set<Long> anchorUserIds = listAnchorUserIds(sysOrigin);
|
||||||
|
if (CollectionUtils.isEmpty(anchorUserIds)) {
|
||||||
|
return CollectionUtils.newHashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Long, Long> bankBalanceMap = mapBankBalance(sysOrigin, anchorUserIds);
|
||||||
|
if (CollectionUtils.isEmpty(bankBalanceMap)) {
|
||||||
|
return CollectionUtils.newHashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Long, String> userCountryMap = mapUserCountry(sysOrigin, bankBalanceMap.keySet());
|
||||||
|
if (CollectionUtils.isEmpty(userCountryMap)) {
|
||||||
|
return CollectionUtils.newHashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BigDecimal> result = CollectionUtils.newHashMap();
|
||||||
|
bankBalanceMap.forEach((userId, balance) -> {
|
||||||
|
String countryCode = userCountryMap.get(userId);
|
||||||
|
if (StringUtils.isBlank(countryCode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.merge(countryCode, pennyToDollar(balance), BigDecimal::add);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Long> listAnchorUserIds(String sysOrigin) {
|
||||||
|
Query query = Query.query(Criteria.where("sysOrigin").is(sysOrigin)
|
||||||
|
.and("role").in("MEMBER", "ADMIN"));
|
||||||
|
query.fields().include("memberId");
|
||||||
|
|
||||||
|
List<BasicDBObject> members = mongoTemplate.find(query, BasicDBObject.class,
|
||||||
|
TEAM_MEMBER_COLLECTION);
|
||||||
|
if (CollectionUtils.isEmpty(members)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
return members.stream()
|
||||||
|
.map(member -> numberToLong(member.get("memberId")))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, Long> mapBankBalance(String sysOrigin, Set<Long> userIds) {
|
||||||
|
Map<Long, Long> result = CollectionUtils.newHashMap();
|
||||||
|
for (List<Long> chunk : partitionUserIds(userIds)) {
|
||||||
|
Query query = Query.query(Criteria.where("sysOrigin").is(sysOrigin).and("_id").in(chunk));
|
||||||
|
query.fields().include("_id").include("balance");
|
||||||
|
|
||||||
|
List<BasicDBObject> balances = mongoTemplate.find(query, BasicDBObject.class,
|
||||||
|
BANK_BALANCE_COLLECTION);
|
||||||
|
if (CollectionUtils.isEmpty(balances)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
balances.forEach(balance -> {
|
||||||
|
Long userId = numberToLong(balance.get("_id"));
|
||||||
|
Long amount = numberToLong(balance.get("balance"));
|
||||||
|
if (Objects.nonNull(userId) && Objects.nonNull(amount)) {
|
||||||
|
result.merge(userId, amount, Long::sum);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, String> mapUserCountry(String sysOrigin, Set<Long> userIds) {
|
||||||
|
Map<Long, String> result = CollectionUtils.newHashMap();
|
||||||
|
for (List<Long> chunk : partitionUserIds(userIds)) {
|
||||||
|
List<UserCountryMetricDTO> users = statisticsDAO.listUserCountryByIds(sysOrigin, chunk);
|
||||||
|
if (CollectionUtils.isEmpty(users)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
users.stream()
|
||||||
|
.filter(user -> Objects.nonNull(user.getUserId()))
|
||||||
|
.filter(user -> StringUtils.isNotBlank(user.getCountryCode()))
|
||||||
|
.forEach(user -> result.put(user.getUserId(), normalizeCountryCode(user.getCountryCode())));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<List<Long>> partitionUserIds(Set<Long> userIds) {
|
||||||
|
if (CollectionUtils.isEmpty(userIds)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> ids = userIds.stream().filter(Objects::nonNull).distinct().toList();
|
||||||
|
List<List<Long>> partitions = new ArrayList<>();
|
||||||
|
for (int index = 0; index < ids.size(); index += USER_ID_CHUNK_SIZE) {
|
||||||
|
partitions.add(ids.subList(index, Math.min(index + USER_ID_CHUNK_SIZE, ids.size())));
|
||||||
|
}
|
||||||
|
return partitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal pennyToDollar(Long pennyAmount) {
|
||||||
|
if (Objects.isNull(pennyAmount)) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
return BigDecimal.valueOf(pennyAmount).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long numberToLong(Object value) {
|
||||||
|
if (value instanceof Number number) {
|
||||||
|
return number.longValue();
|
||||||
|
}
|
||||||
|
if (Objects.isNull(value) || StringUtils.isBlank(value.toString())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Long.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> parseCountryCodes(String countryCodes) {
|
||||||
|
if (StringUtils.isBlank(countryCodes)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> result = new LinkedHashSet<>();
|
||||||
|
Matcher matcher = COUNTRY_CODE_PATTERN.matcher(countryCodes);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String code = normalizeCountryCode(matcher.group());
|
||||||
|
if (StringUtils.isNotBlank(code)) {
|
||||||
|
result.add(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableSet(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeCountryCode(String countryCode) {
|
||||||
|
return StringUtils.isBlank(countryCode)
|
||||||
|
? ""
|
||||||
|
: countryCode.trim().toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package com.red.circle.console.app.dto.clienobject.count.aslan;
|
||||||
|
|
||||||
|
import com.red.circle.framework.dto.DTO;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aslan 国家统计.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class AslanCountryStatisticsCO extends DTO {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国家编码.
|
||||||
|
*/
|
||||||
|
private String countryCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国家名称.
|
||||||
|
*/
|
||||||
|
private String countryName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每日充值.
|
||||||
|
*/
|
||||||
|
private BigDecimal dailyRecharge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日活.
|
||||||
|
*/
|
||||||
|
private Long dailyActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每日新注册.
|
||||||
|
*/
|
||||||
|
private Long dailyRegister;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前金币总量.
|
||||||
|
*/
|
||||||
|
private Long goldBalance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主播当前工资余额.
|
||||||
|
*/
|
||||||
|
private BigDecimal hostSalaryBalance;
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
package com.red.circle.console.app.dto.clienobject.count.aslan;
|
||||||
|
|
||||||
|
import com.red.circle.framework.dto.DTO;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aslan 区域国家统计.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class AslanRegionCountryStatisticsCO extends DTO {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计日期.
|
||||||
|
*/
|
||||||
|
private String date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域ID.
|
||||||
|
*/
|
||||||
|
private String regionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域编码.
|
||||||
|
*/
|
||||||
|
private String regionCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域名称.
|
||||||
|
*/
|
||||||
|
private String regionName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域每日充值合计.
|
||||||
|
*/
|
||||||
|
private BigDecimal dailyRecharge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域日活合计.
|
||||||
|
*/
|
||||||
|
private Long dailyActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域每日新注册合计.
|
||||||
|
*/
|
||||||
|
private Long dailyRegister;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域当前金币总量.
|
||||||
|
*/
|
||||||
|
private Long goldBalance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域主播当前工资余额.
|
||||||
|
*/
|
||||||
|
private BigDecimal hostSalaryBalance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域下国家统计.
|
||||||
|
*/
|
||||||
|
private List<AslanCountryStatisticsCO> countries;
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.red.circle.console.app.service.app.count;
|
||||||
|
|
||||||
|
import com.red.circle.console.app.dto.clienobject.count.aslan.AslanRegionCountryStatisticsCO;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aslan 区域国家统计.
|
||||||
|
*/
|
||||||
|
public interface AslanRegionCountryStatisticsService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定日期的区域国家聚合数据.
|
||||||
|
*/
|
||||||
|
List<AslanRegionCountryStatisticsCO> listRegionCountryStatistics(LocalDate date);
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package com.red.circle.console.infra.database.rds.dao.count;
|
||||||
|
|
||||||
|
import com.red.circle.console.infra.database.rds.dto.count.CountryAmountMetricDTO;
|
||||||
|
import com.red.circle.console.infra.database.rds.dto.count.CountryLongMetricDTO;
|
||||||
|
import com.red.circle.console.infra.database.rds.dto.count.UserCountryMetricDTO;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aslan 区域国家统计 Mapper.
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AslanRegionCountryStatisticsDAO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每日充值,包含订单、后台离线充值和币商充值.
|
||||||
|
*/
|
||||||
|
List<CountryAmountMetricDTO> listDailyRechargeByCountry(
|
||||||
|
@Param("sysOrigin") String sysOrigin,
|
||||||
|
@Param("dateNumber") Integer dateNumber,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每日新注册.
|
||||||
|
*/
|
||||||
|
List<CountryLongMetricDTO> listDailyRegisterByCountry(
|
||||||
|
@Param("sysOrigin") String sysOrigin,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前金币总量.
|
||||||
|
*/
|
||||||
|
List<CountryLongMetricDTO> listGoldBalanceByCountry(@Param("sysOrigin") String sysOrigin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询用户国家.
|
||||||
|
*/
|
||||||
|
List<UserCountryMetricDTO> listUserCountryByIds(
|
||||||
|
@Param("sysOrigin") String sysOrigin,
|
||||||
|
@Param("userIds") List<Long> userIds);
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.red.circle.console.infra.database.rds.dto.count;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国家金额指标.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CountryAmountMetricDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国家编码.
|
||||||
|
*/
|
||||||
|
private String countryCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金额.
|
||||||
|
*/
|
||||||
|
private BigDecimal amount;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.red.circle.console.infra.database.rds.dto.count;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国家数值指标.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CountryLongMetricDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国家编码.
|
||||||
|
*/
|
||||||
|
private String countryCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数量.
|
||||||
|
*/
|
||||||
|
private Long quantity;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.red.circle.console.infra.database.rds.dto.count;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户国家指标.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserCountryMetricDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID.
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国家编码.
|
||||||
|
*/
|
||||||
|
private String countryCode;
|
||||||
|
}
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper
|
||||||
|
namespace="com.red.circle.console.infra.database.rds.dao.count.AslanRegionCountryStatisticsDAO">
|
||||||
|
|
||||||
|
<select id="listDailyRechargeByCountry"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.count.CountryAmountMetricDTO">
|
||||||
|
SELECT countryCode,
|
||||||
|
SUM(amount) AS amount
|
||||||
|
FROM (
|
||||||
|
SELECT UPPER(ubi.country_code) AS countryCode,
|
||||||
|
SUM(oph.unit_price) AS amount
|
||||||
|
FROM order_purchase_history oph
|
||||||
|
INNER JOIN user_base_info ubi ON ubi.id = oph.user_id
|
||||||
|
WHERE oph.sys_origin = #{sysOrigin}
|
||||||
|
AND oph.evn = 'PROD'
|
||||||
|
AND oph.status = 'COMPLETE'
|
||||||
|
AND IFNULL(oph.is_trial_period, 0) = 0
|
||||||
|
AND oph.update_time >= #{startTime}
|
||||||
|
AND oph.update_time < #{endTime}
|
||||||
|
GROUP BY UPPER(ubi.country_code)
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT UPPER(ubi.country_code) AS countryCode,
|
||||||
|
SUM(oor.amount) AS amount
|
||||||
|
FROM order_other_recharge oor
|
||||||
|
INNER JOIN user_base_info ubi ON ubi.id = oor.user_Id
|
||||||
|
WHERE oor.sys_origin = #{sysOrigin}
|
||||||
|
AND oor.payment_date_number = #{dateNumber}
|
||||||
|
GROUP BY UPPER(ubi.country_code)
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT UPPER(ubi.country_code) AS countryCode,
|
||||||
|
SUM(ugcr.recharge_amount) AS amount
|
||||||
|
FROM user_gold_coin_recharge ugcr
|
||||||
|
INNER JOIN user_base_info ubi ON ubi.id = ugcr.user_id
|
||||||
|
WHERE ugcr.sys_origin = #{sysOrigin}
|
||||||
|
AND ugcr.gold_coin_source = 2
|
||||||
|
AND ugcr.create_time >= #{startTime}
|
||||||
|
AND ugcr.create_time < #{endTime}
|
||||||
|
GROUP BY UPPER(ubi.country_code)
|
||||||
|
) recharge
|
||||||
|
WHERE countryCode IS NOT NULL
|
||||||
|
AND countryCode != ''
|
||||||
|
GROUP BY countryCode
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listDailyRegisterByCountry"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.count.CountryLongMetricDTO">
|
||||||
|
SELECT UPPER(country_code) AS countryCode,
|
||||||
|
COUNT(1) AS quantity
|
||||||
|
FROM user_base_info
|
||||||
|
WHERE origin_sys = #{sysOrigin}
|
||||||
|
AND create_time >= #{startTime}
|
||||||
|
AND create_time < #{endTime}
|
||||||
|
AND country_code IS NOT NULL
|
||||||
|
AND country_code != ''
|
||||||
|
GROUP BY UPPER(country_code)
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listGoldBalanceByCountry"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.count.CountryLongMetricDTO">
|
||||||
|
SELECT UPPER(ubi.country_code) AS countryCode,
|
||||||
|
SUM(GREATEST(wgb.income - wgb.expenditure, 0)) AS quantity
|
||||||
|
FROM wallet_gold_balance wgb
|
||||||
|
INNER JOIN user_base_info ubi ON ubi.id = wgb.id
|
||||||
|
WHERE wgb.sys_origin = #{sysOrigin}
|
||||||
|
AND ubi.origin_sys = #{sysOrigin}
|
||||||
|
AND ubi.country_code IS NOT NULL
|
||||||
|
AND ubi.country_code != ''
|
||||||
|
GROUP BY UPPER(ubi.country_code)
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listUserCountryByIds"
|
||||||
|
resultType="com.red.circle.console.infra.database.rds.dto.count.UserCountryMetricDTO">
|
||||||
|
SELECT id AS userId,
|
||||||
|
UPPER(country_code) AS countryCode
|
||||||
|
FROM user_base_info
|
||||||
|
WHERE origin_sys = #{sysOrigin}
|
||||||
|
AND country_code IS NOT NULL
|
||||||
|
AND country_code != ''
|
||||||
|
AND id IN
|
||||||
|
<foreach collection="userIds" item="userId" open="(" separator="," close=")">
|
||||||
|
#{userId}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@ -69,4 +69,5 @@ red-circle:
|
|||||||
- /console/datav/active/user-country-code
|
- /console/datav/active/user-country-code
|
||||||
- /console/datav/online/user/count
|
- /console/datav/online/user/count
|
||||||
- /console/datav/online/room/count
|
- /console/datav/online/room/count
|
||||||
|
- /console/datav/aslan/region-country/statistics
|
||||||
- /console/user/base/info/im/sig
|
- /console/user/base/info/im/sig
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
package com.red.circle.external.adapter.app.oss;
|
package com.red.circle.external.adapter.app.oss;
|
||||||
|
|
||||||
import com.red.circle.component.oss.OssService;
|
import com.red.circle.component.oss.OssService;
|
||||||
import com.red.circle.component.oss.aliyun.service.AliYunStsResponse;
|
import com.red.circle.external.infra.oss.TencentCosCredentialService;
|
||||||
import com.red.circle.component.oss.aliyun.service.AliYunStsService;
|
|
||||||
import com.red.circle.external.response.OssErrorCode;
|
import com.red.circle.external.response.OssErrorCode;
|
||||||
import com.red.circle.external.utils.FileUtils;
|
import com.red.circle.external.utils.FileUtils;
|
||||||
import com.red.circle.framework.dto.ResultResponse;
|
import com.red.circle.framework.dto.ResultResponse;
|
||||||
@ -13,11 +12,10 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阿里云,OSS存储.
|
* 对象存储.
|
||||||
*
|
*
|
||||||
* @author pengliang on 2020/9/7
|
* @author pengliang on 2020/9/7
|
||||||
* @eo.api-type http
|
* @eo.api-type http
|
||||||
@ -30,11 +28,11 @@ import java.util.UUID;
|
|||||||
@RequestMapping(value = "/oss", produces = MediaType.APPLICATION_JSON_VALUE)
|
@RequestMapping(value = "/oss", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public class OssRestController {
|
public class OssRestController {
|
||||||
|
|
||||||
private final AliYunStsService aliYunStsService;
|
private final TencentCosCredentialService tencentCosCredentialService;
|
||||||
private final OssService ossService;
|
private final OssService ossService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sts临时令牌.
|
* COS 上传配置.
|
||||||
*
|
*
|
||||||
* @eo.name sts临时令牌.
|
* @eo.name sts临时令牌.
|
||||||
* @eo.url /sts
|
* @eo.url /sts
|
||||||
@ -43,8 +41,8 @@ public class OssRestController {
|
|||||||
*/
|
*/
|
||||||
@IgnoreResultResponse
|
@IgnoreResultResponse
|
||||||
@GetMapping("/sts")
|
@GetMapping("/sts")
|
||||||
public AliYunStsResponse sts() {
|
public Object sts() {
|
||||||
return aliYunStsService.getAcsResponse();
|
return tencentCosCredentialService.getResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -36,6 +36,12 @@
|
|||||||
<artifactId>component-oss</artifactId>
|
<artifactId>component-oss</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.qcloud</groupId>
|
||||||
|
<artifactId>cos_api</artifactId>
|
||||||
|
<version>${cos_api.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.red.circle</groupId>
|
<groupId>com.red.circle</groupId>
|
||||||
<artifactId>component-censor</artifactId>
|
<artifactId>component-censor</artifactId>
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.red.circle.external.infra.oss;
|
||||||
|
|
||||||
|
import com.red.circle.external.infra.props.TencentCosProperties;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* COS 配置信息服务.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TencentCosCredentialService {
|
||||||
|
|
||||||
|
private final TencentCosProperties properties;
|
||||||
|
|
||||||
|
public TencentCosStsResponse getResponse() {
|
||||||
|
return TencentCosStsResponse.builder()
|
||||||
|
.provider("tencent-cos")
|
||||||
|
.bucketName(properties.getBucketName())
|
||||||
|
.region(properties.getRegion())
|
||||||
|
.accessUrl(properties.normalizedAccessUrl())
|
||||||
|
.uploadMode("server")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,226 @@
|
|||||||
|
package com.red.circle.external.infra.oss;
|
||||||
|
|
||||||
|
import com.qcloud.cos.COSClient;
|
||||||
|
import com.qcloud.cos.ClientConfig;
|
||||||
|
import com.qcloud.cos.auth.BasicCOSCredentials;
|
||||||
|
import com.qcloud.cos.auth.COSCredentials;
|
||||||
|
import com.qcloud.cos.exception.CosClientException;
|
||||||
|
import com.qcloud.cos.model.COSObject;
|
||||||
|
import com.qcloud.cos.model.CopyObjectRequest;
|
||||||
|
import com.qcloud.cos.model.ObjectMetadata;
|
||||||
|
import com.qcloud.cos.model.PutObjectRequest;
|
||||||
|
import com.qcloud.cos.model.PutObjectResult;
|
||||||
|
import com.qcloud.cos.model.ciModel.common.ImageProcessRequest;
|
||||||
|
import com.qcloud.cos.model.ciModel.persistence.PicOperations;
|
||||||
|
import com.qcloud.cos.region.Region;
|
||||||
|
import com.red.circle.component.oss.OssService;
|
||||||
|
import com.red.circle.external.infra.props.TencentCosProperties;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 腾讯云 COS 对象存储实现.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service("ossService")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TencentCosOssService implements OssService {
|
||||||
|
|
||||||
|
private static final String VIDEO_COVER_PROCESS = "?ci-process=snapshot&time=1&format=jpg";
|
||||||
|
private static final String OSS_RESIZE_PREFIX = "image/resize,m_lfit,h_";
|
||||||
|
private static final String COS_IMAGE_RULE_PREFIX = "imageMogr2/";
|
||||||
|
|
||||||
|
private final TencentCosProperties properties;
|
||||||
|
private COSClient cosClient;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
properties.validate();
|
||||||
|
COSCredentials credentials = new BasicCOSCredentials(properties.getSecretId(), properties.getSecretKey());
|
||||||
|
ClientConfig clientConfig = new ClientConfig(new Region(properties.getRegion()));
|
||||||
|
clientConfig.setConnectionTimeout(10_000);
|
||||||
|
clientConfig.setSocketTimeout(30_000);
|
||||||
|
this.cosClient = new COSClient(credentials, clientConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void destroy() {
|
||||||
|
if (cosClient != null) {
|
||||||
|
cosClient.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uploadContent(String content, String fileName) {
|
||||||
|
byte[] bytes = StringUtils.defaultString(content).getBytes(StandardCharsets.UTF_8);
|
||||||
|
uploadByte(bytes, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uploadByte(byte[] bytes, String fileName) {
|
||||||
|
byte[] safeBytes = bytes == null ? new byte[0] : bytes;
|
||||||
|
ObjectMetadata metadata = new ObjectMetadata();
|
||||||
|
metadata.setContentLength(safeBytes.length);
|
||||||
|
uploadInputStream(new ByteArrayInputStream(safeBytes), fileName, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uploadUrl(String url, String fileName) throws IOException {
|
||||||
|
try (InputStream inputStream = new URL(url).openStream()) {
|
||||||
|
uploadInputStream(inputStream, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uploadInputStream(InputStream inputStream, String fileName) {
|
||||||
|
uploadInputStream(inputStream, fileName, new ObjectMetadata());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uploadFile(File file, String fileName) {
|
||||||
|
String key = normalizeKey(fileName);
|
||||||
|
PutObjectRequest request = new PutObjectRequest(properties.getBucketName(), key, file);
|
||||||
|
cosClient.putObject(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String url) {
|
||||||
|
String key = getFilePath(url);
|
||||||
|
if (StringUtils.isBlank(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cosClient.deleteObject(properties.getBucketName(), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccessUrl(String key) {
|
||||||
|
if (StringUtils.isBlank(key)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (isHttpUrl(key)) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
return properties.normalizedAccessUrl() + "/" + normalizeKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVideoCover(String key) {
|
||||||
|
String accessUrl = getAccessUrl(key);
|
||||||
|
if (StringUtils.isBlank(accessUrl)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return accessUrl + VIDEO_COVER_PROCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processFileSaveAs(String source, String target, String styleType) {
|
||||||
|
String sourceKey = getFilePath(source);
|
||||||
|
String targetKey = normalizeKey(target);
|
||||||
|
if (StringUtils.isAnyBlank(sourceKey, targetKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PicOperations picOperations = new PicOperations();
|
||||||
|
picOperations.setIsPicInfo(1);
|
||||||
|
PicOperations.Rule rule = new PicOperations.Rule();
|
||||||
|
rule.setBucket(properties.getBucketName());
|
||||||
|
rule.setFileId(targetKey);
|
||||||
|
rule.setRule(toCosImageRule(styleType));
|
||||||
|
picOperations.setRules(List.of(rule));
|
||||||
|
|
||||||
|
ImageProcessRequest request = new ImageProcessRequest(properties.getBucketName(), sourceKey);
|
||||||
|
request.setPicOperations(picOperations);
|
||||||
|
cosClient.processImage(request);
|
||||||
|
} catch (CosClientException ex) {
|
||||||
|
log.warn("COS image process failed, fallback to object copy. source={}, target={}, message={}",
|
||||||
|
sourceKey, targetKey, ex.getMessage());
|
||||||
|
copySourceToTarget(source, sourceKey, targetKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String processImgSaveAsCompressZoom(String source, String target, int height) {
|
||||||
|
if (isHttpUrl(source)) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
if (StringUtils.endsWith(source, ".gift")) {
|
||||||
|
return getAccessUrl(source);
|
||||||
|
}
|
||||||
|
processFileSaveAs(source, target, OSS_RESIZE_PREFIX + height);
|
||||||
|
return getAccessUrl(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String processImgSaveAsCompressZoom(String source, int height) {
|
||||||
|
return processImgSaveAsCompressZoom(source, source, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void uploadInputStream(InputStream inputStream, String fileName, ObjectMetadata metadata) {
|
||||||
|
String key = normalizeKey(fileName);
|
||||||
|
PutObjectRequest request = new PutObjectRequest(properties.getBucketName(), key, inputStream, metadata);
|
||||||
|
PutObjectResult result = cosClient.putObject(request);
|
||||||
|
log.debug("COS upload success, key={}, requestId={}", key, result.getRequestId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copySourceToTarget(String source, String sourceKey, String targetKey) {
|
||||||
|
if (isHttpUrl(source)) {
|
||||||
|
try {
|
||||||
|
uploadUrl(source, targetKey);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException("copy remote object to COS failed", ex);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CopyObjectRequest request = new CopyObjectRequest(properties.getBucketName(), sourceKey,
|
||||||
|
properties.getBucketName(), targetKey);
|
||||||
|
cosClient.copyObject(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toCosImageRule(String styleType) {
|
||||||
|
String style = StringUtils.trimToEmpty(styleType);
|
||||||
|
if (StringUtils.startsWith(style, COS_IMAGE_RULE_PREFIX)) {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
if (StringUtils.startsWith(style, OSS_RESIZE_PREFIX)) {
|
||||||
|
String height = StringUtils.substringAfter(style, OSS_RESIZE_PREFIX);
|
||||||
|
if (StringUtils.isNumeric(height)) {
|
||||||
|
return "imageMogr2/thumbnail/x" + height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "imageMogr2/thumbnail/x1024";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFilePath(String urlOrKey) {
|
||||||
|
if (StringUtils.isBlank(urlOrKey)) {
|
||||||
|
return urlOrKey;
|
||||||
|
}
|
||||||
|
String value = StringUtils.trim(urlOrKey);
|
||||||
|
if (!isHttpUrl(value)) {
|
||||||
|
return normalizeKey(StringUtils.substringBefore(value, "?"));
|
||||||
|
}
|
||||||
|
URI uri = URI.create(value);
|
||||||
|
return normalizeKey(uri.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeKey(String key) {
|
||||||
|
return StringUtils.stripStart(StringUtils.substringBefore(StringUtils.trimToEmpty(key), "?"), "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHttpUrl(String value) {
|
||||||
|
return StringUtils.startsWithAny(StringUtils.lowerCase(StringUtils.trimToEmpty(value)), "http://", "https://");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.red.circle.external.infra.oss;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* COS 存储公开配置响应.
|
||||||
|
*/
|
||||||
|
@Value
|
||||||
|
@Builder
|
||||||
|
public class TencentCosStsResponse {
|
||||||
|
|
||||||
|
String provider;
|
||||||
|
String bucketName;
|
||||||
|
String region;
|
||||||
|
String accessUrl;
|
||||||
|
String uploadMode;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.red.circle.external.infra.props;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Locale;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 腾讯云 COS 上传配置.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(TencentCosProperties.PREFIX)
|
||||||
|
public class TencentCosProperties {
|
||||||
|
|
||||||
|
public static final String PREFIX = "red-circle.oss.tencent-cos";
|
||||||
|
|
||||||
|
String secretId;
|
||||||
|
String secretKey;
|
||||||
|
String bucketName;
|
||||||
|
String region;
|
||||||
|
String accessUrl;
|
||||||
|
|
||||||
|
public void validate() {
|
||||||
|
if (StringUtils.isAnyBlank(secretId, secretKey, bucketName, region, accessUrl)) {
|
||||||
|
throw new IllegalStateException(PREFIX + " config is incomplete");
|
||||||
|
}
|
||||||
|
URI accessUri = URI.create(accessUrl.trim());
|
||||||
|
String scheme = StringUtils.lowerCase(accessUri.getScheme(), Locale.ROOT);
|
||||||
|
if (!StringUtils.equalsAny(scheme, "http", "https") || StringUtils.isBlank(accessUri.getHost())) {
|
||||||
|
throw new IllegalStateException(PREFIX + ".access-url must be an absolute http url");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String normalizedAccessUrl() {
|
||||||
|
return StringUtils.stripEnd(StringUtils.trimToEmpty(accessUrl), "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* oss 服务.
|
* oss 服务.
|
||||||
@ -27,6 +28,11 @@ public class OssServiceClientEndpoint implements OssServiceClientApi {
|
|||||||
return ResultResponse.success(ossServiceClientService.sts());
|
return ResultResponse.success(ossServiceClientService.sts());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultResponse<String> upload(MultipartFile file, String key) {
|
||||||
|
return ResultResponse.success(ossServiceClientService.upload(file, key));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultResponse<Void> remove(String url) {
|
public ResultResponse<Void> remove(String url) {
|
||||||
ossServiceClientService.remove(url);
|
ossServiceClientService.remove(url);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package com.red.circle.external.inner.service.oss;
|
package com.red.circle.external.inner.service.oss;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* oss 服务api.
|
* oss 服务api.
|
||||||
*
|
*
|
||||||
@ -12,6 +14,11 @@ public interface OssServiceClientService {
|
|||||||
*/
|
*/
|
||||||
Object sts();
|
Object sts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件.
|
||||||
|
*/
|
||||||
|
String upload(MultipartFile file, String key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除文件.
|
* 移除文件.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
package com.red.circle.external.inner.service.oss.impl;
|
package com.red.circle.external.inner.service.oss.impl;
|
||||||
|
|
||||||
import com.red.circle.component.oss.OssService;
|
import com.red.circle.component.oss.OssService;
|
||||||
import com.red.circle.component.oss.aliyun.service.AliYunStsService;
|
import com.red.circle.external.infra.oss.TencentCosCredentialService;
|
||||||
import com.red.circle.external.inner.service.oss.OssServiceClientService;
|
import com.red.circle.external.inner.service.oss.OssServiceClientService;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* oss 服务api.
|
* oss 服务api.
|
||||||
@ -16,11 +20,27 @@ import org.springframework.stereotype.Service;
|
|||||||
public class OssServiceClientServiceImpl implements OssServiceClientService {
|
public class OssServiceClientServiceImpl implements OssServiceClientService {
|
||||||
|
|
||||||
private final OssService ossService;
|
private final OssService ossService;
|
||||||
private final AliYunStsService aliYunStsService;
|
private final TencentCosCredentialService tencentCosCredentialService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object sts() {
|
public Object sts() {
|
||||||
return aliYunStsService.getAcsResponse();
|
return tencentCosCredentialService.getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String upload(MultipartFile file, String key) {
|
||||||
|
if (file == null || file.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("file is empty");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(key)) {
|
||||||
|
throw new IllegalArgumentException("file key is blank");
|
||||||
|
}
|
||||||
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
|
ossService.uploadInputStream(inputStream, key);
|
||||||
|
return ossService.getAccessUrl(key);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException("upload file to object storage failed", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.red.circle;
|
package com.red.circle;
|
||||||
|
|
||||||
|
import com.red.circle.component.oss.OssAutoConfiguration;
|
||||||
import com.red.circle.component.redis.RedisAutoConfiguration;
|
import com.red.circle.component.redis.RedisAutoConfiguration;
|
||||||
import com.red.circle.framework.cloud.annotation.RedCircleCloudApplication;
|
import com.red.circle.framework.cloud.annotation.RedCircleCloudApplication;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -18,7 +19,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@EnableDiscoveryClient
|
@EnableDiscoveryClient
|
||||||
@EnableFeignClients
|
@EnableFeignClients
|
||||||
@SpringBootApplication(exclude = RedisAutoConfiguration.class)
|
@SpringBootApplication(exclude = {RedisAutoConfiguration.class, OssAutoConfiguration.class})
|
||||||
public class ExternalServiceApplication {
|
public class ExternalServiceApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@ -32,3 +32,12 @@ management:
|
|||||||
rtc:
|
rtc:
|
||||||
appId: f424387a480e41088239416e10030034
|
appId: f424387a480e41088239416e10030034
|
||||||
certificate: c96d8494f80542bb83c843ad4045df03
|
certificate: c96d8494f80542bb83c843ad4045df03
|
||||||
|
|
||||||
|
red-circle:
|
||||||
|
oss:
|
||||||
|
tencent-cos:
|
||||||
|
secret-id: ${TENCENT_COS_SECRET_ID:}
|
||||||
|
secret-key: ${TENCENT_COS_SECRET_KEY:}
|
||||||
|
bucket-name: ${TENCENT_COS_BUCKET_NAME:asset-global-interaction-1420526837}
|
||||||
|
region: ${TENCENT_COS_REGION:me-saudi-arabia}
|
||||||
|
access-url: ${TENCENT_COS_ACCESS_URL:https://asset.global-interaction.com}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user