17 Commits
7.2 ... v7.3

Author SHA1 Message Date
wintel
44d5b286aa 切换成SqlSugar 2025-03-16 23:39:30 +08:00
wintel
5664792162 新增测试数据源 2025-03-15 20:36:05 +08:00
wintel
3be8644769 routine update 2025-03-15 00:40:40 +08:00
wintel
32e0ae1a6b 完成外部数据源CRUD 2025-03-15 00:38:08 +08:00
yubaolee
ee396146d3 增加验证数据源 2025-03-14 17:30:22 +08:00
yubaolee
98808a65ea 优化vue3代码生成 2025-03-13 14:03:20 +08:00
wintel
4dd882cf90 增加外部数据源管理 2025-03-13 00:31:22 +08:00
wintel
be1c79799d 更新文档 2025-03-12 21:18:49 +08:00
wintel
e84e5d47ee 更新文档 2025-03-12 21:13:44 +08:00
wintel
0570a58743 update doc 2025-03-12 21:07:48 +08:00
wintel
889445bbcf routine update 2025-03-11 21:32:42 +08:00
yubaolee
2d108586e7 fix #I3RHPD 完成在api中对接口权限进行鉴权 2025-03-11 17:34:54 +08:00
yubaolee
18c6fbfaad fix #I3RHPD 在api中对用户接口权限进行鉴权 2025-03-11 11:21:56 +08:00
wintel
50769b94c6 增加同步API资源到资源列表接口 2025-03-11 01:58:35 +08:00
wintel
a5bbfad2ff 增加获取所有API的接口 2025-03-11 00:40:35 +08:00
wintel
89c3133889 fix #I5FK9U 2025-03-10 21:39:21 +08:00
yubaolee
7ad9b54fd2 完善文档 2025-03-10 14:47:56 +08:00
26 changed files with 955 additions and 329 deletions

View File

@@ -37,6 +37,8 @@
//流程实例知会用户
public const string INSTANCE_NOTICE_USER = "INSTANCE_NOTICE_USER";
//流程实例知会角色
public const string INSTANCE_NOTICE_ROLE = "INSTANCE_NOTICE_ROLE";
public const string INSTANCE_NOTICE_ROLE = "INSTANCE_NOTICE_ROLE";
public const string API = "API_RESOURCE";
}
}

View File

@@ -82,8 +82,8 @@ namespace OpenAuth.App
TypeId = obj.TypeId,
UpdateTime = DateTime.Now,
UpdateUserId = user.Id,
UpdateUserName = user.Name
//todo:要修改的字段赋值
UpdateUserName = user.Name,
SortNo = obj.SortNo
});
}

View File

