数据上传

This commit is contained in:
tanglong 2026-01-12 16:54:43 +08:00
parent e08cf86086
commit b56bfa545c
12 changed files with 7954 additions and 3 deletions

View File

@ -26,5 +26,12 @@ namespace VOL.Ai.IServices
/// <param name="stuNo"></param>
/// <returns></returns>
Task<StudentDetailDto> GetStudentInfo(string stuNo);
/// <summary>
/// 上传数据
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
Task<bool> PushSportsData(List<SportsDataPushDto> list);
}
}

View File

@ -2,6 +2,8 @@
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;
using System.Collections.Concurrent;
using VOL.Ai.IRepositories;
@ -25,6 +27,7 @@ using VOL.Model.Norm.Response;
using VOL.Model.Training.Response;
using VOL.System.IRepositories;
using VOL.System.Repositories;
using static Dapper.SqlMapper;
namespace VOL.Ai.Services
{
@ -51,6 +54,7 @@ namespace VOL.Ai.Services
private readonly IClassroomSettingRepository _classroomSettingRepository;
private readonly IJingShiTiJiaoService _jingShiTiJiaoService;
private readonly ILogger<AiAppService> _logger;
[ActivatorUtilitiesConstructor]
public AiAppService(IMapper mapper,
@ -72,7 +76,8 @@ namespace VOL.Ai.Services
IClassroomStageRepository classroomStageRepository,
IClassroomSettingRepository classroomSettingRepository,
IAiAppVersionRepository aiAppVersionRepository,
IJingShiTiJiaoService jingShiTiJiaoService
IJingShiTiJiaoService jingShiTiJiaoService,
ILogger<AiAppService> logger
)
{
_mapper = mapper;
@ -95,6 +100,7 @@ namespace VOL.Ai.Services
_classroomSettingRepository = classroomSettingRepository;
_aiAppVersionRepository = aiAppVersionRepository;
_jingShiTiJiaoService = jingShiTiJiaoService;
_logger = logger;
}
#region New
@ -1062,9 +1068,42 @@ namespace VOL.Ai.Services
}
catch (Exception e)
{
_logger.LogError(e, "心率数据保存失败");
throw;
}
// 异步上传心率数据,不阻塞主流程
_ = Task.Run(async () =>
{
try
{
var uploadList = heartRateDatas.Select(hr => new SportsDataPushDto
{
StudentNo = hr.StudentNo,
SportsItem = AppSetting.JingShiTiJiao.SportsItem,
SportsDate = hr.ScoreTime.ToString("yyyy-MM-dd"),
SportsService = AppSetting.JingShiTiJiao.Channel,
SportsScene = "HeartRate",
SportsDetail = JsonConvert.SerializeObject(hr)
}).ToList();
var res = await _jingShiTiJiaoService.PushSportsData(uploadList);
if (res)
{
_logger.LogInformation($"心率数据同步成功: {JsonConvert.SerializeObject(uploadList)}");
}
else
{
_logger.LogError($"心率数据同步失败: {JsonConvert.SerializeObject(uploadList)}");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "心率数据上传异常");
}
});
}
/// <summary>
@ -1107,6 +1146,38 @@ namespace VOL.Ai.Services
});
await _fastJumpRopeDataRepository.AddRangeAsync(fastJumpRopeDatas);
await _fastJumpRopeDataRepository.SaveChangesAsync();
// 2️⃣ 异步上传到极智体教平台
_ = Task.Run(async () =>
{
try
{
var uploadList = fastJumpRopeDatas.Select(d => new SportsDataPushDto
{
StudentNo = d.StudentNo,
SportsItem = AppSetting.JingShiTiJiao.SportsItem,
SportsDate = d.ScoreTime.ToString("yyyy-MM-dd"),
SportsService = AppSetting.JingShiTiJiao.Channel,
SportsScene = "FastJumpRope",
SportsDetail = JsonConvert.SerializeObject(d)
}).ToList();
var res = await _jingShiTiJiaoService.PushSportsData(uploadList);
if (res)
{
_logger.LogInformation($"速度跳绳数据同步成功: {JsonConvert.SerializeObject(uploadList)}");
}
else
{
_logger.LogError($"速度跳绳数据同步失败: {JsonConvert.SerializeObject(uploadList)}");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "速度跳绳数据上传异常");
}
});
}
/// <summary>
@ -1245,6 +1316,15 @@ namespace VOL.Ai.Services
return new Ai_ClassRoomRecordDto();
}
var teacher = await _classRepository.DbContext.Set<JS_Teacher>().Where(x => x.Id == paramDto.TeacherId).FirstOrDefaultAsync();
if (teacher==null)
{
Console.WriteLine("未找到老师信息");
return new Ai_ClassRoomRecordDto();
}
entity.TeacherNo = teacher.TeacherNo;
entity.ClassName = classInfo.ClassName;
entity.GradeId = (int)classInfo.GradeId;
entity.GradeName = classInfo.GradeName;

View File

@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System.Text;
using VOL.Ai.IServices;
using VOL.Core.Configuration;
using VOL.Core.Extensions.AutofacManager;
@ -134,5 +135,45 @@ namespace VOL.Ai.Services
return res;
}
/// <summary>
/// 上传成绩数据
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<bool> PushSportsData(List<SportsDataPushDto> list)
{
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
string nonce = SaTokenSignHelper.RandomString();
var paramMap = new Dictionary<string, object>
{
{ "channel", AppSetting.JingShiTiJiao.Channel },
{ "timestamp", timestamp },
{ "nonce", nonce }
};
string jsonBody = JsonConvert.SerializeObject(list);
// ⭐ POST JSON 的签名
string queryStr = SaTokenSignHelper.BuildPostJsonQueryString(
paramMap,
AppSetting.JingShiTiJiao.SecretKey,
jsonBody);
string url = $"{AppSetting.JingShiTiJiao.BaseUrl}/api/sports/data/push?{queryStr}";
using var client = new HttpClient();
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
string result = await response.Content.ReadAsStringAsync();
var res = JsonConvert.DeserializeObject<JingShiResponse<object>>(result);
return res != null && res.Code == 0;
}
}
}

