17 Commits
8.3.3 ... 8.3.4

Author SHA1 Message Date
yubaolee
8beafeb79a 🐛fix: 修复代码生成器样式问题 2025-10-23 20:38:47 +08:00
yubaolee
47245dbd8d 🐛fix: #ID31JE 代码生成器生成的模板,没有高级查询条件 2025-10-23 00:50:17 +08:00
yubaolee
4bf28c77a1 🐛fix: #ID31JE 代码生成器生成的模板,新增修改后直接调整为查询列表 2025-10-23 00:31:31 +08:00
yubaolee
35300b9c43 fix #ID31JE postgreSql类型转换异常 2025-10-22 22:46:25 +08:00
yubaolee
f7230d9092 🐛fix: 部分服务器晚8个小时问题 2025-10-18 14:38:49 +08:00
yubaolee
983e185fa9 🐛fix: #ID0DB6 URL表单审批异常 2025-10-07 21:22:06 +08:00
yubaolee
d986c242b7 ⚠️feat: 优化正式环境swagger提示 2025-09-19 10:40:41 +08:00
yubaolee
6f276a0587 🐛fix: 增加正式环境Swagger界面 2025-09-19 10:25:17 +08:00
yubaolee
edb40b1775 Merge branch 'main' of https://gitee.com/dotnetchina/OpenAuth.Net into main 2025-09-12 22:36:54 +08:00
yubaolee
23de2ae7e2 🐛fix:修复正式环境没有关闭SWAGGER 2025-09-12 22:36:43 +08:00
yubaolee
8fd72ae0d3 🐛fix:修复正式环境没有关闭SWAGGER 2025-09-12 20:19:57 +08:00
yubaolee
57546e5175 Merge branch 'main' of https://gitee.com/dotnetchina/OpenAuth.Net 2025-09-11 09:01:03 +08:00
yubaolee
d7b3ebb1ce ⚠️feat: 正式环境去掉swagger界面 2025-09-10 17:30:45 +08:00
yubaolee
56602c67f3 🐛fix:修复注释失效 2025-08-30 20:56:53 +08:00
yubaolee
6f0c09d8d5 🐛fix:修复app层注释失效 2025-08-30 19:01:06 +08:00
yubaolee
e3f6374ac1 ⚠️feat: 优化个人中心 2025-08-19 21:43:18 +08:00
yubaolee
628aa65938 🔧chore: 优化文档 2025-08-18 21:59:40 +08:00
47 changed files with 908 additions and 355 deletions

View File

@@ -9,13 +9,11 @@
],
"env": {}
},
"mysql": {
"type": "stdio",
"command": "uvx",
"openauthpro": {
"command": "powershell",
"args": [
"--from",
"mysql-mcp-server",
"mysql_mcp_server"
"-Command",
"npx -y @f4ww4z/mcp-mysql-server"
],
"env": {
"MYSQL_HOST": "localhost",

View File

@@ -38,6 +38,7 @@ OpenAuth.Net是一个基于.NET 9的企业级权限管理和快速开发框架
- 私有字段使用_camelCase公共属性使用PascalCase
- 常量使用UPPER_CASE
- 异步方法必须添加Async后缀
- 修改完代码后不需要编译
### 命名规范
- **Controller**: 以Controller结尾如`UsersController`

View File

@@ -28,12 +28,17 @@ RUN dotnet publish -c Release -o /app/publish/identity
FROM base AS final
WORKDIR /app
# 设置 Production 环境变量
ENV ASPNETCORE_ENVIRONMENT=Production
ENV DOTNET_ENVIRONMENT=Production
# 复制 WebApi 发布文件
COPY --from=build /app/publish/webapi ./webapi
# 复制 Identity 发布文件
COPY --from=build /app/publish/identity ./identity
# 启动 WebApi, Mvc, 和 Identity,就算失败也保持运行,方便查询日志
# 启动 WebApi 和 Identity,就算失败也保持运行,方便查询日志
ENTRYPOINT ["sh", "-c", "cd webapi && dotnet OpenAuth.WebApi.dll & cd identity && dotnet OpenAuth.IdentityServer.dll || tail -f /dev/null"]

View File

@@ -0,0 +1,117 @@
using System;
namespace Infrastructure.Helpers
{
/// <summary>
/// 时间处理帮助类
/// 统一处理时区问题,确保所有时间都使用正确的时区
/// </summary>
public static class TimeHelper
{
/// <summary>
/// 获取当前中国标准时间UTC+8
/// </summary>
/// <returns>当前中国标准时间</returns>
public static DateTime Now => GetChinaStandardTime();
/// <summary>
/// 获取当前中国标准时间UTC+8
/// </summary>
/// <returns>当前中国标准时间</returns>
public static DateTime GetChinaStandardTime()
{
// 获取UTC时间
var utcNow = DateTime.UtcNow;
// 转换为中国标准时间UTC+8
var chinaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
// 如果找不到中国时区使用手动计算UTC+8
if (chinaTimeZone == null)
{
return utcNow.AddHours(8);
}
return TimeZoneInfo.ConvertTimeFromUtc(utcNow, chinaTimeZone);
}
/// <summary>
/// 将UTC时间转换为中国标准时间
/// </summary>
/// <param name="utcTime">UTC时间</param>
/// <returns>中国标准时间</returns>
public static DateTime ConvertUtcToChinaTime(DateTime utcTime)
{
if (utcTime.Kind != DateTimeKind.Utc)
{
utcTime = DateTime.SpecifyKind(utcTime, DateTimeKind.Utc);
}
var chinaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
if (chinaTimeZone == null)
{
return utcTime.AddHours(8);
}
return TimeZoneInfo.ConvertTimeFromUtc(utcTime, chinaTimeZone);
}
/// <summary>
/// 将中国标准时间转换为UTC时间
/// </summary>
/// <param name="chinaTime">中国标准时间</param>
/// <returns>UTC时间</returns>
public static DateTime ConvertChinaTimeToUtc(DateTime chinaTime)
{
var chinaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
if (chinaTimeZone == null)
{
return chinaTime.AddHours(-8);
}
return TimeZoneInfo.ConvertTimeToUtc(chinaTime, chinaTimeZone);
}
/// <summary>
/// 获取当前时间的Unix时间戳
/// </summary>
/// <returns>Unix时间戳</returns>
public static long GetUnixTimestamp()
{
return ((DateTimeOffset)Now).ToUnixTimeSeconds();
}
/// <summary>
/// 获取当前时间的Unix时间戳毫秒
/// </summary>
/// <returns>Unix时间戳毫秒</returns>
public static long GetUnixTimestampMilliseconds()
{
return ((DateTimeOffset)Now).ToUnixTimeMilliseconds();
}
/// <summary>
/// 将Unix时间戳转换为中国标准时间
/// </summary>
/// <param name="timestamp">Unix时间戳</param>
/// <returns>中国标准时间</returns>
public static DateTime FromUnixTimestamp(long timestamp)
{
var utcTime = DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime;
return ConvertUtcToChinaTime(utcTime);
}
/// <summary>
/// 将Unix时间戳毫秒转换为中国标准时间
/// </summary>
/// <param name="timestamp">Unix时间戳毫秒</param>
/// <returns>中国标准时间</returns>
public static DateTime FromUnixTimestampMilliseconds(long timestamp)
{
var utcTime = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime;
return ConvertUtcToChinaTime(utcTime);
}
}
}

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Debug\net5.0\Infrastructure.xml</DocumentationFile>
<DocumentationFile>bin\Debug\net9.0\Infrastructure.xml</DocumentationFile>
<NoWarn>1701;1702;1591;1573;1572;1570</NoWarn>
</PropertyGroup>

View File

@@ -212,21 +212,35 @@ namespace OpenAuth.App
, case when con.conname is not null then 1 else 0 end as iskey --是否主键
, case when attr.attnotnull is true then 0 else 1 end as isnull --可空
, t.typname columntype --类型名称
, case
when typname in ('BIT', 'BOOL', 'bit', 'bool') then
'bool'
when typname in ('smallint', 'SMALLINT') then 'short'
when typname in ('tinyint', 'TINYINT') then 'bool'
when typname in ('int4', 'int', 'INT') then
'int'
when typname in ('BIGINT', 'bigint') then
'bigint'
when typname in ('FLOAT', 'DOUBLE', 'DECIMAL', 'float', 'double', 'decimal') then
'decimal'
when typname in ('varchar', 'text') then
'string'
when typname in ('Date', 'DateTime', 'TimeStamp', 'date', 'datetime', 'timestamp') then
'DateTime'
,case
-- 布尔类型
when typname in ('bit', 'bool', 'boolean') then 'bool'
-- 整数类型
when typname in ('int2', 'smallint') then 'short'
when typname in ('int4', 'int', 'integer') then 'int'
when typname in ('int8', 'bigint') then 'bigint'
-- 浮点数类型
when typname in ('real', 'float4') then 'float'
when typname in ('float8', 'double precision') then 'double'
when typname in ('numeric', 'decimal') then 'decimal'
-- 字符串类型
when typname in ('char', 'bpchar', 'character') then 'string'
when typname in ('varchar', 'character varying') then 'string'
when typname in ('text') then 'string'
-- 日期时间类型
when typname in ('time', 'timetz') then 'TimeSpan'
when typname in ('timestamp', 'timestamptz', 'datetime','date') then 'DateTime'
when typname in ('interval') then 'TimeSpan'
-- 其他类型
when typname in ('uuid') then 'Guid'
when typname in ('bytea') then 'byte[]'
when typname in ('json', 'jsonb') then 'string'
else 'string'
end as entitytype
, comment.description as comment

View File

@@ -149,7 +149,7 @@ namespace OpenAuth.App
/// <exception cref="Exception"></exception>
private void SaveFile(string fileName, byte[] fileBuffers)
{
string folder = DateTime.Now.ToString("yyyyMMdd");
string folder = TimeHelper.Now.ToString("yyyyMMdd");
//判断文件是否为空
if (string.IsNullOrEmpty(fileName))

View File

@@ -15,6 +15,7 @@ using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using SqlSugar;
using Microsoft.Extensions.Configuration;
using Infrastructure.Helpers;
namespace OpenAuth.App.Flow
{
@@ -221,7 +222,7 @@ namespace OpenAuth.App.Flow
}
var content =
$"{user.Account}-{DateTime.Now:yyyy-MM-dd HH:mm}审批了【{Nodes[canCheckId].name}】" +
$"{user.Account}-{TimeHelper.Now:yyyy-MM-dd HH:mm}审批了【{Nodes[canCheckId].name}】" +
$"结果:{(tag.Taged == 1 ? "" : "")},备注:{tag.Description}";
SaveOperationHis(content);
@@ -389,7 +390,7 @@ namespace OpenAuth.App.Flow
sugarClient.Updateable(flowInstance).ExecuteCommand();
SaveOperationHis(
$"{user.Account}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm")}驳回了【{currentNode.name}】");
$"{user.Account}-{TimeHelper.Now.ToString("yyyy-MM-dd HH:mm")}驳回了【{currentNode.name}】");
NotifyThirdParty(client, currentNode, tag);
}
@@ -437,7 +438,7 @@ namespace OpenAuth.App.Flow
item.Value.setInfo.UserId = tag.UserId;
item.Value.setInfo.UserName = tag.UserName;
item.Value.setInfo.Description = tag.Description;
item.Value.setInfo.TagedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm");
item.Value.setInfo.TagedTime = TimeHelper.Now.ToString("yyyy-MM-dd HH:mm");
break;
}
}
@@ -462,7 +463,7 @@ namespace OpenAuth.App.Flow
InstanceId = flowInstanceId,
CreateUserId = userId,
CreateUserName = userName,
CreateDate = DateTime.Now,
CreateDate = TimeHelper.Now,
Content = opHis
}; //操作记录
@@ -744,7 +745,7 @@ namespace OpenAuth.App.Flow
ApproveType = node.setInfo.NodeConfluenceType,
ReturnToSignNode = false,
Reason = "",
CreateDate = DateTime.Now
CreateDate = TimeHelper.Now
};
sugarClient.Insertable(flowApprover).ExecuteCommand();
}

