491 lines
18 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}