From 919c295b13af5070e89784d8d7cd5796ab028b4a Mon Sep 17 00:00:00 2001 From: youngq Date: Tue, 19 Nov 2024 16:22:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AI.Api.sln | 28 ++ AI.Api/AI.Api.csproj | 19 ++ AI.Api/Controllers/AuthController.cs | 26 ++ AI.Api/Controllers/BaseController.cs | 25 ++ AI.Api/Controllers/PromptController.cs | 25 ++ AI.Api/Controllers/QuestLogController.cs | 28 ++ AI.Api/Program.cs | 71 +++++ AI.Api/Properties/launchSettings.json | 23 ++ .../AuthenticationServiceExtensions.cs | 47 +++ AI.Api/WebCore/GlobalExceptionFilter.cs | 66 ++++ AI.Api/WebCore/ModelActionFilter.cs | 60 ++++ AI.Api/WebCore/SqlsugarServiceExtensions.cs | 58 ++++ AI.Api/appsettings.Development.json | 26 ++ AI.Api/appsettings.json | 9 + AI.Common/AI.Common.csproj | 20 ++ AI.Common/Dtos/LoginDto.cs | 14 + AI.Common/Dtos/PromptDto.cs | 16 + AI.Common/Dtos/QuestionLogDto.cs | 15 + AI.Common/Entities/Prompts.cs | 53 ++++ AI.Common/Entities/QuestionLog.cs | 53 ++++ AI.Common/Entities/User.cs | 298 ++++++++++++++++++ AI.Common/ExceptionNotice.cs | 44 +++ AI.Common/FriendlyInternalException.cs | 17 + AI.Common/Helpers/JwtHelper.cs | 54 ++++ AI.Common/Helpers/Oops.cs | 68 ++++ AI.Common/Services/AuthService.cs | 50 +++ AI.Common/Services/Interface/IAuthService.cs | 14 + .../Services/Interface/IPromptService.cs | 18 ++ .../Services/Interface/IQuestionLogService.cs | 14 + AI.Common/Services/PromptService.cs | 49 +++ AI.Common/Services/QuestionLogService.cs | 38 +++ AI.Common/UniformResult.cs | 39 +++ 32 files changed, 1385 insertions(+) create mode 100644 AI.Api.sln create mode 100644 AI.Api/AI.Api.csproj create mode 100644 AI.Api/Controllers/AuthController.cs create mode 100644 AI.Api/Controllers/BaseController.cs create mode 100644 AI.Api/Controllers/PromptController.cs create mode 100644 AI.Api/Controllers/QuestLogController.cs create mode 100644 AI.Api/Program.cs create mode 100644 AI.Api/Properties/launchSettings.json create mode 100644 AI.Api/WebCore/AuthenticationServiceExtensions.cs create mode 100644 AI.Api/WebCore/GlobalExceptionFilter.cs create mode 100644 AI.Api/WebCore/ModelActionFilter.cs create mode 100644 AI.Api/WebCore/SqlsugarServiceExtensions.cs create mode 100644 AI.Api/appsettings.Development.json create mode 100644 AI.Api/appsettings.json create mode 100644 AI.Common/AI.Common.csproj create mode 100644 AI.Common/Dtos/LoginDto.cs create mode 100644 AI.Common/Dtos/PromptDto.cs create mode 100644 AI.Common/Dtos/QuestionLogDto.cs create mode 100644 AI.Common/Entities/Prompts.cs create mode 100644 AI.Common/Entities/QuestionLog.cs create mode 100644 AI.Common/Entities/User.cs create mode 100644 AI.Common/ExceptionNotice.cs create mode 100644 AI.Common/FriendlyInternalException.cs create mode 100644 AI.Common/Helpers/JwtHelper.cs create mode 100644 AI.Common/Helpers/Oops.cs create mode 100644 AI.Common/Services/AuthService.cs create mode 100644 AI.Common/Services/Interface/IAuthService.cs create mode 100644 AI.Common/Services/Interface/IPromptService.cs create mode 100644 AI.Common/Services/Interface/IQuestionLogService.cs create mode 100644 AI.Common/Services/PromptService.cs create mode 100644 AI.Common/Services/QuestionLogService.cs create mode 100644 AI.Common/UniformResult.cs diff --git a/AI.Api.sln b/AI.Api.sln new file mode 100644 index 0000000..833e965 --- /dev/null +++ b/AI.Api.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35506.116 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AI.Api", "AI.Api\AI.Api.csproj", "{DC60489F-F76C-47FF-8698-5EF3454FA974}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AI.Common", "AI.Common\AI.Common.csproj", "{B5A93DD0-B38E-40D4-B6A3-40BA62A2FA2D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DC60489F-F76C-47FF-8698-5EF3454FA974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC60489F-F76C-47FF-8698-5EF3454FA974}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC60489F-F76C-47FF-8698-5EF3454FA974}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC60489F-F76C-47FF-8698-5EF3454FA974}.Release|Any CPU.Build.0 = Release|Any CPU + {B5A93DD0-B38E-40D4-B6A3-40BA62A2FA2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5A93DD0-B38E-40D4-B6A3-40BA62A2FA2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5A93DD0-B38E-40D4-B6A3-40BA62A2FA2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5A93DD0-B38E-40D4-B6A3-40BA62A2FA2D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AI.Api/AI.Api.csproj b/AI.Api/AI.Api.csproj new file mode 100644 index 0000000..74074d8 --- /dev/null +++ b/AI.Api/AI.Api.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/AI.Api/Controllers/AuthController.cs b/AI.Api/Controllers/AuthController.cs new file mode 100644 index 0000000..1e1d5a5 --- /dev/null +++ b/AI.Api/Controllers/AuthController.cs @@ -0,0 +1,26 @@ +using AI.Common.Dtos; +using AI.Common.Services.Interface; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace AI.Api.Controllers +{ + [Route("[controller]")] + public class AuthController : BaseController + { + private readonly IAuthService _authService; + + public AuthController(IAuthService authService) + { + this._authService = authService; + } + + + [AllowAnonymous] + [HttpPost("login")] + public async Task LoginAsync([FromBody] LoginDto loginDto) + { + return Ok(await _authService.LoginAsync(loginDto)); + } + } +} diff --git a/AI.Api/Controllers/BaseController.cs b/AI.Api/Controllers/BaseController.cs new file mode 100644 index 0000000..76200f1 --- /dev/null +++ b/AI.Api/Controllers/BaseController.cs @@ -0,0 +1,25 @@ +using AI.Common.Helpers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace AI.Api.Controllers +{ + [ApiController] + [Authorize] + public class BaseController : ControllerBase + { + public long UId + { + get + { + var uid = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier).Value; + if (string.IsNullOrWhiteSpace(uid)) + { + throw Oops.Oh("ûϢµ¼"); + } + return long.Parse(uid); + } + } + } +} diff --git a/AI.Api/Controllers/PromptController.cs b/AI.Api/Controllers/PromptController.cs new file mode 100644 index 0000000..ab2f2a6 --- /dev/null +++ b/AI.Api/Controllers/PromptController.cs @@ -0,0 +1,25 @@ +using AI.Common.Dtos; +using AI.Common.Entities; +using AI.Common.Services.Interface; +using Mapster; +using Microsoft.AspNetCore.Mvc; + +namespace AI.Api.Controllers +{ + [Route("[controller]")] + public class PromptController : BaseController + { + private readonly IPromptService _promptService; + + public PromptController(IPromptService promptService) + { + this._promptService = promptService; + } + + [HttpGet("list")] + public async Task> GetListAsync() + { + return await _promptService.GetListAsync(); + } + } +} diff --git a/AI.Api/Controllers/QuestLogController.cs b/AI.Api/Controllers/QuestLogController.cs new file mode 100644 index 0000000..74466dd --- /dev/null +++ b/AI.Api/Controllers/QuestLogController.cs @@ -0,0 +1,28 @@ +using AI.Common.Dtos; +using AI.Common.Entities; +using AI.Common.Services.Interface; +using Mapster; +using Microsoft.AspNetCore.Mvc; + +namespace AI.Api.Controllers +{ + [Route("[controller]")] + public class QuestLogController : BaseController + { + private readonly IQuestionLogService _questionLogService; + + public QuestLogController(IQuestionLogService questionLogService) + { + this._questionLogService = questionLogService; + } + + [HttpGet("record")] + public async Task RecordAsync([FromBody] QuestionLogDto questionLogDto) + { + var entity = questionLogDto.Adapt(); + entity.Uid = UId; + + return await _questionLogService.RecordAsync(entity); + } + } +} diff --git a/AI.Api/Program.cs b/AI.Api/Program.cs new file mode 100644 index 0000000..e0f6f27 --- /dev/null +++ b/AI.Api/Program.cs @@ -0,0 +1,71 @@ +using AI.Api.WebCore; +using AI.Common.Services; +using AI.Common.Services.Interface; +using SqlSugar; +using DbType = SqlSugar.DbType; + +namespace AI.Api +{ + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + var configuration = builder.Configuration; + // Add services to the container. + + builder.Services.AddControllers(options => + { + // ȫ쳣ڴ д try catch + options.Filters.Add(); + // ȫģ͸ֵĬֵ ͳһظʽ + options.Filters.Add(); + }); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + builder.Services.AddSingleton(new Common.Helpers.JwtHelper(configuration)); + builder.Services.AddAuth(configuration["Jwt:Issuer"], + configuration["Jwt:Audience"], + configuration["Jwt:SecretKey"]); + builder.Services.AddSqlsugar(builder.Environment.EnvironmentName, new ConnectionConfig + { + DbType = DbType.MySql, + ConfigId = "walle", + ConnectionString = configuration.GetConnectionString("walle"), + IsAutoCloseConnection = true + }, + new ConnectionConfig + { + DbType = DbType.MySql, + ConfigId = "usercenter", + ConnectionString = configuration.GetConnectionString("usercenter"), + IsAutoCloseConnection = true + }); + + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + //мUseAuthentication֤Ҫ֤мǰã UseAuthorizationȨ + app.UseAuthentication(); + app.UseAuthorization(); + + + app.MapControllers(); + + app.Run(); + } + } +} diff --git a/AI.Api/Properties/launchSettings.json b/AI.Api/Properties/launchSettings.json new file mode 100644 index 0000000..8c2e3cd --- /dev/null +++ b/AI.Api/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:29214", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5108", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/AI.Api/WebCore/AuthenticationServiceExtensions.cs b/AI.Api/WebCore/AuthenticationServiceExtensions.cs new file mode 100644 index 0000000..ccfd4af --- /dev/null +++ b/AI.Api/WebCore/AuthenticationServiceExtensions.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.Text; + +namespace AI.Api.WebCore +{ + public static class AuthenticationServiceExtensions + { + /// + /// 添加认证和授权 + /// + /// 服务集合 + /// + public static IServiceCollection AddAuth(this IServiceCollection services, string issuer, string audience, string secretKey) + { + services.AddAuthentication(options => + { + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuer = true, //是否验证Issuer + ValidIssuer = issuer, //发行人Issuer + ValidateAudience = true, //是否验证Audience + ValidAudience = audience, //订阅人Audience + ValidateIssuerSigningKey = true, //是否验证SecurityKey + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)), //SecurityKey + ValidateLifetime = true, //是否验证失效时间 + ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒) + RequireExpirationTime = true, + }; + + }); + + + return services; + //services.AddAuthorization(options => + //{ + // options.AddPolicy(Constant.Policy.FreePolicyName, + // policy => policy.RequireClaim(Constant.Auth.PermissionsKey, Constant.Auth.FreeClaimValue, Constant.Auth.VipClaimValue)); + // options.AddPolicy(Constant.Policy.VipPolicyName, + // policy => policy.RequireClaim(Constant.Auth.PermissionsKey, Constant.Auth.VipClaimValue)); + //}); + } + } +} diff --git a/AI.Api/WebCore/GlobalExceptionFilter.cs b/AI.Api/WebCore/GlobalExceptionFilter.cs new file mode 100644 index 0000000..ee682b8 --- /dev/null +++ b/AI.Api/WebCore/GlobalExceptionFilter.cs @@ -0,0 +1,66 @@ +using AI.Common; +using Masuit.Tools; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace AI.Api.WebCore +{ + /// + /// 全局异常捕获 + /// + public class GlobalExceptionFilter : IAsyncExceptionFilter + { + private readonly ILogger _logger; + + public GlobalExceptionFilter(ILogger logger) + { + _logger = logger; //在构造函数中注入日志处理实例 + } + + public async Task OnExceptionAsync(ExceptionContext context) + { + // 如果异常没有被处理则进行处理 + if (context.ExceptionHandled == false) + { + // 定义返回类型 + UniformResult result; + + // 如果为业务逻辑抛出的内部异常 + if (context.Exception is FriendlyInternalException ex) + { + // 企业微信异常通知 + //await ExceptionNotice.SendFriendlyExceptionAsync(ex); + + result = UniformResult + .Create(ex.FriendlyData, ex.ErrorCode, context.Exception.Message); + } + else + { + // 程序异常,不对外暴露程序异常细节 + result = UniformResult + .Create(null, 500, "程序出错啦🐞🐞🐞!"); + + //使用日志对象 _logger 的 LogError() 方法将异常信息写入日志文件 + _logger.LogError(context.Exception, context.Exception.Message); + + // 企业微信异常通知 + await ExceptionNotice.SendAsync(context.Exception, "全局异常捕获"); + } + + context.Result = new ContentResult + { + // 返回状态码设置为200,表示成功 + StatusCode = StatusCodes.Status200OK, + // 设置返回格式 + ContentType = "application/json;charset=utf-8", + Content = result.ToJsonString() + }; + + + } + + // 设置为true,表示异常已经被处理了 + context.ExceptionHandled = true; + } + } +} diff --git a/AI.Api/WebCore/ModelActionFilter.cs b/AI.Api/WebCore/ModelActionFilter.cs new file mode 100644 index 0000000..15d5cca --- /dev/null +++ b/AI.Api/WebCore/ModelActionFilter.cs @@ -0,0 +1,60 @@ +using AI.Common; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace AI.Api.WebCore +{ + public class ModelActionFilter : ActionFilterAttribute + { + /// + /// 在Controller的Action执行后执行 + /// + /// + public override void OnActionExecuted(ActionExecutedContext context) + { + //特殊处理:对有ApiResultIgnoreAttribute标签的,不进行返回结果包装,原样输出 + //var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; + //if (controllerActionDescriptor != null) + //{ + // var isDefined = controllerActionDescriptor.EndpointMetadata.Any(a => a.GetType().Equals(typeof(ApiResultIgnoreAttribute))); + // if (isDefined) + // { + // return; + // } + //} + // 返回结果为JsonResult的请求进行Result包装 + if (context.Result != null) + { + switch (context.Result) + { + case ObjectResult: + { + var result = context.Result as ObjectResult; + context.Result = new JsonResult(UniformResult.Create(result.Value)); + break; + } + case EmptyResult: + context.Result = new JsonResult(UniformResult.Create(null)); + break; + case ContentResult: + { + var result = context.Result as ContentResult; + context.Result = new JsonResult(UniformResult.Create(result.Content)); + break; + } + } + } + + base.OnActionExecuted(context); + } + + /// + /// 在Controller的Action执行前执行 + /// + /// + public override void OnActionExecuting(ActionExecutingContext context) + { + base.OnActionExecuting(context); + } + } +} diff --git a/AI.Api/WebCore/SqlsugarServiceExtensions.cs b/AI.Api/WebCore/SqlsugarServiceExtensions.cs new file mode 100644 index 0000000..9c5ad74 --- /dev/null +++ b/AI.Api/WebCore/SqlsugarServiceExtensions.cs @@ -0,0 +1,58 @@ +using SqlSugar; +using System.Text.RegularExpressions; + +namespace AI.Api.WebCore +{ + public static class SqlsugarServiceExtensions + { + /// + /// 添加SqlSugar + /// + /// 服务集合 + /// + public static IServiceCollection AddSqlsugar(this IServiceCollection services, string env, params ConnectionConfig[] connectionConfigs) + { + SqlSugarScope sqlSugar = new SqlSugarScope(connectionConfigs.ToList(), + db => + { + if (env == Environments.Development) + { + // 正则表达式匹配Ip + var ipMatch = Regex.Match(db.CurrentConnectionConfig.ConnectionString, @"((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))"); + + var connections = db.CurrentConnectionConfig.ConnectionString.Split(';'); + string dbNamne = string.Empty; + foreach (var item in connections) + { + if (item.Contains("Database")) + { + dbNamne = item.Split('=')[1]; + } + } + + //SQL执行前 + db.Aop.OnLogExecuting = (sql, pars) => + { + // 打印 Sql 语句 + Console.WriteLine($@"{DateTime.Now.ToShortTimeString()}{new string('=', 10)}BEGIN{new string('=', 5)} DB-IP:{ipMatch.Value} {new string('=', 5)} DB-Name:{dbNamne} {new string('=', 15)}"); + Console.WriteLine($"执行Sql:{Environment.NewLine}{sql}"); + Console.WriteLine($"参数:{db.Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value))}"); + }; + + //SQL执行完成 + db.Aop.OnLogExecuted = (sql, pars) => + { + //执行完了可以输出SQL执行时间 + Console.WriteLine("Sql用时:" + db.Ado.SqlExecutionTime.ToString()); + Console.WriteLine($@"{DateTime.Now.ToShortTimeString()}{new string('=', 10)}END{new string('=', 7)} DB-IP:{ipMatch.Value} {new string('=', 5)} DB-Name:{dbNamne} {new string('=', 15)}"); + }; + } + }); + + services.AddSingleton(sqlSugar); + StaticConfig.Check_StringIdentity = false; + + return services; + } + } +} diff --git a/AI.Api/appsettings.Development.json b/AI.Api/appsettings.Development.json new file mode 100644 index 0000000..cbe5b53 --- /dev/null +++ b/AI.Api/appsettings.Development.json @@ -0,0 +1,26 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Information" + }, + "Console": { + "LogLevel": { + "Default": "Debug", + "Microsoft": "Information", + "Hangfire": "Information" + } + } + }, + "ConnectionStrings": { + "walle": "Database=walle;Server=192.168.2.9;Port=3306;Uid=root;Pwd=qwe123!@#;AllowZeroDateTime=True;ConvertZeroDateTime=True;", + "usercenter": "Database=usercenter;Server=192.168.2.9;Port=3306;Uid=root;Pwd=qwe123!@#;AllowZeroDateTime=True;ConvertZeroDateTime=True;" + }, + "Jwt": { + "SecretKey": "apDbztyqjSNuvWnezhbdUxduhDidZbF897t2uTJs53RMdY9Cai7eexavBhka3HN6mcTe9oohjFg6bNffRRkcfMqnVKNBnmyPzkRgNopHGJAL7KMwkeZdZ7BaWnT57jCi", + "Issuer": "AI.Api", + "Audience": "AIClient", + // 过期 秒 + "Expires": 108000 + } +} diff --git a/AI.Api/appsettings.json b/AI.Api/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/AI.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/AI.Common/AI.Common.csproj b/AI.Common/AI.Common.csproj new file mode 100644 index 0000000..1f35ca6 --- /dev/null +++ b/AI.Common/AI.Common.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/AI.Common/Dtos/LoginDto.cs b/AI.Common/Dtos/LoginDto.cs new file mode 100644 index 0000000..3b1d435 --- /dev/null +++ b/AI.Common/Dtos/LoginDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AI.Common.Dtos +{ + public class LoginDto + { + public string Account { get; set; } + public string Password { get; set; } + } +} diff --git a/AI.Common/Dtos/PromptDto.cs b/AI.Common/Dtos/PromptDto.cs new file mode 100644 index 0000000..3d8eac3 --- /dev/null +++ b/AI.Common/Dtos/PromptDto.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AI.Common.Dtos +{ + public class PromptDto + { + public long Id { get; set; } + public string Name { get; set; } + public string Prompt { get; set; } + public DateTime CreateTime { get; set; } + } +} diff --git a/AI.Common/Dtos/QuestionLogDto.cs b/AI.Common/Dtos/QuestionLogDto.cs new file mode 100644 index 0000000..802e288 --- /dev/null +++ b/AI.Common/Dtos/QuestionLogDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AI.Common.Dtos +{ + public class QuestionLogDto + { + public long Id { get; set; } + public string QuestionContent { get; set; } + public string? GptAnswer { get; set; } + } +} diff --git a/AI.Common/Entities/Prompts.cs b/AI.Common/Entities/Prompts.cs new file mode 100644 index 0000000..4a38bfb --- /dev/null +++ b/AI.Common/Entities/Prompts.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SqlSugar; +namespace AI.Common.Entities +{ + /// + /// + /// + [SugarTable("prompts")] + public class Prompts + { + + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName="id" ,IsPrimaryKey = true,IsIdentity = true) ] + public long Id { get; set; } + + /// + /// 备 注:角色名称 + /// 默认值: + /// + [SugarColumn(ColumnName="name" ) ] + public string Name { get; set; } = null!; + + /// + /// 备 注:提示词 + /// 默认值: + /// + [SugarColumn(ColumnName="prompt" ) ] + public string Prompt { get; set; } = null!; + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName="create_time", IsOnlyIgnoreInsert = true, IsOnlyIgnoreUpdate = true) ] + public DateTime CreateTime { get; set; } + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName="is_delete" ) ] + public bool IsDelete { get; set; } + + + } + +} \ No newline at end of file diff --git a/AI.Common/Entities/QuestionLog.cs b/AI.Common/Entities/QuestionLog.cs new file mode 100644 index 0000000..37d22d5 --- /dev/null +++ b/AI.Common/Entities/QuestionLog.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SqlSugar; +namespace AI.Common.Entities +{ + /// + /// + /// + [SugarTable("question_log")] + public class QuestionLog + { + + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName = "id", IsPrimaryKey = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName = "create_time", IsOnlyIgnoreInsert = true, IsOnlyIgnoreUpdate = true)] + public DateTime Create_time { get; set; } + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName = "uid")] + public long Uid { get; set; } + + /// + /// 备 注:问题内容 + /// 默认值: + /// + [SugarColumn(ColumnName = "question_content")] + public string? QuestionContent { get; set; } + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName = "gpt_answer")] + public string? GptAnswer { get; set; } + + + } + +} \ No newline at end of file diff --git a/AI.Common/Entities/User.cs b/AI.Common/Entities/User.cs new file mode 100644 index 0000000..ed402cb --- /dev/null +++ b/AI.Common/Entities/User.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SqlSugar; +namespace AI.Common.Entities +{ + /// + /// 用户表【学生,教师,校职工】 + /// + [SugarTable("user")] + public class User + { + + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName="Id" ,IsPrimaryKey = true) ] + public long Id { get; set; } + + /// + /// 备 注:模板id + /// 默认值: + /// + [SugarColumn(ColumnName="TemplateId" ) ] + public long TemplateId { get; set; } + + /// + /// 备 注:学校Id + /// 默认值: + /// + [SugarColumn(ColumnName="SchoolId" ) ] + public long? SchoolId { get; set; } + + /// + /// 备 注:用户类型 1:学生 2:领导 + /// 默认值: + /// + [SugarColumn(ColumnName="UserType" ) ] + public int UserType { get; set; } + + /// + /// 备 注:账号 + /// 默认值: + /// + [SugarColumn(ColumnName="Account" ) ] + public string Account { get; set; } = null!; + + /// + /// 备 注:学号/考号 + /// 默认值: + /// + [SugarColumn(ColumnName="StudentId" ) ] + public string StudentId { get; set; } = null!; + + /// + /// 备 注:身份证号 18位数长度限制 + /// 默认值: + /// + [SugarColumn(ColumnName="IdCard" ) ] + public string? IdCard { get; set; } + + /// + /// 备 注:密码 + /// 默认值: + /// + [SugarColumn(ColumnName="Password" ) ] + public string Password { get; set; } = null!; + + /// + /// 备 注:姓名 + /// 默认值: + /// + [SugarColumn(ColumnName="RealName" ) ] + public string RealName { get; set; } = null!; + + /// + /// 备 注:性别 0=男 1=女 + /// 默认值: + /// + [SugarColumn(ColumnName="Sex" ) ] + public int Sex { get; set; } + + /// + /// 备 注:出生日期 + /// 默认值: + /// + [SugarColumn(ColumnName="BirthDate" ) ] + public DateTime BirthDate { get; set; } + + /// + /// 备 注:家庭户口 + /// 默认值: + /// + [SugarColumn(ColumnName="Residence" ) ] + public string Residence { get; set; } = null!; + + /// + /// 备 注:民族 + /// 默认值: + /// + [SugarColumn(ColumnName="National" ) ] + public string National { get; set; } = null!; + + /// + /// 备 注:头像 + /// 默认值: + /// + [SugarColumn(ColumnName="HeadImage" ) ] + public string HeadImage { get; set; } = null!; + + /// + /// 备 注:省Id + /// 默认值: + /// + [SugarColumn(ColumnName="Pid" ) ] + public int Pid { get; set; } + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName="Pname" ) ] + public string Pname { get; set; } = null!; + + /// + /// 备 注:市Id + /// 默认值: + /// + [SugarColumn(ColumnName="Cid" ) ] + public int Cid { get; set; } + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName="Cname" ) ] + public string Cname { get; set; } = null!; + + /// + /// 备 注:区Id + /// 默认值: + /// + [SugarColumn(ColumnName="Rid" ) ] + public int Rid { get; set; } + + /// + /// 备 注: + /// 默认值: + /// + [SugarColumn(ColumnName="Rname" ) ] + public string Rname { get; set; } = null!; + + /// + /// 备 注:微信号 + /// 默认值: + /// + [SugarColumn(ColumnName="Wx" ) ] + public string Wx { get; set; } = null!; + + /// + /// 备 注:是否完善信息 0:否 1:是 + /// 默认值: + /// + [SugarColumn(ColumnName="IsPerfectInfo" ) ] + public int IsPerfectInfo { get; set; } + + /// + /// 备 注:账户状态 0禁用 1正常 + /// 默认值: + /// + [SugarColumn(ColumnName="State" ) ] + public int State { get; set; } + + /// + /// 备 注:直播平台编号 + /// 默认值: + /// + [SugarColumn(ColumnName="LiveId" ) ] + public long LiveId { get; set; } + + /// + /// 备 注:上次登录时间 + /// 默认值: + /// + [SugarColumn(ColumnName="LastLoginTime" ) ] + public DateTime LastLoginTime { get; set; } + + /// + /// 备 注:登录IP + /// 默认值: + /// + [SugarColumn(ColumnName="LastLoginIP" ) ] + public string LastLoginIP { get; set; } = null!; + + /// + /// 备 注:添加时间 + /// 默认值: + /// + [SugarColumn(ColumnName="CreateTime" ) ] + public DateTime CreateTime { get; set; } + + /// + /// 备 注:层次 + /// 默认值: + /// + [SugarColumn(ColumnName="Level" ) ] + public int Level { get; set; } + + /// + /// 备 注:会议系统账号 + /// 默认值: + /// + [SugarColumn(ColumnName="MeetingAccount" ) ] + public string? MeetingAccount { get; set; } + + /// + /// 备 注:新高考物理化学选科 + /// 默认值: + /// + [SugarColumn(ColumnName="GLSubject" ) ] + public int? GLSubject { get; set; } + + /// + /// 备 注:新高考,政地化生选科1 + /// 默认值: + /// + [SugarColumn(ColumnName="GSubject1" ) ] + public int? GSubject1 { get; set; } + + /// + /// 备 注:新高考,政地化生选科2 + /// 默认值: + /// + [SugarColumn(ColumnName="GSubject2" ) ] + public int? GSubject2 { get; set; } + + /// + /// 备 注:第三方id + /// 默认值: + /// + [SugarColumn(ColumnName="ThirdPartyId" ) ] + public string? ThirdPartyId { get; set; } + + /// + /// 备 注:电话号码 + /// 默认值: + /// + [SugarColumn(ColumnName="Phone" ) ] + public string? Phone { get; set; } + + /// + /// 备 注:点阵笔SN + /// 默认值: + /// + [SugarColumn(ColumnName="PointPenSN" ) ] + public string? PointPenSN { get; set; } + + /// + /// 备 注:删除状态 + /// 默认值: + /// + [SugarColumn(ColumnName="DeleteState" ) ] + public SByte DeleteState { get; set; } + + /// + /// 备 注:互动课堂创建人Id + /// 默认值: + /// + [SugarColumn(ColumnName="HdktCreatorId" ) ] + public long? HdktCreatorId { get; set; } + + /// + /// 备 注:用户来源 + /// 默认值: + /// + [SugarColumn(ColumnName="Source" ) ] + public int Source { get; set; } + + /// + /// 备 注:点阵笔MAC + /// 默认值: + /// + [SugarColumn(ColumnName="PointPenMAC" ) ] + public string? PointPenMAC { get; set; } + + /// + /// 备 注:创建人名称 + /// 默认值: + /// + [SugarColumn(ColumnName="Creator" ) ] + public string? Creator { get; set; } + + + } + +} \ No newline at end of file diff --git a/AI.Common/ExceptionNotice.cs b/AI.Common/ExceptionNotice.cs new file mode 100644 index 0000000..a0e4894 --- /dev/null +++ b/AI.Common/ExceptionNotice.cs @@ -0,0 +1,44 @@ +using System.Net.Http.Json; + +namespace AI.Common +{ + /// + /// 异常通知 + /// + public class ExceptionNotice + { + private static HttpClient httpClient = new HttpClient() + { + BaseAddress = new Uri("https://oapi.dingtalk.com/robot/send?access_token=0ba23267d03084010ee5ffae60e6f4a11e541db8e062f5cde75f3205c10c42c8"), + }; + + /// + /// 发送异常信息 + /// + /// 异常 + /// 异常来源(用于显示) + /// + public static async Task SendAsync(Exception exp, string expSrc) + { +#if DEBUG + Console.WriteLine("*************** Excpetion ***************"); + Console.WriteLine(exp.Message, exp); + Console.WriteLine("*************** Excpetion ***************"); + return true; +#endif + + var reponse = await httpClient.PostAsync(string.Empty, JsonContent.Create(new + { + msgtype = "markdown", + markdown = new + { + title = "AI.Api异常", + text = $"AI.Api异常.描述:{exp.Message}\n详情:{exp}" + }, + })); + return reponse.IsSuccessStatusCode; + + } + } + +} diff --git a/AI.Common/FriendlyInternalException.cs b/AI.Common/FriendlyInternalException.cs new file mode 100644 index 0000000..fef01a3 --- /dev/null +++ b/AI.Common/FriendlyInternalException.cs @@ -0,0 +1,17 @@ +namespace AI.Common +{ + /// + /// Api内部错误(错误细节不暴露给外部) + /// + public class FriendlyInternalException : Exception + { + public int ErrorCode { get; private set; } + public object FriendlyData { get; private set; } + + public FriendlyInternalException(string message, object friendlyData = null, int errorCode = 1000) : base(message) + { + ErrorCode = errorCode; + FriendlyData = friendlyData; + } + } +} diff --git a/AI.Common/Helpers/JwtHelper.cs b/AI.Common/Helpers/JwtHelper.cs new file mode 100644 index 0000000..e1499a3 --- /dev/null +++ b/AI.Common/Helpers/JwtHelper.cs @@ -0,0 +1,54 @@ +using Masuit.Tools; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace AI.Common.Helpers +{ + public class JwtHelper + { + private readonly IConfiguration _configuration; + + public JwtHelper(IConfiguration configuration) + { + _configuration = configuration; + } + + public string CreateToken(string uid, List claims = null) + { + if (claims.IsNullOrEmpty()) + claims = new(); + claims.AddRange(new List + { + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(ClaimTypes.NameIdentifier, uid), + }); + + // 2. 从 appsettings.json 中读取SecretKey + var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"])); + + // 3. 选择加密算法 + var algorithm = SecurityAlgorithms.HmacSha256; + + // 4. 生成Credentials + var signingCredentials = new SigningCredentials(secretKey, algorithm); + + // 5. 根据以上,生成token + var jwtSecurityToken = new JwtSecurityToken( + _configuration["Jwt:Issuer"], //Issuer + _configuration["Jwt:Audience"], //Audience + claims, //Claims, + DateTime.Now, //notBefore + DateTime.Now.AddSeconds(_configuration["Jwt:Expires"].ToDouble()), //expires + signingCredentials //Credentials + ); + + // 6. 将token变为string + var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); + + return token; + } + } +} diff --git a/AI.Common/Helpers/Oops.cs b/AI.Common/Helpers/Oops.cs new file mode 100644 index 0000000..3dff304 --- /dev/null +++ b/AI.Common/Helpers/Oops.cs @@ -0,0 +1,68 @@ +namespace AI.Common.Helpers +{ + /// + /// 异常抛出帮助类 + /// + public static class Oops + { + public static FriendlyInternalException Oh(string message) + { + return new FriendlyInternalException(message); + } + + + /// + /// 删除失败异常 + /// + /// + public static FriendlyInternalException OhDeleteFailed() + { + return new FriendlyInternalException("删除失败!"); + } + + /// + /// 数据不存在异常 + /// + /// + public static FriendlyInternalException OhDataNotExists() + { + return new FriendlyInternalException("数据不存在!"); + } + + /// + /// 更新失败异常 + /// + /// + public static FriendlyInternalException OhUpdateFailed() + { + return new FriendlyInternalException("更新失败!"); + } + + /// + /// 新增失败异常 + /// + /// + public static FriendlyInternalException OhAddFailed() + { + return new FriendlyInternalException("新增失败!"); + } + + /// + /// 业务处理失败自定义消息 + /// + /// + public static FriendlyInternalException OhBusinessFailed(string msg, object data) + { + return new FriendlyInternalException(msg, data, 200); + } + + /// + /// 操作失败 + /// + /// + public static FriendlyInternalException OperationFailed() + { + return new FriendlyInternalException("操作失败!"); + } + } +} diff --git a/AI.Common/Services/AuthService.cs b/AI.Common/Services/AuthService.cs new file mode 100644 index 0000000..15316af --- /dev/null +++ b/AI.Common/Services/AuthService.cs @@ -0,0 +1,50 @@ +using AI.Common.Dtos; +using AI.Common.Entities; +using AI.Common.Helpers; +using AI.Common.Services.Interface; +using Masuit.Tools; +using Microsoft.Extensions.Configuration; +using SqlSugar; +using System.Security.Claims; + +namespace AI.Common.Services +{ + public class AuthService : IAuthService + { + private readonly ISqlSugarClient _sqlSugarClient; + private readonly JwtHelper _jwtHelper; + private readonly IConfiguration _configuration; + + public AuthService(ISqlSugarClient sqlSugarClient, JwtHelper jwtHelper, IConfiguration configuration) + { + this._sqlSugarClient = sqlSugarClient.AsTenant().GetConnection("usercenter"); + this._jwtHelper = jwtHelper; + this._configuration = configuration; + } + + + public async Task LoginAsync(LoginDto loginDto) + { + var user = await _sqlSugarClient.Queryable() + .FirstAsync(x => x.Account == loginDto.Account && x.Password == loginDto.Password && x.DeleteState == 0); + if (user == null) + { + throw Oops.Oh("登录失败,账号或密码错误!"); + } + + var accessToken = _jwtHelper.CreateToken(user.Id.ToString(), new List + { + new Claim("account",user.Account), + new Claim(ClaimTypes.Name,user.RealName), + }); + + return new + { + token = accessToken, + userName = user.RealName, + account = user.Account, + expire = _configuration["Jwt:Expires"].ToInt32(), + }; + } + } +} diff --git a/AI.Common/Services/Interface/IAuthService.cs b/AI.Common/Services/Interface/IAuthService.cs new file mode 100644 index 0000000..81874d2 --- /dev/null +++ b/AI.Common/Services/Interface/IAuthService.cs @@ -0,0 +1,14 @@ +using AI.Common.Dtos; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AI.Common.Services.Interface +{ + public interface IAuthService + { + Task LoginAsync(LoginDto loginDto); + } +} diff --git a/AI.Common/Services/Interface/IPromptService.cs b/AI.Common/Services/Interface/IPromptService.cs new file mode 100644 index 0000000..b60c679 --- /dev/null +++ b/AI.Common/Services/Interface/IPromptService.cs @@ -0,0 +1,18 @@ +using AI.Common.Dtos; +using AI.Common.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AI.Common.Services.Interface +{ + public interface IPromptService + { + Task AddAsync(Prompts prompts); + Task DeleteAsync(long id); + Task> GetListAsync(); + Task UpdateAsync(Prompts prompts); + } +} diff --git a/AI.Common/Services/Interface/IQuestionLogService.cs b/AI.Common/Services/Interface/IQuestionLogService.cs new file mode 100644 index 0000000..b7e251e --- /dev/null +++ b/AI.Common/Services/Interface/IQuestionLogService.cs @@ -0,0 +1,14 @@ +using AI.Common.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AI.Common.Services.Interface +{ + public interface IQuestionLogService + { + Task RecordAsync(QuestionLog questionLog); + } +} diff --git a/AI.Common/Services/PromptService.cs b/AI.Common/Services/PromptService.cs new file mode 100644 index 0000000..997ef8f --- /dev/null +++ b/AI.Common/Services/PromptService.cs @@ -0,0 +1,49 @@ +using AI.Common.Dtos; +using AI.Common.Entities; +using AI.Common.Services.Interface; +using Mapster; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AI.Common.Services +{ + public class PromptService : IPromptService + { + private readonly ISqlSugarClient _sqlSugarClient; + + public PromptService(ISqlSugarClient sqlSugarClient) + { + this._sqlSugarClient = sqlSugarClient; + } + + public async Task> GetListAsync() + { + var entities = await _sqlSugarClient.Queryable().ToListAsync(); + return entities.Adapt>(); + } + + public async Task AddAsync(Prompts prompts) + { + await _sqlSugarClient.Insertable(prompts).ExecuteCommandAsync(); + } + + public async Task UpdateAsync(Prompts prompts) + { + await _sqlSugarClient.Updateable(prompts) + .UpdateColumns(x => new { x.Name, x.Prompt }) + .ExecuteCommandHasChangeAsync(); + } + + public async Task DeleteAsync(long id) + { + await _sqlSugarClient.Updateable() + .SetColumns(x => x.IsDelete == true) + .Where(x => x.Id == id).ExecuteCommandAsync(); + } + } + +} \ No newline at end of file diff --git a/AI.Common/Services/QuestionLogService.cs b/AI.Common/Services/QuestionLogService.cs new file mode 100644 index 0000000..07804be --- /dev/null +++ b/AI.Common/Services/QuestionLogService.cs @@ -0,0 +1,38 @@ +using AI.Common.Dtos; +using AI.Common.Entities; +using AI.Common.Services.Interface; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AI.Common.Services +{ + public class QuestionLogService : IQuestionLogService + { + private readonly ISqlSugarClient _sqlSugarClient; + + public QuestionLogService(ISqlSugarClient sqlSugarClient) + { + this._sqlSugarClient = sqlSugarClient; + } + + public async Task RecordAsync(QuestionLog questionLog) + { + if (questionLog.Id <= 0) + { + // 新增 + return await _sqlSugarClient.Insertable(questionLog).ExecuteReturnBigIdentityAsync(); + } + + // 更新 + await _sqlSugarClient.Updateable(questionLog) + .UpdateColumns(x => new { x.QuestionContent, x.GptAnswer }).ExecuteCommandAsync(); + + return questionLog.Id; + } + } +} diff --git a/AI.Common/UniformResult.cs b/AI.Common/UniformResult.cs new file mode 100644 index 0000000..57fc5e3 --- /dev/null +++ b/AI.Common/UniformResult.cs @@ -0,0 +1,39 @@ +namespace AI.Common +{ + /// + /// 统一返回结果模型 + /// + /// + public class UniformResult + { + public UniformResult(T data, int code = 200, string msg = "success") + { + this.data = data; + this.code = code; + message = msg; + } + + /// + /// 消息 + /// + public string message { get; set; } = "success"; + + /// + /// 返回代码 + /// + public int code { get; set; } + + /// + /// 数据 + /// + public T data { get; set; } + + public static UniformResult Create(T data, int code = 200, string msg = "success") + { + return new UniformResult(data, code, msg); + } + + } + + +}