@@ -3,35 +3,31 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using Autofac.Extensions.DependencyInjection;
using Humanizer;
using Infrastructure;
using Infrastructure.Utilities;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using OpenAuth.Repository;
using OpenAuth.Repository.Core;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.QueryObj;
using SqlSugar;
namespace OpenAuth.App
{
public class DbExtension
{
private List<DbContext> _contexts = new List<DbContext>();
protected ISqlSugarClient SugarClient;
private IOptions<AppSetting> _appConfiguration;
private IHttpContextAccessor _httpContextAccessor;
public DbExtension(IOptions<AppSetting> appConfiguration, OpenAuthDBContext openAuthDbContext, IHttpContextAccessor httpContextAccessor)
public DbExtension(IOptions<AppSetting> appConfiguration, ISqlSugarClient sugarClient, IHttpContextAccessor httpContextAccessor)
{
_appConfiguration = appConfiguration;
_httpContextAccessor = httpContextAccessor;
_contexts.Add(openAuthDbContext); //如果有多个DBContext可以按OpenAuthDBContext同样的方式添加到_contexts中
SugarClient = sugarClient;
}
/// <summary>
@@ -43,19 +39,18 @@ namespace OpenAuth.App
public List<BuilderTableColumn> GetTableColumnsFromDb(string moduleName)
{
var result = new List<BuilderTableColumn>();
const string domain = "openauth.repository.domain.";
IEntityType entity = null;
_contexts.ForEach(u =>
{
entity = u.Model.GetEntityTypes()
.FirstOrDefault(u => u.Name.ToLower() == domain + moduleName.ToLower());
});
if (entity == null)
//获取所有继承了StringEntity的类型
//单元测试时,这个一直报错????奇怪
var entiTypes = typeof(StringEntity).Assembly.GetTypes().Where(u => typeof(StringEntity).IsAssignableFrom(u) && !u.IsAbstract).ToList();
var entityType = entiTypes.FirstOrDefault(u => u.Name.ToLower() == moduleName.ToLower());
if (entityType == null)
{
throw new Exception($"未能找到{moduleName}对应的实体类");
}
foreach (var property in entity.ClrType.GetProperties())
foreach (var property in entityType.GetProperties())
{
object[] objs = property.GetCustomAttributes(typeof(DescriptionAttribute), true);
object[] browsableObjs = property.GetCustomAttributes(typeof(BrowsableAttribute), true);
@@ -82,29 +77,6 @@ namespace OpenAuth.App
return result;
}
/// <summary>
/// 获取数据库DbContext中所有的实体名称。
/// <para>注意!并不能获取数据库中的所有表</para>
/// </summary>
public List<string> GetDbEntityNames()
{
var names = new List<string>();
var models = _contexts.Select(u => u.Model);
foreach (var model in models)
{
// Get all the entity types information contained in the DbContext class, ...
var entityTypes = model.GetEntityTypes();
foreach (var entityType in entityTypes)
{
var tableNameAnnotation = entityType.GetAnnotation("Relational:TableName");
names.Add(tableNameAnnotation.Value.ToString());
}
}
return names;
}
/// <summary>
/// 获取数据库表结构信息
/// </summary>
@@ -184,15 +156,12 @@ namespace OpenAuth.App
and utc.table_name = '{tableName}'
order by column_id; ";
foreach (var context in _contexts)
var columns = SugarClient.SqlQueryable<SysTableColumn>(sql);
var columnList = columns?.ToList();
if (columnList != null && columnList.Any())
{
var columns = context.Set<SysTableColumn>().FromSqlRaw(sql);
var columnList = columns?.ToList();
if (columnList != null && columnList.Any())
{
columnList.ForEach(u => u.ColumnName = u.ColumnName.Transform(To.LowerCase, To.TitleCase));
return columnList;
}
columnList.ForEach(u => u.ColumnName = u.ColumnName.Transform(To.LowerCase, To.TitleCase));
return columnList;
}
return new List<SysTableColumn>();
@@ -250,16 +219,15 @@ namespace OpenAuth.App
WHERE
table_name = '{tableName}'";
foreach (var context in _contexts)
{
var columns = context.Set<SysTableColumn>().FromSqlRaw(sql);
var columns = SugarClient.SqlQueryable<SysTableColumn>(sql);
var columnList = columns?.ToList();
if (columnList != null && columnList.Any())
{
columnList.ForEach(u => u.ColumnName = u.ColumnName.Transform(To.LowerCase, To.TitleCase));
return columnList;
}
}
return new List<SysTableColumn>();
@@ -327,15 +295,12 @@ namespace OpenAuth.App
and schema.nspname = 'public' -- replace 'your_schema' with your schema name
and class.relname = '{tableName}'";
foreach (var context in _contexts)
var columns = SugarClient.SqlQueryable<SysTableColumn>(sql);
var columnList = columns?.ToList();
if (columnList != null && columnList.Any())
{
var columns = context.Set<SysTableColumn>().FromSqlRaw(sql);
var columnList = columns?.ToList();
if (columnList != null && columnList.Any())
{
columnList.ForEach(u => u.ColumnName = u.ColumnName.Transform(To.LowerCase, To.TitleCase));
return columnList;
}
columnList.ForEach(u => u.ColumnName = u.ColumnName.Transform(To.LowerCase, To.TitleCase));
return columnList;
}
return new List<SysTableColumn>();
@@ -435,14 +400,11 @@ namespace OpenAuth.App
WHERE obj.name = '{tableName}') AS t
ORDER BY t.colorder";
foreach (var context in _contexts)
var columns = SugarClient.SqlQueryable<SysTableColumn>(sql);
var columnList = columns?.ToList();
if (columnList != null && columnList.Any())
{
var columns = context.Set<SysTableColumn>().FromSqlRaw(sql);
var columnList = columns?.ToList();
if (columnList != null && columnList.Any())
{
return columnList;
}
return columnList;
}
return new List<SysTableColumn>();
}

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Infrastructure;
using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository.Domain;
using SqlSugar;
namespace OpenAuth.App
{
public class ExtDataSourceApp : SqlSugarBaseApp<ExternalDataSource>
{
/// <summary>
/// 加载列表
/// </summary>
public async Task<TableData> Load(QueryExternalDataSourceListReq request)
{
var loginContext = _auth.GetCurrentUser();
if (loginContext == null)
{
throw new CommonException("登录已过期", Define.INVALID_TOKEN);
}
var result = new TableData();
var objs = GetDataPrivilege("u");
if (!string.IsNullOrEmpty(request.key))
{
objs = objs.Where(u => u.Name.Contains(request.key));
}
result.data = objs.OrderBy(u => u.Name)
.Skip((request.page - 1) * request.limit)
.Take(request.limit).ToList();
result.count = await objs.CountAsync();
return result;
}
/// <summary>
/// 获取数据库类型
/// </summary>
/// <returns></returns>
public TableData GetDbTypes()
{
var result = new TableData();
// 创建包含键值对的列表
var dbTypeList = Enum.GetValues(typeof(DbType))
.Cast<DbType>()
.Select(item => new { label = item.ToString(), value = (int)item })
.ToList();
result.data = dbTypeList;
return result;
}
/// <summary>
/// 测试数据源连接
/// </summary>
/// <param name="id">数据源ID</param>
/// <returns>是否连接成功</returns>
public bool TestConnection(TestConnectionReq req)
{
var conn = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = req.Connectionstring,
DbType = (DbType)Enum.Parse(typeof(DbType), req.Dbtype.ToString()),
IsAutoCloseConnection = true,
});
conn.Open();
return true;
}
public void Add(AddOrUpdateExternalDataSourceReq req)
{
var obj = req.MapTo<ExternalDataSource>();
obj.Id = Guid.NewGuid().ToString();
obj.Createtime = DateTime.Now;
var user = _auth.GetCurrentUser().User;
obj.Createuserid = user.Id;
obj.Createusername = user.Name;
Repository.Insert(obj);
}
public void Update(AddOrUpdateExternalDataSourceReq obj)
{
var user = _auth.GetCurrentUser().User;
Repository.Update(u => new ExternalDataSource
{
Name = obj.Name,
Dbtype = obj.Dbtype,
Databasename = obj.Databasename,
Server = obj.Server,
Port = obj.Port,
Username = obj.Username,
Password = obj.Password,
Description = obj.Description,
Enabled = obj.Enabled,
Testsuccess = obj.Testsuccess,
Connectionstring = obj.Connectionstring,
Updatetime = DateTime.Now,
Updateuserid = user.Id,
Updateusername = user.Name
},u => u.Id == obj.Id);
}
public ExtDataSourceApp(ISqlSugarClient client, IAuth auth) : base(client, auth)
{
}
}
public record TestConnectionReq(string Connectionstring, int Dbtype);
}

View File

