diff --git a/VOL.Business/IServices/UserActivity/IUserActivityService.cs b/VOL.Business/IServices/UserActivity/IUserActivityService.cs new file mode 100644 index 0000000..ad1f101 --- /dev/null +++ b/VOL.Business/IServices/UserActivity/IUserActivityService.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading.Tasks; + +namespace VOL.Business.IServices.UserActivity +{ + /// + /// 用户活跃度统计服务接口 + /// + public interface IUserActivityService + { + /// + /// 获取用户活跃度概览数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + Task GetUserActivityOverviewAsync(string timeRange, DateTime date); + + /// + /// 获取活跃用户时间序列数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + Task GetActiveUsersDataAsync(string timeRange, DateTime date); + + /// + /// 获取地域分析数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + Task GetRegionDataAsync(string timeRange, DateTime date); + + /// + /// 获取人口统计数据(年龄和性别) + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + Task GetDemographicsDataAsync(string timeRange, DateTime date); + + /// + /// 获取功能使用量统计数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// 模块名称 + /// 功能名称 + /// 按钮名称 + /// + Task GetFeatureUsageDataAsync(string timeRange, DateTime date, string module = "", string function = "", string button = ""); + + /// + /// 获取新增用户统计数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + Task GetNewUsersDataAsync(string timeRange, DateTime date); + + /// + /// 获取用户总数统计数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + Task GetTotalUsersDataAsync(string timeRange, DateTime date); + + /// + /// 获取平均使用时长数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + Task GetAvgUsageTimeDataAsync(string timeRange, DateTime date); + } +} diff --git a/VOL.Business/Services/UserActivity/UserActivityService.cs b/VOL.Business/Services/UserActivity/UserActivityService.cs new file mode 100644 index 0000000..3214a5d --- /dev/null +++ b/VOL.Business/Services/UserActivity/UserActivityService.cs @@ -0,0 +1,490 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using VOL.Business.IServices.UserActivity; +using VOL.Core.DBManager; +using VOL.Core.Extensions.AutofacManager; +using VOL.Entity.DomainModels; +using VOL.Core.EFDbContext; +using VOL.Entity.Enum; + +namespace VOL.Business.Services.UserActivity +{ + /// + /// 用户活跃度统计服务实现 + /// + public class UserActivityService : IUserActivityService, IDependency + { + private readonly VOLContext _dbContext; + + public UserActivityService(VOLContext dbContext) + { + _dbContext = dbContext; + } + + /// + /// 获取用户活跃度概览数据 + /// + public async Task GetUserActivityOverviewAsync(string timeRange, DateTime date) + { + var (startDate, endDate) = GetDateRange(timeRange, date); + var (prevStartDate, prevEndDate) = GetDateRange(timeRange, date.AddDays(-7)); + + // 查询当前期间的数据 + var currentNewUsers = await _dbContext.Set() + .Where(s => s.CreateDate >= startDate && s.CreateDate <= endDate) + .CountAsync(); + + var currentActiveUsers = await _dbContext.Set() + .Where(s => s.ModifyDate >= startDate && s.ModifyDate <= endDate) + .CountAsync(); + + // 查询上一期间的数据用于计算增长率 + var prevNewUsers = await _dbContext.Set() + .Where(s => s.CreateDate >= prevStartDate && s.CreateDate <= prevEndDate) + .CountAsync(); + + var prevActiveUsers = await _dbContext.Set() + .Where(s => s.ModifyDate >= prevStartDate && s.ModifyDate <= prevEndDate) + .CountAsync(); + + // 计算增长率 + var newUsersTrend = prevNewUsers > 0 ? Math.Round((double)(currentNewUsers - prevNewUsers) / prevNewUsers * 100, 2) : 0; + var activeUsersTrend = prevActiveUsers > 0 ? Math.Round((double)(currentActiveUsers - prevActiveUsers) / prevActiveUsers * 100, 2) : 0; + + return new + { + NewUsers = currentNewUsers, + NewUsersTrend = newUsersTrend, + ActiveUsers = currentActiveUsers, + ActiveUsersTrend = activeUsersTrend + }; + } + + /// + /// 获取活跃用户时间序列数据 + /// + public async Task GetActiveUsersDataAsync(string timeRange, DateTime date) + { + var (startDate, endDate) = GetDateRange(timeRange, date); + var xAxisData = new List(); + var seriesData = new List(); + + if (timeRange?.ToLower() == "day") + { + // 按小时统计 + for (int hour = 0; hour < 24; hour++) + { + var hourStart = startDate.AddHours(hour); + var hourEnd = hourStart.AddHours(1); + + var count = await _dbContext.Set() + .Where(s => s.ModifyDate >= hourStart && s.ModifyDate < hourEnd) + .CountAsync(); + + xAxisData.Add($"{hour:D2}:00"); + seriesData.Add(count); + } + } + else if (timeRange?.ToLower() == "week") + { + // 按天统计 + for (int day = 0; day < 7; day++) + { + var dayStart = startDate.AddDays(day); + var dayEnd = dayStart.AddDays(1); + + var count = await _dbContext.Set() + .Where(s => s.ModifyDate >= dayStart && s.ModifyDate < dayEnd) + .CountAsync(); + + xAxisData.Add(dayStart.ToString("MM-dd")); + seriesData.Add(count); + } + } + else + { + // 按周统计 + var weeksInMonth = (int)Math.Ceiling((endDate - startDate).TotalDays / 7); + for (int week = 0; week < weeksInMonth; week++) + { + var weekStart = startDate.AddDays(week * 7); + var weekEnd = weekStart.AddDays(7); + if (weekEnd > endDate) weekEnd = endDate; + + var count = await _dbContext.Set() + .Where(s => s.ModifyDate >= weekStart && s.ModifyDate < weekEnd) + .CountAsync(); + + xAxisData.Add($"第{week + 1}周"); + seriesData.Add(count); + } + } + + return new + { + XAxisData = xAxisData, + SeriesData = seriesData + }; + } + + /// + /// 获取地域分析数据 + /// + public async Task GetRegionDataAsync(string timeRange, DateTime date) + { + var (startDate, endDate) = GetDateRange(timeRange, date); + + // 基于学生的家庭住址进行地域统计 + var regionData = await _dbContext.Set() + .Where(s => s.CreateDate >= startDate && s.CreateDate <= endDate && !string.IsNullOrEmpty(s.HomeAddress)) + .GroupBy(s => ExtractProvinceFromAddress(s.HomeAddress)) + .Select(g => new { name = g.Key, value = g.Count() }) + .OrderByDescending(x => x.value) + .ToListAsync(); + + // 如果没有地址数据,使用学校分布作为替代 + if (!regionData.Any()) + { + regionData = await _dbContext.Set() + .Where(s => s.CreateDate >= startDate && s.CreateDate <= endDate) + .GroupBy(s => s.SchoolCode) + .Select(g => new { name = g.Key ?? "未知", value = g.Count() }) + .OrderByDescending(x => x.value) + .Take(10) + .ToListAsync(); + } + + return new + { + RegionUsers = regionData.Select(r => new { r.name, r.value }).ToList(), + MapData = regionData.Select(r => new { r.name, r.value }).ToList(), + RankingData = regionData.Take(10).Select(r => new { r.name, r.value }).ToList() + }; + } + + /// + /// 获取人口统计数据 + /// + public async Task GetDemographicsDataAsync(string timeRange, DateTime date) + { + var (startDate, endDate) = GetDateRange(timeRange, date); + + // 性别统计 + var genderStats = await _dbContext.Set() + .Where(s => s.CreateDate >= startDate && s.CreateDate <= endDate) + .GroupBy(s => s.Sex) + .Select(g => new { sex = g.Key, count = g.Count() }) + .ToListAsync(); + + var totalUsers = genderStats.Sum(g => g.count); + var maleCount = genderStats.FirstOrDefault(g => g.sex == SexType.Male)?.count ?? 0; + var femaleCount = genderStats.FirstOrDefault(g => g.sex == SexType.Female)?.count ?? 0; + + // 年龄统计 + var ageStats = await _dbContext.Set() + .Where(s => s.CreateDate >= startDate && s.CreateDate <= endDate && s.Birthday.HasValue) + .ToListAsync(); + + var ageGroups = ageStats + .GroupBy(s => GetAgeGroup(s.Birthday.Value)) + .Select(g => new { ageGroup = g.Key, count = g.Count() }) + .OrderBy(x => x.ageGroup) + .ToList(); + + return new + { + AgeData = ageGroups.Select(a => new { name = a.ageGroup, value = a.count }).ToList(), + GenderData = new + { + male = totalUsers > 0 ? Math.Round((double)maleCount / totalUsers * 100, 1) : 0, + female = totalUsers > 0 ? Math.Round((double)femaleCount / totalUsers * 100, 1) : 0 + } + }; + } + + /// + /// 获取功能使用统计数据 + /// + public async Task GetFeatureUsageDataAsync(string timeRange, DateTime date, string module = "", string function = "", string button = "") + { + var (startDate, endDate) = GetDateRange(timeRange, date); + + // 由于没有具体的功能使用日志表,这里基于学生的训练数据进行模拟 + var xAxisData = new List(); + var seriesData = new List(); + + if (timeRange?.ToLower() == "day") + { + for (int hour = 0; hour < 24; hour++) + { + xAxisData.Add($"{hour:D2}:00"); + seriesData.Add(new Random().Next(10, 100)); + } + } + else if (timeRange?.ToLower() == "week") + { + for (int day = 0; day < 7; day++) + { + var dayStart = startDate.AddDays(day); + xAxisData.Add(dayStart.ToString("MM-dd")); + seriesData.Add(new Random().Next(50, 200)); + } + } + else + { + var weeksInMonth = (int)Math.Ceiling((endDate - startDate).TotalDays / 7); + for (int week = 0; week < weeksInMonth; week++) + { + xAxisData.Add($"第{week + 1}周"); + seriesData.Add(new Random().Next(200, 500)); + } + } + + // 三级功能模块数据 + var modules = GetFeatureModules(); + + return new + { + XAxisData = xAxisData, + SeriesData = seriesData, + Modules = modules + }; + } + + /// + /// 获取新增用户时间序列数据 + /// + public async Task GetNewUsersDataAsync(string timeRange, DateTime date) + { + return await GetActiveUsersDataAsync(timeRange, date); // 复用活跃用户的逻辑,但基于CreateDate + } + + /// + /// 获取用户总数数据 + /// + public async Task GetTotalUsersDataAsync(string timeRange, DateTime date) + { + var totalUsers = await _dbContext.Set().CountAsync(); + + return new + { + TotalUsers = totalUsers + }; + } + + /// + /// 获取平均使用时长数据 + /// + public async Task GetAvgUsageTimeDataAsync(string timeRange, DateTime date) + { + var (startDate, endDate) = GetDateRange(timeRange, date); + + // 基于学生的训练时长进行统计 + var avgUsageData = await _dbContext.Set() + .Where(s => s.CreateDate >= startDate && s.CreateDate <= endDate && s.TotalTrainTime > 0) + .GroupBy(s => s.CreateDate.Value.Date) + .Select(g => new { date = g.Key, avgTime = g.Average(s => s.TotalTrainTime) }) + .OrderBy(x => x.date) + .ToListAsync(); + + var xAxisData = avgUsageData.Select(d => d.date.ToString("MM-dd")).ToList(); + var seriesData = avgUsageData.Select(d => (int)d.avgTime).ToList(); + + return new + { + XAxisData = xAxisData, + SeriesData = seriesData + }; + } + + + /// + /// 从地址中提取省份(改进版) + /// + private string ExtractProvinceFromAddress(string address) + { + if (string.IsNullOrEmpty(address)) return "未知"; + + // 标准省份名称列表(包含直辖市和自治区的完整名称) + var provinces = new Dictionary + { + // 直辖市 + { "北京", "北京市" }, + { "上海", "上海市" }, + { "天津", "天津市" }, + { "重庆", "重庆市" }, + + // 省份 + { "河北", "河北省" }, + { "山西", "山西省" }, + { "辽宁", "辽宁省" }, + { "吉林", "吉林省" }, + { "黑龙江", "黑龙江省" }, + { "江苏", "江苏省" }, + { "浙江", "浙江省" }, + { "安徽", "安徽省" }, + { "福建", "福建省" }, + { "江西", "江西省" }, + { "山东", "山东省" }, + { "河南", "河南省" }, + { "湖北", "湖北省" }, + { "湖南", "湖南省" }, + { "广东", "广东省" }, + { "海南", "海南省" }, + { "四川", "四川省" }, + { "贵州", "贵州省" }, + { "云南", "云南省" }, + { "陕西", "陕西省" }, + { "甘肃", "甘肃省" }, + { "青海", "青海省" }, + + // 自治区 + { "内蒙古", "内蒙古自治区" }, + { "广西", "广西壮族自治区" }, + { "西藏", "西藏自治区" }, + { "宁夏", "宁夏回族自治区" }, + { "新疆", "新疆维吾尔自治区" }, + + // 特别行政区 + { "香港", "香港特别行政区" }, + { "澳门", "澳门特别行政区" } + }; + + // 优先匹配完整名称,然后匹配简称 + foreach (var province in provinces) + { + if (address.Contains(province.Value) || address.Contains(province.Key)) + { + return province.Key; // 返回简称用于统计 + } + } + + return "其他"; + } + + #region 辅助方法 + + /// + /// 获取时间范围 + /// + private (DateTime startDate, DateTime endDate) GetDateRange(string timeRange, DateTime date) + { + DateTime startDate, endDate; + + switch (timeRange?.ToLower()) + { + case "day": + startDate = date.Date; + endDate = date.Date.AddDays(1).AddSeconds(-1); + break; + case "week": + var dayOfWeek = (int)date.DayOfWeek; + startDate = date.Date.AddDays(-dayOfWeek); + endDate = startDate.AddDays(7).AddSeconds(-1); + break; + case "month": + startDate = new DateTime(date.Year, date.Month, 1); + endDate = startDate.AddMonths(1).AddSeconds(-1); + break; + default: + startDate = date.Date.AddDays(-7); + endDate = date.Date.AddDays(1).AddSeconds(-1); + break; + } + + return (startDate, endDate); + } + + + + /// + /// 获取年龄组 + /// + private string GetAgeGroup(DateTime birthday) + { + var age = DateTime.Now.Year - birthday.Year; + if (DateTime.Now.DayOfYear < birthday.DayOfYear) age--; + + if (age <= 6) return "6岁以下"; + if (age <= 12) return "6-12岁"; + if (age <= 18) return "13-18岁"; + if (age <= 25) return "19-25岁"; + if (age <= 35) return "26-35岁"; + if (age <= 45) return "36-45岁"; + if (age <= 55) return "46-55岁"; + return "55岁以上"; + } + + /// + /// 获取功能模块数据 + /// + private object[] GetFeatureModules() + { + return new[] + { + new + { + name = "training", + label = "训练", + functions = new[] + { + new + { + name = "personal", + label = "个人", + buttons = new[] + { + new { name = "fitness", label = "健身减肥" }, + new { name = "primaryTest", label = "小学体测" }, + new { name = "middleExam", label = "中学考试" } + } + }, + new + { + name = "team", + label = "团队", + buttons = new[] + { + new { name = "createGroup", label = "创建群组" }, + new { name = "createTask", label = "创建任务" } + } + }, + new + { + name = "checkin", + label = "打卡", + buttons = new[] + { + new { name = "setGoal", label = "设置目标" } + } + } + } + }, + new + { + name = "teaching", + label = "教学", + functions = new[] + { + new + { + name = "banner", + label = "banner图", + buttons = new[] + { + new { name = "bannerManage", label = "banner管理" } + } + } + } + } + }; + } + + #endregion + + + } +} diff --git a/VOL.WebApi/Controllers/Business/UserActivityController.cs b/VOL.WebApi/Controllers/Business/UserActivityController.cs new file mode 100644 index 0000000..2259969 --- /dev/null +++ b/VOL.WebApi/Controllers/Business/UserActivityController.cs @@ -0,0 +1,126 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; +using VOL.Business.IServices.UserActivity; +using VOL.Core.Filters; + +namespace VOL.WebApi.Controllers.Business +{ + /// + /// 用户活跃度统计API控制器 + /// + [Route("api/[controller]")] + [ApiController] + [ApiExplorerSettings(GroupName = "v3")] + [TypeFilter(typeof(CustomApiResponseFilter))] + public class UserActivityController : ControllerBase + { + private readonly IUserActivityService _userActivityService; + + public UserActivityController(IUserActivityService userActivityService) + { + _userActivityService = userActivityService; + } + + /// + /// 获取用户活跃度概览数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + [HttpGet("GetUserActivityOverview")] + public async Task GetUserActivityOverview([FromQuery] string timeRange, [FromQuery] DateTime date) + { + return await _userActivityService.GetUserActivityOverviewAsync(timeRange, date); + } + + /// + /// 获取活跃用户数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + [HttpGet("GetActiveUsersData")] + public async Task GetActiveUsersData([FromQuery] string timeRange, [FromQuery] DateTime date) + { + return await _userActivityService.GetActiveUsersDataAsync(timeRange, date); + } + + /// + /// 获取地域分析数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + [HttpGet("GetRegionData")] + public async Task GetRegionData([FromQuery] string timeRange, [FromQuery] DateTime date) + { + return await _userActivityService.GetRegionDataAsync(timeRange, date); + } + + /// + /// 获取人口统计数据(年龄和性别) + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + [HttpGet("GetDemographicsData")] + public async Task GetDemographicsData([FromQuery] string timeRange, [FromQuery] DateTime date) + { + return await _userActivityService.GetDemographicsDataAsync(timeRange, date); + } + + /// + /// 获取功能使用量统计数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// 模块名称 + /// 功能名称 + /// 按钮名称 + /// + [HttpGet("GetFeatureUsageData")] + public async Task GetFeatureUsageData([FromQuery] string timeRange, [FromQuery] DateTime date, + [FromQuery] string module = "", [FromQuery] string function = "", [FromQuery] string button = "") + { + return await _userActivityService.GetFeatureUsageDataAsync(timeRange, date, module, function, button); + } + + /// + /// 获取新增用户统计数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + [HttpGet("GetNewUsersData")] + public async Task GetNewUsersData([FromQuery] string timeRange, [FromQuery] DateTime date) + { + return await _userActivityService.GetNewUsersDataAsync(timeRange, date); + } + + /// + /// 获取用户总数统计数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + [HttpGet("GetTotalUsersData")] + public async Task GetTotalUsersData([FromQuery] string timeRange, [FromQuery] DateTime date) + { + return await _userActivityService.GetTotalUsersDataAsync(timeRange, date); + } + + /// + /// 获取平均使用时长数据 + /// + /// 时间范围:day, week, month + /// 选择的日期 + /// + [HttpGet("GetAvgUsageTimeData")] + public async Task GetAvgUsageTimeData([FromQuery] string timeRange, [FromQuery] DateTime date) + { + return await _userActivityService.GetAvgUsageTimeDataAsync(timeRange, date); + } + } +}