diff --git a/rc-dependencies/pom.xml b/rc-dependencies/pom.xml index c071656c..c99fd26f 100644 --- a/rc-dependencies/pom.xml +++ b/rc-dependencies/pom.xml @@ -289,6 +289,7 @@ 1.0.4 3.1.877 + 5.6.151 1.9.2-alpha 3.5.1 3.1.3 diff --git a/rc-gateway/src/main/resources/application.yml b/rc-gateway/src/main/resources/application.yml index cbf63c46..5c2ef17a 100644 --- a/rc-gateway/src/main/resources/application.yml +++ b/rc-gateway/src/main/resources/application.yml @@ -36,3 +36,13 @@ management: enabled: true health: 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 diff --git a/rc-service/rc-inner-api/external-inner/external-inner-api/src/main/java/com/red/circle/external/inner/endpoint/oss/api/OssServiceClientApi.java b/rc-service/rc-inner-api/external-inner/external-inner-api/src/main/java/com/red/circle/external/inner/endpoint/oss/api/OssServiceClientApi.java index ece9902f..e86820f4 100644 --- a/rc-service/rc-inner-api/external-inner/external-inner-api/src/main/java/com/red/circle/external/inner/endpoint/oss/api/OssServiceClientApi.java +++ b/rc-service/rc-inner-api/external-inner/external-inner-api/src/main/java/com/red/circle/external/inner/endpoint/oss/api/OssServiceClientApi.java @@ -2,7 +2,11 @@ package com.red.circle.external.inner.endpoint.oss.api; import com.red.circle.framework.dto.ResultResponse; 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.http.MediaType; +import org.springframework.web.multipart.MultipartFile; /** * oss 服务api. @@ -19,6 +23,13 @@ public interface OssServiceClientApi { @GetMapping("/sts") ResultResponse sts(); + /** + * 上传文件. + */ + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + ResultResponse upload(@RequestPart("file") MultipartFile file, + @RequestParam("key") String key); + /** * 移除文件. */ diff --git a/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/party3rd/AliYunRestController.java b/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/party3rd/AliYunRestController.java index 055149f3..e6166597 100644 --- a/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/party3rd/AliYunRestController.java +++ b/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/party3rd/AliYunRestController.java @@ -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.framework.core.asserts.ResponseAssert; +import java.util.LinkedHashMap; +import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; 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.RestController; +import org.springframework.web.multipart.MultipartFile; /** - * 阿里云服务. + * 对象存储服务. */ @Slf4j @RequiredArgsConstructor @@ -21,11 +27,25 @@ public class AliYunRestController { private final OssServiceClient ossServiceClient; /** - * 阿里云sts. + * 对象存储上传配置. */ @GetMapping("/oss/sts") public Object sts() { return ResponseAssert.requiredSuccess(ossServiceClient.sts()); } + /** + * 上传文件. + */ + @PostMapping(value = "/oss/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Map upload(@RequestPart("file") MultipartFile file, + @RequestParam("key") String key) { + String url = ResponseAssert.requiredSuccess(ossServiceClient.upload(file, key)); + Map result = new LinkedHashMap<>(); + result.put("name", key); + result.put("url", url); + result.put("res", Map.of("status", 200)); + return result; + } + } diff --git a/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java b/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java index 72fb473c..b7170f89 100644 --- a/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java +++ b/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java @@ -1,14 +1,18 @@ package com.red.circle.console.adapter.app.user; 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.framework.web.controller.BaseController; import com.red.circle.other.inner.model.cmd.user.OnlineCountQryCmd; import com.red.circle.other.inner.model.dto.user.ActiveUserCountryCodeDTO; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Objects; import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -25,6 +29,7 @@ import org.springframework.web.bind.annotation.RestController; public class DatavRestController extends BaseController { private final DatavService userExpandService; + private final AslanRegionCountryStatisticsService aslanRegionCountryStatisticsService; @GetMapping("/active/user-country-code") public List latestActiveUserCountryCode() { @@ -41,4 +46,11 @@ public class DatavRestController extends BaseController { return Objects.toString(userExpandService.count(sysOrigin)); } + @GetMapping("/aslan/region-country/statistics") + public List aslanRegionCountryStatistics( + @RequestParam(value = "date", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) { + return aslanRegionCountryStatisticsService.listRegionCountryStatistics(date); + } + } diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/count/AslanRegionCountryStatisticsServiceImpl.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/count/AslanRegionCountryStatisticsServiceImpl.java new file mode 100644 index 00000000..fd41168e --- /dev/null +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/count/AslanRegionCountryStatisticsServiceImpl.java @@ -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 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 regions = listRegionConfigs(sysOrigin); + if (CollectionUtils.isEmpty(regions)) { + return CollectionUtils.newArrayList(); + } + + Map countryMap = mapCountryCode(); + Map rechargeMap = mapAmount( + statisticsDAO.listDailyRechargeByCountry(sysOrigin, dateNumber, startTime, endTime)); + Map registerMap = mapLong( + statisticsDAO.listDailyRegisterByCountry(sysOrigin, startTime, endTime)); + Map goldBalanceMap = mapLong(statisticsDAO.listGoldBalanceByCountry(sysOrigin)); + Map dailyActiveMap = mapDailyActive(sysOrigin, statDate); + Map 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 listRegionConfigs(String sysOrigin) { + SysRegionConfigQryCmd query = new SysRegionConfigQryCmd(); + query.setSysOrigin(sysOrigin); + return Optional.ofNullable(ResponseAssert.requiredSuccess(regionConfigClient.listByCondition(query))) + .orElseGet(CollectionUtils::newArrayList); + } + + private Map mapCountryCode() { + List 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 countryMap, + Map rechargeMap, + Map registerMap, + Map goldBalanceMap, + Map dailyActiveMap, + Map hostSalaryBalanceMap) { + + List 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 countryMap, + Map rechargeMap, + Map registerMap, + Map goldBalanceMap, + Map dailyActiveMap, + Map 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 mapAmount(List 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 mapLong(List 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 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 mapHostSalaryBalance(String sysOrigin) { + Set anchorUserIds = listAnchorUserIds(sysOrigin); + if (CollectionUtils.isEmpty(anchorUserIds)) { + return CollectionUtils.newHashMap(); + } + + Map bankBalanceMap = mapBankBalance(sysOrigin, anchorUserIds); + if (CollectionUtils.isEmpty(bankBalanceMap)) { + return CollectionUtils.newHashMap(); + } + + Map userCountryMap = mapUserCountry(sysOrigin, bankBalanceMap.keySet()); + if (CollectionUtils.isEmpty(userCountryMap)) { + return CollectionUtils.newHashMap(); + } + + Map 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 listAnchorUserIds(String sysOrigin) { + Query query = Query.query(Criteria.where("sysOrigin").is(sysOrigin) + .and("role").in("MEMBER", "ADMIN")); + query.fields().include("memberId"); + + List 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 mapBankBalance(String sysOrigin, Set userIds) { + Map result = CollectionUtils.newHashMap(); + for (List chunk : partitionUserIds(userIds)) { + Query query = Query.query(Criteria.where("sysOrigin").is(sysOrigin).and("_id").in(chunk)); + query.fields().include("_id").include("balance"); + + List 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 mapUserCountry(String sysOrigin, Set userIds) { + Map result = CollectionUtils.newHashMap(); + for (List chunk : partitionUserIds(userIds)) { + List 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> partitionUserIds(Set userIds) { + if (CollectionUtils.isEmpty(userIds)) { + return Collections.emptyList(); + } + + List ids = userIds.stream().filter(Objects::nonNull).distinct().toList(); + List> 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 parseCountryCodes(String countryCodes) { + if (StringUtils.isBlank(countryCodes)) { + return Collections.emptySet(); + } + + Set 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); + } +} diff --git a/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/count/aslan/AslanCountryStatisticsCO.java b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/count/aslan/AslanCountryStatisticsCO.java new file mode 100644 index 00000000..031a0896 --- /dev/null +++ b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/count/aslan/AslanCountryStatisticsCO.java @@ -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; +} diff --git a/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/count/aslan/AslanRegionCountryStatisticsCO.java b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/count/aslan/AslanRegionCountryStatisticsCO.java new file mode 100644 index 00000000..8d905064 --- /dev/null +++ b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/count/aslan/AslanRegionCountryStatisticsCO.java @@ -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 countries; +} diff --git a/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/service/app/count/AslanRegionCountryStatisticsService.java b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/service/app/count/AslanRegionCountryStatisticsService.java new file mode 100644 index 00000000..654de8f2 --- /dev/null +++ b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/service/app/count/AslanRegionCountryStatisticsService.java @@ -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 listRegionCountryStatistics(LocalDate date); +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/count/AslanRegionCountryStatisticsDAO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/count/AslanRegionCountryStatisticsDAO.java new file mode 100644 index 00000000..62d49585 --- /dev/null +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/count/AslanRegionCountryStatisticsDAO.java @@ -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 listDailyRechargeByCountry( + @Param("sysOrigin") String sysOrigin, + @Param("dateNumber") Integer dateNumber, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 每日新注册. + */ + List listDailyRegisterByCountry( + @Param("sysOrigin") String sysOrigin, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 当前金币总量. + */ + List listGoldBalanceByCountry(@Param("sysOrigin") String sysOrigin); + + /** + * 批量查询用户国家. + */ + List listUserCountryByIds( + @Param("sysOrigin") String sysOrigin, + @Param("userIds") List userIds); +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/count/CountryAmountMetricDTO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/count/CountryAmountMetricDTO.java new file mode 100644 index 00000000..03710181 --- /dev/null +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/count/CountryAmountMetricDTO.java @@ -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; +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/count/CountryLongMetricDTO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/count/CountryLongMetricDTO.java new file mode 100644 index 00000000..1e560ee2 --- /dev/null +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/count/CountryLongMetricDTO.java @@ -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; +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/count/UserCountryMetricDTO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/count/UserCountryMetricDTO.java new file mode 100644 index 00000000..598e20a4 --- /dev/null +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/count/UserCountryMetricDTO.java @@ -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; +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/count/AslanRegionCountryStatisticsDAO.xml b/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/count/AslanRegionCountryStatisticsDAO.xml new file mode 100644 index 00000000..90fae4e3 --- /dev/null +++ b/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/count/AslanRegionCountryStatisticsDAO.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + diff --git a/rc-service/rc-service-console/console-start/src/main/resources/application.yml b/rc-service/rc-service-console/console-start/src/main/resources/application.yml index 5e7237ed..524148bb 100644 --- a/rc-service/rc-service-console/console-start/src/main/resources/application.yml +++ b/rc-service/rc-service-console/console-start/src/main/resources/application.yml @@ -69,4 +69,5 @@ red-circle: - /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 diff --git a/rc-service/rc-service-external/external-adapter/src/main/java/com/red/circle/external/adapter/app/oss/OssRestController.java b/rc-service/rc-service-external/external-adapter/src/main/java/com/red/circle/external/adapter/app/oss/OssRestController.java index db92e4d1..1196054b 100644 --- a/rc-service/rc-service-external/external-adapter/src/main/java/com/red/circle/external/adapter/app/oss/OssRestController.java +++ b/rc-service/rc-service-external/external-adapter/src/main/java/com/red/circle/external/adapter/app/oss/OssRestController.java @@ -1,8 +1,7 @@ package com.red.circle.external.adapter.app.oss; import com.red.circle.component.oss.OssService; -import com.red.circle.component.oss.aliyun.service.AliYunStsResponse; -import com.red.circle.component.oss.aliyun.service.AliYunStsService; +import com.red.circle.external.infra.oss.TencentCosCredentialService; import com.red.circle.external.response.OssErrorCode; import com.red.circle.external.utils.FileUtils; 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.multipart.MultipartFile; -import java.io.IOException; import java.util.UUID; /** - * 阿里云,OSS存储. + * 对象存储. * * @author pengliang on 2020/9/7 * @eo.api-type http @@ -30,11 +28,11 @@ import java.util.UUID; @RequestMapping(value = "/oss", produces = MediaType.APPLICATION_JSON_VALUE) public class OssRestController { - private final AliYunStsService aliYunStsService; + private final TencentCosCredentialService tencentCosCredentialService; private final OssService ossService; /** - * sts临时令牌. + * COS 上传配置. * * @eo.name sts临时令牌. * @eo.url /sts @@ -43,8 +41,8 @@ public class OssRestController { */ @IgnoreResultResponse @GetMapping("/sts") - public AliYunStsResponse sts() { - return aliYunStsService.getAcsResponse(); + public Object sts() { + return tencentCosCredentialService.getResponse(); } /** diff --git a/rc-service/rc-service-external/external-infrastructure/pom.xml b/rc-service/rc-service-external/external-infrastructure/pom.xml index be5796e8..2e9ff045 100644 --- a/rc-service/rc-service-external/external-infrastructure/pom.xml +++ b/rc-service/rc-service-external/external-infrastructure/pom.xml @@ -36,6 +36,12 @@ component-oss + + com.qcloud + cos_api + ${cos_api.version} + + com.red.circle component-censor diff --git a/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/oss/TencentCosCredentialService.java b/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/oss/TencentCosCredentialService.java new file mode 100644 index 00000000..0662dd71 --- /dev/null +++ b/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/oss/TencentCosCredentialService.java @@ -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(); + } + +} diff --git a/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/oss/TencentCosOssService.java b/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/oss/TencentCosOssService.java new file mode 100644 index 00000000..ae553477 --- /dev/null +++ b/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/oss/TencentCosOssService.java @@ -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://"); + } + +} diff --git a/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/oss/TencentCosStsResponse.java b/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/oss/TencentCosStsResponse.java new file mode 100644 index 00000000..1c6e4363 --- /dev/null +++ b/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/oss/TencentCosStsResponse.java @@ -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; + +} diff --git a/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/props/TencentCosProperties.java b/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/props/TencentCosProperties.java new file mode 100644 index 00000000..07b436e8 --- /dev/null +++ b/rc-service/rc-service-external/external-infrastructure/src/main/java/com/red/circle/external/infra/props/TencentCosProperties.java @@ -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), "/"); + } + +} diff --git a/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/endpoint/oss/OssServiceClientEndpoint.java b/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/endpoint/oss/OssServiceClientEndpoint.java index d68bbf83..384386a4 100644 --- a/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/endpoint/oss/OssServiceClientEndpoint.java +++ b/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/endpoint/oss/OssServiceClientEndpoint.java @@ -8,6 +8,7 @@ import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; /** * oss 服务. @@ -27,6 +28,11 @@ public class OssServiceClientEndpoint implements OssServiceClientApi { return ResultResponse.success(ossServiceClientService.sts()); } + @Override + public ResultResponse upload(MultipartFile file, String key) { + return ResultResponse.success(ossServiceClientService.upload(file, key)); + } + @Override public ResultResponse remove(String url) { ossServiceClientService.remove(url); diff --git a/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/service/oss/OssServiceClientService.java b/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/service/oss/OssServiceClientService.java index e2675475..ec262605 100644 --- a/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/service/oss/OssServiceClientService.java +++ b/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/service/oss/OssServiceClientService.java @@ -1,5 +1,7 @@ package com.red.circle.external.inner.service.oss; +import org.springframework.web.multipart.MultipartFile; + /** * oss 服务api. * @@ -12,6 +14,11 @@ public interface OssServiceClientService { */ Object sts(); + /** + * 上传文件. + */ + String upload(MultipartFile file, String key); + /** * 移除文件. */ diff --git a/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/service/oss/impl/OssServiceClientServiceImpl.java b/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/service/oss/impl/OssServiceClientServiceImpl.java index 5cf978e8..8818ef22 100644 --- a/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/service/oss/impl/OssServiceClientServiceImpl.java +++ b/rc-service/rc-service-external/external-inner-endpoint/src/main/java/com/red/circle/external/inner/service/oss/impl/OssServiceClientServiceImpl.java @@ -1,10 +1,14 @@ package com.red.circle.external.inner.service.oss.impl; 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 java.io.IOException; +import java.io.InputStream; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; /** * oss 服务api. @@ -16,11 +20,27 @@ import org.springframework.stereotype.Service; public class OssServiceClientServiceImpl implements OssServiceClientService { private final OssService ossService; - private final AliYunStsService aliYunStsService; + private final TencentCosCredentialService tencentCosCredentialService; @Override 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 diff --git a/rc-service/rc-service-external/external-start/src/main/java/com/red/circle/ExternalServiceApplication.java b/rc-service/rc-service-external/external-start/src/main/java/com/red/circle/ExternalServiceApplication.java index 8b9712fa..06201bfd 100644 --- a/rc-service/rc-service-external/external-start/src/main/java/com/red/circle/ExternalServiceApplication.java +++ b/rc-service/rc-service-external/external-start/src/main/java/com/red/circle/ExternalServiceApplication.java @@ -1,5 +1,6 @@ package com.red.circle; +import com.red.circle.component.oss.OssAutoConfiguration; import com.red.circle.component.redis.RedisAutoConfiguration; import com.red.circle.framework.cloud.annotation.RedCircleCloudApplication; import lombok.extern.slf4j.Slf4j; @@ -18,7 +19,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling @EnableDiscoveryClient @EnableFeignClients -@SpringBootApplication(exclude = RedisAutoConfiguration.class) +@SpringBootApplication(exclude = {RedisAutoConfiguration.class, OssAutoConfiguration.class}) public class ExternalServiceApplication { public static void main(String[] args) { diff --git a/rc-service/rc-service-external/external-start/src/main/resources/application.yml b/rc-service/rc-service-external/external-start/src/main/resources/application.yml index 04ef598c..3c4e87e4 100644 --- a/rc-service/rc-service-external/external-start/src/main/resources/application.yml +++ b/rc-service/rc-service-external/external-start/src/main/resources/application.yml @@ -32,3 +32,12 @@ management: rtc: appId: f424387a480e41088239416e10030034 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}