@@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
// This code was generated by a CodeSmith Template.
//
// DO NOT MODIFY contents of this file. Changes to this
// file will be lost if the code is regenerated.
// Author:Yubao Li
//------------------------------------------------------------------------------
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using OpenAuth.Repository.Core;
namespace OpenAuth.App.Request
{
/// <summary>
///
/// </summary>
public class AddOrUpdateExternalDataSourceReq
{
/// <summary>
///是否测试成功
/// </summary>
public bool? Testsuccess { get; set; }
/// <summary>
///主键
/// </summary>
public string Id { get; set; }
/// <summary>
///是否启用
/// </summary>
public bool Enabled { get; set; }
/// <summary>
///连接字符串
/// </summary>
public string Connectionstring { get; set; }
/// <summary>
///描述
/// </summary>
public string Description { get; set; }
/// <summary>
///密码
/// </summary>
public string Password { get; set; }
/// <summary>
///用户名
/// </summary>
public string Username { get; set; }
/// <summary>
///端口
/// </summary>
public int? Port { get; set; }
/// <summary>
///服务器地址
/// </summary>
public string Server { get; set; }
/// <summary>
///数据库名称
/// </summary>
public string Databasename { get; set; }
/// <summary>
///数据库类型
/// </summary>
public int Dbtype { get; set; }
/// <summary>
///数据源名称
/// </summary>
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace OpenAuth.App.Request
{
public class QueryExternalDataSourceListReq : PageReq
{
}
}

View File

@@ -27,6 +27,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.23" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />

View File

@@ -0,0 +1,17 @@
namespace OpenAuth.App.Request
{
/// <summary>
/// 角色分配资源
/// </summary>
public class AssignRoleResources
{
/// <summary>
/// 角色id
/// </summary>
public string RoleId { get; set; }
/// <summary>
/// 资源id列表
/// </summary>
public string[] ResourceIds { get; set; }
}
}

View File

@@ -234,5 +234,28 @@ namespace OpenAuth.App
UnitWork.Save();
});
}
/// <summary>
/// 为角色分配资源,需要统一提交,会删除以前该角色的所有资源
/// </summary>
/// <param name="request"></param>
public void AssignRoleResources(AssignRoleResources request)
{
UnitWork.ExecuteWithTransaction(() =>
{
//删除以前的所有资源
UnitWork.Delete<Relevance>(u => u.FirstId == request.RoleId && u.Key == Define.ROLERESOURCE);
//批量分配角色资源
UnitWork.BatchAdd((from firstId in request.ResourceIds
select new Relevance
{
Key = Define.ROLERESOURCE,
FirstId = request.RoleId,
SecondId = firstId,
OperateTime = DateTime.Now
}).ToArray());
UnitWork.Save();
});
}
}
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using System.Threading.Tasks;
using Infrastructure;
using NUnit.Framework;
using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
@@ -17,6 +19,16 @@ namespace OpenAuth.App
public class ResourceApp:SqlSugarBaseApp<SysResource>
{
private RevelanceManagerApp _revelanceApp;
private ApiService _apiService;
private readonly IAuth _auth;
public ResourceApp(ISqlSugarClient client, IAuth auth, RevelanceManagerApp revelanceApp, ApiService apiService) : base(client, auth)
{
_revelanceApp = revelanceApp;
_apiService = apiService;
_auth = auth;
}
public void Add(AddOrUpdateResReq resource)
{
@@ -95,10 +107,92 @@ namespace OpenAuth.App
return result;
}
public ResourceApp(ISqlSugarClient client, IAuth auth, RevelanceManagerApp revelanceApp) : base(client, auth)
/// <summary>
/// 获取资源类型
/// </summary>
/// <returns></returns>
public async Task<List<SysResourceApp>> GetResourceApps()
{
_revelanceApp = revelanceApp;
var types = await SugarClient.Queryable<SysResource>()
.Distinct()
.Select(u => new {u.AppId,u.AppName})
.ToListAsync();
return types.Select(u => new SysResourceApp(u.AppId, u.AppName)).ToList();
}
/// <summary>
/// 同步站点API到资源列表
/// <para>读取站点API信息如果资源列表中不存在则添加</para>
/// </summary>
public async Task Sync()
{
var apis = await _apiService.GetSwaggerEndpoints();
var user = _auth.GetCurrentUser().User;
foreach (var api in apis)
{
//检查资源是否存在
var resource = Repository.GetFirst(u => u.Name == api.Path && u.TypeId == Define.API);
if (resource != null)
{
continue;
}
resource = new SysResource
{
Id = api.Path,
Name = api.Path,
Disable = false,
SortNo = 0,
AppId = $"{Define.API}_{api.Tag}",
AppName = $"API接口-{api.Tag}",
TypeId = Define.API,
TypeName = "API接口",
Description = api.Summary??"",
CreateTime = DateTime.Now,
CreateUserId = user.Id,
CreateUserName = user.Name
};
CaculateCascade(resource);
Repository.Insert(resource);
}
}
/// <summary>
/// 判断当前登录用户是否拥有访问该API的权限
/// <para>如果角色没有做任何分配,则默认拥有权限。这个可以根据实际需要修改。</para>
/// </summary>
/// <param name="apiPath">API路径</param>
/// <returns>true:拥有权限,false:没有权限</returns>
public bool CanAccess(string apiPath)
{
var loginContext = _auth.GetCurrentUser();
if (loginContext == null)
{
throw new CommonException("登录已过期", Define.INVALID_TOKEN);
}
//如果当前登录用户是管理员,则拥有所有权限
if(loginContext.User.Account == Define.SYSTEM_USERNAME){
return true;
}
var elementIds = _revelanceApp.Get(Define.ROLERESOURCE, true, loginContext.Roles.Select(u => u.Id).ToArray());
//如果角色没有做任何分配,则默认拥有权限。这个可以根据实际需要修改。
if(elementIds.Count == 0)
{
return true;
}
//如果分配了资源,则判断是否拥有权限
var resource = Repository.GetFirst(u => u.Name.Contains(apiPath) && u.TypeId == Define.API && elementIds.Contains(u.Id));
if(resource == null)
{
return false;
}
return true;
}
}
/// <summary>
/// 资源类型
/// </summary>
public record SysResourceApp(string Id, string Name);
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.OpenApi.Readers;
public class ApiService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
public ApiService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
}
/// <summary>
/// 获取所有API接口信息
/// <para>这个方法单元测试必须启动WebApi站点</para>
/// </summary>
public async Task<List<SwaggerEndpointInfo>> GetSwaggerEndpoints()
{
var reader = new OpenApiStringReader();
var client = _httpClientFactory.CreateClient();
var baseUrl = _configuration["AppSetting:HttpHost"]?.Replace("*", "localhost");
var apis = new List<SwaggerEndpointInfo>();
var controllers = GetControllers();
foreach (var controller in controllers)
{
var groupname = GetSwaggerGroupName(controller);
var swaggerJsonUrl = $"{baseUrl}/swagger/{groupname}/swagger.json";
var response = await client.GetAsync(swaggerJsonUrl).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var document = reader.Read(content, out var diagnostic);
//获取所有api
var controllerApis = document.Paths
.SelectMany(path => path.Value.Operations
.Select(op => new SwaggerEndpointInfo(
path.Key,
op.Key.ToString(),
op.Value.Summary,
op.Value.Description,
op.Value.Tags.FirstOrDefault()?.Name)));
apis.AddRange(controllerApis);
}
return apis;
}
/// <summary>
/// 获取控制器对应的swagger分组值
/// </summary>
private string GetSwaggerGroupName(Type controller)
{
var groupname = controller.Name.Replace("Controller", "");
var apisetting = controller.GetCustomAttribute(typeof(ApiExplorerSettingsAttribute));
if (apisetting != null)
{
groupname = ((ApiExplorerSettingsAttribute)apisetting).GroupName;
}
return groupname;
}
/// <summary>
/// 获取所有的控制器
/// </summary>
private List<Type> GetControllers()
{
var webApiAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name.Contains("OpenAuth.WebApi"));
var controlleractionlist = webApiAssembly.GetTypes()
.Where(type => typeof(Microsoft.AspNetCore.Mvc.ControllerBase).IsAssignableFrom(type))
.ToList();
return controlleractionlist;
}
}
public record SwaggerEndpointInfo(
string Path,
string HttpMethod,
string Summary,
string Description,
string Tag);

View File

@@ -19,15 +19,6 @@ namespace OpenAuth.App.Test
var result = app.GetTableColumnsFromDb("Category");
Console.WriteLine(JsonHelper.Instance.Serialize(result));
}
[Test]
public void GetDbEntityNames()
{
var app = _autofacServiceProvider.GetService<DbExtension>();
var result = app.GetDbEntityNames();
Console.WriteLine(JsonHelper.Instance.Serialize(result));
}
[Test]
public void TestGetTables()

