491 lines
18 KiB
C#
491 lines
18 KiB
C#
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
|
||
|
||
|
||
}
|
||
}
|