diff --git a/OpenAuth.App/DynamicApiApp/DynamicApiApp.cs b/OpenAuth.App/DynamicApiApp/DynamicApiApp.cs index 703332c8..30d60c79 100644 --- a/OpenAuth.App/DynamicApiApp/DynamicApiApp.cs +++ b/OpenAuth.App/DynamicApiApp/DynamicApiApp.cs @@ -1,18 +1,18 @@ using System; using System.Linq; -using System.Reflection; using System.Threading.Tasks; +using System.Collections.Generic; using Infrastructure; using OpenAuth.App.Interface; using OpenAuth.App.Response; -using OpenAuth.Repository.Core; using SqlSugar; +using OpenAuth.App.Request; namespace OpenAuth.App { /// /// 动态API应用层 - /// 用于处理任意实体的CRUD操作 + /// 用于直接操作数据库表,不依赖实体 /// public class DynamicApiApp { @@ -26,45 +26,39 @@ namespace OpenAuth.App } /// - /// 获取实体列表 + /// 获取表数据列表 /// - /// 实体名称,例如:ExternalDataSource - /// 页码 - /// 每页记录数 - /// 搜索关键字 + /// 查询参数 /// - public async Task GetList(string entityName, int page = 1, int limit = 10, string key = "") + public async Task GetList(QueryDynamicListReq req) { var result = new TableData(); try { - // 获取实体类型 - var entityType = GetEntityType(entityName); - if (entityType == null) + // 验证表名是否存在 + if (!TableExists(req.TableName)) { result.code = 500; - result.msg = $"未找到实体:{entityName}"; + result.msg = $"表不存在:{req.TableName}"; return result; } // 创建动态查询 - dynamic queryable = _client.GetType().GetMethod("Queryable", new Type[] { }) - .MakeGenericMethod(entityType) - .Invoke(_client, null); + var queryable = _client.Queryable().AS(req.TableName); + + // 获取表结构 + var columns = GetTableColumns(req.TableName); // 如果有搜索关键字,尝试在常见字段中搜索 - if (!string.IsNullOrEmpty(key)) + if (!string.IsNullOrEmpty(req.key)) { - // 获取实体的所有属性 - var properties = entityType.GetProperties(); + //todo: 尝试在Name、Title等常见字段中搜索 + var nameColumn = columns.FirstOrDefault(c => + c.DbColumnName.Equals("Name", StringComparison.OrdinalIgnoreCase)); - // 尝试在Name、Title等常见字段中搜索 - var nameProperty = properties.FirstOrDefault(p => - p.Name.Equals("Name", StringComparison.OrdinalIgnoreCase)); - - if (nameProperty != null) + if (nameColumn != null) { - queryable = queryable.Where($"{nameProperty.Name} like @key", new { key = $"%{key}%" }); + queryable = queryable.Where($"{nameColumn.DbColumnName} like @key", new { key = $"%{req.key}%" }); } } @@ -72,9 +66,10 @@ namespace OpenAuth.App var total = await queryable.CountAsync(); // 分页查询 - var list = await queryable.OrderBy("CreateTime DESC") - .Skip((page - 1) * limit) - .Take(limit) + var list = await queryable + .OrderBy($"{ columns[0].DbColumnName } DESC") + .Skip((req.page - 1) * req.limit) + .Take(req.limit) .ToListAsync(); result.data = list; @@ -90,31 +85,36 @@ namespace OpenAuth.App } /// - /// 获取实体详情 + /// 获取表数据详情 /// - /// 实体名称,例如:ExternalDataSource - /// 实体ID + /// 表名称 + /// 记录ID /// - public Response Get(string entityName, string id) + public async Task> Get(QueryDynamicEntityReq req) { var result = new Response(); try { - // 获取实体类型 - var entityType = GetEntityType(entityName); - if (entityType == null) + // 验证表名是否存在 + if (!TableExists(req.TableName)) { result.Code = 500; - result.Message = $"未找到实体:{entityName}"; + result.Message = $"表不存在:{req.TableName}"; return result; } - // 获取实体 - dynamic queryable = _client.GetType().GetMethod("Queryable", new Type[] { }) - .MakeGenericMethod(entityType) - .Invoke(_client, null); + // 获取数据 + var entity = await _client.Queryable() + .AS(req.TableName) + .Where("Id = @id", new { id = req.Id }) + .FirstAsync(); - var entity = queryable.Where("Id = @id", new { id }).First(); + if (entity == null) + { + result.Code = 500; + result.Message = "未找到记录"; + return result; + } result.Result = entity; } @@ -128,55 +128,63 @@ namespace OpenAuth.App } /// - /// 添加实体 + /// 添加表数据 /// - /// 实体名称,例如:ExternalDataSource - /// 实体对象 + /// 表名称 + /// 数据对象 + /// 如果数据里面没有Id,自动会添加ID + /// /// - public Infrastructure.Response Add(string entityName, object obj) + public async Task> Add(AddOrUpdateDynamicEntityReq req) { - var result = new Infrastructure.Response(); + var result = new Response(); try { - // 获取实体类型 - var entityType = GetEntityType(entityName); - if (entityType == null) + // 验证表名是否存在 + if (!TableExists(req.TableName)) { result.Code = 500; - result.Message = $"未找到实体:{entityName}"; + result.Message = $"表不存在:{req.TableName}"; return result; } - // 将传入的对象转换为实体类型 - var entity = obj.MapTo(entityType); + // 将对象转换为字典 + var dict = req.Obj.ToDictionary(); - // 设置创建信息 - var idProperty = entityType.GetProperty("Id"); - if (idProperty != null && idProperty.PropertyType == typeof(string)) + // 设置ID + if (!dict.ContainsKey("Id")) { - idProperty.SetValue(entity, Guid.NewGuid().ToString()); + dict["Id"] = Guid.NewGuid().ToString(); } - - var createTimeProperty = entityType.GetProperty("CreateTime"); - if (createTimeProperty != null && createTimeProperty.PropertyType == typeof(DateTime)) + else if (string.IsNullOrEmpty(dict["Id"]?.ToString())) { - createTimeProperty.SetValue(entity, DateTime.Now); + dict["Id"] = Guid.NewGuid().ToString(); } - - // 如果有用户信息,设置创建用户 - var currentUser = _auth.GetCurrentUser(); - if (currentUser != null) + else { - // 尝试设置创建用户信息 - TrySetProperty(entity, "CreateUserId", currentUser.User.Id); - TrySetProperty(entity, "CreateUserName", currentUser.User.Name); - } + //如果Id不为空,需要判断Id是否存在 + var entity = await _client.Queryable() + .AS(req.TableName) + .Where("Id = @id", new { id = dict["Id"] }) + .FirstAsync(); + if (entity != null) + { + result.Code = 500; + result.Message = "Id已存在"; + return result; + } + } - // 添加实体 - var insertMethod = _client.GetType().GetMethod("Insertable") - .MakeGenericMethod(entityType); - dynamic insertable = insertMethod.Invoke(_client, new[] { entity }); - insertable.ExecuteCommand(); + //如果有CreateTime字段,自动设置 + if (dict.ContainsKey("CreateTime")) + { + dict["CreateTime"] = DateTime.Now; + } + + // 添加数据 + await _client.Insertable(dict) + .AS(req.TableName) + .ExecuteCommandAsync(); result.Message = "添加成功"; } @@ -190,46 +198,55 @@ namespace OpenAuth.App } /// - /// 更新实体 + /// 更新表数据 /// - /// 实体名称,例如:ExternalDataSource - /// 实体对象 + /// 表名称 + /// 数据对象 /// - public Infrastructure.Response Update(string entityName, object obj) + public async Task Update(AddOrUpdateDynamicEntityReq req) { var result = new Infrastructure.Response(); try { - // 获取实体类型 - var entityType = GetEntityType(entityName); - if (entityType == null) + // 验证表名是否存在 + if (!TableExists(req.TableName)) { result.Code = 500; - result.Message = $"未找到实体:{entityName}"; + result.Message = $"表不存在:{req.TableName}"; return result; } - // 将传入的对象转换为实体类型 - var entity = obj.MapTo(entityType); + // 将对象转换为字典 + var dict = req.Obj.ToDictionary(); - // 设置更新信息 - // 尝试设置更新时间 - TrySetProperty(entity, "UpdateTime", DateTime.Now); + // 检查ID是否存在 + if (!dict.ContainsKey("Id")) + { + result.Code = 500; + result.Message = "更新数据必须包含Id字段"; + return result; + } + + // 设置更新时间 + if (!dict.ContainsKey("UpdateTime")) + { + dict["UpdateTime"] = DateTime.Now; + } // 如果有用户信息,设置更新用户 var currentUser = _auth.GetCurrentUser(); - if (currentUser != null) + if (dict.ContainsKey("UpdateUserId") && currentUser != null) { - // 尝试设置更新用户信息 - TrySetProperty(entity, "UpdateUserId", currentUser.User.Id); - TrySetProperty(entity, "UpdateUserName", currentUser.User.Name); + // 设置更新用户信息 + dict["UpdateUserId"] = currentUser.User.Id; + dict["UpdateUserName"] = currentUser.User.Name; } - // 更新实体 - var updateMethod = _client.GetType().GetMethod("Updateable") - .MakeGenericMethod(entityType); - dynamic updateable = updateMethod.Invoke(_client, new[] { entity }); - updateable.ExecuteCommand(); + // 更新数据 + await _client.Updateable(dict) + .AS(req.TableName) + .Where("Id = @id", new { id = dict["Id"] }) + .ExecuteCommandAsync(); result.Message = "更新成功"; } @@ -243,97 +260,29 @@ namespace OpenAuth.App } /// - /// 删除实体 + /// 批量删除表数据 /// - /// 实体名称,例如:ExternalDataSource - /// 实体ID + /// 表名称 + /// 记录ID数组 /// - public Infrastructure.Response Delete(string entityName, string id) + public async Task Delete(DelDynamicReq req) { var result = new Infrastructure.Response(); try { - // 获取实体类型 - var entityType = GetEntityType(entityName); - if (entityType == null) + // 验证表名是否存在 + if (!TableExists(req.TableName)) { result.Code = 500; - result.Message = $"未找到实体:{entityName}"; + result.Message = $"表不存在:{req.TableName}"; return result; } - // 创建动态查询 - dynamic queryable = _client.GetType().GetMethod("Queryable", new Type[] { }) - .MakeGenericMethod(entityType) - .Invoke(_client, null); - - // 获取要删除的实体 - var entity = queryable.Where("Id = @id", new { id }).First(); - - if (entity == null) - { - result.Code = 500; - result.Message = "未找到要删除的实体"; - return result; - } - - // 删除实体 - var deleteMethod = _client.GetType().GetMethod("Deleteable") - .MakeGenericMethod(entityType); - dynamic deleteable = deleteMethod.Invoke(_client, new[] { entity }); - deleteable.ExecuteCommand(); - - result.Message = "删除成功"; - } - catch (Exception ex) - { - result.Code = 500; - result.Message = ex.InnerException?.Message ?? ex.Message; - } - - return result; - } - - /// - /// 批量删除实体 - /// - /// 实体名称,例如:ExternalDataSource - /// 实体ID数组 - /// - public Infrastructure.Response BatchDelete(string entityName, string[] ids) - { - var result = new Infrastructure.Response(); - try - { - // 获取实体类型 - var entityType = GetEntityType(entityName); - if (entityType == null) - { - result.Code = 500; - result.Message = $"未找到实体:{entityName}"; - return result; - } - - // 创建动态查询 - dynamic queryable = _client.GetType().GetMethod("Queryable", new Type[] { }) - .MakeGenericMethod(entityType) - .Invoke(_client, null); - - // 获取要删除的实体列表 - var entities = queryable.Where("Id in (@ids)", new { ids }).ToList(); - - if (entities == null || entities.Count == 0) - { - result.Code = 500; - result.Message = "未找到要删除的实体"; - return result; - } - - // 批量删除实体 - var deleteMethod = _client.GetType().GetMethod("Deleteable") - .MakeGenericMethod(entityType); - dynamic deleteable = deleteMethod.Invoke(_client, new[] { entities }); - deleteable.ExecuteCommand(); + // 批量删除数据 + await _client.Deleteable() + .AS(req.TableName) + .Where("Id in (@ids)", new { ids = req.Ids }) + .ExecuteCommandAsync(); result.Message = "批量删除成功"; } @@ -347,37 +296,82 @@ namespace OpenAuth.App } /// - /// 获取实体类型 + /// 检查表是否存在 /// - /// 实体名称 + /// 表名 /// - private Type GetEntityType(string entityName) + private bool TableExists(string tableName) { - // 获取所有实体类型 - var entityTypes = typeof(StringEntity).Assembly.GetTypes() - .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(StringEntity))) - .ToList(); - - // 查找匹配的实体类型 - return entityTypes.FirstOrDefault(t => - t.Name.Equals(entityName, StringComparison.OrdinalIgnoreCase)); + // 获取数据库类型 + var dbType = _client.CurrentConnectionConfig.DbType; + string sql = string.Empty; + + switch (dbType) + { + case DbType.SqlServer: + sql = $"SELECT COUNT(1) FROM sys.tables WHERE name = '{tableName}'"; + break; + case DbType.MySql: + sql = $"SELECT COUNT(1) FROM information_schema.tables WHERE table_name = '{tableName}' AND table_schema = DATABASE()"; + break; + case DbType.PostgreSQL: + sql = $"SELECT COUNT(1) FROM pg_tables WHERE tablename = '{tableName.ToLower()}'"; + break; + case DbType.Oracle: + sql = $"SELECT COUNT(1) FROM user_tables WHERE table_name = '{tableName.ToUpper()}'"; + break; + default: + throw new NotSupportedException($"不支持的数据库类型:{dbType}"); + } + + var count = _client.Ado.GetInt(sql); + return count > 0; } /// - /// 尝试设置属性值 + /// 获取表字段信息 + /// + /// 表名 + /// + private List GetTableColumns(string tableName) + { + return _client.DbMaintenance.GetColumnInfosByTableName(tableName); + } + } + + /// + /// 对象扩展方法 + /// + public static class ObjectExtensions + { + /// + /// 将对象转换为字典 /// /// 对象 - /// 属性名 - /// 属性值 - private void TrySetProperty(object obj, string propertyName, object value) + /// + public static Dictionary ToDictionary(this object obj) { - var property = obj.GetType().GetProperty(propertyName, - BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + var dict = new Dictionary(); - if (property != null && property.CanWrite) + if (obj == null) return dict; + + // 如果已经是字典类型,直接返回 + if (obj is Dictionary dictionary) { - property.SetValue(obj, value); + return dictionary; } + + // 获取所有属性 + foreach (var prop in obj.GetType().GetProperties()) + { + var value = prop.GetValue(obj); + if (value != null) + { + dict[prop.Name] = value; + } + } + + return dict; } } } \ No newline at end of file diff --git a/OpenAuth.App/DynamicApiApp/Request/AddOrUpdateDynamicEntityReq.cs b/OpenAuth.App/DynamicApiApp/Request/AddOrUpdateDynamicEntityReq.cs new file mode 100644 index 00000000..231e4c2d --- /dev/null +++ b/OpenAuth.App/DynamicApiApp/Request/AddOrUpdateDynamicEntityReq.cs @@ -0,0 +1,9 @@ +namespace OpenAuth.App.Request +{ + public class AddOrUpdateDynamicEntityReq + { + public string TableName { get; set; } + + public object Obj { get; set; } + } +} diff --git a/OpenAuth.App/DynamicApiApp/Request/DelDynamicReq.cs b/OpenAuth.App/DynamicApiApp/Request/DelDynamicReq.cs new file mode 100644 index 00000000..e54b3173 --- /dev/null +++ b/OpenAuth.App/DynamicApiApp/Request/DelDynamicReq.cs @@ -0,0 +1,9 @@ +namespace OpenAuth.App.Request +{ + public class DelDynamicReq + { + public string TableName { get; set; } + + public string[] Ids { get; set; } + } +} diff --git a/OpenAuth.App/DynamicApiApp/Request/QueryDynamicEntityReq.cs b/OpenAuth.App/DynamicApiApp/Request/QueryDynamicEntityReq.cs new file mode 100644 index 00000000..c6dae164 --- /dev/null +++ b/OpenAuth.App/DynamicApiApp/Request/QueryDynamicEntityReq.cs @@ -0,0 +1,9 @@ +namespace OpenAuth.App.Request +{ + public class QueryDynamicEntityReq + { + public string TableName { get; set; } + + public string Id { get; set; } + } +} diff --git a/OpenAuth.App/DynamicApiApp/Request/QueryDynamicListReq.cs b/OpenAuth.App/DynamicApiApp/Request/QueryDynamicListReq.cs new file mode 100644 index 00000000..dbdb38fc --- /dev/null +++ b/OpenAuth.App/DynamicApiApp/Request/QueryDynamicListReq.cs @@ -0,0 +1,10 @@ +namespace OpenAuth.App.Request +{ + public class QueryDynamicListReq: PageReq + { + /// + /// 表名 + /// + public string TableName { get; set; } + } +} diff --git a/OpenAuth.App/Test/TestDynamicApiApp.cs b/OpenAuth.App/Test/TestDynamicApiApp.cs new file mode 100644 index 00000000..46ca46ef --- /dev/null +++ b/OpenAuth.App/Test/TestDynamicApiApp.cs @@ -0,0 +1,77 @@ +using System; +using System.Threading.Tasks; +using Infrastructure; +using Infrastructure.Cache; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using NUnit.Framework; +using OpenAuth.App.Request; +using OpenAuth.App.SSO; + +namespace OpenAuth.App.Test +{ + class TestDynamicApiApp :TestBase + { + public override ServiceCollection GetService() + { + var services = new ServiceCollection(); + + var cachemock = new Mock(); + cachemock.Setup(x => x.Get("tokentest")).Returns(new UserAuthSession { Account = "test" }); + services.AddScoped(x => cachemock.Object); + + var httpContextAccessorMock = new Mock(); + httpContextAccessorMock.Setup(x => x.HttpContext.Request.Query[Define.TOKEN_NAME]).Returns("tokentest"); + + services.AddScoped(x => httpContextAccessorMock.Object); + + return services; + } + + [Test] + public async Task TestGet() + { + var app = _autofacServiceProvider.GetService(); + + var obj = await app.Get(new QueryDynamicEntityReq { TableName = "noentity", Id = "1" }); + Console.WriteLine(JsonHelper.Instance.Serialize(obj)); + } + + [Test] + public async Task TestGetList() + { + var app = _autofacServiceProvider.GetService(); + + var obj = await app.GetList(new QueryDynamicListReq { TableName = "noentity", page = 1, limit = 10, key = "" }); + Console.WriteLine(JsonHelper.Instance.Serialize(obj)); + } + + [Test] + public async Task TestAdd() + { + var app = _autofacServiceProvider.GetService(); + + var obj = await app.Add(new AddOrUpdateDynamicEntityReq { TableName = "noentity", Obj = new { Id = "10", P1 = DateTime.Now.ToString() } }); + Console.WriteLine(JsonHelper.Instance.Serialize(obj)); + } + + [Test] + public async Task TestUpdate() + { + var app = _autofacServiceProvider.GetService(); + + var obj = await app.Update(new AddOrUpdateDynamicEntityReq { TableName = "noentity", Obj = new { Id = "1", P1 = DateTime.Now.ToString() } }); + Console.WriteLine(JsonHelper.Instance.Serialize(obj)); + } + + [Test] + public async Task TestDelete() + { + var app = _autofacServiceProvider.GetService(); + + var obj = await app.Delete(new DelDynamicReq() { TableName = "noentity", Ids = new string[] { "10" } }); + Console.WriteLine(JsonHelper.Instance.Serialize(obj)); + } + } +} diff --git a/OpenAuth.WebApi/Controllers/DynamicApiController.cs b/OpenAuth.WebApi/Controllers/DynamicApiController.cs index e24527a1..88d9d453 100644 --- a/OpenAuth.WebApi/Controllers/DynamicApiController.cs +++ b/OpenAuth.WebApi/Controllers/DynamicApiController.cs @@ -4,16 +4,18 @@ using Infrastructure; using Microsoft.AspNetCore.Mvc; using OpenAuth.App; using OpenAuth.App.Response; +using OpenAuth.App.Request; +using Microsoft.AspNetCore.Authorization; namespace OpenAuth.WebApi.Controllers { /// - /// 动态实体API控制器 - /// 用于处理任意实体的CRUD操作 + /// 动态API控制器 + /// 用于处理任意表的CRUD操作 /// [Route("api/dynamic/[action]")] [ApiController] - [ApiExplorerSettings(GroupName = "动态API_DynamicEntity")] + [ApiExplorerSettings(GroupName = "动态API_DynamicApi")] public class DynamicApiController : ControllerBase { private readonly DynamicApiApp _app; @@ -24,21 +26,19 @@ namespace OpenAuth.WebApi.Controllers } /// - /// 获取实体列表 + /// 获取表数据列表 /// - /// 实体名称,例如:ExternalDataSource - /// 页码 - /// 每页记录数 - /// 搜索关键字 + /// 查询参数 /// - [HttpGet] - public async Task GetList(string entityName, int page = 1, int limit = 10, string key = "") + [HttpPost] + [AllowAnonymous] + public async Task GetList([FromBody] QueryDynamicListReq req) { TableData result = new TableData(); try { // 获取实体类型 - result = await _app.GetList(entityName, page, limit, key); + result = await _app.GetList(req); } catch (Exception ex) { @@ -50,19 +50,19 @@ namespace OpenAuth.WebApi.Controllers } /// - /// 获取实体详情 + /// 获取表数据详情 /// - /// 实体名称,例如:ExternalDataSource - /// 实体ID + /// 查询参数 /// - [HttpGet] - public Response Get(string entityName, string id) + [HttpPost] + [AllowAnonymous] + public async Task> Get([FromBody] QueryDynamicEntityReq req) { var result = new Response(); try { // 获取实体类型 - result = _app.Get(entityName, id); + result = await _app.Get(req); } catch (Exception ex) { @@ -74,19 +74,19 @@ namespace OpenAuth.WebApi.Controllers } /// - /// 添加实体 + /// 添加表数据 /// - /// 实体名称,例如:ExternalDataSource - /// 实体对象 + /// 添加参数 /// [HttpPost] - public Response Add(string entityName, [FromBody] object obj) + [AllowAnonymous] + public async Task Add([FromBody] AddOrUpdateDynamicEntityReq req) { var result = new Response(); try { // 获取实体类型 - result = _app.Add(entityName, obj); + result = await _app.Add(req); } catch (Exception ex) { @@ -104,37 +104,14 @@ namespace OpenAuth.WebApi.Controllers /// 实体对象 /// [HttpPost] - public Response Update(string entityName, [FromBody] object obj) + [AllowAnonymous] + public async Task Update([FromBody] AddOrUpdateDynamicEntityReq req) { var result = new Response(); try { // 获取实体类型 - result = _app.Update(entityName, obj); - } - catch (Exception ex) - { - result.Code = 500; - result.Message = ex.InnerException?.Message ?? ex.Message; - } - - return result; - } - - /// - /// 删除实体 - /// - /// 实体名称,例如:ExternalDataSource - /// 实体ID - /// - [HttpPost] - public Response Delete(string entityName, string id) - { - var result = new Response(); - try - { - // 获取实体类型 - result = _app.Delete(entityName, id); + result = await _app.Update(req); } catch (Exception ex) { @@ -152,13 +129,13 @@ namespace OpenAuth.WebApi.Controllers /// 实体ID数组 /// [HttpPost] - public Response BatchDelete(string entityName, [FromBody] string[] ids) + [AllowAnonymous] + public async Task Delete([FromBody] DelDynamicReq req) { var result = new Response(); try { - // 获取实体类型 - result = _app.BatchDelete(entityName, ids); + result = await _app.Delete(req); } catch (Exception ex) {