View File

@@ -0,0 +1,139 @@
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using OpenAuth.Repository.Core;
namespace OpenAuth.Repository.Domain
{
/// <summary>
///外部数据源
/// </summary>
[Table("ExternalDataSource")]
public class ExternalDataSource : StringEntity
{
public ExternalDataSource()
{
this.Createtime = DateTime.Now;
this.Createusername = "";
this.Createuserid = "";
this.Updateuserid = "";
this.Updateusername = "";
this.Testsuccess = false;
this.Updatetime = DateTime.Now;
this.Enabled = false;
this.Connectionstring = "";
this.Description = "";
this.Password = "";
this.Username = "";
this.Port = null;
this.Server = "";
this.Databasename = "";
this.Dbtype = 0;
this.Name = "";
}
/// <summary>
///创建时间
/// </summary>
[Description("创建时间")]
public DateTime Createtime { get; set; }
/// <summary>
///创建用户名
/// </summary>
[Description("创建用户名")]
public string Createusername { get; set; }
/// <summary>
///创建用户ID
/// </summary>
[Description("创建用户ID")]
public string Createuserid { get; set; }
/// <summary>
///更新用户ID
/// </summary>
[Description("更新用户ID")]
public string Updateuserid { get; set; }
/// <summary>
///更新用户名
/// </summary>
[Description("更新用户名")]
public string Updateusername { get; set; }
/// <summary>
///是否测试成功
/// </summary>
[Description("是否测试成功")]
public bool? Testsuccess { get; set; }
/// <summary>
///最后测试时间
/// </summary>
[Description("最后测试时间")]
public DateTime? Updatetime { get; set; }
/// <summary>
///是否启用
/// </summary>
[Description("是否启用")]
public bool Enabled { get; set; }
/// <summary>
///连接字符串
/// </summary>
[Description("连接字符串")]
public string Connectionstring { get; set; }
/// <summary>
///描述
/// </summary>
[Description("描述")]
public string Description { get; set; }
/// <summary>
///密码
/// </summary>
[Description("密码")]
public string Password { get; set; }
/// <summary>
///用户名
/// </summary>
[Description("用户名")]
public string Username { get; set; }
/// <summary>
///端口
/// </summary>
[Description("端口")]
public int? Port { get; set; }
/// <summary>
///服务器地址
/// </summary>
[Description("服务器地址")]
public string Server { get; set; }
/// <summary>
///数据库名称
/// </summary>
[Description("数据库名称")]
public string Databasename { get; set; }
/// <summary>
///数据库类型
/// </summary>
[Description("数据库类型")]
public int Dbtype { get; set; }
/// <summary>
///数据源名称
/// </summary>
[Description("数据源名称")]
public string Name { get; set; }
}
}

View File

@@ -133,6 +133,26 @@ namespace OpenAuth.WebApi.Controllers
return result;
}
/// <summary>
/// 角色分配资源,整体提交,会覆盖之前的配置
/// </summary>
[HttpPost]
public Response AssignRoleResources([FromBody] AssignRoleResources request)
{
var result = new Response();
try
{
_app.AssignRoleResources(request);
}
catch (Exception ex)
{
result.Code = 500;
result.Message = ex.InnerException?.Message ?? ex.Message;
}
return result;
}
/// <summary>
/// 部门分配用户,整体提交,会覆盖之前的配置

View File

