|
|
@@ -3,13 +3,19 @@ package com.etotem.cfc.controller.stats;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
import com.etotem.cfc.common.Result;
|
|
|
import com.etotem.cfc.entity.Child;
|
|
|
+import com.etotem.cfc.entity.CommissionRecord;
|
|
|
import com.etotem.cfc.entity.Family;
|
|
|
+import com.etotem.cfc.entity.MemberUpgradeRecord;
|
|
|
import com.etotem.cfc.entity.Task;
|
|
|
+import com.etotem.cfc.entity.TrialMembership;
|
|
|
import com.etotem.cfc.entity.User;
|
|
|
import com.etotem.cfc.mapper.ChildMapper;
|
|
|
+import com.etotem.cfc.mapper.CommissionRecordMapper;
|
|
|
import com.etotem.cfc.mapper.FamilyMapper;
|
|
|
import com.etotem.cfc.mapper.GuidePackageMapper;
|
|
|
+import com.etotem.cfc.mapper.MemberUpgradeRecordMapper;
|
|
|
import com.etotem.cfc.mapper.TaskMapper;
|
|
|
+import com.etotem.cfc.mapper.TrialMembershipMapper;
|
|
|
import com.etotem.cfc.mapper.UserMapper;
|
|
|
import com.etotem.cfc.service.TaskStatsService;
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
|
@@ -19,12 +25,14 @@ import org.springframework.web.bind.annotation.*;
|
|
|
import javax.annotation.Resource;
|
|
|
import java.text.ParseException;
|
|
|
import java.text.SimpleDateFormat;
|
|
|
+import java.util.Calendar;
|
|
|
import java.util.Date;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
-import java.util.concurrent.ThreadPoolExecutor;
|
|
|
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
|
|
|
|
|
@Tag(name = "任务统计", description = "任务完成率分析")
|
|
|
@RestController
|
|
|
@@ -50,7 +58,16 @@ public class StatsController {
|
|
|
private GuidePackageMapper guidePackageMapper;
|
|
|
|
|
|
@Resource
|
|
|
- private ThreadPoolExecutor taskExecutor;
|
|
|
+ private ThreadPoolTaskExecutor taskExecutor;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private CommissionRecordMapper commissionRecordMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private TrialMembershipMapper trialMembershipMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private MemberUpgradeRecordMapper memberUpgradeRecordMapper;
|
|
|
|
|
|
@Operation(summary = "管理后台仪表盘汇总")
|
|
|
@PostMapping("/dashboard")
|
|
|
@@ -130,4 +147,201 @@ public class StatsController {
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ @Operation(summary = "管理后台数据总览(新)")
|
|
|
+ @PostMapping("/overview")
|
|
|
+ public Result<Map<String, Object>> getOverview() {
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ try {
|
|
|
+ CompletableFuture<Long> familyFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ familyMapper.selectCount(null), taskExecutor);
|
|
|
+ CompletableFuture<Long> parentFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ userMapper.selectCount(new QueryWrapper<User>().eq("role", "parent")), taskExecutor);
|
|
|
+ CompletableFuture<Long> childFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ childMapper.selectCount(null), taskExecutor);
|
|
|
+ CompletableFuture<Long> teacherFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ userMapper.selectCount(new QueryWrapper<User>().eq("role", "teacher")), taskExecutor);
|
|
|
+
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ String monthStart = sdf.format(new Date()).substring(0, 7) + "-01";
|
|
|
+ Date monthStartDate = sdf.parse(monthStart);
|
|
|
+ CompletableFuture<Long> monthFamiliesFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ familyMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Family>()
|
|
|
+ .ge(Family::getCreatedAt, monthStartDate)), taskExecutor);
|
|
|
+
|
|
|
+ CompletableFuture<Long> monthParentsFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ userMapper.selectCount(new QueryWrapper<User>()
|
|
|
+ .eq("role", "parent")
|
|
|
+ .ge("created_at", monthStartDate)), taskExecutor);
|
|
|
+
|
|
|
+ CompletableFuture<Long> pendingGuideFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ userMapper.selectCount(new QueryWrapper<User>()
|
|
|
+ .eq("role", "teacher").eq("teacher_status", "pending")), taskExecutor);
|
|
|
+
|
|
|
+ CompletableFuture<Long> pendingPackageFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ guidePackageMapper.selectCount(new QueryWrapper<com.etotem.cfc.entity.GuidePackage>()
|
|
|
+ .eq("status", "pending")), taskExecutor);
|
|
|
+
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.add(Calendar.DAY_OF_MONTH, -7);
|
|
|
+ CompletableFuture<Long> activeUsersFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ userMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User>()
|
|
|
+ .ge(User::getUpdatedAt, cal.getTime())), taskExecutor);
|
|
|
+
|
|
|
+ data.put("totalFamilies", familyFuture.get());
|
|
|
+ data.put("totalParents", parentFuture.get());
|
|
|
+ data.put("totalChildren", childFuture.get());
|
|
|
+ data.put("totalTeachers", teacherFuture.get());
|
|
|
+ data.put("monthFamilies", monthFamiliesFuture.get());
|
|
|
+ data.put("monthParents", monthParentsFuture.get());
|
|
|
+ data.put("pendingGuides", pendingGuideFuture.get());
|
|
|
+ data.put("pendingPackages", pendingPackageFuture.get());
|
|
|
+ data.put("activeUsers", activeUsersFuture.get());
|
|
|
+ } catch (Exception e) {
|
|
|
+ return Result.error("获取数据失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Operation(summary = "收入统计")
|
|
|
+ @PostMapping("/revenue")
|
|
|
+ public Result<Map<String, Object>> getRevenueStats(@RequestBody Map<String, Object> params) {
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ try {
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
+ cal.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
+ cal.set(Calendar.MINUTE, 0);
|
|
|
+ cal.set(Calendar.SECOND, 0);
|
|
|
+ Date monthStart = cal.getTime();
|
|
|
+ CompletableFuture<Double> monthCommissionFuture = CompletableFuture.supplyAsync(() -> {
|
|
|
+ try {
|
|
|
+ List<CommissionRecord> records = commissionRecordMapper.selectList(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CommissionRecord>()
|
|
|
+ .ge(CommissionRecord::getCreatedAt, monthStart)
|
|
|
+ .eq(CommissionRecord::getStatus, "settled"));
|
|
|
+ return records.stream()
|
|
|
+ .filter(r -> r.getCommissionAmount() != null)
|
|
|
+ .mapToDouble(CommissionRecord::getCommissionAmount)
|
|
|
+ .sum();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+ }, taskExecutor);
|
|
|
+
|
|
|
+ CompletableFuture<Double> totalCommissionFuture = CompletableFuture.supplyAsync(() -> {
|
|
|
+ try {
|
|
|
+ List<CommissionRecord> records = commissionRecordMapper.selectList(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CommissionRecord>()
|
|
|
+ .eq(CommissionRecord::getStatus, "settled"));
|
|
|
+ return records.stream()
|
|
|
+ .filter(r -> r.getCommissionAmount() != null)
|
|
|
+ .mapToDouble(CommissionRecord::getCommissionAmount)
|
|
|
+ .sum();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+ }, taskExecutor);
|
|
|
+
|
|
|
+ CompletableFuture<Double> pendingCommissionFuture = CompletableFuture.supplyAsync(() -> {
|
|
|
+ try {
|
|
|
+ List<CommissionRecord> records = commissionRecordMapper.selectList(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CommissionRecord>()
|
|
|
+ .eq(CommissionRecord::getStatus, "pending"));
|
|
|
+ return records.stream()
|
|
|
+ .filter(r -> r.getCommissionAmount() != null)
|
|
|
+ .mapToDouble(CommissionRecord::getCommissionAmount)
|
|
|
+ .sum();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+ }, taskExecutor);
|
|
|
+
|
|
|
+ data.put("monthCommission", monthCommissionFuture.get() / 100.0);
|
|
|
+ data.put("totalCommission", totalCommissionFuture.get() / 100.0);
|
|
|
+ data.put("pendingCommission", pendingCommissionFuture.get() / 100.0);
|
|
|
+ } catch (Exception e) {
|
|
|
+ data.put("monthCommission", 0.0);
|
|
|
+ data.put("totalCommission", 0.0);
|
|
|
+ data.put("pendingCommission", 0.0);
|
|
|
+ }
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Operation(summary = "会员分布统计")
|
|
|
+ @PostMapping("/membership")
|
|
|
+ public Result<Map<String, Object>> getMembershipStats() {
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ try {
|
|
|
+ CompletableFuture<Long> freeFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ userMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User>()
|
|
|
+ .or().eq(User::getMemberLevel, "FREE").isNull(User::getMemberLevel)), taskExecutor);
|
|
|
+ CompletableFuture<Long> familyFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ userMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User>()
|
|
|
+ .eq(User::getMemberLevel, "FAMILY")), taskExecutor);
|
|
|
+ CompletableFuture<Long> providerFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ userMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User>()
|
|
|
+ .eq(User::getMemberLevel, "PROVIDER")), taskExecutor);
|
|
|
+
|
|
|
+ CompletableFuture<Long> trialFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ trialMembershipMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TrialMembership>()
|
|
|
+ .eq(TrialMembership::getStatus, "ACTIVE")), taskExecutor);
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
+ cal.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
+ cal.set(Calendar.MINUTE, 0);
|
|
|
+ cal.set(Calendar.SECOND, 0);
|
|
|
+ CompletableFuture<Long> monthUpgradesFuture = CompletableFuture.supplyAsync(() ->
|
|
|
+ memberUpgradeRecordMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MemberUpgradeRecord>()
|
|
|
+ .eq(MemberUpgradeRecord::getToLevel, "FAMILY")
|
|
|
+ .ge(MemberUpgradeRecord::getCreatedAt, cal.getTime())), taskExecutor);
|
|
|
+
|
|
|
+ data.put("freeCount", freeFuture.get());
|
|
|
+ data.put("familyCount", familyFuture.get());
|
|
|
+ data.put("providerCount", providerFuture.get());
|
|
|
+ data.put("trialCount", trialFuture.get());
|
|
|
+ data.put("monthUpgrades", monthUpgradesFuture.get());
|
|
|
+ } catch (Exception e) {
|
|
|
+ return Result.error("获取会员统计失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Operation(summary = "每日数据趋势(近30天)")
|
|
|
+ @PostMapping("/trend")
|
|
|
+ public Result<Map<String, Object>> getTrend(@RequestBody Map<String, Object> params) {
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ try {
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.add(Calendar.DAY_OF_MONTH, -30);
|
|
|
+ Date thirtyDaysAgo = cal.getTime();
|
|
|
+
|
|
|
+ List<Family> families = familyMapper.selectList(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Family>().ge(Family::getCreatedAt, thirtyDaysAgo));
|
|
|
+ Map<String, Long> familyTrend = new LinkedHashMap<>();
|
|
|
+ SimpleDateFormat dayFmt = new SimpleDateFormat("MM-dd");
|
|
|
+ for (Family f : families) {
|
|
|
+ if (f.getCreatedAt() != null) {
|
|
|
+ String day = dayFmt.format(f.getCreatedAt());
|
|
|
+ familyTrend.put(day, familyTrend.getOrDefault(day, 0L) + 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ data.put("familyTrend", familyTrend);
|
|
|
+
|
|
|
+ List<User> users = userMapper.selectList(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<User>().ge(User::getCreatedAt, thirtyDaysAgo));
|
|
|
+ Map<String, Long> userTrend = new LinkedHashMap<>();
|
|
|
+ for (User u : users) {
|
|
|
+ if (u.getCreatedAt() != null) {
|
|
|
+ String day = dayFmt.format(u.getCreatedAt());
|
|
|
+ userTrend.put(day, userTrend.getOrDefault(day, 0L) + 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ data.put("userTrend", userTrend);
|
|
|
+ } catch (Exception e) {
|
|
|
+ data.put("familyTrend", new LinkedHashMap<>());
|
|
|
+ data.put("userTrend", new LinkedHashMap<>());
|
|
|
+ }
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
}
|