View File

@ -262,5 +262,7 @@ namespace VOL.Core.Configuration
public string Channel { get; set; }
public string SecretKey { get; set; }
public string BaseUrl { get; set; }
public string SchoolCode { get; set; }
public string SportsItem { get; set; }
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace VOL.Core.Migrations
{
public partial class _20260112v1 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "TeacherNo",
table: "S_HomeWork",
type: "nvarchar(200)",
nullable: true,
comment: "老师编号");
migrationBuilder.AddColumn<string>(
name: "TeacherNo",
table: "Ai_ClassRoomRecord",
type: "nvarchar(200)",
nullable: true,
comment: "老师编号");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TeacherNo",
table: "S_HomeWork");
migrationBuilder.DropColumn(
name: "TeacherNo",
table: "Ai_ClassRoomRecord");
}
}
}

View File

@ -299,6 +299,10 @@ namespace VOL.Core.Migrations
.HasColumnType("nvarchar(200)")
.HasComment("老师姓名");
b.Property<string>("TeacherNo")
.HasColumnType("nvarchar(200)")
.HasComment("老师编号");
b.Property<int>("TrainingModuleEnum")
.HasColumnType("int")
.HasComment("训练模块枚举");
@ -4228,6 +4232,10 @@ namespace VOL.Core.Migrations
.HasColumnType("nvarchar(100)")
.HasComment("老师名称");
b.Property<string>("TeacherNo")
.HasColumnType("nvarchar(200)")
.HasComment("老师编号");
b.Property<string>("TeacherPhoneNo")
.HasColumnType("nvarchar(100)")
.HasComment("老师手机号");

View File

@ -47,6 +47,57 @@ namespace VOL.Core.Utilities
return sbQuery.ToString();
}
/// <summary>
/// 构建 POST JSON 上传接口的 QueryString(包含 sign)
/// ⚠️ 不与 GET 共用,避免误用
/// </summary>
public static string BuildPostJsonQueryString(
Dictionary<string, object> paramMap,
string secretKey,
string jsonBody)
{
if (paramMap == null)
throw new ArgumentNullException(nameof(paramMap));
if (string.IsNullOrWhiteSpace(jsonBody))
throw new ArgumentException("POST JSON body 不能为空");
// 1️⃣ 参与签名的参数(TreeMap + ASCII 排序)
var signParams = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var kv in paramMap)
signParams[kv.Key] = kv.Value.ToString();
// ⭐ POST JSON 特有:整个 body 作为 reqbody
signParams["reqbody"] = jsonBody;
// 2️⃣ 拼接签名字符串
// k=v&k=v&...&key=secretKey
var sbSign = new StringBuilder();
foreach (var kv in signParams)
sbSign.Append(kv.Key)
.Append("=")
.Append(kv.Value)
.Append("&");
sbSign.Append("key=").Append(secretKey.Trim());
string sign = Md5(sbSign.ToString());
// 3️⃣ 构建 URL QueryString(⚠️ 不包含 reqbody)
var sbQuery = new StringBuilder();
foreach (var kv in paramMap)
sbQuery.Append(Uri.EscapeDataString(kv.Key))
.Append("=")
.Append(Uri.EscapeDataString(kv.Value.ToString()))
.Append("&");
sbQuery.Append("sign=").Append(sign);
return sbQuery.ToString();
}
/// <summary>
/// MD5 小写 32 位
/// </summary>

View File

@ -109,6 +109,14 @@ namespace VOL.Entity.DomainModels
[Column(TypeName = "int")]
public int TeacherId { get; set; }
/// <summary>
/// 老师编号
/// </summary>
[Display(Name = "老师编号")]
[Comment("老师编号")]
[Column(TypeName = "nvarchar(200)")]
public string TeacherNo { get; set; }
/// <summary>
/// 老师姓名
/// </summary>

View File

@ -143,6 +143,14 @@ namespace VOL.Entity.DomainModels
[Column(TypeName = "int")]
public int TeacherId { get; set; }
/// <summary>
/// 老师编号
/// </summary>
[Display(Name = "老师编号")]
[Comment("老师编号")]
[Column(TypeName = "nvarchar(200)")]
public string TeacherNo { get; set; }
/// <summary>
///老师名称
/// </summary>

View File

@ -0,0 +1,30 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VOL.Model.JingShiTiJiao
{
public class SportsDataPushDto
{
[JsonProperty("studentNo")]
public string StudentNo { get; set; }
[JsonProperty("sportsItem")]
public string SportsItem { get; set; }
[JsonProperty("sportsDate")]
public string SportsDate { get; set; } // yyyy-MM-dd
[JsonProperty("sportsService")]
public string SportsService { get; set; } // channel
[JsonProperty("sportsScene")]
public string SportsScene { get; set; }
[JsonProperty("sportsDetail")]
public string SportsDetail { get; set; } // json string
}
}

View File

@ -120,7 +120,9 @@
"JingShiTiJiao": {
"Channel": "sk-yuedong",
"SecretKey": "JSTJ-kGfs6J7bkgdxr9mz",
"BaseUrl": "https://api-test.aitibao.com"
"BaseUrl": "https://api-test.aitibao.com",
"SchoolCode": "202512240056",
"SportsItem": "JS010000"
},
"QuartzAccessKey": "65EC9387355E4717899C552963CE59FF", //定时任务的值,请自行修改