用户活跃度统计接口

This commit is contained in:
马梓彭 2025-07-17 13:11:29 +08:00
parent c96f2087f2
commit c754e106cc
3 changed files with 694 additions and 0 deletions

View File

@ -0,0 +1,78 @@
using System;
using System.Threading.Tasks;
namespace VOL.Business.IServices.UserActivity
{
/// <summary>
/// 用户活跃度统计服务接口
/// </summary>
public interface IUserActivityService
{
/// <summary>
/// 获取用户活跃度概览数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
Task<object> GetUserActivityOverviewAsync(string timeRange, DateTime date);
/// <summary>
/// 获取活跃用户时间序列数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
Task<object> GetActiveUsersDataAsync(string timeRange, DateTime date);
/// <summary>
/// 获取地域分析数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
Task<object> GetRegionDataAsync(string timeRange, DateTime date);
/// <summary>
/// 获取人口统计数据(年龄和性别)
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
Task<object> GetDemographicsDataAsync(string timeRange, DateTime date);
/// <summary>
/// 获取功能使用量统计数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <param name="module">模块名称</param>
/// <param name="function">功能名称</param>
/// <param name="button">按钮名称</param>
/// <returns></returns>
Task<object> GetFeatureUsageDataAsync(string timeRange, DateTime date, string module = "", string function = "", string button = "");
/// <summary>
/// 获取新增用户统计数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
Task<object> GetNewUsersDataAsync(string timeRange, DateTime date);
/// <summary>
/// 获取用户总数统计数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
Task<object> GetTotalUsersDataAsync(string timeRange, DateTime date);
/// <summary>
/// 获取平均使用时长数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
Task<object> GetAvgUsageTimeDataAsync(string timeRange, DateTime date);
}
}

View File

@ -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
{
/// <summary>
/// 用户活跃度统计服务实现
/// </summary>
public class UserActivityService : IUserActivityService, IDependency
{
private readonly VOLContext _dbContext;
public UserActivityService(VOLContext dbContext)
{
_dbContext = dbContext;
}
/// <summary>
/// 获取用户活跃度概览数据
/// </summary>
public async Task<object> GetUserActivityOverviewAsync(string timeRange, DateTime date)
{
var (startDate, endDate) = GetDateRange(timeRange, date);
var (prevStartDate, prevEndDate) = GetDateRange(timeRange, date.AddDays(-7));
// 查询当前期间的数据
var currentNewUsers = await _dbContext.Set<S_Student>()
.Where(s => s.CreateDate >= startDate && s.CreateDate <= endDate)
.CountAsync();
var currentActiveUsers = await _dbContext.Set<S_Student>()
.Where(s => s.ModifyDate >= startDate && s.ModifyDate <= endDate)
.CountAsync();
// 查询上一期间的数据用于计算增长率
var prevNewUsers = await _dbContext.Set<S_Student>()
.Where(s => s.CreateDate >= prevStartDate && s.CreateDate <= prevEndDate)
.CountAsync();
var prevActiveUsers = await _dbContext.Set<S_Student>()
.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
};
}
/// <summary>
/// 获取活跃用户时间序列数据
/// </summary>
public async Task<object> GetActiveUsersDataAsync(string timeRange, DateTime date)
{
var (startDate, endDate) = GetDateRange(timeRange, date);
var xAxisData = new List<string>();
var seriesData = new List<int>();
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<S_Student>()
.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<S_Student>()
.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<S_Student>()
.Where(s => s.ModifyDate >= weekStart && s.ModifyDate < weekEnd)
.CountAsync();
xAxisData.Add($"第{week + 1}周");
seriesData.Add(count);
}
}
return new
{
XAxisData = xAxisData,
SeriesData = seriesData
};
}
/// <summary>
/// 获取地域分析数据
/// </summary>
public async Task<object> GetRegionDataAsync(string timeRange, DateTime date)
{
var (startDate, endDate) = GetDateRange(timeRange, date);
// 基于学生的家庭住址进行地域统计
var regionData = await _dbContext.Set<S_Student>()
.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<S_Student>()
.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()
};
}
/// <summary>
/// 获取人口统计数据
/// </summary>
public async Task<object> GetDemographicsDataAsync(string timeRange, DateTime date)
{
var (startDate, endDate) = GetDateRange(timeRange, date);
// 性别统计
var genderStats = await _dbContext.Set<S_Student>()
.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<S_Student>()
.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
}
};
}
/// <summary>
/// 获取功能使用统计数据
/// </summary>
public async Task<object> GetFeatureUsageDataAsync(string timeRange, DateTime date, string module = "", string function = "", string button = "")
{
var (startDate, endDate) = GetDateRange(timeRange, date);
// 由于没有具体的功能使用日志表,这里基于学生的训练数据进行模拟
var xAxisData = new List<string>();
var seriesData = new List<int>();
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
};
}
/// <summary>
/// 获取新增用户时间序列数据
/// </summary>
public async Task<object> GetNewUsersDataAsync(string timeRange, DateTime date)
{
return await GetActiveUsersDataAsync(timeRange, date); // 复用活跃用户的逻辑但基于CreateDate
}
/// <summary>
/// 获取用户总数数据
/// </summary>
public async Task<object> GetTotalUsersDataAsync(string timeRange, DateTime date)
{
var totalUsers = await _dbContext.Set<S_Student>().CountAsync();
return new
{
TotalUsers = totalUsers
};
}
/// <summary>
/// 获取平均使用时长数据
/// </summary>
public async Task<object> GetAvgUsageTimeDataAsync(string timeRange, DateTime date)
{
var (startDate, endDate) = GetDateRange(timeRange, date);
// 基于学生的训练时长进行统计
var avgUsageData = await _dbContext.Set<S_Student>()
.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
};
}
/// <summary>
/// 从地址中提取省份(改进版)
/// </summary>
private string ExtractProvinceFromAddress(string address)
{
if (string.IsNullOrEmpty(address)) return "未知";
// 标准省份名称列表(包含直辖市和自治区的完整名称)
var provinces = new Dictionary<string, string>
{
// 直辖市
{ "北京", "北京市" },
{ "上海", "上海市" },
{ "天津", "天津市" },
{ "重庆", "重庆市" },
// 省份
{ "河北", "河北省" },
{ "山西", "山西省" },
{ "辽宁", "辽宁省" },
{ "吉林", "吉林省" },
{ "黑龙江", "黑龙江省" },
{ "江苏", "江苏省" },
{ "浙江", "浙江省" },
{ "安徽", "安徽省" },
{ "福建", "福建省" },
{ "江西", "江西省" },
{ "山东", "山东省" },
{ "河南", "河南省" },
{ "湖北", "湖北省" },
{ "湖南", "湖南省" },
{ "广东", "广东省" },
{ "海南", "海南省" },
{ "四川", "四川省" },
{ "贵州", "贵州省" },
{ "云南", "云南省" },
{ "陕西", "陕西省" },
{ "甘肃", "甘肃省" },
{ "青海", "青海省" },
// 自治区
{ "内蒙古", "内蒙古自治区" },
{ "广西", "广西壮族自治区" },
{ "西藏", "西藏自治区" },
{ "宁夏", "宁夏回族自治区" },
{ "新疆", "新疆维吾尔自治区" },
// 特别行政区
{ "香港", "香港特别行政区" },
{ "澳门", "澳门特别行政区" }
};
// 优先匹配完整名称,然后匹配简称
foreach (var province in provinces)
{
if (address.Contains(province.Value) || address.Contains(province.Key))
{
return province.Key; // 返回简称用于统计
}
}
return "其他";
}
#region
/// <summary>
/// 获取时间范围
/// </summary>
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);
}
/// <summary>
/// 获取年龄组
/// </summary>
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岁以上";
}
/// <summary>
/// 获取功能模块数据
/// </summary>
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
}
}