View File

@@ -2,7 +2,7 @@
* @Author: yubaolee <yubaolee@163.com> | ahfu~ <954478625@qq.com>
* @Date: 2024-12-13 16:55:17
* @Description: 工作流实例表操作
* @LastEditTime: 2025-04-21 10:54:45
* @LastEditTime: 2025-10-18 14:35:01
* Copyright (c) 2024 by yubaolee | ahfu~ , All Rights Reserved.
*/
@@ -199,7 +199,7 @@ namespace OpenAuth.App
val = "";
break;
case "DateTime":
val = DateTime.Now.ToString("yyyy-MM-dd");
val = TimeHelper.Now.ToString("yyyy-MM-dd");
break;
}
}
@@ -516,7 +516,7 @@ namespace OpenAuth.App
}
var content =
$"{user.Account}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm")}审批了【{wfruntime.currentNode.name}】" +
$"{user.Account}-{TimeHelper.Now.ToString("yyyy-MM-dd HH:mm")}审批了【{wfruntime.currentNode.name}】" +
$"结果:{(tag.Taged == 1 ? "" : "")},备注:{tag.Description}";
wfruntime.SaveOperationHis(tag.UserId, tag.UserName, content);

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Infrastructure;
using Infrastructure.Helpers;
using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
@@ -41,7 +42,7 @@ namespace OpenAuth.App
{
SchemeContent = flowScheme.SchemeContent,
SchemeName = flowScheme.SchemeName,
ModifyDate = DateTime.Now,
ModifyDate = TimeHelper.Now,
FrmId = flowScheme.FrmId,
FrmType = flowScheme.FrmType,
FrmUrlTemplate = flowScheme.FrmUrlTemplate,

View File

@@ -11,6 +11,7 @@ using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository.Domain;
using Infrastructure.Helpers;
using Quartz;
using SqlSugar;
@@ -62,7 +63,7 @@ namespace OpenAuth.App
public void Add(AddOrUpdateOpenJobReq req)
{
var obj = req.MapTo<OpenJob>();
obj.CreateTime = DateTime.Now;
obj.CreateTime = TimeHelper.Now;
var user = _auth.GetCurrentUser().User;
obj.CreateUserId = user.Id;
obj.CreateUserName = user.Name;
@@ -81,7 +82,7 @@ namespace OpenAuth.App
Cron = obj.Cron,
Status = obj.Status,
Remark = obj.Remark,
UpdateTime = DateTime.Now,
UpdateTime = TimeHelper.Now,
UpdateUserId = user.Id,
UpdateUserName = user.Name
},u => u.Id == obj.Id);
@@ -126,7 +127,7 @@ namespace OpenAuth.App
var user = _auth.GetCurrentUser().User;
job.Status = req.Status;
job.UpdateTime = DateTime.Now;
job.UpdateTime = TimeHelper.Now;
job.UpdateUserId = user.Id;
job.UpdateUserName = user.Name;
Repository.Update(job);
@@ -150,7 +151,7 @@ namespace OpenAuth.App
}
job.RunCount++;
job.LastRunTime = DateTime.Now;
job.LastRunTime = TimeHelper.Now;
Repository.Update(job);
_sysLogApp.Add(new SysLog

View File

@@ -5,10 +5,14 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Debug\net5.0\OpenAuth.App.xml</DocumentationFile>
<DocumentationFile>bin\Debug\net9.0\OpenAuth.App.xml</DocumentationFile>
<NoWarn>1701;1702;1591;1573;1572;1570</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DocumentationFile>bin\Release\net9.0\OpenAuth.App.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Model\**" />
<EmbeddedResource Remove="Model\**" />

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Infrastructure;
using Infrastructure.Helpers;
using Microsoft.Extensions.Logging;
using OpenAuth.App.Interface;
using OpenAuth.App.Request;
@@ -44,7 +45,7 @@ namespace OpenAuth.App
RelKey = key,
FirstId = sameVals.Key,
SecondId = value,
OperateTime = DateTime.Now
OperateTime = TimeHelper.Now
}).ToArray());
UnitWork.Save();
}
@@ -152,7 +153,7 @@ namespace OpenAuth.App
FirstId = request.RoleId,
SecondId = request.ModuleCode,
ThirdId = requestProperty,
OperateTime = DateTime.Now
OperateTime = TimeHelper.Now
});
}
@@ -206,7 +207,7 @@ namespace OpenAuth.App
RelKey = Define.USERROLE,
FirstId = firstId,
SecondId = request.RoleId,
OperateTime = DateTime.Now
OperateTime = TimeHelper.Now
}).ToArray());
UnitWork.Save();
});
@@ -229,7 +230,7 @@ namespace OpenAuth.App
RelKey = Define.USERORG,
FirstId = firstId,
SecondId = request.OrgId,
OperateTime = DateTime.Now
OperateTime = TimeHelper.Now
}).ToArray());
UnitWork.Save();
});
@@ -252,7 +253,7 @@ namespace OpenAuth.App
RelKey = Define.ROLERESOURCE,
FirstId = request.RoleId,
SecondId = firstId,
OperateTime = DateTime.Now
OperateTime = TimeHelper.Now
}).ToArray());
UnitWork.Save();
});

View File

@@ -10,6 +10,7 @@ using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository.Domain;
using SqlSugar;
using Infrastructure.Helpers;
namespace OpenAuth.App
{
@@ -34,7 +35,7 @@ namespace OpenAuth.App
{
var obj = resource.MapTo<SysResource>();
CaculateCascade(obj);
obj.CreateTime = DateTime.Now;
obj.CreateTime = TimeHelper.Now;
var user = _auth.GetCurrentUser().User;
obj.CreateUserId = user.Id;
obj.CreateUserName = user.Name;
@@ -56,7 +57,7 @@ namespace OpenAuth.App
TypeId = obj.TypeId,
TypeName = obj.TypeName,
Description = obj.Description,
UpdateTime = DateTime.Now,
UpdateTime = TimeHelper.Now,
UpdateUserId = user.Id,
UpdateUserName = user.Name
//todo:要修改的字段赋值
@@ -157,7 +158,7 @@ namespace OpenAuth.App
TypeId = Define.API,
TypeName = "API接口",
Description = api.Summary??"",
CreateTime = DateTime.Now,
CreateTime = TimeHelper.Now,
CreateUserId = user.Id,
CreateUserName = user.Name
};

View File

@@ -10,7 +10,7 @@ using Infrastructure;
using Microsoft.EntityFrameworkCore;
using OpenAuth.App.Request;
using OpenAuth.Repository;
using Infrastructure.Helpers;
using SqlSugar;
namespace OpenAuth.App
@@ -72,7 +72,7 @@ namespace OpenAuth.App
{
SugarClient.Ado.BeginTran();
Role role = obj;
role.CreateTime = DateTime.Now;
role.CreateTime = TimeHelper.Now;
Repository.Insert(role);
obj.Id = role.Id; //要把保存后的ID存入view

View File

@@ -5,6 +5,7 @@
using System;
using Infrastructure;
using Infrastructure.Cache;
using Infrastructure.Helpers;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.Interface;
@@ -76,12 +77,12 @@ namespace OpenAuth.App.SSO
Name = sysUserInfo.Name,
Token = Guid.NewGuid().ToString().GetHashCode().ToString("x"),
AppKey = model.AppKey,
CreateTime = DateTime.Now
CreateTime = TimeHelper.Now
// , IpAddress = HttpContext.Current.Request.UserHostAddress
};
//创建Session
_cacheContext.Set(currentSession.Token, currentSession, DateTime.Now.AddDays(10));
_cacheContext.Set(currentSession.Token, currentSession, TimeHelper.Now.AddDays(10));
result.Code = 200;
result.Token = currentSession.Token;

View File

@@ -5,9 +5,15 @@ namespace OpenAuth.App.SSO
public class PassportLoginRequest
{
/// <summary>
/// 登录账号
/// </summary>
/// <example>System</example>
public string Account { get; set; }
/// <summary>
/// 登录密码
/// </summary>
/// <example>123456</example>
public string Password { get; set; }

View File

@@ -10,6 +10,7 @@ using OpenAuth.App.Response;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.Interface;
using Infrastructure.Helpers;
namespace OpenAuth.App
@@ -90,7 +91,7 @@ namespace OpenAuth.App
FromId = Guid.Empty.ToString(),
FromName = "系统管理员",
Content = message,
CreateTime = DateTime.Now
CreateTime = TimeHelper.Now
});
}

View File

@@ -12,6 +12,7 @@ using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using OpenAuth.Repository.Core;
using Infrastructure.Helpers;
namespace OpenAuth.Repository.Domain
{
@@ -36,10 +37,10 @@ namespace OpenAuth.Repository.Domain
this.DeleteMark= 0;
this.Disabled= 0;
this.Description= string.Empty;
this.CreateDate= DateTime.Now;
this.CreateDate= TimeHelper.Now;
this.CreateUserId= string.Empty;
this.CreateUserName= string.Empty;
this.ModifyDate= DateTime.Now;
this.ModifyDate= TimeHelper.Now;
this.ModifyUserId= string.Empty;
this.ModifyUserName= string.Empty;
}

View File

@@ -13,6 +13,7 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
using OpenAuth.Repository.Core;
using Infrastructure.Helpers;
namespace OpenAuth.Repository.Domain
{
@@ -27,19 +28,19 @@ namespace OpenAuth.Repository.Domain
this.JobName= string.Empty;
this.RunCount= 0;
this.ErrorCount= 0;
this.NextRunTime= DateTime.Now;
this.LastRunTime= DateTime.Now;
this.LastErrorTime= DateTime.Now;
this.NextRunTime= TimeHelper.Now;
this.LastRunTime= TimeHelper.Now;
this.LastErrorTime= TimeHelper.Now;
this.JobType= 0;
this.JobCall= string.Empty;
this.JobCallParams= string.Empty;
this.Cron= string.Empty;
this.Status= 0;
this.Remark= string.Empty;
this.CreateTime= DateTime.Now;
this.CreateTime= TimeHelper.Now;
this.CreateUserId= string.Empty;
this.CreateUserName= string.Empty;
this.UpdateTime= DateTime.Now;
this.UpdateTime= TimeHelper.Now;
this.UpdateUserId= string.Empty;
this.UpdateUserName= string.Empty;
this.OrgId= string.Empty;

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Debug\net5.0\OpenAuth.Repository.xml</DocumentationFile>
<DocumentationFile>bin\Debug\net9.0\OpenAuth.Repository.xml</DocumentationFile>
<NoWarn>1701;1702;1591;1573;1572;1570</NoWarn>
</PropertyGroup>

View File

@@ -0,0 +1,41 @@
using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
namespace OpenAuth.WebApi.Controllers
{
/// <summary>
/// 系统信息相关接口
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class SystemController : ControllerBase
{
private readonly IHostEnvironment _environment;
public SystemController(IHostEnvironment environment)
{
_environment = environment;
}
/// <summary>
/// 获取系统环境信息
/// </summary>
/// <returns></returns>
[HttpGet]
[AllowAnonymous]
public IActionResult GetEnvironmentInfo()
{
return Ok(new
{
EnvironmentName = _environment.EnvironmentName,
IsDevelopment = _environment.IsDevelopment(),
IsProduction = _environment.IsProduction(),
IsStaging = _environment.IsStaging(),
SwaggerEnabled = _environment.IsDevelopment(), // Swagger 只在开发环境启用
Timestamp = DateTime.Now
});
}
}
}

View File

@@ -23,6 +23,9 @@
<None Remove="log\**" />
<None Remove="wwwroot\**" />
<EmbeddedResource Include="index.html" />
<None Update="swagger\index.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>

View File

@@ -61,67 +61,73 @@ namespace OpenAuth.WebApi
});
}
// 添加MiniProfiler服务
services.AddMiniProfiler(options =>
// 只在开发环境中添加MiniProfiler服务
if (Environment.IsDevelopment())
{
// 设定访问分析结果URL的路由基地址
options.RouteBasePath = "/profiler";
services.AddMiniProfiler(options =>
{
// 设定访问分析结果URL的路由基地址
options.RouteBasePath = "/profiler";
options.ColorScheme = StackExchange.Profiling.ColorScheme.Auto;
options.PopupRenderPosition = StackExchange.Profiling.RenderPosition.BottomLeft;
options.PopupShowTimeWithChildren = true;
options.PopupShowTrivial = true;
options.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();
// options.IgnoredPaths.Add("/swagger/");
}).AddEntityFramework(); //显示SQL语句及耗时
options.ColorScheme = StackExchange.Profiling.ColorScheme.Auto;
options.PopupRenderPosition = StackExchange.Profiling.RenderPosition.BottomLeft;
options.PopupShowTimeWithChildren = true;
options.PopupShowTrivial = true;
options.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();
// options.IgnoredPaths.Add("/swagger/");
}).AddEntityFramework(); //显示SQL语句及耗时
}
//添加swagger
services.AddSwaggerGen(option =>
// 只在开发环境中添加swagger
if (Environment.IsDevelopment())
{
foreach (var controller in GetControllers())
services.AddSwaggerGen(option =>
{
var groupname = GetSwaggerGroupName(controller);
option.SwaggerDoc(groupname, new OpenApiInfo
foreach (var controller in GetControllers())
{
Version = "v1",
Title = groupname,
Description = "by yubaolee"
});
}
var groupname = GetSwaggerGroupName(controller);
logger.LogInformation($"api doc basepath:{AppContext.BaseDirectory}");
foreach (var name in Directory.GetFiles(AppContext.BaseDirectory, "*.*",
SearchOption.AllDirectories).Where(f => Path.GetExtension(f).ToLower() == ".xml"))
{
option.IncludeXmlComments(name, includeControllerXmlComments: true);
// logger.LogInformation($"find api file{name}");
}
option.OperationFilter<GlobalHttpHeaderOperationFilter>(); // 添加httpHeader参数
if (!string.IsNullOrEmpty(identityServer))
{
//接入identityserver
option.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Description = "OAuth2登陆授权",
Flows = new OpenApiOAuthFlows
option.SwaggerDoc(groupname, new OpenApiInfo
{
Implicit = new OpenApiOAuthFlow
Version = "v1",
Title = groupname,
Description = "by yubaolee"
});
}
logger.LogInformation($"api doc basepath:{AppContext.BaseDirectory}");
foreach (var name in Directory.GetFiles(AppContext.BaseDirectory, "*.*",
SearchOption.AllDirectories).Where(f => Path.GetExtension(f).ToLower() == ".xml"))
{
option.IncludeXmlComments(name, includeControllerXmlComments: true);
// logger.LogInformation($"find api file{name}");
}
option.OperationFilter<GlobalHttpHeaderOperationFilter>(); // 添加httpHeader参数
if (!string.IsNullOrEmpty(identityServer))
{
//接入identityserver
option.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Description = "OAuth2登陆授权",
Flows = new OpenApiOAuthFlows
{
AuthorizationUrl = new Uri($"{identityServer}/connect/authorize"),
Scopes = new Dictionary<string, string>
Implicit = new OpenApiOAuthFlow
{
{"openauthapi", "同意openauth.webapi 的访问权限"} //指定客户端请求的api作用域。 如果为空,则客户端无法访问
AuthorizationUrl = new Uri($"{identityServer}/connect/authorize"),
Scopes = new Dictionary<string, string>
{
{"openauthapi", "同意openauth.webapi 的访问权限"} //指定客户端请求的api作用域。 如果为空,则客户端无法访问
}
}
}
}
});
option.OperationFilter<AuthResponsesOperationFilter>();
}
});
});
option.OperationFilter<AuthResponsesOperationFilter>();
}
});
}
services.Configure<AppSetting>(Configuration.GetSection("AppSetting"));
services.AddControllers(option => { option.Filters.Add<OpenAuthFilter>(); })
.ConfigureApiBehaviorOptions(options =>
@@ -241,12 +247,49 @@ namespace OpenAuth.WebApi
{
loggerFactory.AddLog4Net();
//todo:测试可以允许任意跨域,正式环境要加权限
app.UseCors(builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseRouting();
app.UseAuthentication();
// 启用日志追踪记录和异常友好提示
app.UseLogMiddleware();
// 只在开发环境中启用 Swagger 和 MiniProfiler
if (env.IsDevelopment())
{
app.UseMiniProfiler();
app.UseDeveloperExceptionPage();
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.IndexStream = () =>
IntrospectionExtensions.GetTypeInfo(GetType()).Assembly
.GetManifestResourceStream("OpenAuth.WebApi.index.html");
foreach (var controller in GetControllers())
{
var groupname = GetSwaggerGroupName(controller);
c.SwaggerEndpoint($"/swagger/{groupname}/swagger.json", groupname);
}
c.DocExpansion(DocExpansion.List); //默认展开列表
c.OAuthClientId("OpenAuth.WebApi"); //oauth客户端名称
c.OAuthAppName("开源版webapi认证"); // 描述
});
}
app.UseMiniProfiler();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
//配置ServiceProvider
AutofacContainerModule.ConfigServiceProvider(app.ApplicationServices);
//可以访问根目录下面的静态文件
var staticfile = new StaticFileOptions
@@ -260,45 +303,9 @@ namespace OpenAuth.WebApi
};
app.UseStaticFiles(staticfile);
//todo:测试可以允许任意跨域,正式环境要加权限
app.UseCors(builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseRouting();
app.UseAuthentication();
// 启用日志追踪记录和异常友好提示
app.UseLogMiddleware();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
//配置ServiceProvider
AutofacContainerModule.ConfigServiceProvider(app.ApplicationServices);
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.IndexStream = () =>
IntrospectionExtensions.GetTypeInfo(GetType()).Assembly
.GetManifestResourceStream("OpenAuth.WebApi.index.html");
foreach (var controller in GetControllers())
{
var groupname = GetSwaggerGroupName(controller);
c.SwaggerEndpoint($"/swagger/{groupname}/swagger.json", groupname);
}
c.DocExpansion(DocExpansion.List); //默认展开列表
c.OAuthClientId("OpenAuth.WebApi"); //oauth客户端名称
c.OAuthAppName("开源版webapi认证"); // 描述
});
}
/// <summary>

View File

@@ -17,7 +17,7 @@
<div class="app-container flex-item flex-column">
<div class="flex-item">
<el-card shadow="nerver" class="demo-card fh">
<auth-table style="height:calc(100% - 52px)" ref="mainTable" :select-type="'radio'" :table-fields="firstHeaderList" :data="mainList" :v-loading="listLoading" @row-click="rowClickFirstTable"></auth-table>
<auth-table style="height:calc(100% - 52px)" ref="mainTable" :select-type="'radio'" :table-fields="firstHeaderList" :data="mainList" :loading="listLoading" @row-click="rowClickFirstTable"></auth-table>
<pagination v-show="firstTotal > 0" :total="firstTotal" :page.sync="firstQuery.page" :limit.sync="firstQuery.limit" @pagination="handleCurrentChange" />
</el-card>
</div>
@@ -249,7 +249,7 @@ export default {
tempData = this.setDetails(tempData)
tempData.OrgId = this.defaultorgid
{FirstTableName}s.add(tempData).then(() => {
this.mainList.unshift(this.firstTemp)
this.getList()
this.editModel = false
this.$notify({
title: '成功',
@@ -289,13 +289,7 @@ export default {
let tempData = Object.assign({}, this.firstTemp)
tempData = this.setDetails(tempData)
{FirstTableName}s.update(tempData).then(() => {
for (const v of this.mainList) {
if (v.id === this.firstTemp.id) {
const index = this.mainList.indexOf(v)
this.mainList.splice(index, 1, this.firstTemp)
break
}
}
this.getList()
this.editModel = false
this.$notify({

View File

@@ -8,9 +8,9 @@
<div class="search-box">
<div class="flex items-center">
<query-builder storageName="{FirstTableName}s_query_conditions" :columns="firstHeaderList"
:query-options="firstQuery" @search="handleFilter" class="flex-1" />
:query-options="firstQuery" @search="handleFilter" />
<column-setting storageName="{FirstTableName}s_column_settings" :columns="firstHeaderList"
@update:columns="handleColumnsUpdate" class="ml-2" />
@update:columns="handleColumnsUpdate" />
</div>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
@@ -18,7 +18,7 @@
<div class="flex-item flex flex-column main-context">
<!-- 主表 -->
<div class="flex-item flex flex-column b-w" v-show="showNotFullScreen && !showDetailInTable">
<auth-table size="small" ref="mainTable" id="mainTable" :table-fields="firstHeaderList" :data="mainList" :v-loading="listLoading" @row-click="rowClickFirstTable"
<auth-table size="small" ref="mainTable" id="mainTable" :table-fields="firstHeaderList" :data="mainList" :loading="listLoading" @row-click="rowClickFirstTable"
@row-dblclick="rowDblClickFirstTable"></auth-table>
<div class="flex flex-direction-r p-r-10 b-w p-b-5 p-t-5">
<el-pagination size="small" background v-show="firstTotal > 0" v-model:currentPage="firstQuery.page"
@@ -71,7 +71,7 @@
删除
</el-button>
<column-setting buttonClass="filter-item" storageName="{SecondTableName}s_column_settings"
:columns="secondHeaderList" @update:columns="updateSecondColumns" class="ml-2" />
:columns="secondHeaderList" @update:columns="updateSecondColumns" />
<el-button size="small" :icon="showNotFullScreen ? FullScreen : Close"
@click="showNotFullScreen = !showNotFullScreen">
{{ showNotFullScreen ? '全屏' : '退出全屏' }}
@@ -385,13 +385,7 @@ const updateData = () => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
{FirstTableName}s.update(tempData).then(() => {
for (const v of mainList.value) {
if (v.id === firstTemp.id) {
const index = mainList.value.indexOf(v)
mainList.value.splice(index, 1, tempData)
break
}
}
getList()
editModel.value = false
showDetailInTable.value = false // 保存后返回列表视图
ElNotification.success('更新成功')

View File

@@ -8,9 +8,9 @@
<div class="search-box">
<div class="flex items-center">
<query-builder storageName="{FirstTableName}s_query_conditions" :columns="firstHeaderList"
:query-options="firstQuery" @search="handleFilter" class="flex-1" />
:query-options="firstQuery" @search="handleFilter" />
<column-setting storageName="{FirstTableName}s_column_settings" :columns="firstHeaderList"
@update:columns="handleColumnsUpdate" class="ml-2" />
@update:columns="handleColumnsUpdate" />
</div>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
@@ -18,7 +18,7 @@
<div class="flex-item flex flex-column main-context">
<!-- 主表 -->
<div class="flex-item flex flex-column b-w" v-show="showNotFullScreen && !showDetailInTable">
<auth-table size="small" ref="mainTable" id="mainTable" :table-fields="firstHeaderList" :data="mainList" :v-loading="listLoading" @row-click="rowClickFirstTable"
<auth-table size="small" ref="mainTable" id="mainTable" :table-fields="firstHeaderList" :data="mainList" :loading="listLoading" @row-click="rowClickFirstTable"
@row-dblclick="rowDblClickFirstTable"></auth-table>
<div class="flex flex-direction-r p-r-10 b-w p-b-5 p-t-5">
<el-pagination size="small" background v-show="firstTotal > 0" v-model:currentPage="firstQuery.page"
@@ -71,7 +71,7 @@
删除
</el-button>
<column-setting buttonClass="filter-item" storageName="{SecondTableName}s_column_settings"
:columns="secondHeaderList" @update:columns="updateSecondColumns" class="ml-2" />
:columns="secondHeaderList" @update:columns="updateSecondColumns" />
<el-button size="small" :icon="showNotFullScreen ? FullScreen : Close"
@click="showNotFullScreen = !showNotFullScreen">
{{ showNotFullScreen ? '全屏' : '退出全屏' }}
@@ -374,13 +374,7 @@ const updateData = () => {
let tempData = Object.assign({}, firstTemp)
tempData = setDetails(tempData)
{FirstTableName}s.update(tempData).then(() => {
for (const v of mainList.value) {
if (v.id === firstTemp.id) {
const index = mainList.value.indexOf(v)
mainList.value.splice(index, 1, tempData)
break
}
}
getList()
editModel.value = false
showDetailInTable.value = false // 保存后返回列表视图
ElNotification.success('更新成功')

View File

@@ -17,7 +17,7 @@
<div class="app-container flex-item flex-column">
<div class="flex-item">
<el-card shadow="nerver" class="demo-card fh">
<auth-table style="height:calc(100% - 52px)" ref="mainTable" :select-type="'radio'" :table-fields="firstHeaderList" :data="mainList" :v-loading="listLoading" @row-click="rowClickFirstTable"></auth-table>
<auth-table style="height:calc(100% - 52px)" ref="mainTable" :select-type="'radio'" :table-fields="firstHeaderList" :data="mainList" :loading="listLoading" @row-click="rowClickFirstTable"></auth-table>
<pagination v-show="firstTotal > 0" :total="firstTotal" :page.sync="firstQuery.page" :limit.sync="firstQuery.limit" @pagination="handleCurrentChange" />
</el-card>
</div>
@@ -246,7 +246,7 @@ export default {
tempData = this.setDetails(tempData)
tempData.OrgId = this.defaultorgid
{FirstTableName}s.add(tempData).then(() => {
this.mainList.unshift(this.firstTemp)
this.getList()
this.editModel = false
this.$notify({
title: '成功',
@@ -286,14 +286,7 @@ export default {
let tempData = Object.assign({}, this.firstTemp)
tempData = this.setDetails(tempData)
{FirstTableName}s.update(tempData).then(() => {
for (const v of this.mainList) {
if (v.id === this.firstTemp.id) {
const index = this.mainList.indexOf(v)
this.mainList.splice(index, 1, this.firstTemp)
break
}
}
this.getList()
this.editModel = false
this.$notify({
title: '成功',

View File

@@ -39,6 +39,10 @@ namespace OpenAuth.App
//增加筛选条件,如:
objs = objs.Where(u => u.Name.Contains(request.key));
}
if (!string.IsNullOrEmpty(request.sqlWhere))
{
objs = objs.Where(request.sqlWhere);
}
{ForeignKeyTemplate}

View File

@@ -20,7 +20,7 @@
:select-type="'checkbox'"
:table-fields="headerList"
:data="list"
:v-loading="listLoading"
:loading="listLoading"
@row-click="rowClick"
@selection-change="handleSelectionChange"
></auth-table>
@@ -180,7 +180,7 @@ export default {
// 保存提交
this.$refs['dataForm'].validate(() => {
{TableName}s.add(this.temp).then(() => {
this.list.unshift(this.temp)
this.getList()
this.dialogFormVisible = false
this.$notify({
title: '成功',
@@ -205,13 +205,7 @@ export default {
this.$refs['dataForm'].validate(() => {
const tempData = Object.assign({}, this.temp)
{TableName}s.update(tempData).then(() => {
for (const v of this.list) {
if (v.id === this.temp.id) {
const index = this.list.indexOf(v)
this.list.splice(index, 1, this.temp)
break
}
}
this.getList()
this.dialogFormVisible = false
this.$notify({
title: '成功',

View File

@@ -8,15 +8,15 @@
<sticky>
<div class="search-box">
<div class="flex items-center">
<query-builder storageName="{TableName}_query_conditions" :columns="headerList" :query-options="listQuery" @search="handleFilter" class="flex-1" />
<query-builder storageName="{TableName}_query_conditions" :columns="headerList" :query-options="listQuery" @search="handleFilter" />
<column-setting storageName="{TableName}_column_settings" :columns="headerList"
@update:columns="handleColumnsUpdate" class="ml-2" />
@update:columns="handleColumnsUpdate" />
</div>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
</sticky>
<auth-table ref="mainTableRef" :select-type="'checkbox'" :table-fields="headerList" :data="list"
:v-loading="listLoading" @row-click="rowClick" @selection-change="handleSelectionChange"></auth-table>
: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>
@@ -171,7 +171,7 @@ const handleCreate = async function () {
const createData = function () {
dataFormRef.value.validate(() => {
{TableName}s.add(temp).then(() => {
list.value.unshift(temp)
getList()
dialogFormVisible.value = false
ElNotification.success('创建成功')
})
@@ -188,13 +188,7 @@ const updateData = function () {
dataFormRef.value.validate(() => {
const tempData = Object.assign({}, temp)
{TableName}s.update(tempData).then(() => {
for (const v of list.value) {
if (v.id === temp.id) {
const index = list.value.indexOf(v)
list.value.splice(index, 1, temp)
break
}
}
getList()
dialogFormVisible.value = false
ElNotification.success('更新成功')
})

View File

@@ -8,15 +8,15 @@
<sticky>
<div class="search-box">
<div class="flex items-center">
<query-builder storageName="{TableName}_query_conditions" :columns="headerList" :query-options="listQuery" @search="handleFilter" class="flex-1" />
<query-builder storageName="{TableName}_query_conditions" :columns="headerList" :query-options="listQuery" @search="handleFilter" />
<column-setting storageName="{TableName}_column_settings" :columns="headerList"
@update:columns="handleColumnsUpdate" class="ml-2" />
@update:columns="handleColumnsUpdate" />
</div>
</div>
<permission-btn v-on:btn-event="onBtnClicked"></permission-btn>
</sticky>
<auth-table ref="mainTableRef" :select-type="'checkbox'" :table-fields="headerList" :data="list"
:v-loading="listLoading" @row-click="rowClick" @selection-change="handleSelectionChange"></auth-table>
: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>
@@ -172,7 +172,7 @@ const handleCreate = async function () {
const createData = function () {
dataFormRef.value.validate(() => {
{TableName}s.add(temp).then(() => {
list.value.unshift(temp)
getList()
dialogFormVisible.value = false
ElNotification.success('创建成功')
})
@@ -189,13 +189,7 @@ const updateData = function () {
dataFormRef.value.validate(() => {
const tempData = Object.assign({}, temp)
{TableName}s.update(tempData).then(() => {
for (const v of list.value) {
if (v.id === temp.id) {
const index = list.value.indexOf(v)
list.value.splice(index, 1, temp)
break
}
}
getList()
dialogFormVisible.value = false
ElNotification.success('更新成功')
})

View File

@@ -20,7 +20,7 @@
:select-type="'checkbox'"
:table-fields="headerList"
:data="list"
:v-loading="listLoading"
:loading="listLoading"
@row-click="rowClick"
@selection-change="handleSelectionChange"
></auth-table>
@@ -181,7 +181,7 @@ export default {
// 保存提交
this.$refs['dataForm'].validate(() => {
{TableName}s.add(this.temp).then(() => {
this.list.unshift(this.temp)
this.getList()
this.dialogFormVisible = false
this.$notify({
title: '成功',
@@ -206,13 +206,7 @@ export default {
this.$refs['dataForm'].validate(() => {
const tempData = Object.assign({}, this.temp)
{TableName}s.update(tempData).then(() => {
for (const v of this.list) {
if (v.id === this.temp.id) {
const index = this.list.indexOf(v)
this.list.splice(index, 1, this.temp)
break
}
}
this.getList()
this.dialogFormVisible = false
this.$notify({
title: '成功',

View File

@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenAuth.Net</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #007acc;
}
.header h1 {
color: #007acc;
margin: 0;
font-size: 2.5em;
}
.header p {
color: #666;
margin: 10px 0 0 0;
font-size: 1.1em;
}
.info-box {
background: #e8f4fd;
border: 1px solid #007acc;
border-radius: 5px;
padding: 20px;
margin: 20px 0;
}
.info-box h3 {
color: #007acc;
margin-top: 0;
}
.btn {
display: inline-block;
background: #007acc;
color: white;
padding: 12px 24px;
text-decoration: none;
border-radius: 5px;
font-weight: bold;
transition: background-color 0.3s;
margin: 10px 10px 10px 0;
}
.btn:hover {
background: #005a9e;
}
.btn-secondary {
background: #6c757d;
}
.btn-secondary:hover {
background: #545b62;
}
.features {
margin: 30px 0;
}
.features h3 {
color: #007acc;
border-bottom: 1px solid #ddd;
padding-bottom: 10px;
}
.features ul {
list-style-type: none;
padding: 0;
}
.features li {
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓ ";
color: #28a745;
font-weight: bold;
}
.warning {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 5px;
padding: 15px;
margin: 20px 0;
color: #856404;
}
.warning strong {
color: #856404;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>OpenAuth.Net</h1>
<p>最好用的.NET开源权限工作流框架</p>
</div>
<div class="info-box">
<h3>⚠️重要提示</h3>
<p>在生产环境中SwaggerUI 默认会被停用以确保安全性。如需在生产环境显示 API 文档请在OpenAuth.WebApi的Startup Configure中调整 </p>
</div>
<div style="text-align: center; margin-top: 30px; color: #666;">
<p>© 2025 OpenAuth.Net - 开源 .NET 权限工作流框架</p>
</div>
</div>
</body>
</html>

View File

@@ -21,7 +21,7 @@
<div class="flex-item" v-show="showNotFullScreen">
<el-card shadow="nerver" class="demo-card fh">
<auth-table style="height:calc(100% - 52px)" ref="mainTable" :select-type="'radio'"
:table-fields="firstHeaderList" :data="mainList" :v-loading="listLoading"
:table-fields="firstHeaderList" :data="mainList" :loading="listLoading"
@row-click="rowClickFirstTable"></auth-table>
<pagination v-show="firstTotal > 0" :total="firstTotal" :page.sync="firstQuery.page"
:limit.sync="firstQuery.limit" @pagination="handleCurrentChange" />

View File

@@ -39,7 +39,7 @@
</div>
</sticky>
<auth-table style="height:calc(100% - 60px)" ref="mainTable" :select-type="'checkbox'" :table-fields="headerList" :templates="['privilegeRules']" :data="list" :v-loading="listLoading" @row-click="rowClick" @selection-change="handleSelectionChange"></auth-table>
<auth-table style="height:calc(100% - 60px)" ref="mainTable" :select-type="'checkbox'" :table-fields="headerList" :templates="['privilegeRules']" :data="list" :loading="listLoading" @row-click="rowClick" @selection-change="handleSelectionChange"></auth-table>
<pagination v-show="total > 0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="handleCurrentChange" />
</el-main>
</div>

View File

@@ -17,7 +17,7 @@
</sticky>
<div class="app-container flex-item">
<div class="bg-white" style="height: 100%;">
<auth-table style="height:calc(100% - 60px)" ref="mainTable" :select-type="'checkbox'" :table-fields="headerList" :templates="['privilegeRules']" :data="list" :v-loading="listLoading" @row-click="rowClick" @selection-change="handleSelectionChange"></auth-table>
<auth-table style="height:calc(100% - 60px)" ref="mainTable" :select-type="'checkbox'" :table-fields="headerList" :templates="['privilegeRules']" :data="list" :loading="listLoading" @row-click="rowClick" @selection-change="handleSelectionChange"></auth-table>
<pagination v-show="total > 0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="handleCurrentChange" />
</div>

View File

@@ -19,7 +19,7 @@
<div class="app-container flex-item">
<div class="bg-white" style="height: 100%;">
<auth-table style="height:calc(100% - 60px)" ref="mainTable" :select-type="'checkbox'"
:table-fields="headerList" :data="list" :v-loading="listLoading" @row-click="rowClick"
:table-fields="headerList" :data="list" :loading="listLoading" @row-click="rowClick"
@selection-change="handleSelectionChange"></auth-table>
<pagination v-show="total > 0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit"
@pagination="handleCurrentChange" />

View File

@@ -21,11 +21,13 @@
</el-select>
</el-col>
<el-col :span="6">
<select-users v-if="postObj.verificationFinally == '1' && postObj.NodeDesignateType === 'RUNTIME_SPECIAL_USER'"
<select-users
v-if="postObj.verificationFinally == '1' && postObj.NodeDesignateType === 'RUNTIME_SPECIAL_USER'"
placeholder="*选择下一个节点执行用户" :userNames.sync="postObj.NodeDesignateTxts" :users="postObj.NodeDesignates"
:ignore-auth="true" v-on:users-change="usersChange">
</select-users>
<select-roles v-if="postObj.verificationFinally == '1' && postObj.NodeDesignateType === 'RUNTIME_SPECIAL_ROLE'"
<select-roles
v-if="postObj.verificationFinally == '1' && postObj.NodeDesignateType === 'RUNTIME_SPECIAL_ROLE'"
placeholder="*选择下一个节点执行角色" :userNames.sync="postObj.NodeDesignateTxts" :roles="postObj.NodeDesignates"
:ignore-auth="true" v-on:roles-change="rolesChange">
</select-roles>
@@ -47,6 +49,10 @@
</v-form-render>
</div>
</el-card>
<!-- url表单 -->
<iframe v-if="flowObj.frmType === 3"
:src="flowObj.frmData.startsWith('http') ? flowObj.frmData : ('/#' + flowObj.frmData)"
style="width:100%;height:500px;border:none;"></iframe>
<el-card class="box-card" v-else>
<!--动态表单渲染-->
<form ref="frmData">
@@ -54,9 +60,9 @@
<div v-else>
<p v-html="flowObj.frmPreviewHtml"></p>
<!-- 展示只读的数据但需要隐藏动态表单不然提交时没有数据 -->
<p v-html="flowObj.frmHtml" v-show="false" ></p>
<p v-html="flowObj.frmHtml" v-show="false"></p>
</div>
</form>
</el-card>
<el-card class="box-card">
@@ -231,11 +237,13 @@ export default {
frmdata[_this.$refs.frmData[i].name] = _this.$refs.frmData[i].value
}
_this.postObj.frmData = JSON.stringify(frmdata)
} else {
} else if (_this.postObj.frmType === 2) {
var frmdata = await _this.$refs.vFormRef.getFormData()
_this.postObj.frmData = JSON.stringify(frmdata)
return
} else {
//URL表单暂时没有获取数据的方式
}
},
usersChange(name, val) {

View File

@@ -26,7 +26,7 @@
:select-type="'checkbox'"
:table-fields="headerList"
:data="list"
:v-loading="listLoading"
:loading="listLoading"
@row-click="rowClick"
@selection-change="handleSelectionChange"
></auth-table>

View File

@@ -23,7 +23,7 @@
:select-type="'null'"
:table-fields="headerList"
:data="list"
:v-loading="listLoading"
:loading="listLoading"
@row-click="rowClick"
@selection-change="handleSelectionChange"
></auth-table>

View File

@@ -19,7 +19,7 @@
<div class="app-container flex-item">
<div class="bg-white" style="height: 100%;">
<auth-table style="height:calc(100% - 60px)" ref="mainTable" :select-type="'checkbox'" :table-fields="headerList"
:data="list" :v-loading="listLoading" @row-click="rowClick"
:data="list" :loading="listLoading" @row-click="rowClick"
@selection-change="handleSelectionChange"></auth-table>
<pagination v-show="total > 0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit"
@pagination="handleCurrentChange" />

View File

@@ -1,81 +1,129 @@
<template>
<div class="flex-column">
<div class="profile-page">
<div class="app-container flex-item">
<el-row style="height: 100%;">
<el-col :span="15" style="height: 100%;">
<div class="bg-white" style="height: 50%;">
<el-card shadow="never" class="body-small" style="height: 100%;overflow:auto;">
<div slot="header" class="clearfix">
<el-button type="text" style="padding: 0 11px">基本资料</el-button>
</div>
<el-form ref="dataForm" :model="temp" label-position="right" label-width="100px">
<el-form-item size="small" :label="'账号'" prop="account">
<span>{{temp.account}}</span>
</el-form-item>
<el-form-item size="small" :label="'姓名'" prop="name">
<el-input v-model="temp.name"></el-input>
</el-form-item>
<el-form-item size="small" :label="'性别'">
<el-radio v-model="temp.sex" :label="0"></el-radio>
<el-radio v-model="temp.sex" :label="1"></el-radio>
</el-form-item>
<el-form-item size="small" :label="' '">
<el-button size="mini" type="primary" @click="changeProfile">确认修改</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<div class="bg-white" style="height: 50%;">
<el-card shadow="never" class="body-small" style="height: 100%;overflow:auto;">
<div slot="header" class="clearfix">
<el-button type="text" style="padding: 0 11px">修改密码</el-button>
</div>
<el-form ref="dataForm" :model="temp" label-position="right" label-width="100px">
<el-form-item size="small" :label="'新密码'" prop="name">
<el-input v-model="newpwd" show-password></el-input>
</el-form-item>
<el-form-item size="small" :label="' '">
<el-button size="mini" type="primary" @click="changePassword">确认修改</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</el-col>
<el-col :span="4" style="height: 100%;">
<div class="bg-white" style="height: 100%;">
<el-card shadow="never" class="body-small" style="height: 100%;overflow:auto;">
<div slot="header" class="clearfix">
<el-button type="text" style="padding: 0 11px">可访问的模块</el-button>
<!-- 主内容区 -->
<div class="profile-main">
<div class="container">
<el-row :gutter="20">
<!-- 左侧用户信息区 -->
<el-col :span="8">
<div class="user-info-section">
<!-- 用户头像卡片 -->
<div class="user-avatar-card">
<div class="avatar-container">
<div class="user-avatar">
<i class="el-icon-user-solid"></i>
</div>
<div class="user-basic-info">
<h3 class="user-name">{{temp.name || temp.account}}</h3>
<p class="user-account">@{{temp.account}}</p>
</div>
</div>
</div>
<el-tree :data="modulesTree" :expand-on-click-node="false" default-expand-all :props="orgTreeProps">
</el-tree>
</el-card>
</div>
</el-col>
<el-col :span="5" style="height: 100%;border: 1px solid #EBEEF5;">
<el-card shadow="never" class="body-small" style="height: 100%;overflow:auto;">
<div slot="header" class="clearfix">
<el-button type="text" style="padding: 0 11px">可访问的机构为当前默认,点击可切换</el-button>
<!-- 基本信息编辑卡片 -->
<div class="info-edit-card">
<div class="card-header">
<h4 class="card-title">基本信息</h4>
</div>
<div class="card-content">
<el-form :model="temp" label-width="80px" class="user-form">
<el-form-item label="账号">
<div class="readonly-text">{{temp.account}}</div>
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="temp.name" placeholder="请输入姓名" size="medium"></el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="temp.sex">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="changeProfile" size="medium">
保存修改
</el-button>
</el-form-item>
</el-form>
</div>
</div>
<!-- 密码修改卡片 -->
<div class="password-edit-card">
<div class="card-header">
<h4 class="card-title">修改密码</h4>
</div>
<div class="card-content">
<el-form label-width="80px" class="user-form">
<el-form-item label="新密码">
<el-input v-model="newpwd" type="password" placeholder="请输入新密码" size="medium" show-password></el-input>
</el-form-item>
<el-form-item>
<el-button type="warning" @click="changePassword" size="medium">
修改密码
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</el-col>
<el-tree :data="orgsTree" :expand-on-click-node="false" default-expand-all :props="orgTreeProps"
@node-click="handleNodeClick"></el-tree>
</el-card>
</el-col>
</el-row>
<!-- 右侧功能区 -->
<el-col :span="16">
<div class="function-section">
<el-row :gutter="20">
<!-- 可访问模块 -->
<el-col :span="12">
<div class="function-card modules-card">
<div class="card-header">
<h4 class="card-title">
<i class="el-icon-menu"></i>
可访问模块
</h4>
</div>
<div class="card-content tree-content">
<el-tree
:data="modulesTree"
:expand-on-click-node="false"
default-expand-all
:props="orgTreeProps"
class="access-tree">
</el-tree>
</div>
</div>
</el-col>
<!-- 可访问机构 -->
<el-col :span="12">
<div class="function-card orgs-card">
<div class="card-header">
<h4 class="card-title">
<i class="el-icon-office-building"></i>
可访问机构
</h4>
<span class="card-tips">当前默认机构点击切换</span>
</div>
<div class="card-content tree-content">
<el-tree
:data="orgsTree"
:expand-on-click-node="false"
default-expand-all
:props="orgTreeProps"
@node-click="handleNodeClick"
class="access-tree clickable-tree">
</el-tree>
</div>
</div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</div>
</div>
</div>
</template>
<script>
@@ -200,22 +248,259 @@
}
}
}
</script>
<style scoped>
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
/* 页面整体样式 */
.profile-page {
min-height: 100vh;
background: #f5f5f5;
}
.clearfix:after {
clear: both
}
/* 顶部导航 */
.profile-header {
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.el-select.filter-item.el-select--small {
width: 100%;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
padding: 20px 24px;
}
.page-title {
margin: 0;
font-size: 24px;
font-weight: 500;
color: #262626;
}
/* 主内容区 */
.profile-main {
padding: 24px 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
/* 左侧用户信息区 */
.user-info-section {
display: flex;
flex-direction: column;
gap: 16px;
}
/* 用户头像卡片 */
.user-avatar-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 24px;
text-align: center;
}
.avatar-container {
display: flex;
flex-direction: column;
align-items: center;
}
.user-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
}
.user-avatar i {
font-size: 36px;
color: #fff;
}
.user-name {
margin: 0 0 8px 0;
font-size: 20px;
font-weight: 500;
color: #262626;
}
.user-account {
margin: 0;
font-size: 14px;
color: #8c8c8c;
}
/* 通用卡片样式 */
.info-edit-card,
.password-edit-card,
.function-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.card-header {
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.card-title {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #262626;
}
.card-title i {
margin-right: 8px;
color: #1890ff;
}
.card-tips {
font-size: 12px;
color: #8c8c8c;
}
.card-content {
padding: 20px;
}
/* 表单样式 */
.user-form .el-form-item {
margin-bottom: 20px;
}
.user-form .el-form-item:last-child {
margin-bottom: 0;
}
.readonly-text {
color: #8c8c8c;
background: #fafafa;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
}
.user-form .el-input .el-input__inner {
border-radius: 4px;
border: 1px solid #d9d9d9;
}
.user-form .el-input .el-input__inner:focus {
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.user-form .el-radio {
margin-right: 16px;
}
.user-form .el-button {
padding: 8px 20px;
border-radius: 4px;
font-weight: 400;
}
/* 右侧功能区 */
.function-section {
height: 100%;
}
.function-card {
height: 600px;
display: flex;
flex-direction: column;
}
.tree-content {
flex: 1;
overflow: hidden;
padding: 16px 20px 20px;
}
/* 树形组件样式 */
.access-tree {
height: 100%;
overflow-y: auto;
}
.access-tree .el-tree-node__content {
height: auto;
padding: 6px 0;
font-size: 14px;
color: #595959;
}
.access-tree .el-tree-node__content:hover {
background: #f5f5f5;
}
.clickable-tree .el-tree-node__content {
cursor: pointer;
}
.clickable-tree .el-tree-node__content:hover {
background: #e6f7ff;
color: #1890ff;
}
/* 滚动条样式 */
.access-tree::-webkit-scrollbar {
width: 6px;
}
.access-tree::-webkit-scrollbar-track {
background: #f5f5f5;
border-radius: 3px;
}
.access-tree::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.access-tree::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.container {
padding: 0 16px;
}
}
@media (max-width: 768px) {
.profile-main {
padding: 16px 0;
}
.container {
padding: 0 12px;
}
.header-content {
padding: 16px 12px;
}
.page-title {
font-size: 20px;
}
}
</style>

View File

@@ -19,7 +19,7 @@
<div class="flex-item">
<el-card shadow="nerver" class="demo-card fh">
<auth-table style="height:calc(100% - 52px)" ref="mainTable" :select-type="'radio'"
:table-fields="firstHeaderList" :data="mainList" :v-loading="listLoading"
:table-fields="firstHeaderList" :data="mainList" :loading="listLoading"
@row-click="rowClickFirstTable"></auth-table>
<pagination v-show="firstTotal > 0" :total="firstTotal" :page.sync="firstQuery.page"
:limit.sync="firstQuery.limit" @pagination="handleCurrentChange" />

View File

@@ -8,14 +8,14 @@ permalink: /core/identity/
## 前言
OpenAuth.Net支持两种登录认证方式Token认证和==自己搭建=={.tip}的OpenAuth.IdentityServer认证。
这两种方式通过配置webapi或mvc的appsettings.json可以自由切换:
这两种方式通过配置webapi的appsettings.json可以自由切换:
```json
"IdentityServerUrl": "http://localhost:12796", //IdentityServer服务器地址。如果为空则不启用OAuth认证
```
## Token认证
当我们启动OpenAuth.WebApi/Mvc如果IdentityServerUrl为空则采用普通的token认证这时不需要启动OpenAuth.Identity项目
当我们启动OpenAuth.WebApi时如果IdentityServerUrl为空则采用普通的token认证这时不需要启动OpenAuth.Identity项目
```json
"IdentityServerUrl": "", //如果为空则采用普通的token认证
```

View File

@@ -3,10 +3,9 @@ title: 日志操作
createTime: 2025/04/23 21:03:10
permalink: /core/log/
---
## 普通日志
框架默认使用Log4Net作为记录日志的方式可以在Program.cs中配置日志参数或调整为其他日志。日志默认按日期生成日志文件并存放在`log\`目录下。简单用法如下:
框架默认使用Log4Net作为记录日志的方式可以在Program.cs中配置日志参数或调整为其他日志。日志默认按日期生成日志文件并存放在 `log\`目录下。简单用法如下:
```csharp
//具体代码参考OpenAuth.App/OpenJobApp.cs此处简化真实逻辑方便理解
@@ -25,7 +24,7 @@ permalink: /core/log/
## 数据库日志
如果想使用数据库记录业务日志(如系统默认的用户操作日志等),可以使用`SysLogApp`模块功能。日志可以在站点【消息日志】->【系统日志】中查看到记录的日志信息。简单用法如下:
如果想使用数据库记录业务日志(如系统默认的用户操作日志等),可以使用 `SysLogApp`模块功能。日志可以在站点【消息日志】->【系统日志】中查看到记录的日志信息。简单用法如下:
```csharp
//具体代码参考OpenAuth.App/OpenJobApp.cs此处简化真实逻辑方便理解
@@ -49,30 +48,9 @@ permalink: /core/log/
}
```
## EF打印Sql日志
## EF输出Sql到log4net文件/控制台)
在调试数据库时需要打印真正执行的SQL信息。最简单的方式是使用下面方法输出到控制台
```csharp
public partial class OpenAuthDBContext : DbContext
{
public static readonly ILoggerFactory MyLoggerFactory
= LoggerFactory.Create(builder => { builder.AddConsole(); });
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.EnableSensitiveDataLogging (true); //允许打印参数
optionsBuilder.UseLoggerFactory (MyLoggerFactory);
base.OnConfiguring (optionsBuilder);
}
}
```
## EF输出Sql到log4net
框架目前直接配置`appsettings.Development.json`即可完成输出sql语句到log4net对应的日志文件中。如下
框架目前直接配置 `appsettings.Development.json`即可完成输出sql语句到log4net对应的日志文件中。如下
```
"Logging": {
@@ -82,7 +60,7 @@ permalink: /core/log/
}
```
正式发布环境下,如无特殊需求,建议在`appsettings.Production.json`配置中关闭该输出
正式发布环境下,如无特殊需求,建议在 `appsettings.Production.json`配置中关闭该输出
## 在Swagger中输出日志
@@ -90,7 +68,7 @@ permalink: /core/log/
![2025-04-24-00-23-43](http://img.openauth.net.cn/2025-04-24-00-23-43.png)
点击`sql`列的时间查看详细的sql执行情况
点击 `sql`列的时间查看详细的sql执行情况
![2025-04-24-00-23-53](http://img.openauth.net.cn/2025-04-24-00-23-53.png)
@@ -119,6 +97,5 @@ public LoginResult Login(PassportLoginRequest request)
return result;
}
```
这时调用该API后即可看到具体步骤一、步骤二、步骤三的执行时间情况了

View File

@@ -18,7 +18,7 @@ permalink: /pro/table/
:table-fields="tableFields"
:data="dataList"
:edit-model="true"
:v-loading="loading"
:loading="loading"
:templates="{'id': idtemplate}"
select-type="checkbox"
@row-click="handleRowClick"