From 42ebe0b3d200f8416d706b12f933731bdf9821bf Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Wed, 7 Jan 2026 09:34:32 +0800 Subject: [PATCH] =?UTF-8?q?jingshitijiao=20=20=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/JingShiTiJiaoController.cs | 64 ++++++++ .../Services/Impl/JingShiTiJiaoService.cs | 140 ++++++++++++++++++ .../Interface/IJingShiTiJiaoService.cs | 30 ++++ WeChatApplet/Startup.cs | 2 + WeChatApplet/Utilities/AppSettings.cs | 13 ++ WeChatApplet/appsettings.json | 6 + .../Dto/JingShiTiJiao/JingShiResponse.cs | 66 +++++++++ .../Dto/JingShiTiJiao/StudentDetailDto.cs | 48 ++++++ .../Dto/JingShiTiJiao/StudentDto.cs | 51 +++++++ .../Utils/JSSaTokenSignHelper.cs | 73 +++++++++ 10 files changed, 493 insertions(+) create mode 100644 WeChatApplet/Controllers/JingShiTiJiaoController.cs create mode 100644 WeChatApplet/Services/Impl/JingShiTiJiaoService.cs create mode 100644 WeChatApplet/Services/Interface/IJingShiTiJiaoService.cs create mode 100644 YD_WeChatApplet.Commons/Dto/JingShiTiJiao/JingShiResponse.cs create mode 100644 YD_WeChatApplet.Commons/Dto/JingShiTiJiao/StudentDetailDto.cs create mode 100644 YD_WeChatApplet.Commons/Dto/JingShiTiJiao/StudentDto.cs create mode 100644 YD_WeChatApplet.Commons/Utils/JSSaTokenSignHelper.cs diff --git a/WeChatApplet/Controllers/JingShiTiJiaoController.cs b/WeChatApplet/Controllers/JingShiTiJiaoController.cs new file mode 100644 index 0000000..ac1fb64 --- /dev/null +++ b/WeChatApplet/Controllers/JingShiTiJiaoController.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using YD_WeChatApplet.Api.Services.Interface; +using YD_WeChatApplet.Commons.Dto.HomeWork; +using YD_WeChatApplet.Commons.Dto.JingShiTiJiao; +using YD_WeChatApplet.Services; + +namespace YD_WeChatApplet.Api.Controllers +{ + /// + /// 京师体教 + /// + [ApiController] + [ApiExplorerSettings(GroupName = "v1")] + [Route("[controller]")] + [AllowAnonymous] + public class JingShiTiJiaoController : ControllerBase + { + private readonly IJingShiTiJiaoService _jingShiTiJiaoService; + + /// + /// 京师体教 + /// + /// + public JingShiTiJiaoController(IJingShiTiJiaoService jingShiTiJiaoService) + { + _jingShiTiJiaoService = jingShiTiJiaoService; + } + + /// + /// 根据教师获取教师年级班级信息 + /// + /// + [HttpGet("GetClassListByTeacher")] + public async Task GetClassListByTeacher(string teacherNo) + { + var res = await _jingShiTiJiaoService.GetClassListByTeacher(teacherNo); + return res; + } + + /// + /// 根据班级编号获取学生列表 + /// + /// + [HttpGet("GetStuListByClass")] + public async Task> GetStuListByClass(string classNo) + { + var res = await _jingShiTiJiaoService.GetStuListByClass(classNo); + return res; + } + + /// + /// 根据学生编号获取学生详情 + /// + /// + [HttpGet("GetStudentInfo")] + public async Task GetStudentInfo(string stuNo) + { + var res = await _jingShiTiJiaoService.GetStudentInfo(stuNo); + return res; + } + } +} diff --git a/WeChatApplet/Services/Impl/JingShiTiJiaoService.cs b/WeChatApplet/Services/Impl/JingShiTiJiaoService.cs new file mode 100644 index 0000000..da6dd9b --- /dev/null +++ b/WeChatApplet/Services/Impl/JingShiTiJiaoService.cs @@ -0,0 +1,140 @@ +using Newtonsoft.Json; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Channels; +using YD_WeChatApplet.Api.Services.Interface; +using YD_WeChatApplet.Api.Utilities; +using YD_WeChatApplet.Commons.Dto.JingShiTiJiao; +using YD_WeChatApplet.Commons.Utils; + +namespace YD_WeChatApplet.Api.Services.Impl +{ + /// + /// 京师体教接口服务 + /// + public class JingShiTiJiaoService : IJingShiTiJiaoService + { + /// + /// 根据教师获取教师年级班级信息 + /// + /// + public async Task GetClassListByTeacher(string teacherNo) + { + var res = new TeacherClassData(); + + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string nonce = SaTokenSignHelper.RandomString(20); + + // 1. 签名参数 + var paramMap = new Dictionary + { + { "channel", AppSettings.JingShiTiJiao.Channel }, + { "teacherNo", teacherNo }, + { "timestamp", timestamp }, + { "nonce", nonce } + }; + + // 4. 构建签名 QueryString + string queryStr = SaTokenSignHelper.BuildQueryString(paramMap, AppSettings.JingShiTiJiao.SecretKey); + + // 5. 拼接最终 URL + string url = $"{AppSettings.JingShiTiJiao.BaseUrl}/api/open/class/by/teacher?{queryStr}"; + + string json = await HttpManager.HttpGetAsync(url); + + if (string.IsNullOrWhiteSpace(json)) + return res; + + var data = JsonConvert.DeserializeObject>(json); + if (data != null && data.Code == 0) + { + return data.Data; + } + + return res; + } + + /// + /// 根据班级编号获取学生列表 + /// + /// + /// + /// + public async Task> GetStuListByClass(string classNo) + { + var res = new List(); + + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string nonce = SaTokenSignHelper.RandomString(20); + + // 1. 签名参数 + var paramMap = new Dictionary + { + { "channel", AppSettings.JingShiTiJiao.Channel }, + { "classNo", classNo }, + { "timestamp", timestamp }, + { "nonce", nonce } + }; + + // 4. 构建签名 QueryString + string queryStr = SaTokenSignHelper.BuildQueryString(paramMap, AppSettings.JingShiTiJiao.SecretKey); + + // 5. 拼接最终 URL + string url = $"{AppSettings.JingShiTiJiao.BaseUrl}/api/open/stu/by/class?{queryStr}"; + + string json = await HttpManager.HttpGetAsync(url); + + if (string.IsNullOrWhiteSpace(json)) + return res; + + var data = JsonConvert.DeserializeObject>>(json); + if (data != null && data.Code == 0) + { + return data.Data; + } + + return res; + } + + /// + /// 根据学生编号获取学生详情 + /// + /// + /// + public async Task GetStudentInfo(string stuNo) + { + var res = new StudentDetailDto(); + + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string nonce = SaTokenSignHelper.RandomString(20); + + // 1. 签名参数 + var paramMap = new Dictionary + { + { "channel", AppSettings.JingShiTiJiao.Channel }, + { "stuNo", stuNo }, + { "timestamp", timestamp }, + { "nonce", nonce } + }; + + // 4. 构建签名 QueryString + string queryStr = SaTokenSignHelper.BuildQueryString(paramMap, AppSettings.JingShiTiJiao.SecretKey); + + // 5. 拼接最终 URL + string url = $"{AppSettings.JingShiTiJiao.BaseUrl}/api/open//stu/info?{queryStr}"; + + string json = await HttpManager.HttpGetAsync(url); + + if (string.IsNullOrWhiteSpace(json)) + return res; + + var data = JsonConvert.DeserializeObject>(json); + if (data != null && data.Code == 0) + { + return data.Data; + } + + return res; + } + } +} diff --git a/WeChatApplet/Services/Interface/IJingShiTiJiaoService.cs b/WeChatApplet/Services/Interface/IJingShiTiJiaoService.cs new file mode 100644 index 0000000..0fb805e --- /dev/null +++ b/WeChatApplet/Services/Interface/IJingShiTiJiaoService.cs @@ -0,0 +1,30 @@ +using YD_WeChatApplet.Commons.Dto.JingShiTiJiao; + +namespace YD_WeChatApplet.Api.Services.Interface +{ + /// + /// 京师体教接口 + /// + public interface IJingShiTiJiaoService + { + /// + /// 根据教师获取教师年级班级信息 + /// + /// + Task GetClassListByTeacher(string teacherNo); + + /// + /// 根据班级编号获取学生列表 + /// + /// + /// + Task> GetStuListByClass(string classNo); + + /// + /// 根据学生编号获取学生详情 + /// + /// + /// + Task GetStudentInfo(string stuNo); + } +} diff --git a/WeChatApplet/Startup.cs b/WeChatApplet/Startup.cs index 78d9512..e35011e 100644 --- a/WeChatApplet/Startup.cs +++ b/WeChatApplet/Startup.cs @@ -70,6 +70,8 @@ namespace YD_WeChatApplet services.AddScoped(); services.AddSingleton(); + + services.AddSingleton(); services.AddSession(); services.AddMemoryCache(); services.AddHttpContextAccessor(); diff --git a/WeChatApplet/Utilities/AppSettings.cs b/WeChatApplet/Utilities/AppSettings.cs index 11d3106..723d361 100644 --- a/WeChatApplet/Utilities/AppSettings.cs +++ b/WeChatApplet/Utilities/AppSettings.cs @@ -30,6 +30,11 @@ /// public static YDWeChatAppletOSS YDWeChatAppletOSS { get; set; } + /// + /// JingShiTiJiao + /// + public static JingShiTiJiao JingShiTiJiao { get; set; } + public static string TokenHeaderName = "Authorization"; @@ -47,6 +52,7 @@ ALiYunOSS = configuration.GetSection("ALiYunOSS").Get(); WeChatQA = configuration.GetSection("WeChatQA").Get(); YDWeChatAppletOSS = configuration.GetSection("YDWeChatAppletOSS").Get(); + JingShiTiJiao = configuration.GetSection("JingShiTiJiao").Get(); } public class LoggingConfig @@ -103,4 +109,11 @@ public string BucketName { get; set; } public string Prefix { get; set; } } + + public class JingShiTiJiao + { + public string Channel { get; set; } + public string SecretKey { get; set; } + public string BaseUrl { get; set; } + } } diff --git a/WeChatApplet/appsettings.json b/WeChatApplet/appsettings.json index 8d491f6..7749d3a 100644 --- a/WeChatApplet/appsettings.json +++ b/WeChatApplet/appsettings.json @@ -62,5 +62,11 @@ "YDWeChatAppletOSS": { "BucketName": "yuedong-wechatapplet", "Prefix": "Upload" + }, + + "JingShiTiJiao": { + "Channel": "sk-yuedong", + "SecretKey": "JSTJ-kGfs6J7bkgdxr9mz", + "BaseUrl": "https://api-test.aitibao.com" } } diff --git a/YD_WeChatApplet.Commons/Dto/JingShiTiJiao/JingShiResponse.cs b/YD_WeChatApplet.Commons/Dto/JingShiTiJiao/JingShiResponse.cs new file mode 100644 index 0000000..94e3af8 --- /dev/null +++ b/YD_WeChatApplet.Commons/Dto/JingShiTiJiao/JingShiResponse.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace YD_WeChatApplet.Commons.Dto.JingShiTiJiao +{ + /// + /// 京师接口统一返回结果泛型封装 + /// + /// data 对应的数据类型 + public class JingShiResponse + { + [JsonProperty("code")] + public int Code { get; set; } + + [JsonProperty("msg")] + public string Msg { get; set; } + + [JsonProperty("data")] + public T Data { get; set; } + + /// + /// 判断接口是否成功 + /// + public bool IsSuccess => Code == 0; + } + + /// + /// 老师信息 + /// + public class TeacherClassData + { + [JsonProperty("school")] + public string School { get; set; } + + [JsonProperty("teacherNo")] + public string TeacherNo { get; set; } + + [JsonProperty("teacherName")] + public string TeacherName { get; set; } + + [JsonProperty("classList")] + public List GradeList { get; set; } + } + + public class GradeDto + { + [JsonProperty("gradeNo")] + public string GradeNo { get; set; } + + [JsonProperty("gradeName")] + public string GradeName { get; set; } + + [JsonProperty("claArray")] + public List ClassList { get; set; } + } + + public class ClassDto + { + [JsonProperty("classNo")] + public string ClassNo { get; set; } + + [JsonProperty("className")] + public string ClassName { get; set; } + } +} diff --git a/YD_WeChatApplet.Commons/Dto/JingShiTiJiao/StudentDetailDto.cs b/YD_WeChatApplet.Commons/Dto/JingShiTiJiao/StudentDetailDto.cs new file mode 100644 index 0000000..aaec942 --- /dev/null +++ b/YD_WeChatApplet.Commons/Dto/JingShiTiJiao/StudentDetailDto.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YD_WeChatApplet.Commons.Dto.JingShiTiJiao +{ + /// + /// 学生详情 DTO + /// + public class StudentDetailDto + { + [JsonProperty("stuNo")] + public string StuNo { get; set; } + + [JsonProperty("stuName")] + public string StuName { get; set; } + + [JsonProperty("school")] + public string School { get; set; } + + [JsonProperty("teacherNo")] + public string TeacherNo { get; set; } + + [JsonProperty("teacherName")] + public string TeacherName { get; set; } + + [JsonProperty("gradeNo")] + public string GradeNo { get; set; } + + [JsonProperty("gradeName")] + public string GradeName { get; set; } + + [JsonProperty("classNo")] + public string ClassNo { get; set; } + + [JsonProperty("className")] + public string ClassName { get; set; } + + [JsonProperty("gender")] + public string Gender { get; set; } + + [JsonProperty("age")] + public int Age { get; set; } + } +} diff --git a/YD_WeChatApplet.Commons/Dto/JingShiTiJiao/StudentDto.cs b/YD_WeChatApplet.Commons/Dto/JingShiTiJiao/StudentDto.cs new file mode 100644 index 0000000..cf58528 --- /dev/null +++ b/YD_WeChatApplet.Commons/Dto/JingShiTiJiao/StudentDto.cs @@ -0,0 +1,51 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YD_WeChatApplet.Commons.Dto.JingShiTiJiao +{ + /// + /// 学生信息 DTO + /// + public class StudentDto + { + [JsonProperty("stuNo")] + public string StuNo { get; set; } + + [JsonProperty("stuName")] + public string StuName { get; set; } + + [JsonProperty("school")] + public string School { get; set; } + + [JsonProperty("teacherNo")] + public string TeacherNo { get; set; } + + [JsonProperty("teacherName")] + public string TeacherName { get; set; } + + [JsonProperty("gradeNo")] + public string GradeNo { get; set; } + + [JsonProperty("gradeName")] + public string GradeName { get; set; } + + [JsonProperty("classNo")] + public string ClassNo { get; set; } + + [JsonProperty("className")] + public string ClassName { get; set; } + + [JsonProperty("gender")] + public string Gender { get; set; } + + [JsonProperty("age")] + public int Age { get; set; } + + [JsonProperty("avatar")] + public string Avatar { get; set; } + } +} diff --git a/YD_WeChatApplet.Commons/Utils/JSSaTokenSignHelper.cs b/YD_WeChatApplet.Commons/Utils/JSSaTokenSignHelper.cs new file mode 100644 index 0000000..03bb937 --- /dev/null +++ b/YD_WeChatApplet.Commons/Utils/JSSaTokenSignHelper.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace YD_WeChatApplet.Commons.Utils +{ + public static class SaTokenSignHelper + { + /// + /// 根据业务参数生成 Java Sa-Token Sign 兼容的 QueryString + /// 自动生成 timestamp 和 nonce,如果外部未提供 + /// + /// 业务参数字典(可包含 teacherNo 等) + /// 应用密钥 + /// 应用编码,例如 sk-yuedong + /// 带 sign 的完整 QueryString + public static string BuildQueryString(Dictionary paramMap, string secretKey) + { + if (paramMap == null) + paramMap = new Dictionary(); + + // 1️⃣ TreeMap 排序(ASCII 升序) + var sorted = new SortedDictionary(StringComparer.Ordinal); + foreach (var kv in paramMap) + sorted[kv.Key] = kv.Value.ToString(); + + // 2️⃣ 拼接签名字符串 k=v&k=v&...&key=secretKey + var sbSign = new StringBuilder(); + foreach (var kv in sorted) + sbSign.Append(kv.Key).Append("=").Append(kv.Value).Append("&"); + sbSign.Append("key=").Append(secretKey.Trim()); + + string sign = Md5(sbSign.ToString()); + + // 3️⃣ 拼接 QueryString(保留外部字典原顺序) + 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(); + } + + /// + /// MD5 小写 32 位 + /// + private static string Md5(string input) + { + using var md5 = MD5.Create(); + var bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(input)); + var sb = new StringBuilder(); + foreach (var b in bytes) + sb.Append(b.ToString("x2")); + return sb.ToString(); + } + + /// + /// 生成随机字符串 nonce,可指定长度 + /// + public static string RandomString(int length = 20) + { + const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var random = new Random(); + return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); + } + } +}