View File

@ -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
{
/// <summary>
/// 用户活跃度统计API控制器
/// </summary>
[Route("api/[controller]")]
[ApiController]
[ApiExplorerSettings(GroupName = "v3")]
[TypeFilter(typeof(CustomApiResponseFilter))]
public class UserActivityController : ControllerBase
{
private readonly IUserActivityService _userActivityService;
public UserActivityController(IUserActivityService userActivityService)
{
_userActivityService = userActivityService;
}
/// <summary>
/// 获取用户活跃度概览数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
[HttpGet("GetUserActivityOverview")]
public async Task<object> GetUserActivityOverview([FromQuery] string timeRange, [FromQuery] DateTime date)
{
return await _userActivityService.GetUserActivityOverviewAsync(timeRange, date);
}
/// <summary>
/// 获取活跃用户数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
[HttpGet("GetActiveUsersData")]
public async Task<object> GetActiveUsersData([FromQuery] string timeRange, [FromQuery] DateTime date)
{
return await _userActivityService.GetActiveUsersDataAsync(timeRange, date);
}
/// <summary>
/// 获取地域分析数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
[HttpGet("GetRegionData")]
public async Task<object> GetRegionData([FromQuery] string timeRange, [FromQuery] DateTime date)
{
return await _userActivityService.GetRegionDataAsync(timeRange, date);
}
/// <summary>
/// 获取人口统计数据(年龄和性别)
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
[HttpGet("GetDemographicsData")]
public async Task<object> GetDemographicsData([FromQuery] string timeRange, [FromQuery] DateTime date)
{
return await _userActivityService.GetDemographicsDataAsync(timeRange, date);
}
/// <summary>
/// 获取功能使用量统计数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <param name="module">模块名称</param>
/// <param name="function">功能名称</param>
/// <param name="button">按钮名称</param>
/// <returns></returns>
[HttpGet("GetFeatureUsageData")]
public async Task<object> 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);
}
/// <summary>
/// 获取新增用户统计数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
[HttpGet("GetNewUsersData")]
public async Task<object> GetNewUsersData([FromQuery] string timeRange, [FromQuery] DateTime date)
{
return await _userActivityService.GetNewUsersDataAsync(timeRange, date);
}
/// <summary>
/// 获取用户总数统计数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
[HttpGet("GetTotalUsersData")]
public async Task<object> GetTotalUsersData([FromQuery] string timeRange, [FromQuery] DateTime date)
{
return await _userActivityService.GetTotalUsersDataAsync(timeRange, date);
}
/// <summary>
/// 获取平均使用时长数据
/// </summary>
/// <param name="timeRange">时间范围day, week, month</param>
/// <param name="date">选择的日期</param>
/// <returns></returns>
[HttpGet("GetAvgUsageTimeData")]
public async Task<object> GetAvgUsageTimeData([FromQuery] string timeRange, [FromQuery] DateTime date)
{
return await _userActivityService.GetAvgUsageTimeDataAsync(timeRange, date);
}
}
}