@@ -0,0 +1,143 @@
using System;
using System.Threading.Tasks;
using Infrastructure;
using Microsoft.AspNetCore.Mvc;
using OpenAuth.App;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository.Domain;
namespace OpenAuth.WebApi.Controllers
{
/// <summary>
/// 外部数据源管理接口
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
[ApiExplorerSettings(GroupName = "外部数据源管理接口_ExternalDataSources")]
public class ExternalDataSourcesController : ControllerBase
{
private readonly ExtDataSourceApp _app;
//获取详情
[HttpGet]
public Response<ExternalDataSource> Get(string id)
{
var result = new Response<ExternalDataSource>();
try
{
result.Result = _app.Get(id);
}
catch (Exception ex)
{
result.Code = 500;
result.Message = ex.InnerException?.Message ?? ex.Message;
}
return result;
}
//添加
[HttpPost]
public Response Add([FromBody]AddOrUpdateExternalDataSourceReq obj)
{
var result = new Response();
try
{
_app.Add(obj);
}
catch (Exception ex)
{
result.Code = 500;
result.Message = ex.InnerException?.Message ?? ex.Message;
}
return result;
}
//测试连接
[HttpPost]
public Response TestConnection([FromBody]TestConnectionReq obj)
{
var result = new Response();
try
{
_app.TestConnection(obj);
}
catch (Exception ex)
{
result.Code = 500;
result.Message = ex.InnerException?.Message ?? ex.Message;
}
return result;
}
//修改
[HttpPost]
public Response Update([FromBody]AddOrUpdateExternalDataSourceReq obj)
{
var result = new Response();
try
{
_app.Update(obj);
}
catch (Exception ex)
{
result.Code = 500;
result.Message = ex.InnerException?.Message ?? ex.Message;
}
return result;
}
/// <summary>
/// 加载列表
/// </summary>
[HttpGet]
public async Task<TableData> Load([FromQuery]QueryExternalDataSourceListReq request)
{
return await _app.Load(request);
}
/// <summary>
/// 获取数据库类型
/// </summary>
/// <returns></returns>
[HttpGet]
public TableData GetDbTypes()
{
return _app.GetDbTypes();
}
/// <summary>
/// 批量删除
/// </summary>
[HttpPost]
public Response Delete([FromBody]string[] ids)
{
var result = new Response();
try
{
_app.Delete(ids);
}
catch (Exception ex)
{
result.Code = 500;
result.Message = ex.InnerException?.Message ?? ex.Message;
}
return result;
}
public ExternalDataSourcesController(ExtDataSourceApp app)
{
_app = app;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -22,7 +22,7 @@ namespace OpenAuth.WebApi.Controllers
{
private readonly ResourceApp _app;
public ResourcesController(IAuth authUtil, ResourceApp app)
public ResourcesController(ResourceApp app)
{
_app = app;
}
@@ -32,6 +32,28 @@ namespace OpenAuth.WebApi.Controllers
return await _app.Load(request);
}
/// <summary>
/// 同步站点API到资源列表
/// <para>读取站点API信息如果资源列表中不存在则添加</para>
/// </summary>
[HttpPost]
public async Task<Response> Sync()
{
var result = new Response();
try
{
await _app.Sync();
return result;
}
catch (Exception ex)
{
result.Code = 500;
result.Message = ex.InnerException?.Message ?? ex.Message;
}
return result;
}
[HttpPost]
public Response Delete([FromBody]string[] ids)
{
@@ -104,5 +126,23 @@ namespace OpenAuth.WebApi.Controllers
return result;
}
/// <summary>
/// 获取资源所属应用
/// </summary>
[HttpGet]
public async Task<Response<List<SysResourceApp>>> GetResourceApps()
{
var result = new Response<List<SysResourceApp>>();
try
{
result.Result = await _app.GetResourceApps();
}
catch (Exception e)
{
result.Code = 500;
result.Message = e.InnerException?.Message ?? e.Message;
}
return result;
}
}
}

View File

@@ -14,10 +14,13 @@ namespace OpenAuth.WebApi.Model
private readonly IAuth _authUtil;
private readonly SysLogApp _logApp;
public OpenAuthFilter(IAuth authUtil, SysLogApp logApp)
private readonly ResourceApp _resourceApp;
public OpenAuthFilter(IAuth authUtil, SysLogApp logApp, ResourceApp resourceApp)
{
_authUtil = authUtil;
_logApp = logApp;
_resourceApp = resourceApp;
}
public void OnActionExecuting(ActionExecutingContext context)
@@ -28,9 +31,9 @@ namespace OpenAuth.WebApi.Model
var Controllername = description.ControllerName.ToLower();
var Actionname = description.ActionName.ToLower();
//匿名标识
var authorize = description.MethodInfo.GetCustomAttribute(typeof(AllowAnonymousAttribute));
if (authorize != null)
//匿名访问的不需要验证
var allowAnonymous = description.MethodInfo.GetCustomAttribute(typeof(AllowAnonymousAttribute));
if (allowAnonymous != null)
{
return;
}
@@ -41,14 +44,29 @@ namespace OpenAuth.WebApi.Model
context.Result = new JsonResult(new Response
{
Code = 401,
Message = "认证失败,请提供认证信息"
Message = "登录已过期,请重新登录"
});
return;
}
var apiPath = $"{Controllername}/{Actionname}";
//判断登录角色是否拥有访问该URL的权限
var resource = _resourceApp.CanAccess(apiPath);
if(!resource)
{
context.Result = new JsonResult(new Response
{
Code = 500,
Message = $"当前用户没有访问{apiPath}的权限,请在【角色管理】中分配资源"
});
return;
}
_logApp.Add(new SysLog
{
Content = $"用户访问",
Href = $"{Controllername}/{Actionname}",
Href = apiPath,
CreateName = _authUtil.GetUserName(),
CreateId = _authUtil.GetCurrentUser().User.Id,
TypeName = "访问日志"

View File

@@ -32,6 +32,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.2" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="3.1.0" />
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.23" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.2.22" />
<PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.2.22" />

View File

@@ -1,7 +1,7 @@
<!--
* @Author: yubaolee <yubaolee@163.com> | ahfu~ <954478625@qq.com>
* @Description: 代码生成器自动生成动态头部列表
* @Copyright (c) 2022 by yubaolee | ahfu~ , All Rights Reserved.
* @Description: OpenAuth.Pro代码生成器自动生成
* Copyright (c) 2025 by yubaolee | ahfu~ , All Rights Reserved.
-->
<template>
<div class="flex flex-column">
@@ -9,50 +9,25 @@
<div class="search-box">
<el-input @keyup.enter="handleFilter" size="small" class="custom-input w-200" :placeholder="'名称'"
v-model="listQuery.key"></el-input>
<el-button size="small" class="custom-button filter-item" icon="el-icon-search" @click="handleFilter">搜索</el-button>
<el-button size="small" class="custom-button filter-item" icon="el-icon-search"
@click="handleFilter">搜索</el-button>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
</sticky>
<auth-table
ref="mainTable"
:select-type="'checkbox'"
:table-fields="headerList"
:data="list"
:v-loading="listLoading"
@row-click="rowClick"
@selection-change="handleSelectionChange"></auth-table>
<el-pagination
v-show="total > 0"
:total="total"
v-model:currentPage="listQuery.page"
v-model:page-size="listQuery.limit"
@current-change="handleCurrentChange" />
<auth-table ref="mainTableRef" :select-type="'checkbox'" :table-fields="headerList" :data="list"
:v-loading="listLoading" @row-click="rowClick" @selection-change="handleSelectionChange"></auth-table>
<el-pagination v-show="total > 0" :total="total" v-model:currentPage="listQuery.page"
v-model:page-size="listQuery.limit" @current-change="handleCurrentChange" />
</div>
<el-dialog
draggable
class="dialog-mini"
width="500px"
:title="txtDlgTitle[dialogStatus]"
v-model="dialogFormVisible">
<auth-form
ref="dataForm"
:rules="rules"
:edit-model="true"
:form-fields="headerList"
:data="temp"
<el-dialog draggable class="dialog-mini" width="500px" :title="dialogStatus==0?'新增':'编辑'" v-model="dialogFormVisible">
<auth-form ref="dataFormRef" :rules="rules" :edit-model="true" :form-fields="headerList" :data="temp"
:col-num="2"></auth-form>
<template v-slot:footer>
<div>
<el-button size="small" @click="dialogFormVisible = false">
取消
</el-button>
<el-button
size="small"
v-if="dialogStatus == 'create'"
type="primary"
@click="createData">
<el-button size="small" v-if="dialogStatus == 0" type="primary" @click="createData">
确认
</el-button>
<el-button size="small" v-else type="primary" @click="updateData">
@@ -76,46 +51,35 @@ import Sticky from '@/components/Sticky/index.vue'
import permissionBtn from '@/components/PermissionBtn/index.vue'
import AuthForm from '../../components/Base/AuthForm.vue'
import AuthTable from '../../components/Base/AuthTable.vue'
const headerList = ref([]) //列表头
const multipleSelection = ref([]) //列表checkbox选中的值
const list = ref([]) //列表值
const total = ref(0) //总条数
const listLoading = ref(true) //进度条
const dialogFormVisible = ref(false) //是否显示编辑框
const dialogStatus = ref('')
const dialogStatus = ref(0) //0:新增 1:编辑
let temp = reactive({}) //新增(编辑)绑定对话框
const listQuery = reactive({
// 查询条件
page: 1,
limit: 20,
key: undefined,
})
const txtDlgTitle = reactive({
//对话框标题
update: '编辑',
create: '添加',
})
const rules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
})
//组件refs
const mainTable = ref(null)
const dataForm = ref(null)
const mainTableRef = ref(null)
const dataFormRef = ref(null)
onMounted(() => {
headerList.value = [
{HeaderList}
{HeaderList}
]
getList()
})
const rowClick = function (row) {
mainTable.value.clearSelection()
mainTable.value.toggleRowSelection(row)
mainTableRef.value.clearSelection()
mainTableRef.value.toggleRowSelection(row)
}
const handleSelectionChange = function (val) {
multipleSelection.value = val
@@ -127,27 +91,18 @@ const onBtnClicked = function (domId, callback) {
break
case 'btnEdit':
if (multipleSelection.value.length !== 1) {
ElMessage.error({
message: '只能选中一个进行编辑',
type: 'error',
})
ElMessage.error('只能选中一个进行编辑')
return
}
handleUpdate(multipleSelection.value[0])
break
case 'btnDel':
if (multipleSelection.value.length < 1) {
ElMessage.error({
message: '至少删除一个',
type: 'error',
})
ElMessage.error('至少删除一个')
return
}
handleDelete(multipleSelection.value)
break
case 'btnExport':
mainTable.value.exportExcel('资源文件', callback)
break
default:
break
}
@@ -164,22 +119,12 @@ const handleFilter = function () {
listQuery.page = 1
getList()
}
const handleSizeChange = function (val) {
listQuery.limit = val
getList()
}
const handleCurrentChange = function (val) {
listQuery.page = val
getList()
}
const handleModifyStatus = function (row, disable) {
// 模拟修改状态
ElMessage.success({
message: '操作成功',
type: 'success',
})
row.disable = disable
}
const resetTemp = function () {
let obj = {}
headerList.value.forEach(item => {
@@ -188,39 +133,30 @@ const resetTemp = function () {
Object.assign(temp, obj)
}
const handleCreate = async function () {
// 弹出添加框
resetTemp()
dialogStatus.value = 'create'
dialogStatus.value = 0
dialogFormVisible.value = true
await nextTick()
dataForm.value.clearValidate()
dataFormRef.value.clearValidate()
}
const createData = function () {
// 保存提交
dataForm.value.validate(() => {
dataFormRef.value.validate(() => {
{TableName}s.add(temp).then(() => {
list.value.unshift(temp)
dialogFormVisible.value = false
ElNotification({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000,
})
ElNotification.success('创建成功')
})
})
}
const handleUpdate = async function (row) {
// 弹出编辑框
Object.assign(temp, row) //必需这样赋值才能响应式
dialogStatus.value = 'update'
dialogStatus.value = 1
dialogFormVisible.value = true
await nextTick()
dataForm.value.clearValidate()
dataFormRef.value.clearValidate()
}
const updateData = function () {
// 更新提交
dataForm.value.validate(() => {
dataFormRef.value.validate(() => {
const tempData = Object.assign({}, temp)
{TableName}s.update(tempData).then(() => {
for (const v of list.value) {
@@ -231,17 +167,11 @@ const updateData = function () {
}
}
dialogFormVisible.value = false
ElNotification({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000,
})
ElNotification.success('更新成功')
})
})
}
const handleDelete = function (rows) {
// 多行删除
delrows({TableName}s, rows)
delrows({TableName}s, rows, getList)
}
</script>

View File

@@ -1,7 +1,7 @@
<!--
* @Author: yubaolee <yubaolee@163.com> | ahfu~ <954478625@qq.com>
* @Description: 代码生成器自动生成动态头部列表
* @Copyright (c) 2022 by yubaolee | ahfu~ , All Rights Reserved.
* @Description: OpenAuth.Pro代码生成器自动生成
* Copyright (c) 2025 by yubaolee | ahfu~ , All Rights Reserved.
-->
<template>
<div class="flex flex-column">
@@ -9,50 +9,25 @@
<div class="search-box">
<el-input @keyup.enter="handleFilter" size="small" class="custom-input w-200" :placeholder="'名称'"
v-model="listQuery.key"></el-input>
<el-button size="small" class="custom-button filter-item" icon="el-icon-search" @click="handleFilter">搜索</el-button>
<el-button size="small" class="custom-button filter-item" icon="el-icon-search"
@click="handleFilter">搜索</el-button>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
</sticky>
<auth-table
ref="mainTable"
:select-type="'checkbox'"
:table-fields="headerList"
:data="list"
:v-loading="listLoading"
@row-click="rowClick"
@selection-change="handleSelectionChange"></auth-table>
<el-pagination
v-show="total > 0"
:total="total"
v-model:currentPage="listQuery.page"
v-model:page-size="listQuery.limit"
@current-change="handleCurrentChange" />
<auth-table ref="mainTableRef" :select-type="'checkbox'" :table-fields="headerList" :data="list"
:v-loading="listLoading" @row-click="rowClick" @selection-change="handleSelectionChange"></auth-table>
<el-pagination v-show="total > 0" :total="total" v-model:currentPage="listQuery.page"
v-model:page-size="listQuery.limit" @current-change="handleCurrentChange" />
</div>
<el-dialog
draggable
class="dialog-mini"
width="500px"
:title="txtDlgTitle[dialogStatus]"
v-model="dialogFormVisible">
<auth-form
ref="dataForm"
:rules="rules"
:edit-model="true"
:form-fields="headerList"
:data="temp"
<el-dialog draggable class="dialog-mini" width="500px" :title="dialogStatus==0?'新增':'编辑'" v-model="dialogFormVisible">
<auth-form ref="dataFormRef" :rules="rules" :edit-model="true" :form-fields="headerList" :data="temp"
:col-num="2"></auth-form>
<template v-slot:footer>
<div>
<el-button size="small" @click="dialogFormVisible = false">
取消
</el-button>
<el-button
size="small"
v-if="dialogStatus == 'create'"
type="primary"
@click="createData">
<el-button size="small" v-if="dialogStatus == 0" type="primary" @click="createData">
确认
</el-button>
<el-button size="small" v-else type="primary" @click="updateData">
@@ -70,48 +45,41 @@ import { onMounted, ref, reactive, nextTick } from 'vue'
import * as {TableName}s from '@/api/{TableName}s'
import { defaultVal } from '@/utils/index'
import { delrows } from '@/utils/delRows.js'
import ColumnDefine from '@/interface/columnDefine'
//引入组件
import Sticky from '@/components/Sticky/index.vue'
import permissionBtn from '@/components/PermissionBtn/index.vue'
import AuthForm from '../../components/Base/AuthForm.vue'
import AuthTable from '../../components/Base/AuthTable.vue'
const headerList = ref([]) //列表头
const multipleSelection = ref([]) //列表checkbox选中的值
const list = ref([]) //列表值
const total = ref(0) //总条数
const listLoading = ref(true) //进度条
const dialogFormVisible = ref(false) //是否显示编辑框
const dialogStatus = ref('')
const dialogStatus = ref(0) //0:新增 1:编辑
let temp = reactive({}) //新增(编辑)绑定对话框
const listQuery = reactive({
// 查询条件
page: 1,
limit: 20,
key: undefined,
})
const txtDlgTitle = reactive({
//对话框标题
update: '编辑',
create: '添加',
})
const rules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
})
//组件refs
const mainTable = ref(null)
const dataForm = ref(null)
const mainTableRef = ref(null)
const dataFormRef = ref(null)
onMounted(() => {
headerList.value = [
{HeaderList}
]
getList()
})
const rowClick = function (row) {
mainTable.value.clearSelection()
mainTable.value.toggleRowSelection(row)
mainTableRef.value.clearSelection()
mainTableRef.value.toggleRowSelection(row)
}
const handleSelectionChange = function (val) {
multipleSelection.value = val
@@ -123,27 +91,18 @@ const onBtnClicked = function (domId, callback) {
break
case 'btnEdit':
if (multipleSelection.value.length !== 1) {
ElMessage.error({
message: '只能选中一个进行编辑',
type: 'error',
})
ElMessage.error('只能选中一个进行编辑')
return
}
handleUpdate(multipleSelection.value[0])
break
case 'btnDel':
if (multipleSelection.value.length < 1) {
ElMessage.error({
message: '至少删除一个',
type: 'error',
})
ElMessage.error('至少删除一个')
return
}
handleDelete(multipleSelection.value)
break
case 'btnExport':
mainTable.value.exportExcel('资源文件', callback)
break
default:
break
}
@@ -167,22 +126,12 @@ const handleFilter = function () {
listQuery.page = 1
getList()
}
const handleSizeChange = function (val) {
listQuery.limit = val
getList()
}
const handleCurrentChange = function (val) {
listQuery.page = val
getList()
}
const handleModifyStatus = function (row, disable) {
// 模拟修改状态
ElMessage.success({
message: '操作成功',
type: 'success',
})
row.disable = disable
}
const resetTemp = function () {
let obj = {}
headerList.value.forEach(item => {
@@ -191,39 +140,30 @@ const resetTemp = function () {
Object.assign(temp, obj)
}
const handleCreate = async function () {
// 弹出添加框
resetTemp()
dialogStatus.value = 'create'
dialogStatus.value = 0
dialogFormVisible.value = true
await nextTick()
dataForm.value.clearValidate()
dataFormRef.value.clearValidate()
}
const createData = function () {
// 保存提交
dataForm.value.validate(() => {
dataFormRef.value.validate(() => {
{TableName}s.add(temp).then(() => {
list.value.unshift(temp)
dialogFormVisible.value = false
ElNotification({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000,
})
ElNotification.success('创建成功')
})
})
}
const handleUpdate = async function (row) {
// 弹出编辑框
Object.assign(temp, row) //必需这样赋值才能响应式
dialogStatus.value = 'update'
dialogStatus.value = 1
dialogFormVisible.value = true
await nextTick()
dataForm.value.clearValidate()
dataFormRef.value.clearValidate()
}
const updateData = function () {
// 更新提交
dataForm.value.validate(() => {
dataFormRef.value.validate(() => {
const tempData = Object.assign({}, temp)
{TableName}s.update(tempData).then(() => {
for (const v of list.value) {
@@ -234,17 +174,11 @@ const updateData = function () {
}
}
dialogFormVisible.value = false
ElNotification({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000,
})
ElNotification.success('更新成功')
})
})
}
const handleDelete = function (rows) {
// 多行删除
delrows({TableName}s, rows)
delrows({TableName}s, rows, getList)
}
</script>

View File

@@ -1,4 +1,4 @@
🔥.Net权限管理及快速开发框架、最好用的权限工作流系统。源于Martin Fowler企业级应用开发思想及最新技术组合SqlSugar、EF、Quartz、AutoFac、WebAPI、Swagger、Mock、NUnit、Vue2/3、Element-ui/plus、IdentityServer等。核心模块包括角色授权、代码生成、智能打印、表单设计、工作流、定时任务等。架构易扩展是中小企业的首选。
🔥.Net权限管理及快速开发框架、最好用的权限工作流系统。源于Martin Fowler企业级应用开发思想及最新技术组合SqlSugar、EF、Quartz、AutoFac、WebAPI、Swagger、Mock、NUnit、Vue2/3、Element-ui/plus、IdentityServer等。核心模块包括角色授权、代码生成、API鉴权、智能打印、表单设计、工作流、定时任务等。架构易扩展,是中小企业的首选。
![LOGO](https://gitee.com/uploads/images/2018/0425/163228_7077c3fd_362401.png "1.png")
@@ -93,7 +93,7 @@
* 超强的自定义权限控制功能,请参考:[通用权限设计与实现](https://www.cnblogs.com/yubaolee/p/DataPrivilege.html)
* 完整的字段权限控制,可以控制字段可见及API是否返回字段值
* 完整API鉴权可以控制角色可访问的API资源及模块功能字段可见及是否返回,请参考:[按角色授权API资源](http://doc.openauth.net.cn/core/apiauth.html#%E6%8C%89%E8%A7%92%E8%89%B2%E6%8E%88%E6%9D%83api%E8%B5%84%E6%BA%90) 及 [字段权限](http://doc.openauth.net.cn/core/datapropertyrule.html)
* 可拖拽的表单设计。详情:[可拖拽表单](http://doc.openauth.net.cn/pro/dragform.html)
@@ -101,7 +101,7 @@
* 全网最好用的打印解决方案。详情:[智能打印](http://doc.openauth.net.cn/pro/printerplan.html)
* 基于Quartz.Net的定时任务控制,可随时启/停可视化配置Cron表达式功能
* 基于Quartz.Net的定时任务控制,可随时启/停可视化配置Cron表达式功能,请参考:[定时任务](http://doc.openauth.net.cn/core/job.html)
* 基于CodeSmith的代码生成功能可快速生成带有头/明细结构的页面
@@ -111,7 +111,7 @@
* 支持多租户
* 集成IdentityServer4实现基于OAuth2的登录体系
* 支持搭建自己的IdentityServer服务器实现基于OAuth2的登录体系,请参考:[登录认证及OAuth集成](http://doc.openauth.net.cn/core/identity.html)
* 建立三方对接规范,已有系统可以无缝对接流程引擎

View File

@@ -15,7 +15,7 @@ features:
- title: 紧随潮流
details: 最新的.net sdk配合最炫的vue框架。
- title: 功能强大
details: 角色授权、代码生成、智能打印、数据权限、拖拽表单、工作流引擎、定时任务。
details: 角色授权、代码生成、API鉴权、智能打印、数据权限、拖拽表单、工作流引擎、定时任务。
- title: 主流技术
details: IdentityServer、EF、SqlSugar、Quartz、AutoFac、WebAPI、Swagger、Mock、NUnit、VUE2、VUE3、Element-ui、Element-plus。
footer: Copyright © 2025- yubaolee

View File

@@ -1,7 +1,7 @@
![LOGO](/logocore.png "1.png")
OpenAuth.Net是基于最新版.Net的开源权限工作流快速开发框架。源于Martin Fowler企业级应用开发思想及最新技术组合SqlSugar、EF、Quartz、AutoFac、WebAPI、Swagger、Mock、NUnit、Vue2/3、Element-ui/plus、IdentityServer等。核心模块包括组织机构、角色用户、权限授权、表单设计、工作流等。
OpenAuth.Net是基于最新版.Net的开源权限工作流快速开发框架。源于Martin Fowler企业级应用开发思想及最新技术组合SqlSugar、EF、Quartz、AutoFac、WebAPI、Swagger、Mock、NUnit、Vue2/3、Element-ui/plus、IdentityServer等。核心模块包括角色授权、代码生成、API鉴权、智能打印、表单设计、工作流、定时任务等。
开源版本演示:[http://demo.openauth.net.cn:1802/](http://demo.openauth.net.cn:1802/)
@@ -33,7 +33,7 @@ gitee上面两个版本。其中
* 超强的自定义权限控制功能,请参考:[通用权限设计与实现](https://www.cnblogs.com/yubaolee/p/DataPrivilege.html)
* 完整的字段权限控制,可以控制字段可见及API是否返回字段值
* 完整API鉴权可以控制角色可访问的API资源及模块功能字段可见及是否返回,请参考:[按角色授权API资源](http://doc.openauth.net.cn/core/apiauth.html#%E6%8C%89%E8%A7%92%E8%89%B2%E6%8E%88%E6%9D%83api%E8%B5%84%E6%BA%90) 及 [字段权限](http://doc.openauth.net.cn/core/datapropertyrule.html)
* 可拖拽的表单设计。详情:[可拖拽表单](http://doc.openauth.net.cn/pro/dragform.html)
@@ -41,7 +41,7 @@ gitee上面两个版本。其中
* 全网最好用的打印解决方案。详情:[智能打印](http://doc.openauth.net.cn/pro/printerplan.html)
* 基于Quartz.Net的定时任务控制,可随时启/停可视化配置Cron表达式功能
* 基于Quartz.Net的定时任务控制,可随时启/停可视化配置Cron表达式功能,请参考:[定时任务](http://doc.openauth.net.cn/core/job.html)
* 基于CodeSmith的代码生成功能可快速生成带有头/明细结构的页面
@@ -51,7 +51,7 @@ gitee上面两个版本。其中
* 支持多租户
* 集成IdentityServer4实现基于OAuth2的登录体系
* 支持搭建自己的IdentityServer服务器实现基于OAuth2的登录体系,请参考:[登录认证及OAuth集成](http://doc.openauth.net.cn/core/identity.html)
* 建立三方对接规范,已有系统可以无缝对接流程引擎

View File

@@ -30,6 +30,15 @@ Host: localhost:52789
X-Token: e4a5aa00
```
## 按角色授权API资源
目前主流的接口平台都提供按角色或账号授权访问API的功能OpenAuth.Net也不例外。在OpenAuth.Net中接口API被当作资源处理。如图
![2025-03-11-11-18-31](http://img.openauth.net.cn/2025-03-11-11-18-31.png)
如果后端新增或删除API点击【同步系统API资源】按钮即可同步到资源列表中。在角色管理功能中可以对登录的角色进行API资源授权。
![](http://img.openauth.net.cn/2025-03-11-11-13-54.png)
## 不登录直接访问

View File

@@ -38,6 +38,25 @@ OpenAuth.Net支持两种登录认证方式自定义token认证和基于Identi
当启用了Identity时客户端调用API需要先通过OpenAuth.IdentityServer服务器的OAuth验证才能调用接口。OpenAuth.Net调用API的客户端有两种
#### OpenAuth.Pro vue3
在使用企业版vue界面访问OpenAuth.WebApi时已经在文件`.env.dev`中配置好相关选项,可以直接使用,无需其他处理。
```python
VITE_OIDC_AUTHORITY = http://localhost:12796 #Identity服务器地址
VITE_OIDC_CLIENTID = OpenAuth.Pro #客户端名称
VITE_OIDC_REDIRECTURI = http://localhost:1803 #登录回调
VITE_OIDC_RESPONSETYPE = code #认证方式
VITE_OIDC_SCOPE = openid profile openauthapi #认证范围
VITE_OIDC_AUTOMATICSILENTRENEW = true #自动续期
```
如果服务端启用了Identity认证则打开登录界面如下
![2025-03-10-14-46-47](http://img.openauth.net.cn/2025-03-10-14-46-47.png)
这时点击登录超链接操作同OpenAuth.Mvc一样。
#### SwaggerUI
当我们启动了OpenAuth.WebApi在浏览器打开[http://localhost:52789/swagger/index.html](http://localhost:52789/swagger/index.html)时其实是Swagger框架给我们生成的一个调动客户端对于启用了Identity的服务Swagger会有一个Authorise的操作按钮
@@ -54,21 +73,6 @@ OpenAuth.Net支持两种登录认证方式自定义token认证和基于Identi
![20220119180608](http://img.openauth.net.cn/20220119180608.png)
#### OpenAuth.Pro
在使用企业版vue界面访问OpenAuth.WebApi时已经在文件`.env.dev`中配置好相关选项,可以直接使用,无需其他处理。
```python
VUE_APP_OIDC_AUTHORITY = http://localhost:12796 #Identity服务器地址
VUE_APP_OIDC_CLIENTID = OpenAuth.Pro #客户端名称
VUE_APP_OIDC_REDIRECTURI = http://localhost:1803/#/oidc-callback #登录回调
VUE_APP_OIDC_POSTLOGOUTREDIRECTURI = http://localhost:1803 #退出登录回调
VUE_APP_OIDC_RESPONSETYPE = code #认证方式
VUE_APP_OIDC_SCOPE = openid profile openauthapi #认证范围
VUE_APP_OIDC_AUTOMATICSILENTRENEW = true
VUE_APP_OIDC_SILENTREDIRECTURI = http://localhost:1803/silent-renew-oidc.html
```

View File

@@ -1,23 +1,17 @@
# 开发规范
# 后端开发规范
## 新增数据库名称规范
## 数据库表及字段命
子系统名称+业务名称+表尾,其中表尾名称规则如下:
SqlServer采用PascalCase命名Oracle采用全大写命名其他数据库采用camelCase命名。
::: tip 提示
开源版代码生成时通过表结尾Dtbl来判断是否是生成明细表代码。因此建议数据库表命名时按子系统名称+业务名称+表尾,其中表尾名称规则:
- 基础主数据以Mst结尾
- 普通业务表以Tbl结尾
- 业务明细表以Dtbl结尾
比如:
- WMS系统客户主数据表WmsCustomerMst
- WMS系统入库订单头表WmsInboundOrderTbl
- WMS系统入库订单明细表WmsInboundOrderDtbl
WMS系统入库订单明细表WmsInboundOrderDtbl
:::
## 数据库字段类型