Commit f2e21d79 by renyizhao

大屏修改

parent 3206414c
...@@ -12,5 +12,7 @@ import lombok.experimental.Accessors; ...@@ -12,5 +12,7 @@ import lombok.experimental.Accessors;
@Accessors(chain = true) @Accessors(chain = true)
public class HomeIndexUsersCountRespVO { public class HomeIndexUsersCountRespVO {
private Integer usersCount; private Integer usersCount;
private Integer growthUsersCount;
private Integer activeUsersCount;
private String countDate; private String countDate;
} }
...@@ -22,6 +22,8 @@ import com.luhu.computility.module.compute.api.order.dto.*; ...@@ -22,6 +22,8 @@ import com.luhu.computility.module.compute.api.order.dto.*;
import com.luhu.computility.module.member.controller.admin.user.vo.MemberUserPageReqVO; import com.luhu.computility.module.member.controller.admin.user.vo.MemberUserPageReqVO;
import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO; import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO;
import com.luhu.computility.module.member.service.user.MemberUserService; import com.luhu.computility.module.member.service.user.MemberUserService;
import com.luhu.computility.module.system.api.logger.LoginLogApi;
import com.luhu.computility.module.system.dal.dataobject.logger.LoginLogDO;
import com.luhu.computility.module.trade.service.order.TradeOrderQueryService; import com.luhu.computility.module.trade.service.order.TradeOrderQueryService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
...@@ -61,6 +63,8 @@ public class HomeIndexServiceImpl implements HomeIndexService { ...@@ -61,6 +63,8 @@ public class HomeIndexServiceImpl implements HomeIndexService {
ComputeStatisticsApi computeStatisticsApi; ComputeStatisticsApi computeStatisticsApi;
@Resource @Resource
ApiHubStatisticsApi apiHubStatisticsApi; ApiHubStatisticsApi apiHubStatisticsApi;
@Resource
LoginLogApi loginLogApi;
@Override @Override
public List<HomeIndexUsersCountRespVO> getRegisterUsersCount() { public List<HomeIndexUsersCountRespVO> getRegisterUsersCount() {
...@@ -139,6 +143,9 @@ public class HomeIndexServiceImpl implements HomeIndexService { ...@@ -139,6 +143,9 @@ public class HomeIndexServiceImpl implements HomeIndexService {
queryVO.setCreateTime(allTimePeriod); queryVO.setCreateTime(allTimePeriod);
List<MemberUserDO> allUserList = memberUserService.getUserList(queryVO); List<MemberUserDO> allUserList = memberUserService.getUserList(queryVO);
// 查询时间范围内的登录日志(用于活跃用户数统计)
List<LoginLogDO> loginLogList = loginLogApi.getLoginLogList(allTimePeriod);
// 按统计节点计算累计用户数(截至每个节点的总用户数) // 按统计节点计算累计用户数(截至每个节点的总用户数)
Map<LocalDate, Long> totalMap = new HashMap<>(); Map<LocalDate, Long> totalMap = new HashMap<>();
for (LocalDate node : timeNodes) { for (LocalDate node : timeNodes) {
...@@ -149,6 +156,23 @@ public class HomeIndexServiceImpl implements HomeIndexService { ...@@ -149,6 +156,23 @@ public class HomeIndexServiceImpl implements HomeIndexService {
totalMap.put(node, count); totalMap.put(node, count);
} }
// 按统计节点计算活跃用户数(按 createTime 日期分组,对 userId 去重计数)
// d/m 维度:精确到天;y 维度:按月聚合
Map<LocalDate, Long> activeMap = new HashMap<>();
for (LocalDate node : timeNodes) {
if (CollectionUtils.isEmpty(loginLogList)) {
activeMap.put(node, 0L);
continue;
}
long count = loginLogList.stream()
.filter(log -> log.getCreateTime() != null && log.getUserId() != null)
.filter(log -> isDateMatch(log.getCreateTime().toLocalDate(), node, dateType))
.map(LoginLogDO::getUserId)
.distinct()
.count();
activeMap.put(node, count);
}
// 构建结果列表(格式化日期显示) // 构建结果列表(格式化日期显示)
List<HomeIndexUsersCountRespVO> resultList = new ArrayList<>(); List<HomeIndexUsersCountRespVO> resultList = new ArrayList<>();
for (LocalDate node : timeNodes) { for (LocalDate node : timeNodes) {
...@@ -164,6 +188,7 @@ public class HomeIndexServiceImpl implements HomeIndexService { ...@@ -164,6 +188,7 @@ public class HomeIndexServiceImpl implements HomeIndexService {
} }
respVO.setUsersCount(totalMap.get(node).intValue()); respVO.setUsersCount(totalMap.get(node).intValue());
respVO.setActiveUsersCount(activeMap.get(node).intValue());
resultList.add(respVO); resultList.add(respVO);
} }
...@@ -655,6 +680,8 @@ public class HomeIndexServiceImpl implements HomeIndexService { ...@@ -655,6 +680,8 @@ public class HomeIndexServiceImpl implements HomeIndexService {
HomeDashboardRespVO.UserStatisticsVO vo = new HomeDashboardRespVO.UserStatisticsVO(); HomeDashboardRespVO.UserStatisticsVO vo = new HomeDashboardRespVO.UserStatisticsVO();
vo.setCountDate(item.getCountDate()); vo.setCountDate(item.getCountDate());
vo.setUsersCount(item.getUsersCount()); vo.setUsersCount(item.getUsersCount());
vo.setGrowthUsersCount(item.getGrowthUsersCount());
vo.setActiveUsersCount(item.getActiveUsersCount());
return vo; return vo;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
......
...@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; ...@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.*;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.util.*; import java.util.*;
import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*; import com.alibaba.excel.annotation.*;
...@@ -38,6 +39,10 @@ public class ResourceConfigRespVO { ...@@ -38,6 +39,10 @@ public class ResourceConfigRespVO {
@ExcelProperty("详情备注(如:i7-13700K 16核24线程)") @ExcelProperty("详情备注(如:i7-13700K 16核24线程)")
private String detailRemark; private String detailRemark;
@Schema(description = "算力值(TOPS),默认 0", example = "624.00")
@ExcelProperty("算力(TOPS)")
private BigDecimal compute;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间") @ExcelProperty("创建时间")
private LocalDateTime createTime; private LocalDateTime createTime;
......
...@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; ...@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.*;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.util.*; import java.util.*;
import java.math.BigDecimal;
import javax.validation.constraints.*; import javax.validation.constraints.*;
@Schema(description = "管理后台 - 算力资源配置新增/修改 Request VO") @Schema(description = "管理后台 - 算力资源配置新增/修改 Request VO")
...@@ -32,4 +33,7 @@ public class ResourceConfigSaveReqVO { ...@@ -32,4 +33,7 @@ public class ResourceConfigSaveReqVO {
@Schema(description = "详情备注(如:i7-13700K 16核24线程)", example = "你猜") @Schema(description = "详情备注(如:i7-13700K 16核24线程)", example = "你猜")
private String detailRemark; private String detailRemark;
@Schema(description = "算力值(TOPS),默认 0,可由运营手动配置", example = "624.00")
private BigDecimal compute;
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ import lombok.*; ...@@ -4,6 +4,7 @@ import lombok.*;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.util.*; import java.util.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import com.luhu.computility.framework.mybatis.core.dataobject.BaseDO; import com.luhu.computility.framework.mybatis.core.dataobject.BaseDO;
...@@ -49,6 +50,13 @@ public class ResourceConfigDO extends BaseDO { ...@@ -49,6 +50,13 @@ public class ResourceConfigDO extends BaseDO {
* 详情备注(如:i7-13700K 16核24线程) * 详情备注(如:i7-13700K 16核24线程)
*/ */
private String detailRemark; private String detailRemark;
/**
* 算力值(TOPS),默认 0
* 业务语义:该配置项代表的算力值
* 如 1×A100 (40GB) 的 compute = 624,8×A100 (40GB) 的 compute = 4992
* SPU 总算力 = SUM(SPU 选中的所有 config.compute)
*/
private BigDecimal compute;
} }
\ No newline at end of file
...@@ -14,6 +14,8 @@ import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO; ...@@ -14,6 +14,8 @@ import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.*; import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.*;
import static com.luhu.computility.module.compute.enums.ResourceOrderStatus.DELIVERED;
import static com.luhu.computility.module.compute.enums.ResourceOrderStatus.FINISHED;
import static com.luhu.computility.module.compute.enums.ResourceOrderStatus.PENDING_DELIVERY; import static com.luhu.computility.module.compute.enums.ResourceOrderStatus.PENDING_DELIVERY;
/** /**
...@@ -103,7 +105,14 @@ public interface ResourceOrderMapper extends BaseMapperX<ResourceOrderDO> { ...@@ -103,7 +105,14 @@ public interface ResourceOrderMapper extends BaseMapperX<ResourceOrderDO> {
default List<ResourceOrderRespDTO> getPaidOrderList(LocalDateTime[] timeRange) { default List<ResourceOrderRespDTO> getPaidOrderList(LocalDateTime[] timeRange) {
List<ResourceOrderDO> list = selectList(new LambdaQueryWrapperX<ResourceOrderDO>() List<ResourceOrderDO> list = selectList(new LambdaQueryWrapperX<ResourceOrderDO>()
.eq(ResourceOrderDO::getStatus, PENDING_DELIVERY.getValue()) // 算力订单"成交"三态:待发货(5)、已发货(6)、已结束(2)
// 排除:未支付(0)、已取消(3)、待退款(7)、已退款(8)
// 注:必须用 Collection 重载,不能用 varargs(LambdaQueryWrapperX 未重写 varargs 版本,
// 会回到父类 LambdaQueryWrapper.in 返回非 X 类型,导致后续 betweenIfPresent 找不到)
.in(ResourceOrderDO::getStatus, Arrays.asList(
PENDING_DELIVERY.getValue(),
DELIVERED.getValue(),
FINISHED.getValue()))
.betweenIfPresent(ResourceOrderDO::getCreateTime, timeRange)); .betweenIfPresent(ResourceOrderDO::getCreateTime, timeRange));
return BeanUtil.copyToList(list, ResourceOrderRespDTO.class); return BeanUtil.copyToList(list, ResourceOrderRespDTO.class);
} }
......
package com.luhu.computility.module.compute.dal.mysql.resourceordersnapshot; package com.luhu.computility.module.compute.dal.mysql.resourceordersnapshot;
import java.math.BigDecimal;
import java.util.*; import java.util.*;
import com.luhu.computility.framework.common.pojo.PageResult; import com.luhu.computility.framework.common.pojo.PageResult;
...@@ -10,12 +11,12 @@ import com.luhu.computility.module.compute.api.order.dto.ResourceOrderSnapshotSt ...@@ -10,12 +11,12 @@ import com.luhu.computility.module.compute.api.order.dto.ResourceOrderSnapshotSt
import com.luhu.computility.module.compute.dal.dataobject.resourceordersnapshot.ResourceOrderSnapshotDO; import com.luhu.computility.module.compute.dal.dataobject.resourceordersnapshot.ResourceOrderSnapshotDO;
import com.luhu.computility.module.compute.controller.app.resourceordersnapshot.vo.AppResourceOrderSnapshotRespVO; import com.luhu.computility.module.compute.controller.app.resourceordersnapshot.vo.AppResourceOrderSnapshotRespVO;
import com.luhu.computility.module.compute.controller.app.resourceordersnapshot.vo.AppResourceOrderSnapshotPageReqVO; import com.luhu.computility.module.compute.controller.app.resourceordersnapshot.vo.AppResourceOrderSnapshotPageReqVO;
import com.luhu.computility.module.compute.dal.dataobject.resourcespu.ResourceSpuDO;
import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO; import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/** /**
* 算力资源订单硬件配置快照 Mapper * 绠楀姏璧勬簮璁㈠崟纭欢閰嶇疆蹇収 Mapper
* *
* @author jony * @author jony
*/ */
...@@ -62,13 +63,31 @@ public interface ResourceOrderSnapshotMapper extends BaseMapperX<ResourceOrderSn ...@@ -62,13 +63,31 @@ public interface ResourceOrderSnapshotMapper extends BaseMapperX<ResourceOrderSn
.eq(ResourceOrderSnapshotDO::getOrderId, orderId)); .eq(ResourceOrderSnapshotDO::getOrderId, orderId));
} }
/**
* 闃舵 3锛氱粺璁?路 绉熸湡鍐呭凡绉熻祦绠楀姏鎬诲拰
*
* 鏉′欢锛氳鍗?status = DELIVERED(=6) 涓?rent_start_time &lt;= NOW() &lt;= rent_end_time
* 閫氳繃 璁㈠崟 鈫?SPU 鈫?spec_value 鈫?config 閾捐矾绱姞 config.compute銆? *
* spec_snapshot 鏍煎紡锛歿"GPU":"8xA100 (40GB)","CPU":"i7-13700K",...}
* JOIN 鏃舵寜 config_category 鍖归厤 spec_snapshot 鐨?key銆乧onfig_option 鍖归厤 value銆? */
BigDecimal sumLeasedComputeNow();
/**
* 闃舵 3锛氱粺璁?路 宸茬璧佺畻鍔涳紙鑱氬悎鍒?DTO锛? *
* 璁$畻绉熸湡鍐咃紙status=DELIVERED 涓?NOW() 鍦ㄧ鏈熷尯闂村唴锛夎鍗曞搴旂殑 SPU 绠楀姏涔嬪拰銆? * 璺緞锛氳鍗?鈫?SPU 鈫?spec_value 鈫?config锛屾寜 SPU 鑱氬悎绠楀姏鍚庡鎵€鏈夌鏈熷唴鐨?SPU 姹傚拰銆? *
* 鎬荤畻鍔涜涔夛細涓€涓?SPU 鐨勭畻鍔?= SUM(鍏跺悕涓嬫墍鏈?config.compute)锛? * 涓€涓鏈熷唴璁㈠崟鐨勭畻鍔?= 璇?SPU 鐨勬€荤畻鍔涳紝DTO 鐨?totalCompute 鍗宠繖浜?SPU 绠楀姏涔嬪拰銆? *
* 宸插簾寮冨瓧娈?totalGpuCount / totalCoreCount / totalMemoryCapacity锛氶樁娈?1 閲嶆瀯鍚庡凡鏃犳剰涔夛紝
* 0 濉厖浠ヤ繚鎸?DTO 鍏煎锛岃皟鐢ㄦ柟鏆傛湭浣跨敤銆? */
default ResourceOrderSnapshotStatisticDTO queryResourceOrderSnapshotStatistic() { default ResourceOrderSnapshotStatisticDTO queryResourceOrderSnapshotStatistic() {
// 阶段 6 改造:compute / gpu_count / core_count / memory_capacity 列已从 compute_resource_order_snapshot 删除,
// 真实统计需改走规格中间表(compute_resource_spu_spec_value),不在本阶段范围。
// 当前先返回零值占位,保证 HomeIndexOverallSituation 看板不报错。
// TODO 后续阶段:从规格中间表按 config_category='算力'/'GPU' 等汇总真实数据
ResourceOrderSnapshotStatisticDTO resp = new ResourceOrderSnapshotStatisticDTO(); ResourceOrderSnapshotStatisticDTO resp = new ResourceOrderSnapshotStatisticDTO();
BigDecimal total = sumLeasedComputeNow();
if (total == null) {
total = BigDecimal.ZERO;
}
resp.setTotalCompute(total.setScale(2, java.math.RoundingMode.HALF_UP).doubleValue());
resp.setTotalGpuCount(0L);
resp.setTotalCoreCount(0L);
resp.setTotalMemoryCapacity(0L);
return resp; return resp;
} }
}
} \ No newline at end of file
...@@ -3,6 +3,7 @@ package com.luhu.computility.module.compute.dal.mysql.resourcespu; ...@@ -3,6 +3,7 @@ package com.luhu.computility.module.compute.dal.mysql.resourcespu;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.luhu.computility.framework.common.pojo.PageResult; import com.luhu.computility.framework.common.pojo.PageResult;
...@@ -13,7 +14,10 @@ import com.luhu.computility.module.compute.api.order.dto.ResourceSpuStatisticDTO ...@@ -13,7 +14,10 @@ import com.luhu.computility.module.compute.api.order.dto.ResourceSpuStatisticDTO
import com.luhu.computility.module.compute.dal.dataobject.resourcecategory.ResourceCategoryDO; import com.luhu.computility.module.compute.dal.dataobject.resourcecategory.ResourceCategoryDO;
import com.luhu.computility.module.compute.dal.dataobject.resourcespu.ResourceSpuDO; import com.luhu.computility.module.compute.dal.dataobject.resourcespu.ResourceSpuDO;
import com.luhu.computility.module.compute.dal.mysql.resourcecategory.ResourceCategoryMapper; import com.luhu.computility.module.compute.dal.mysql.resourcecategory.ResourceCategoryMapper;
import com.luhu.computility.module.compute.enums.ResourceSpuStatus;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.util.CollectionUtils;
import com.luhu.computility.module.compute.controller.admin.resourcespu.vo.*; import com.luhu.computility.module.compute.controller.admin.resourcespu.vo.*;
/** /**
...@@ -24,7 +28,7 @@ import com.luhu.computility.module.compute.controller.admin.resourcespu.vo.*; ...@@ -24,7 +28,7 @@ import com.luhu.computility.module.compute.controller.admin.resourcespu.vo.*;
@Mapper @Mapper
public interface ResourceSpuMapper extends BaseMapperX<ResourceSpuDO> { public interface ResourceSpuMapper extends BaseMapperX<ResourceSpuDO> {
default PageResult<ResourceSpuRespVO> selectPage(ResourceSpuPageReqVO reqVO) { default PageResult<ResourceSpuRespVO> selectPage(ResourceSpuPageReqVO reqVO) {
return selectJoinPage(reqVO, ResourceSpuRespVO.class,new MPJLambdaWrapperX<ResourceSpuDO>() return selectJoinPage(reqVO, ResourceSpuRespVO.class,new MPJLambdaWrapperX<ResourceSpuDO>()
.selectAll(ResourceSpuDO.class) .selectAll(ResourceSpuDO.class)
.selectAs(ResourceCategoryDO::getName,ResourceSpuRespVO::getCategoryName) .selectAs(ResourceCategoryDO::getName,ResourceSpuRespVO::getCategoryName)
...@@ -41,39 +45,87 @@ public interface ResourceSpuMapper extends BaseMapperX<ResourceSpuDO> { ...@@ -41,39 +45,87 @@ public interface ResourceSpuMapper extends BaseMapperX<ResourceSpuDO> {
.orderByDesc(ResourceSpuDO::getId)); .orderByDesc(ResourceSpuDO::getId));
} }
/**
* 统计算力总规模(TOPS)
*
* 阶段 1 改造后 SPU 自身不再存 compute / gpu_count / core_count / memory_capacity,
* 这些数值改为存放在 compute_resource_config.compute 字段中(每个 SPU 通过 spu_spec_value 关联多个 config)。
*
* 本方法实现:
* 1) 过滤 spu.status = ONLINE(=1)且未删除
* 2) 关联 spec_value → config
* 3) 按 spu 聚合:每个 SPU 的算力 = 其名下所有 config.compute 之和
* 4) 再对所有 SPU 的算力求总和
*
* 已废弃字段 totalGpuCount / totalCoreCount / totalMemoryCapacity:
* 历史上 SPU 实体本来直接存了这些数字,但阶段 1 重构后它们不存在了。统计意义已转移到
* config.compute(TOPS)。为保持 DTO 兼容,仍返回三个字段,0 填充,调用方暂未使用。
*/
default ResourceSpuStatisticDTO selectResourceSpuStatistic() { default ResourceSpuStatisticDTO selectResourceSpuStatistic() {
ResourceSpuStatisticDTO resp = new ResourceSpuStatisticDTO(); ResourceSpuStatisticDTO resp = new ResourceSpuStatisticDTO();
resp.setTotalCompute(0.0);
resp.setTotalGpuCount(0L);
resp.setTotalCoreCount(0L);
resp.setTotalMemoryCapacity(0L);
// SQL sum 查询 // 1) 上架的 SPU
List<Map<String, Object>> result = selectMaps(new QueryWrapper<ResourceSpuDO>() List<ResourceSpuDO> onlineSpuList = selectList(new LambdaQueryWrapperX<ResourceSpuDO>()
.select("IFNULL(SUM(compute), 0) as totalCompute, IFNULL(SUM(gpu_count), 0) as totalGpuCount, " + .eq(ResourceSpuDO::getStatus, ResourceSpuStatus.ONLINE.getValue())
"IFNULL(SUM(core_count), 0) as totalCoreCount, IFNULL(SUM(memory_capacity), 0) as totalMemoryCapacity")); .eq(ResourceSpuDO::getDeleted, false));
if (CollectionUtils.isEmpty(onlineSpuList)) {
return resp;
}
if (!result.isEmpty()) { // 2) 批量查出这些 SPU 名下所有 spec_value → config 映射
// compute -> BigDecimal,保留两位小数 List<Long> spuIds = onlineSpuList.stream().map(ResourceSpuDO::getId).collect(Collectors.toList());
Map<String, Object> row = result.get(0);
//算力(保留 2 位)
BigDecimal compute = getBigDecimal(row, "totalCompute")
.setScale(2, RoundingMode.HALF_UP);
//强烈建议:VO 用 BigDecimal // 阶段 1:依赖 XML 中的 sumComputeBySpuIds
resp.setTotalCompute(compute.doubleValue()); List<Map<String, Object>> rows = selectSumComputeBySpuIds(spuIds);
//GPU 数量 if (CollectionUtils.isEmpty(rows)) {
resp.setTotalGpuCount( getBigDecimal(row, "totalGpuCount").longValue() ); return resp;
//CPU 核心数
resp.setTotalCoreCount( getBigDecimal(row, "totalCoreCount").longValue() );
//内存容量
resp.setTotalMemoryCapacity( getBigDecimal(row, "totalMemoryCapacity").longValue());
} }
BigDecimal total = BigDecimal.ZERO;
for (Map<String, Object> row : rows) {
Object v = row.get("spu_compute");
if (v != null) {
total = total.add(new BigDecimal(v.toString()));
}
}
resp.setTotalCompute(total.setScale(2, RoundingMode.HALF_UP).doubleValue());
return resp; return resp;
} }
static BigDecimal getBigDecimal(Map<String, Object> map, String key) { static BigDecimal getBigDecimal(Map<String, Object> map, String key) {
Object val = map.get(key); Object val = map.get(key);
return val == null ? BigDecimal.ZERO : (BigDecimal) val; if (val == null) {
return BigDecimal.ZERO;
}
if (val instanceof BigDecimal) {
return (BigDecimal) val;
}
return new BigDecimal(val.toString());
} }
/**
* 按 SPU 分组汇总算力
* 入参:SPU ID 列表;返回 [{spu_id, spu_compute}]
*/
List<Map<String, Object>> selectSumComputeBySpuIds(@Param("spuIds") List<Long> spuIds);
/**
* 按 GPU 配置选项(config_option)分组汇总算力
* 仅统计 status=ONLINE 的 SPU,且 config_category='GPU'
* 返回 [{name, value}],name = config_option,value = 选用该 GPU 型号的 SPU 数量
* 与算力来源 / 计算资源分布保持按 SPU 数量统计的口径一致
*/
List<Map<String, Object>> selectGpuSumByCategory(@Param("category") String category);
/**
* 按 area_id 分组汇总算力
* 仅统计 status=ONLINE 的 SPU
* 返回 [{areaId, compute}]
*/
List<Map<String, Object>> selectSumComputeByAreaId();
} }
...@@ -22,7 +22,7 @@ import java.util.Map; ...@@ -22,7 +22,7 @@ import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* 算力资源订单统计API实现类 * 算力资源订单统计 API 实现类
* *
* @author jony * @author jony
*/ */
...@@ -55,17 +55,31 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi { ...@@ -55,17 +55,31 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi {
public ComputeDistributionDTO getComputeDistribution() { public ComputeDistributionDTO getComputeDistribution() {
ComputeDistributionDTO result = new ComputeDistributionDTO(); ComputeDistributionDTO result = new ComputeDistributionDTO();
List<ResourceSpuDO> spuList = resourceSpuMapper.selectList(); // 1) GPU 型号分布(阶段 3 改:按 SPU 数量,与 source/resource 口径一致)
// 走 ResourceSpuMapper.selectGpuSumByCategory("GPU"),按 config_option 统计 SPU 数量
List<Map<String, Object>> gpuRows = resourceSpuMapper.selectGpuSumByCategory("GPU");
List<ComputeDistributionDTO.DistributionItem> gpuList = gpuRows.stream()
.map(row -> {
ComputeDistributionDTO.DistributionItem item = new ComputeDistributionDTO.DistributionItem();
item.setName(asString(row.get("name")));
item.setValue(asLong(row.get("value")));
return item;
})
.collect(Collectors.toList());
// 2) 算力来源分布(保持原样,按 SPU 数量)
List<ResourceSpuDO> spuList = resourceSpuMapper.selectList();
List<ResourceSpuDO> onlineSpuList = spuList.stream() List<ResourceSpuDO> onlineSpuList = spuList.stream()
.filter(spu -> spu.getStatus() != null && spu.getStatus().equals(ResourceSpuStatus.ONLINE.getValue())) .filter(spu -> spu.getStatus() != null && spu.getStatus().equals(ResourceSpuStatus.ONLINE.getValue()))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (CollectionUtils.isEmpty(onlineSpuList)) { if (CollectionUtils.isEmpty(onlineSpuList)) {
result.setGpu(gpuList);
result.setSource(Collections.emptyList());
result.setResource(Collections.emptyList());
return result; return result;
} }
// 算力来源分布
Map<String, Long> sourceCountMap = onlineSpuList.stream() Map<String, Long> sourceCountMap = onlineSpuList.stream()
.filter(spu -> spu.getSource() != null) .filter(spu -> spu.getSource() != null)
.collect(Collectors.groupingBy(ResourceSpuDO::getSource, Collectors.counting())); .collect(Collectors.groupingBy(ResourceSpuDO::getSource, Collectors.counting()));
...@@ -79,8 +93,7 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi { ...@@ -79,8 +93,7 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi {
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
// 计算资源分布(按分类) // 3) 计算资源分布(按分类,SPU 数量)
// 查询所有分类,用于将 categoryId 转换为分类名称
List<ResourceCategoryDO> categoryList = resourceCategoryMapper.selectList(); List<ResourceCategoryDO> categoryList = resourceCategoryMapper.selectList();
Map<Long, String> categoryIdToNameMap = categoryList.stream() Map<Long, String> categoryIdToNameMap = categoryList.stream()
.collect(Collectors.toMap(ResourceCategoryDO::getId, ResourceCategoryDO::getName)); .collect(Collectors.toMap(ResourceCategoryDO::getId, ResourceCategoryDO::getName));
...@@ -92,7 +105,6 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi { ...@@ -92,7 +105,6 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi {
List<ComputeDistributionDTO.DistributionItem> resourceList = categoryCountMap.entrySet().stream() List<ComputeDistributionDTO.DistributionItem> resourceList = categoryCountMap.entrySet().stream()
.map(entry -> { .map(entry -> {
ComputeDistributionDTO.DistributionItem item = new ComputeDistributionDTO.DistributionItem(); ComputeDistributionDTO.DistributionItem item = new ComputeDistributionDTO.DistributionItem();
// 使用分类名称而不是 categoryId
String categoryName = categoryIdToNameMap.get(entry.getKey()); String categoryName = categoryIdToNameMap.get(entry.getKey());
item.setName(categoryName != null ? categoryName : "未知分类"); item.setName(categoryName != null ? categoryName : "未知分类");
item.setValue(entry.getValue()); item.setValue(entry.getValue());
...@@ -100,10 +112,6 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi { ...@@ -100,10 +112,6 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi {
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
// 阶段 1 改造后 SPU 不再保存 gpu 列,原按显卡型号分组的逻辑已无法执行,
// 暂时返回空列表,DTO 字段保留以便后续阶段 6 改造成从规格中间表统计
List<ComputeDistributionDTO.DistributionItem> gpuList = Collections.emptyList();
result.setGpu(gpuList); result.setGpu(gpuList);
result.setSource(sourceList); result.setSource(sourceList);
result.setResource(resourceList); result.setResource(resourceList);
...@@ -113,17 +121,19 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi { ...@@ -113,17 +121,19 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi {
@Override @Override
public ResourceAreaInfoDTO getAllResourceAreaIds() { public ResourceAreaInfoDTO getAllResourceAreaIds() {
// 查询所有算力资源 // 阶段 3:按 area_id 预聚合的算力总和,调用新 SQL selectSumComputeByAreaId
List<ResourceSpuDO> spuList = resourceSpuMapper.selectList(); List<Map<String, Object>> rows = resourceSpuMapper.selectSumComputeByAreaId();
ResourceAreaInfoDTO result = new ResourceAreaInfoDTO(); ResourceAreaInfoDTO result = new ResourceAreaInfoDTO();
// 提取每个资源的地区ID;算力值在阶段 1 后不再直接存在 SPU 上,暂置 0 List<ResourceAreaInfoDTO.ResourceAreaItem> items = rows.stream()
List<ResourceAreaInfoDTO.ResourceAreaItem> items = spuList.stream() .map(row -> {
.filter(spu -> spu.getAreaId() != null) // 只处理有地区ID的资源
.map(spu -> {
ResourceAreaInfoDTO.ResourceAreaItem item = new ResourceAreaInfoDTO.ResourceAreaItem(); ResourceAreaInfoDTO.ResourceAreaItem item = new ResourceAreaInfoDTO.ResourceAreaItem();
item.setAreaId(spu.getAreaId()); // 地区ID Object areaId = row.get("areaId");
item.setCompute(0.0); // TODO: 阶段 1 改造后算力从规格中间表统计,暂置 0 if (areaId != null) {
item.setAreaId(areaId instanceof Integer ? (Integer) areaId
: Integer.valueOf(areaId.toString()));
}
item.setCompute(asDouble(row.get("compute")));
return item; return item;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
...@@ -132,4 +142,34 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi { ...@@ -132,4 +142,34 @@ public class ComputeStatisticsApiImpl implements ComputeStatisticsApi {
return result; return result;
} }
private static String asString(Object v) {
return v == null ? null : v.toString();
}
private static Long asLong(Object v) {
if (v == null) {
return 0L;
}
if (v instanceof Long) {
return (Long) v;
}
if (v instanceof Number) {
return ((Number) v).longValue();
}
return Long.parseLong(v.toString());
}
private static Double asDouble(Object v) {
if (v == null) {
return 0.0;
}
if (v instanceof Double) {
return (Double) v;
}
if (v instanceof Number) {
return ((Number) v).doubleValue();
}
return Double.parseDouble(v.toString());
}
} }
<?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.luhu.computility.module.compute.dal.mysql.resourceordersnapshot.ResourceOrderSnapshotMapper">
<!--
阶段 3:算力统计相关 SQL(订单侧)。
ResourceOrderDO 不再保存 spu_id(只有 skuId + spuName 文本快照),
spec_snapshot 是 JSON:{"GPU":"8xA100 (40GB)","CPU":"i7-13700K",...}
统计已租赁算力:JOIN config 表,配置项的 (config_category, config_option)
与 spec_snapshot 中 key/value 一致时,把 config.compute 累加进来。
-->
<select id="sumLeasedComputeNow" resultType="java.math.BigDecimal">
SELECT COALESCE(SUM(c.compute), 0)
FROM compute_resource_order_snapshot s
INNER JOIN compute_resource_order o
ON o.id = s.order_id AND o.deleted = 0
INNER JOIN compute_resource_config c
ON c.config_option = JSON_UNQUOTE(JSON_EXTRACT(s.spec_snapshot, CONCAT('$."', c.config_category, '"')))
AND c.deleted = 0
WHERE s.deleted = 0
AND o.status = 6
AND s.rent_start_time IS NOT NULL
AND s.rent_end_time IS NOT NULL
AND s.rent_start_time &lt;= NOW()
AND s.rent_end_time &gt;= NOW()
AND JSON_EXTRACT(s.spec_snapshot, CONCAT('$."', c.config_category, '"')) IS NOT NULL
</select>
</mapper>
...@@ -3,10 +3,56 @@ ...@@ -3,10 +3,56 @@
<mapper namespace="com.luhu.computility.module.compute.dal.mysql.resourcespu.ResourceSpuMapper"> <mapper namespace="com.luhu.computility.module.compute.dal.mysql.resourcespu.ResourceSpuMapper">
<!-- <!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可 阶段 3:算力统计相关 SQL
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。 SPU 实体本身不再存 compute / gpu_count / core_count / memory_capacity,
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。 这些数值都改存放在 compute_resource_config.compute 字段中。统计时通过 SPU → spec_value → config 三表关联,
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/ 按 SPU 分组求 config.compute 之和。
--> -->
</mapper> <select id="selectSumComputeBySpuIds" resultType="map">
\ No newline at end of file SELECT spu.id AS spu_id,
COALESCE(SUM(c.compute), 0) AS spu_compute
FROM compute_resource_spu spu
INNER JOIN compute_resource_spu_spec_value sv
ON sv.spu_id = spu.id AND sv.deleted = 0
INNER JOIN compute_resource_config c
ON c.id = sv.config_id
WHERE spu.deleted = 0
AND spu.status = 1
AND spu.id IN
<foreach collection="spuIds" item="spuId" open="(" close=")" separator=",">
#{spuId}
</foreach>
GROUP BY spu.id
</select>
<select id="selectGpuSumByCategory" resultType="map">
SELECT c.config_option AS name,
COUNT(DISTINCT sv.spu_id) AS value
FROM compute_resource_spu spu
INNER JOIN compute_resource_spu_spec_value sv
ON sv.spu_id = spu.id AND sv.deleted = 0
INNER JOIN compute_resource_config c
ON c.id = sv.config_id
WHERE spu.deleted = 0
AND spu.status = 1
AND c.config_category = #{category}
GROUP BY c.config_option
ORDER BY value DESC
</select>
<select id="selectSumComputeByAreaId" resultType="map">
SELECT spu.area_id AS areaId,
COALESCE(SUM(c.compute), 0) AS compute
FROM compute_resource_spu spu
INNER JOIN compute_resource_spu_spec_value sv
ON sv.spu_id = spu.id AND sv.deleted = 0
INNER JOIN compute_resource_config c
ON c.id = sv.config_id
WHERE spu.deleted = 0
AND spu.status = 1
AND spu.area_id IS NOT NULL
GROUP BY spu.area_id
</select>
</mapper>
...@@ -179,6 +179,8 @@ public class RechargeAgreementPdfService { ...@@ -179,6 +179,8 @@ public class RechargeAgreementPdfService {
*/ */
private LoadedFont loadChineseFont() { private LoadedFont loadChineseFont() {
String[] candidates = {"NotoSansSC-Regular.otf", "NotoSansCJKsc-Regular.otf", String[] candidates = {"NotoSansSC-Regular.otf", "NotoSansCJKsc-Regular.otf",
"NotoSerifSC-Regular.otf",
"SourceHanSerifSC-Regular.ttf", "SourceHanSerifSC-Regular.otf",
"NotoSansSC-Regular.ttf", "NotoSansCJKsc-Regular.ttf"}; "NotoSansSC-Regular.ttf", "NotoSansCJKsc-Regular.ttf"};
for (String name : candidates) { for (String name : candidates) {
ClassPathResource resource = new ClassPathResource("fonts/" + name); ClassPathResource resource = new ClassPathResource("fonts/" + name);
......
package com.luhu.computility.module.system.api.logger; package com.luhu.computility.module.system.api.logger;
import com.luhu.computility.module.system.api.logger.dto.LoginLogCreateReqDTO; import com.luhu.computility.module.system.api.logger.dto.LoginLogCreateReqDTO;
import com.luhu.computility.module.system.dal.dataobject.logger.LoginLogDO;
import javax.validation.Valid; import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* 登录日志的 API 接口 * 登录日志的 API 接口
...@@ -18,4 +21,12 @@ public interface LoginLogApi { ...@@ -18,4 +21,12 @@ public interface LoginLogApi {
*/ */
void createLoginLog(@Valid LoginLogCreateReqDTO reqDTO); void createLoginLog(@Valid LoginLogCreateReqDTO reqDTO);
/**
* 获得登录成功日志列表(用于大屏活跃用户数统计)
*
* @param createTime 时间范围 [start, end]
* @return 登录日志列表
*/
List<LoginLogDO> getLoginLogList(LocalDateTime[] createTime);
} }
package com.luhu.computility.module.system.api.logger; package com.luhu.computility.module.system.api.logger;
import com.luhu.computility.module.system.api.logger.dto.LoginLogCreateReqDTO; import com.luhu.computility.module.system.api.logger.dto.LoginLogCreateReqDTO;
import com.luhu.computility.module.system.dal.dataobject.logger.LoginLogDO;
import com.luhu.computility.module.system.service.logger.LoginLogService; import com.luhu.computility.module.system.service.logger.LoginLogService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* 登录日志的 API 实现类 * 登录日志的 API 实现类
...@@ -24,4 +27,9 @@ public class LoginLogApiImpl implements LoginLogApi { ...@@ -24,4 +27,9 @@ public class LoginLogApiImpl implements LoginLogApi {
loginLogService.createLoginLog(reqDTO); loginLogService.createLoginLog(reqDTO);
} }
@Override
public List<LoginLogDO> getLoginLogList(LocalDateTime[] createTime) {
return loginLogService.getLoginLogList(createTime);
}
} }
...@@ -5,9 +5,14 @@ import com.luhu.computility.framework.mybatis.core.mapper.BaseMapperX; ...@@ -5,9 +5,14 @@ import com.luhu.computility.framework.mybatis.core.mapper.BaseMapperX;
import com.luhu.computility.framework.mybatis.core.query.LambdaQueryWrapperX; import com.luhu.computility.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.luhu.computility.module.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO; import com.luhu.computility.module.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO;
import com.luhu.computility.module.system.dal.dataobject.logger.LoginLogDO; import com.luhu.computility.module.system.dal.dataobject.logger.LoginLogDO;
import com.luhu.computility.module.system.enums.logger.LoginLogTypeEnum;
import com.luhu.computility.module.system.enums.logger.LoginResultEnum; import com.luhu.computility.module.system.enums.logger.LoginResultEnum;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
@Mapper @Mapper
public interface LoginLogMapper extends BaseMapperX<LoginLogDO> { public interface LoginLogMapper extends BaseMapperX<LoginLogDO> {
...@@ -25,4 +30,25 @@ public interface LoginLogMapper extends BaseMapperX<LoginLogDO> { ...@@ -25,4 +30,25 @@ public interface LoginLogMapper extends BaseMapperX<LoginLogDO> {
return selectPage(reqVO, query); return selectPage(reqVO, query);
} }
/**
* 查询指定时间范围内的登录成功日志(用于大屏活跃用户数统计)
*
* @param createTime 时间范围 [start, end]
* @return 登录日志列表
*/
default List<LoginLogDO> selectList(LocalDateTime[] createTime) {
return selectList(new LambdaQueryWrapperX<LoginLogDO>()
// 仅统计登录行为:账号登录(100)、社交登录(101)、手机登录(103)、短信登录(104)
// 排除:登出(200)、强制退出(202)
.in(LoginLogDO::getLogType, Arrays.asList(
LoginLogTypeEnum.LOGIN_USERNAME.getType(),
LoginLogTypeEnum.LOGIN_SOCIAL.getType(),
LoginLogTypeEnum.LOGIN_MOBILE.getType(),
LoginLogTypeEnum.LOGIN_SMS.getType()))
// 仅统计登录成功
.eq(LoginLogDO::getResult, LoginResultEnum.SUCCESS.getResult())
.betweenIfPresent(LoginLogDO::getCreateTime, createTime)
.orderByDesc(LoginLogDO::getId));
}
} }
...@@ -6,6 +6,8 @@ import com.luhu.computility.module.system.controller.admin.logger.vo.loginlog.Lo ...@@ -6,6 +6,8 @@ import com.luhu.computility.module.system.controller.admin.logger.vo.loginlog.Lo
import com.luhu.computility.module.system.dal.dataobject.logger.LoginLogDO; import com.luhu.computility.module.system.dal.dataobject.logger.LoginLogDO;
import javax.validation.Valid; import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* 登录日志 Service 接口 * 登录日志 Service 接口
...@@ -21,6 +23,14 @@ public interface LoginLogService { ...@@ -21,6 +23,14 @@ public interface LoginLogService {
PageResult<LoginLogDO> getLoginLogPage(LoginLogPageReqVO pageReqVO); PageResult<LoginLogDO> getLoginLogPage(LoginLogPageReqVO pageReqVO);
/** /**
* 获得登录成功日志列表(用于大屏活跃用户数统计)
*
* @param createTime 时间范围 [start, end]
* @return 登录日志列表
*/
List<LoginLogDO> getLoginLogList(LocalDateTime[] createTime);
/**
* 创建登录日志 * 创建登录日志
* *
* @param reqDTO 日志信息 * @param reqDTO 日志信息
......
...@@ -10,6 +10,8 @@ import org.springframework.stereotype.Service; ...@@ -10,6 +10,8 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* 登录日志 Service 实现 * 登录日志 Service 实现
...@@ -27,6 +29,11 @@ public class LoginLogServiceImpl implements LoginLogService { ...@@ -27,6 +29,11 @@ public class LoginLogServiceImpl implements LoginLogService {
} }
@Override @Override
public List<LoginLogDO> getLoginLogList(LocalDateTime[] createTime) {
return loginLogMapper.selectList(createTime);
}
@Override
public void createLoginLog(LoginLogCreateReqDTO reqDTO) { public void createLoginLog(LoginLogCreateReqDTO reqDTO) {
LoginLogDO loginLog = BeanUtils.toBean(reqDTO, LoginLogDO.class); LoginLogDO loginLog = BeanUtils.toBean(reqDTO, LoginLogDO.class);
loginLogMapper.insert(loginLog); loginLogMapper.insert(loginLog);
......
-- ============================================================
-- 算力资源配置 · 算力字段扩展
-- 目标:为 compute_resource_config 表加 compute 字段
-- 适用库:new_computility(线上库名若不同请修改下方 USE 那一行)
-- 前置:无
-- 配套:
-- - ResourceConfigDO.compute
-- - ResourceConfigSaveReqVO.compute(新增/修改时可填,可选)
-- - ResourceConfigRespVO.compute(详情/列表/导出 Excel 包含)
-- 业务语义:
-- - 该配置项代表的算力值(TOPS)
-- - 如 1×A100 (40GB) 的 compute = 624,8×A100 (40GB) 的 compute = 4992
-- - SPU 总算力 = SUM(SPU 选中的所有 config.compute)
-- - 默认 0,老数据不破坏
--
-- 生产环境安全说明:
-- - ALTER TABLE ... ADD COLUMN 是 MySQL 5.6+ 的 Online DDL,
-- 仅修改表元数据,不全表重建;ALGORITHM=INPLACE, LOCK=NONE
-- 期间允许 DML(不阻塞业务),但首尾仍需元数据锁几毫秒
-- - 适用于表数据量 1w 行以内秒级完成;若该表已超 50w 行,
-- 建议低峰期执行(22:00 之后)
-- - 本脚本幂等:若已存在 compute 字段会跳过并提示
-- ============================================================
USE `new_computility`;
-- ---------- 幂等性检查 ----------
SET @col_exists := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'compute_resource_config'
AND COLUMN_NAME = 'compute'
);
SET @msg := IF(@col_exists > 0,
'compute 字段已存在,跳过本次新增',
'compute 字段不存在,开始执行新增');
SELECT @msg AS info;
-- ---------- 新增字段 ----------
-- 使用 Online DDL:INPLACE 算法不重建表,NONE 锁允许并发 DML
ALTER TABLE `compute_resource_config`
ADD COLUMN `compute` DECIMAL(18,2) NOT NULL DEFAULT 0
COMMENT '算力值(TOPS),默认 0,由运营手动配置' AFTER `detail_remark`,
ALGORITHM=INPLACE, LOCK=NONE;
-- ---------- 验证 ----------
-- SHOW COLUMNS FROM `compute_resource_config` LIKE 'compute';
-- 期望结果:
-- compute | decimal(18,2) | NO | | 0.00 | 算力值(TOPS),默认 0,由运营手动配置
-- ============================================================
-- 回滚脚本(仅在确认要回滚时手动执行,默认不执行)
-- ============================================================
-- ALTER TABLE `compute_resource_config`
-- DROP COLUMN `compute`,
-- ALGORITHM=INPLACE, LOCK=NONE;
-- ============================================================
-- 算力资源 SPU source 字段脏数据清洗
-- 库:new_computility
-- 文件:2026_08_18_fix_compute_resource_spu_source.sql
--
-- 【问题现象】
-- 大屏"算力来源"饼图在真实数据模式下出现 4 种 source 标签:
-- 自有、社会、own、cooperative
-- 后端 ComputeStatisticsApiImpl.getComputeDistribution() 直接
-- 对 compute_resource_spu.source 做 GROUP BY,原值返回;
-- 前端 getDictLabel 只能翻译字典里有的英文 key,脏的"自有""社会"
-- 字典没匹配上就回退原值。
--
-- 【根因】
-- compute_resource_source 字典 value 是英文:
-- own / cooperative / social(label=自有/合作/社会)
-- ResourceSpuDO.source 注释也明确写"own-自有, cooperative-合作,
-- social-社会",按设计 SPU.source 应该存字典 value(英文)。
-- SPU 表里出现的"自有"和"社会"是历史脏数据:
-- - 早期字典 value 可能是中文(自有/合作/社会)
-- - 或通过 SQL / 导入手工写入了中文 source
--
-- 【目标】
-- 把 SPU 表 source 字段的中文脏数据反向迁移为字典 value(英文)。
-- 字典 compute_resource_source 标准 value:own / cooperative / social
--
-- 【执行顺序】
-- 1. 跑第 1 段探测 SELECT(确认当前 distinct 值和数量)
-- 2. 跑第 2 段 UPDATE(清洗中文脏数据 → 英文字典 value)
-- 3. 跑第 3 段验证 SELECT(确认只剩字典英文 value)
-- 4. 出问题跑第 4 段 ROLLBACK(默认注释)
--
-- 【安全说明】
-- - 动数据前先跑探测 SELECT
-- - UPDATE WHERE source = 'XXX' 精准匹配中文值,不会影响英文 key
-- - 受影响行数应等于探测结果中"中文脏数据"的 SPU 数量
-- ============================================================
USE `new_computility`;
-- ============================================================
-- 1) 探测:当前 SPU 表 source 字段所有 distinct 值
-- ============================================================
SELECT source, COUNT(*) AS spu_count
FROM compute_resource_spu
WHERE source IS NOT NULL
GROUP BY source
ORDER BY spu_count DESC, source ASC;
-- 期望看到类似(典型 4 类混合):
-- source spu_count
-- cooperative 3
-- own 2
-- 社会 1 ← 脏数据
-- 自有 1 ← 脏数据
-- 清洗后期望只剩 own / cooperative / social
-- ============================================================
-- 2) 数据迁移:中文脏数据 → 字典英文 value
-- ============================================================
-- 字典 value 映射(label 自有/合作/社会 → value own/cooperative/social)
-- 注意:以下 UPDATE 仅匹配数据库中真实存在的中文值,按你探测结果的实际值写
-- 2.1 自有 → own
UPDATE compute_resource_spu
SET source = 'own'
WHERE source = '自有';
-- 2.2 社会 → social
UPDATE compute_resource_spu
SET source = 'social'
WHERE source = '社会';
-- 2.3 合作 → cooperative
UPDATE compute_resource_spu
SET source = 'cooperative'
WHERE source = '合作';
-- 2.4 如果探测出上面未覆盖的中文脏数据,按下面格式追加:
-- UPDATE compute_resource_spu
-- SET source = '字典 value'
-- WHERE source = '数据库里的中文脏值';
-- ============================================================
-- 3) 验证:清洗后只剩字典英文 value
-- ============================================================
SELECT source, COUNT(*) AS spu_count
FROM compute_resource_spu
WHERE source IS NOT NULL
GROUP BY source
ORDER BY spu_count DESC, source ASC;
-- 期望:结果集里不再出现"自有""社会""合作"等中文,
-- 全部是 own / cooperative / social 之一
-- ============================================================
-- 4) ROLLBACK(默认不执行;如需回滚请手动取消注释)
-- ============================================================
-- 4.1 跑 UPDATE 前建议先建备份表(取消注释执行一次)
-- CREATE TABLE compute_resource_spu_source_backup_20260818 AS
-- SELECT id, source
-- FROM compute_resource_spu
-- WHERE source IN ('自有', '社会', '合作');
-- 4.2 误清洗时按相反方向回滚(按你实际改过的列)
-- UPDATE compute_resource_spu
-- SET source = '自有'
-- WHERE source = 'own'
-- AND id IN (SELECT id FROM compute_resource_spu_source_backup_20260818);
--
-- UPDATE compute_resource_spu
-- SET source = '社会'
-- WHERE source = 'social'
-- AND id IN (SELECT id FROM compute_resource_spu_source_backup_20260818);
--
-- UPDATE compute_resource_spu
-- SET source = '合作'
-- WHERE source = 'cooperative'
-- AND id IN (SELECT id FROM compute_resource_spu_source_backup_20260818);
-- 4.3 删备份表
-- DROP TABLE IF EXISTS compute_resource_spu_source_backup_20260818;
-- ============================================================
-- 验证大屏"API 请求趋势"图表数据准确性(v2 - 修正表名)
-- 库:new_computility
-- 文件:2026_08_18_verify_api_calls_trend_v2.sql
-- 修正:原 v1 误用 infra_api_access_log,实际是 apihub_api_call_log
--
-- 【业务语义】
-- "API 请求趋势" = API 服务市场应用被用户调用的次数
-- 数据源表:apihub_api_call_log(项目自有的 apihub 模块)
-- 过滤字段:create_time(HomeIndexServiceImpl:213 setCreateTime)
-- 注意:表里还有 call_time 字段,但 service 实际按 create_time 过滤
--
-- 【与后端 service 对齐】
-- HomeIndexServiceImpl.getApiCallsData(dateType)
-- ├─ d (日): 近 7 天,截止到昨天
-- ├─ m (月): 近 30 天,截止到昨天
-- └─ y (年): 近 12 个月,截止到上月末
-- endTime = yesterday.atTime(23, 59, 59, 999_999_999)
-- ============================================================
USE `new_computility`;
-- ============================================================
-- 0) 今日日期确认 + 表数据健康检查
-- ============================================================
SELECT CURDATE() AS today, DATE_SUB(CURDATE(), INTERVAL 1 DAY) AS yesterday;
SELECT
COUNT(*) AS total_cnt,
MIN(create_time) AS min_ct,
MAX(create_time) AS max_ct
FROM apihub_api_call_log;
-- 如果 total_cnt = 0,说明 API 服务市场还没人调用过,图表必然为 0
-- 如果 max_ct 早于昨天,说明可能没新调用
-- ============================================================
-- 1) d(日)维度验证:近 7 天,截止到昨天
-- 假设今天是 2026-08-18 → 窗口 2026-08-11 ~ 2026-08-17
-- ============================================================
SELECT DATE(create_time) AS day, COUNT(*) AS cnt
FROM apihub_api_call_log
WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
AND create_time < CURDATE()
AND create_time >= '1970-01-01'
GROUP BY DATE(create_time)
ORDER BY day;
-- 期望 7 行,每行 day + cnt 对应图表日维度的 x + y
-- ============================================================
-- 2) m(月)维度验证:近 30 天,截止到昨天
-- 假设今天是 2026-08-18 → 窗口 2026-07-19 ~ 2026-08-17
-- ============================================================
SELECT DATE(create_time) AS day, COUNT(*) AS cnt
FROM apihub_api_call_log
WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
AND create_time < CURDATE()
AND create_time >= '1970-01-01'
GROUP BY DATE(create_time)
ORDER BY day;
-- 期望 30 行,对应图表月维度的 x + y
-- ============================================================
-- 3) y(年)维度验证:近 12 个月,截止到上月末
-- 假设今天是 2026-08-18 → 上月末 2026-07-31
-- 时间窗口 2025-08-01 00:00:00 ~ 2026-07-31 23:59:59.999999
-- ============================================================
SELECT DATE_FORMAT(create_time, '%Y-%m') AS month, COUNT(*) AS cnt
FROM apihub_api_call_log
WHERE create_time >= DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 12 MONTH), '%Y-%m-01')
AND create_time < DATE_FORMAT(CURDATE(), '%Y-%m-01')
AND create_time >= '1970-01-01'
GROUP BY DATE_FORMAT(create_time, '%Y-%m')
ORDER BY month;
-- 期望 12 行(每个月末一天),对应图表年维度的 x + y
-- 注意:图表 x 显示 "yyyy-MM" 格式(如 "2025-08"),不是月末日期
-- ============================================================
-- 4) 总数校验
-- ============================================================
SELECT
CURDATE() AS today,
(SELECT COUNT(*) FROM apihub_api_call_log WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)) AS cnt_7d,
(SELECT COUNT(*) FROM apihub_api_call_log WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)) AS cnt_30d,
(SELECT COUNT(*) FROM apihub_api_call_log) AS cnt_total;
-- cnt_7d 应等于 d 维度 7 行 cnt 之和
-- cnt_30d 应等于 m 维度 30 行 cnt 之和
-- cnt_total 是整张表总数
-- ============================================================
-- 5) 抽样验证:看最近 10 条记录
-- ============================================================
SELECT id, user_id, api_endpoint_name, call_time, response_status, create_time
FROM apihub_api_call_log
ORDER BY create_time DESC
LIMIT 10;
-- 用来:
-- (a) 确认有真实数据流入
-- (b) 看 create_time 与 call_time 是否一致(绝大多数应一致,差几毫秒)
-- (c) 看 response_status 分布(0=成功,其他=异常)
-- ============================================================
-- 6) 按用户/接口维度统计(辅助排查"为什么是这个数字")
-- ============================================================
SELECT
api_endpoint_name,
COUNT(*) AS cnt
FROM apihub_api_call_log
WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
AND create_time < CURDATE()
GROUP BY api_endpoint_name
ORDER BY cnt DESC
LIMIT 20;
-- 用来确认是哪些 API 贡献了大部分调用量
-- 也帮助理解图表上某个高点为什么突增
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment