28 Commits
8.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
yubaolee
04142acaaa ⚠️feat: 优化用户选择组件 2025-08-02 19:42:42 +08:00
yubaolee
c084e2409f 🔧chore: 增加cursor项目rules 2025-08-02 19:06:48 +08:00
yubaolee
4ed8c0819b 🐛fix: #ICGHSO 修复因统一Sqlsugar引起的BUG 2025-08-02 18:53:10 +08:00
yubaolee
217b407cd9 ⚠️feat: #ICGHSO 统一使用SqlSugar 2025-07-29 23:05:41 +08:00
yubaolee
2fb964f9d0 ⚠️feat: #ICGHSO 统一使用SqlSugar 2025-07-29 21:55:38 +08:00
yubaolee
6a8eb675e9 📃docs: 文档更新 2025-07-27 13:29:29 +08:00
yubaolee
d7f48e0537 🐛fix: #ICP1R9 当有多个租户时,默认租户不起作用 2025-07-27 12:46:21 +08:00
yubaolee
e6410c9da6 Merge branch 'main' of https://gitee.com/dotnetchina/OpenAuth.Net into main 2025-07-24 00:08:29 +08:00
yubaolee
4730ac7a36 📃docs: 文档更新 2025-07-24 00:07:50 +08:00
yubaolee
33cc0b1e3b ⚠️feat: 为前端穿梭框提供接口 2025-07-23 17:50:55 +08:00
yubaolee
87866d08fb 📃docs: 文档更新 2025-07-21 23:14:38 +08:00
58 changed files with 1688 additions and 803 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",

238
.cursor/rules/openauth.mdc Normal file
View File

@@ -0,0 +1,238 @@
---
alwaysApply: false
---
# OpenAuth.Net Cursor Rules
## 项目概述
OpenAuth.Net是一个基于.NET 9的企业级权限管理和快速开发框架采用Martin Fowler企业级应用开发思想集成了最新的技术栈。
## 技术栈
- **后端**: .NET 9, ASP.NET Core WebAPI
- **ORM**: SqlSugar (主要) + Entity Framework Core (兼容)
- **依赖注入**: Autofac
- **数据库**: 支持SqlServer、MySQL、Oracle、PostgreSQL
- **定时任务**: Quartz.NET
- **缓存**: Redis, MemoryCache
- **前端**: Vue2 + Element-UI
- **测试**: NUnit
- **文档**: Swagger
## 项目架构
```
📦OpenAuth.Net
┣ 📂Infrastructure # 基础设施层 - 工具类、扩展方法、帮助类
┣ 📂OpenAuth.Repository # 数据访问层 - 实体定义、数据访问
┣ 📂OpenAuth.App # 应用服务层 - 业务逻辑
┣ 📂OpenAuth.WebApi # 表示层 - WebAPI控制器
┣ 📂OpenAuth.Identity # 身份认证服务 - IdentityServer4
┣ 📂Vue2 # 前端项目
┗ 📂数据库脚本 # 数据库初始化脚本
```
## 编码规范
### 通用规范
- 使用C# 9+语法特性
- 遵循Microsoft C#编码规范
- 类名使用PascalCase方法名使用PascalCase
- 私有字段使用_camelCase公共属性使用PascalCase
- 常量使用UPPER_CASE
- 异步方法必须添加Async后缀
- 修改完代码后不需要编译
### 命名规范
- **Controller**: 以Controller结尾如`UsersController`
- **Service/App**: 以App结尾如`UserManagerApp`
- **Repository**: 以Repository结尾如`UserRepository`
- **Entity**: 实体类直接使用名词,如`SysUser`
- **Request**: 请求类以Req结尾如`QueryUserListReq`
- **Response**: 响应类以Resp结尾如`PagedListDataResp`
- **DTO**: 视图模型以View结尾如`UserView`
### 注释规范
- 所有公共方法必须添加XML注释
- 复杂业务逻辑必须添加行内注释
- 使用/// <summary>标记方法说明
- 参数使用/// <param name="参数名">说明</param>
- 返回值使用/// <returns>说明</returns>
## 分层架构规则
### Infrastructure层 (基础设施层)
- **职责**: 提供通用工具类、扩展方法、帮助类、常量定义
- **规则**:
- 不依赖其他业务层
- 提供可复用的基础功能
- 包含缓存、配置、工具类等
- **文件组织**:
- `/Cache` - 缓存相关
- `/Extensions` - 扩展方法
- `/Helpers` - 帮助类
- `/Const` - 常量定义
### Repository层 (数据访问层)
- **职责**: 数据访问、实体定义、数据库操作
- **规则**:
- 继承自`BaseRepository<T,TContext>`
- 实体类继承自`StringEntity`、`LongEntity`或`IntAutoGenEntity`
- 使用SqlSugar或EF Core进行数据访问
- 包含DbContext配置
- **基类使用**:
- `StringEntity`: 字符串主键实体
- `LongEntity`: 长整型主键实体
- `IntAutoGenEntity`: 自增整型主键实体
### App层 (应用服务层)
- **职责**: 业务逻辑处理、数据传输对象、业务规则
- **规则**:
- 继承自`SqlSugarBaseApp<T>`、`BaseStringApp<T>`、`BaseLongApp<T>`等基类
- 通过构造函数注入依赖
- 实现业务逻辑,不直接操作数据库
- 使用Request/Response模式
- **依赖注入**:
```csharp
public UserManagerApp(ISqlSugarClient client, RevelanceManagerApp app, IAuth auth)
: base(client, auth)
```
### WebApi层 (表示层)
- **职责**: HTTP请求处理、参数验证、响应格式化
- **规则**:
- 继承自`ControllerBase`
- 使用`[ApiController]`特性
- 统一返回`Response<T>`格式
- 进行参数验证
- 添加Swagger文档注释
## 数据库操作规范
### SqlSugar使用规范
```csharp
// 查询
var users = SugarClient.Queryable<SysUser>()
.Where(u => u.Status == 1)
.ToList();
// 分页查询
var result = new PagedListDataResp<Role>();
var objs = SugarClient.Queryable<Role>();
if (!string.IsNullOrEmpty(request.key))
{
objs = objs.Where(u => u.Name.Contains(request.key));
}
result.Data = await objs.OrderBy(u => u.Name)
.Skip((request.page - 1) * request.limit)
.Take(request.limit).ToListAsync();
result.Count = await objs.CountAsync();
return result;
// 联表查询
var result = SugarClient.Queryable<Relevance>()
.LeftJoin<SysOrg>((u, o) => u.SecondId == o.Id)
.Where((u, o) => u.FirstId == userId && u.RelKey == Define.USERORG)
.Select((u, o) => o);
return result.ToList();
```
### 事务处理
```csharp
SugarClient.Ado.BeginTran();
try
{
// 数据库操作
SugarClient.Ado.CommitTran();
}
catch
{
SugarClient.Ado.RollbackTran();
throw;
}
```
## 权限和安全规范
### 权限验证
- 使用`IAuth`接口获取当前用户信息
- 通过`_auth.GetCurrentUser()`获取登录用户
- 数据权限通过机构级联控制
## API设计规范
### 控制器设计
```csharp
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly UserManagerApp _app;
public UsersController(UserManagerApp app)
{
_app = app;
}
/// <summary>
/// 获取用户列表
/// </summary>
[HttpGet]
public async Task<Response<PagedListDataResp<UserView>>> Get([FromQuery] QueryUserListReq request)
{
var result = await _app.Load(request);
return Response.Ok(result);
}
}
```
## 依赖注入规范
### Autofac配置
- 在`AutofacExt.cs`中配置依赖注入
- 实现`IDependency`接口的类自动注册
- 使用构造函数注入
## 测试规范
### 单元测试
- 使用NUnit框架
- 继承自`TestBase`基类
- 测试类以Test结尾
- 测试方法使用描述性名称
### 集成测试
- 使用`AutofacWebApplicationFactory`
- 模拟HTTP请求
- 验证完整的业务流程
## 工作流规范
### 流程定义
- 使用`FlowScheme`定义流程模板
- 通过`FlowInstance`管理流程实例
- `FlowNode`表示流程节点
### 流程引擎
- 使用`FlowRuntime`执行流程
- 支持顺序、并行、条件等流程类型
- 流程状态通过`FlowInstanceStatus`管理
## 定时任务规范
### Quartz使用
- 继承自`IJob`接口
- 在`QuartzService`中注册任务
- 支持Cron表达式配置
## 文档规范
### 代码文档
- 重要业务逻辑添加注释
- 复杂算法添加说明
- 配置文件添加说明注释
## 配置管理
### 配置文件
- `appsettings.json`: 基础配置
- `appsettings.Development.json`: 开发环境配置
- `appsettings.Production.json`: 生产环境配置

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

@@ -0,0 +1,58 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using OpenAuth.App.Interface;
using OpenAuth.Repository.Core;
using SqlSugar;
using OpenAuth.Repository.Interface;
namespace OpenAuth.App
{
/// <summary>
/// 树状结构处理
/// </summary>
/// <typeparam name="T"></typeparam>
/// /// <typeparam name="TDbContext"></typeparam>
public class SqlSugarBaseTreeApp<T> :SqlSugarBaseApp<T> where T : TreeEntity, new()
{
public SqlSugarBaseTreeApp(ISqlSugarClient client, IAuth auth)
: base(client, auth)
{
}
/// <summary>
/// 更新树状结构实体
/// </summary>
/// <param name="obj"></param>
/// <typeparam name="U"></typeparam>
public void UpdateTreeObj<U>(U obj) where U : TreeEntity, new()
{
CaculateCascade(obj);
//获取旧的的CascadeId
var cascadeId = SugarClient.Queryable<U>().First(o => o.Id == obj.Id).CascadeId;
//根据CascadeId查询子部门
var objs = SugarClient.Queryable<U>().Where(u => u.CascadeId.Contains(cascadeId) && u.Id != obj.Id)
.OrderBy(u => u.CascadeId).ToList();
//更新操作
SugarClient.Updateable(obj).ExecuteCommand();
//更新子模块的CascadeId
foreach (var a in objs)
{
a.CascadeId = a.CascadeId.Replace(cascadeId, obj.CascadeId);
if (a.ParentId == obj.Id)
{
a.ParentName = obj.Name;
}
SugarClient.Updateable(a).ExecuteCommand();
}
}
}
}

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

@@ -4,13 +4,15 @@ using System.Linq;
using Infrastructure;
using OpenAuth.App.Interface;
using OpenAuth.App.Request;
using OpenAuth.App.Response;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.Interface;
using SqlSugar;
namespace OpenAuth.App
{
public class OrgManagerApp : BaseTreeApp<SysOrg,OpenAuthDBContext>
public class OrgManagerApp : SqlSugarBaseTreeApp<SysOrg>
{
private RevelanceManagerApp _revelanceApp;
/// <summary>
@@ -28,22 +30,20 @@ namespace OpenAuth.App
}
CaculateCascade(sysOrg);
UnitWork.ExecuteWithTransaction(() =>
{
UnitWork.Add(sysOrg);
UnitWork.Save();
SugarClient.Ado.BeginTran();
Repository.Insert(sysOrg);
//如果当前账号不是SYSTEM则直接分配
if (loginContext.User.Account != Define.SYSTEM_USERNAME)
//如果当前账号不是SYSTEM则直接分配
if (loginContext.User.Account != Define.SYSTEM_USERNAME)
{
_revelanceApp.Assign(new AssignReq
{
_revelanceApp.Assign(new AssignReq
{
type = Define.USERORG,
firstId = loginContext.User.Id,
secIds = new[] { sysOrg.Id }
});
}
});
type = Define.USERORG,
firstId = loginContext.User.Id,
secIds = new[] { sysOrg.Id }
});
}
SugarClient.Ado.CommitTran();
return sysOrg.Id;
}
@@ -58,47 +58,48 @@ namespace OpenAuth.App
return sysOrg.Id;
}
/// <summary>
/// 删除指定ID的部门及其所有子部门
/// </summary>
public void DelOrgCascade(string[] ids)
{
var delOrgCascadeIds = UnitWork.Find<SysOrg>(u => ids.Contains(u.Id)).Select(u => u.CascadeId).ToArray();
var delOrgCascadeIds = SugarClient.Queryable<SysOrg>().Where(u => ids.Contains(u.Id)).Select(u => u.CascadeId).ToArray();
var delOrgIds = new List<string>();
foreach (var cascadeId in delOrgCascadeIds)
{
delOrgIds.AddRange(UnitWork.Find<SysOrg>(u=>u.CascadeId.Contains(cascadeId)).Select(u =>u.Id).ToArray());
delOrgIds.AddRange(SugarClient.Queryable<SysOrg>().Where(u => u.CascadeId.Contains(cascadeId)).Select(u => u.Id).ToArray());
}
UnitWork.Delete<Relevance>(u =>u.RelKey == Define.USERORG && delOrgIds.Contains(u.SecondId));
UnitWork.Delete<SysOrg>(u => delOrgIds.Contains(u.Id));
UnitWork.Save();
SugarClient.Deleteable<Relevance>().Where(u => u.RelKey == Define.USERORG && delOrgIds.Contains(u.SecondId)).ExecuteCommand();
SugarClient.Deleteable<SysOrg>().Where(u => delOrgIds.Contains(u.Id)).ExecuteCommand();
}
/// <summary>
/// 加载特定用户的部门
/// 获取所有机构
/// </summary>
/// <param name="userId">The user unique identifier.</param>
public List<SysOrg> LoadForUser(string userId)
/// <returns></returns>
public List<OrgView> LoadAll()
{
var result = from userorg in UnitWork.Find<Relevance>(null)
join org in UnitWork.Find<SysOrg>(null) on userorg.SecondId equals org.Id
where userorg.FirstId == userId && userorg.RelKey == Define.USERORG
select org;
return result.ToList();
return SugarClient.Queryable<SysOrg>()
.LeftJoin<SysUser>((org, user) => org.ChairmanId ==user.Id)
.Select((org,user)=>new OrgView
{
Id = org.Id.SelectAll(),
ChairmanName = user.Name
}).ToList();
}
public OrgManagerApp(IUnitWork<OpenAuthDBContext> unitWork, IRepository<SysOrg,OpenAuthDBContext> repository,IAuth auth,
RevelanceManagerApp revelanceApp) : base(unitWork, repository, auth)
public OrgManagerApp(ISqlSugarClient client, IAuth auth,
RevelanceManagerApp revelanceApp) : base(client, auth)
{
_revelanceApp = revelanceApp;
}
public string[] GetChairmanId(string[] orgIds)
{
return Repository.Find(u => orgIds.Contains(u.Id)&&u.ChairmanId!= null).Select(u => u.ChairmanId).ToArray();
return SugarClient.Queryable<SysOrg>().Where(u => orgIds.Contains(u.Id) && u.ChairmanId != null).Select(u => u.ChairmanId).ToArray();
}
}
}

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:要修改的字段赋值
@@ -68,6 +69,11 @@ namespace OpenAuth.App
var elementIds = _revelanceApp.Get(Define.ROLERESOURCE, true, roleId);
return SugarClient.Queryable<SysResource>().Where(u => elementIds.Contains(u.Id) && (appId == null || appId =="" || u.AppId == appId)).ToArray();
}
public List<SysResource> LoadByIds(string[] ids)
{
return SugarClient.Queryable<SysResource>().Where(u => ids.Contains(u.Id)).ToList();
}
public async Task<PagedDynamicDataResp> Load(QueryResourcesReq request)
{
@@ -152,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,10 +10,12 @@ using Infrastructure;
using Microsoft.EntityFrameworkCore;
using OpenAuth.App.Request;
using OpenAuth.Repository;
using Infrastructure.Helpers;
using SqlSugar;
namespace OpenAuth.App
{
public class RoleApp : BaseStringApp<Role,OpenAuthDBContext>
public class RoleApp : SqlSugarBaseApp<Role>
{
private RevelanceManagerApp _revelanceApp;
@@ -39,7 +41,7 @@ namespace OpenAuth.App
public async Task<PagedListDataResp<Role>> LoadAll(QueryRoleListReq request)
{
var result = new PagedListDataResp<Role>();
var objs = UnitWork.Find<Role>(null);
var objs = SugarClient.Queryable<Role>();
if (!string.IsNullOrEmpty(request.key))
{
objs = objs.Where(u => u.Name.Contains(request.key));
@@ -59,7 +61,7 @@ namespace OpenAuth.App
/// <returns></returns>
public List<Role> LoadByIds(string[] ids)
{
return UnitWork.Find<Role>(u => ids.Contains(u.Id)).ToList();
return Repository.GetList(u => ids.Contains(u.Id));
}
@@ -68,41 +70,38 @@ namespace OpenAuth.App
/// </summary>
public void Add(RoleView obj)
{
UnitWork.ExecuteWithTransaction(() =>
{
Role role = obj;
role.CreateTime = DateTime.Now;
UnitWork.Add(role);
UnitWork.Save();
obj.Id = role.Id; //要把保存后的ID存入view
//如果当前账号不是SYSTEM则直接分配
var loginUser = _auth.GetCurrentUser();
if (loginUser.User.Account != Define.SYSTEM_USERNAME)
{
_revelanceApp.Assign(new AssignReq
{
type = Define.USERROLE,
firstId = loginUser.User.Id,
secIds = new[] {role.Id}
});
}
});
SugarClient.Ado.BeginTran();
Role role = obj;
role.CreateTime = TimeHelper.Now;
Repository.Insert(role);
obj.Id = role.Id; //要把保存后的ID存入view
//如果当前账号不是SYSTEM则直接分配
var loginUser = _auth.GetCurrentUser();
if (loginUser.User.Account != Define.SYSTEM_USERNAME)
{
_revelanceApp.Assign(new AssignReq
{
type = Define.USERROLE,
firstId = loginUser.User.Id,
secIds = new[] {role.Id}
});
}
SugarClient.Ado.CommitTran();
}
/// <summary>
/// 删除角色时,需要删除角色对应的权限
/// </summary>
/// <param name="ids"></param>
public override void Delete(string[] ids)
public new void Delete(string[] ids)
{
UnitWork.ExecuteWithTransaction(() =>
{
UnitWork.Delete<Relevance>(u=>(u.RelKey == Define.ROLEMODULE || u.RelKey == Define.ROLEELEMENT) && ids.Contains(u.FirstId));
UnitWork.Delete<Relevance>(u=>u.RelKey == Define.USERROLE && ids.Contains(u.SecondId));
UnitWork.Delete<Role>(u =>ids.Contains(u.Id));
UnitWork.Save();
});
SugarClient.Ado.BeginTran();
SugarClient.Deleteable<Relevance>().Where(u=>(u.RelKey == Define.ROLEMODULE || u.RelKey == Define.ROLEELEMENT) && ids.Contains(u.FirstId)).ExecuteCommand();
SugarClient.Deleteable<Relevance>().Where(u=>u.RelKey == Define.USERROLE && ids.Contains(u.SecondId)).ExecuteCommand();
SugarClient.Deleteable<Role>().Where(u =>ids.Contains(u.Id)).ExecuteCommand();
SugarClient.Ado.CommitTran();
}
/// <summary>
@@ -111,19 +110,16 @@ namespace OpenAuth.App
/// <param name="obj"></param>
public void Update(RoleView obj)
{
Role role = obj;
UnitWork.Update<Role>(u => u.Id == obj.Id, u => new Role
Repository.Update(u => new Role
{
Name = role.Name,
Status = role.Status
});
Name = obj.Name,
Status = obj.Status
}, u => u.Id == obj.Id);
}
public RoleApp(IUnitWork<OpenAuthDBContext> unitWork, IRepository<Role,OpenAuthDBContext> repository,
RevelanceManagerApp app,IAuth auth) : base(unitWork, repository, auth)
public RoleApp(ISqlSugarClient client,
RevelanceManagerApp app,IAuth auth) : base(client, auth)
{
_revelanceApp = app;
}

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

@@ -100,6 +100,15 @@ namespace OpenAuth.App.Test
Console.WriteLine($"添加数据库连接: {conn.Key} / {(dbtypes.ContainsKey(conn.Key) ? dbtypes[conn.Key] : "")},连接字符串:{conn.Value}");
}
//通过ConfigId为空判断是否有默认的连接字符串
if(!connectionConfigs.Any(x => x.ConfigId == null))
{
throw new Exception($"没有找到默认的连接字符串:{Define.DEFAULT_TENANT_ID}");
}
//把connectionConfigs排序ConfigId为空的放在最前面即默认的连接字符串必须排最前面
connectionConfigs = connectionConfigs.OrderBy(x => x.ConfigId == null ? 0 : 1).ToList();
var sqlSugar = new SqlSugarClient(connectionConfigs);
// 配置PostgreSQL数据库处理

View File

@@ -12,100 +12,120 @@ using OpenAuth.App.Response;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.Interface;
using SqlSugar;
namespace OpenAuth.App
{
public class UserManagerApp : BaseStringApp<SysUser,OpenAuthDBContext>
public class UserManagerApp : SqlSugarBaseApp<SysUser>
{
private RevelanceManagerApp _revelanceApp;
private OrgManagerApp _orgManagerApp;
public UserManagerApp(IUnitWork<OpenAuthDBContext> unitWork, IRepository<SysUser,OpenAuthDBContext> repository,
RevelanceManagerApp app,IAuth auth, OrgManagerApp orgManagerApp) : base(unitWork, repository, auth)
public UserManagerApp(ISqlSugarClient client,
RevelanceManagerApp app, IAuth auth, OrgManagerApp orgManagerApp) : base(client, auth)
{
_revelanceApp = app;
_orgManagerApp = orgManagerApp;
}
public SysUser GetByAccount(string account)
{
return Repository.FirstOrDefault(u => u.Account == account);
return SugarClient.Queryable<SysUser>().First(u => u.Account == account);
}
/// <summary>
/// 加载用户列表
/// </summary>
/// <param name="request"></param>
/// <param name="ignoreAuth">是否忽略权限,如果忽略权限,可以获取到所有用户</param>
/// <returns></returns>
private async Task<PagedListDataResp<UserView>> LoadUsers(QueryUserListReq request, bool ignoreAuth = false)
{
var loginUser = _auth.GetCurrentUser();
var query = SugarClient.Queryable<SysUser>();
if (!string.IsNullOrEmpty(request.key))
{
query = query.Where(u => u.Name.Contains(request.key) || u.Account.Contains(request.key));
}
var orgs = SugarClient.Queryable<SysOrg>();
if(!ignoreAuth) //如果没有忽略权限,则只能访问自己所在的机构
{
var orgIds = loginUser.Orgs.Select(u => u.Id).ToArray();
orgs = orgs.Where(u => orgIds.Contains(u.Id));
}
if(!string.IsNullOrEmpty(request.orgId)) //如果请求的orgId不为空加载这个机构及该机构下级的所有用户
{
var reqorg = SugarClient.Queryable<SysOrg>().First(u => u.Id == request.orgId);
var cascadeId = reqorg.CascadeId;
var orgIds = orgs.Where(u => u.CascadeId.Contains(cascadeId)).Select(u => u.Id).ToArray();
var userIds = SugarClient.Queryable<Relevance>().Where(r => r.RelKey == Define.USERORG
&& orgIds.Contains(r.SecondId)).Select(r => r.FirstId).Distinct().ToList();
query = query.Where(u => userIds.Contains(u.Id));
}else{
if(!ignoreAuth) //如果没有忽略权限,则根据用户所在的机构获取用户
{
var orgIds = orgs.Select(o => o.Id).ToArray();
var userIds = SugarClient.Queryable<Relevance>().Where(r => r.RelKey == Define.USERORG
&& orgIds.Contains(r.SecondId)).Select(r => r.FirstId).Distinct().ToList();
query = query.Where(u => userIds.Contains(u.Id));
}
//没有限制权限、没有传入orgId则query就是获取最原始的所有用户
}
var userOrgs = query
.LeftJoin<SysUser>((user, u) => user.ParentId == u.Id)
.LeftJoin<Relevance>((user, u, r) => user.Id == r.FirstId && r.RelKey == Define.USERORG)
.LeftJoin<SysOrg>((user, u, r, o) => r.SecondId == o.Id);
var userOrgsResult = userOrgs.Select((user, u, r, o) => new
{
Account = user.Account,
Name = user.Name,
Id = user.Id,
Sex = user.Sex,
Status = user.Status,
BizCode = user.BizCode,
CreateId = user.CreateId,
CreateTime = user.CreateTime,
TypeId = user.TypeId,
TypeName = user.TypeName,
ParentId = user.ParentId,
ParentName = u.Name,
Key = r.RelKey,
OrgId = o.Id,
OrgName = o.Name
});
var userViews = (await userOrgsResult.ToListAsync()).GroupBy(b => b.Account)
.Select(u => new UserView
{
Id = u.First().Id,
Account = u.Key,
Name = u.First().Name,
ParentName = u.First().ParentName,
Sex = u.First().Sex,
Status = u.First().Status,
ParentId = u.First().ParentId,
CreateTime = u.First().CreateTime,
CreateUser = u.First().CreateId,
OrganizationIds = string.Join(",", u.Select(x => x.OrgId)),
Organizations = string.Join(",", u.Select(x => x.OrgName))
});
return new PagedListDataResp<UserView>
{
Count = userViews.Count(),
Data = userViews.OrderBy(u => u.Name)
.Skip((request.page - 1) * request.limit)
.Take(request.limit).ToList(),
};
}
/// <summary>
/// 加载当前登录用户可访问的一个部门及子部门全部用户
/// 如果请求的request.OrgId为空则可以获取到已被删除机构的用户没有分配任何机构的用户
/// </summary>
public async Task<PagedDynamicDataResp> Load(QueryUserListReq request)
public async Task<PagedListDataResp<UserView>> Load(QueryUserListReq request)
{
var loginUser = _auth.GetCurrentUser();
IQueryable<SysUser> query = UnitWork.Find<SysUser>(null);
if (!string.IsNullOrEmpty(request.key))
{
query = UnitWork.Find<SysUser>(u => u.Name.Contains(request.key) || u.Account.Contains(request.key));
}
var userOrgs = from user in query
join user2 in UnitWork.Find<SysUser>(null)
on user.ParentId equals user2.Id into tempuser
from u in tempuser.DefaultIfEmpty()
join relevance in UnitWork.Find<Relevance>(u => u.RelKey == "UserOrg")
on user.Id equals relevance.FirstId into temp
from r in temp.DefaultIfEmpty()
join org in UnitWork.Find<SysOrg>(null)
on r.SecondId equals org.Id into orgtmp
from o in orgtmp.DefaultIfEmpty()
select new
{
user.Account,
user.Name,
user.Id,
user.Sex,
user.Status,
user.BizCode,
user.CreateId,
user.CreateTime,
user.TypeId,
user.TypeName,
user.ParentId,
ParentName = u.Name, //直属上级
Key = r.RelKey,
r.SecondId,
OrgId = o.Id,
OrgName = o.Name
};
//如果请求的orgId不为空
if (!string.IsNullOrEmpty(request.orgId))
{
var org = loginUser.Orgs.SingleOrDefault(u => u.Id == request.orgId);
var cascadeId = org.CascadeId;
var orgIds = loginUser.Orgs.Where(u => u.CascadeId.Contains(cascadeId)).Select(u => u.Id).ToArray();
//只获取机构里面的用户
userOrgs = userOrgs.Where(u => u.Key == Define.USERORG && orgIds.Contains(u.OrgId));
}
else //todo:如果请求的orgId为空即为跟节点这时可以额外获取到机构已经被删除的用户从而进行机构分配。可以根据自己需求进行调整
{
var orgIds = loginUser.Orgs.Select(u => u.Id).ToArray();
//获取用户可以访问的机构的用户和没有任何机构关联的用户(机构被删除后,没有删除这里面的关联关系)
userOrgs = userOrgs.Where(u => (u.Key == Define.USERORG && orgIds.Contains(u.OrgId)) || (u.OrgId == null));
}
var userViews = (await userOrgs.ToListAsync()).GroupBy(b => b.Account)
.Select(u =>new UserView
{
Id = u.First().Id,
Account = u.Key,
Name = u.First().Name,
ParentName = u.First().ParentName,
Sex = u.First().Sex,
Status = u.First().Status,
ParentId = u.First().ParentId,
CreateTime = u.First().CreateTime,
CreateUser = u.First().CreateId,
OrganizationIds = string.Join(",", u.Select(x=>x.OrgId))
,Organizations = string.Join(",", u.Select(x=>x.OrgName))
});
return new PagedDynamicDataResp
{
Count =userViews.Count(),
Data =userViews.OrderBy(u => u.Name)
.Skip((request.page - 1) * request.limit)
.Take(request.limit),
};
return await LoadUsers(request, false);
}
/// <summary>
/// 获取所有的用户
@@ -113,128 +133,66 @@ namespace OpenAuth.App
/// </summary>
public async Task<PagedListDataResp<UserView>> LoadAll(QueryUserListReq request)
{
IQueryable<SysUser> query = UnitWork.Find<SysUser>(null);
if (!string.IsNullOrEmpty(request.key))
{
query = UnitWork.Find<SysUser>(u => u.Name.Contains(request.key) || u.Account.Contains(request.key));
}
var userOrgs = from user in query
join user2 in UnitWork.Find<SysUser>(null)
on user.ParentId equals user2.Id into tempuser
from u in tempuser.DefaultIfEmpty()
join relevance in UnitWork.Find<Relevance>(u => u.RelKey == "UserOrg")
on user.Id equals relevance.FirstId into temp
from r in temp.DefaultIfEmpty()
join org in UnitWork.Find<SysOrg>(null)
on r.SecondId equals org.Id into orgtmp
from o in orgtmp.DefaultIfEmpty()
select new
{
user.Account,
user.Name,
user.Id,
user.Sex,
user.Status,
user.BizCode,
user.CreateId,
user.CreateTime,
user.TypeId,
user.TypeName,
user.ParentId,
ParentName = u.Name, //直属上级
Key = r.RelKey,
r.SecondId,
OrgId = o.Id,
OrgName = o.Name
};
//如果请求的orgId不为空
if (!string.IsNullOrEmpty(request.orgId))
{
userOrgs = userOrgs.Where(u => u.Key == Define.USERORG && u.OrgId == request.orgId);
}
var userViews = (await userOrgs.ToListAsync()).GroupBy(b => b.Account).Select(u =>new UserView
{
Id = u.First().Id,
Account = u.Key,
Name = u.First().Name,
Sex = u.First().Sex,
Status = u.First().Status,
CreateTime = u.First().CreateTime,
CreateUser = u.First().CreateId,
ParentName = u.First().ParentName,
ParentId = u.First().ParentId,
OrganizationIds = string.Join(",", u.Select(x=>x.OrgId))
,Organizations = string.Join(",", u.Select(x=>x.OrgName))
});
return new PagedListDataResp<UserView>()
{
Count = userViews.Count(),
Data = userViews.OrderBy(u => u.Name)
.Skip((request.page - 1) * request.limit)
.Take(request.limit).ToList()
};
return await LoadUsers(request, true);
}
public void AddOrUpdate(UpdateUserReq request)
{
request.ValidationEntity(u => new {u.Account,u.Name, u.OrganizationIds});
request.ValidationEntity(u => new { u.Account, u.Name, u.OrganizationIds });
if (string.IsNullOrEmpty(request.OrganizationIds))
throw new Exception("请为用户分配机构");
SysUser requser = request;
requser.CreateId = _auth.GetCurrentUser().User.Id;
UnitWork.ExecuteWithTransaction(() =>
SugarClient.Ado.BeginTran();
if (string.IsNullOrEmpty(request.Id))
{
if (string.IsNullOrEmpty(request.Id))
if (SugarClient.Queryable<SysUser>().Any(u => u.Account == request.Account))
{
if (UnitWork.Any<SysUser>(u => u.Account == request.Account))
{
throw new Exception("用户账号已存在");
}
if (string.IsNullOrEmpty(requser.Password))
{
requser.Password = requser.Account; //如果客户端没提供密码,默认密码同账号
}
requser.CreateTime = DateTime.Now;
UnitWork.Add(requser);
request.Id = requser.Id; //要把保存后的ID存入view
throw new Exception("用户账号已存在");
}
else
if (string.IsNullOrEmpty(requser.Password))
{
UnitWork.Update<SysUser>(u => u.Id == request.Id, u => new SysUser
{
Account = requser.Account,
BizCode = requser.BizCode,
Name = requser.Name,
Sex = requser.Sex,
Status = requser.Status,
ParentId = request.ParentId
});
if (!string.IsNullOrEmpty(requser.Password)) //密码为空的时候,不做修改
{
UnitWork.Update<SysUser>(u => u.Id == request.Id, u => new SysUser
{
Password = requser.Password
});
}
requser.Password = requser.Account; //如果客户端没提供密码,默认密码同账号
}
UnitWork.Save();
string[] orgIds = request.OrganizationIds.Split(',').ToArray();
_revelanceApp.DeleteBy(Define.USERORG, requser.Id);
_revelanceApp.Assign(Define.USERORG, orgIds.ToLookup(u => requser.Id));
});
requser.CreateTime = DateTime.Now;
Repository.Insert(requser);
request.Id = requser.Id; //要把保存后的ID存入view
}
else
{
Repository.Update(u => new SysUser
{
Account = requser.Account,
BizCode = requser.BizCode,
Name = requser.Name,
Sex = requser.Sex,
Status = requser.Status,
ParentId = request.ParentId
}, u => u.Id == request.Id);
if (!string.IsNullOrEmpty(requser.Password)) //密码为空的时候,不做修改
{
Repository.Update(u => new SysUser
{
Password = requser.Password
}, u => u.Id == request.Id);
}
}
string[] orgIds = request.OrganizationIds.Split(',').ToArray();
_revelanceApp.DeleteBy(Define.USERORG, requser.Id);
_revelanceApp.Assign(Define.USERORG, orgIds.ToLookup(u => requser.Id));
SugarClient.Ado.CommitTran();
}
/// <summary>
/// 删除用户,包含用户与组织关系、用户与角色关系
/// </summary>
/// <param name="ids"></param>
public override void Delete(string[] ids)
public new void Delete(string[] ids)
{
UnitWork.ExecuteWithTransaction(() =>
{
UnitWork.Delete<Relevance>(u =>(u.RelKey == Define.USERROLE || u.RelKey == Define.USERORG)
&& ids.Contains(u.FirstId));
UnitWork.Delete<SysUser>(u => ids.Contains(u.Id));
UnitWork.Save();
});
SugarClient.Ado.BeginTran();
SugarClient.Deleteable<Relevance>().Where(u => (u.RelKey == Define.USERROLE || u.RelKey == Define.USERORG)
&& ids.Contains(u.FirstId)).ExecuteCommand();
SugarClient.Deleteable<SysUser>().Where(u => ids.Contains(u.Id)).ExecuteCommand();
SugarClient.Ado.CommitTran();
}
/// <summary>
/// 修改密码
@@ -242,10 +200,10 @@ namespace OpenAuth.App
/// <param name="request"></param>
public void ChangePassword(ChangePasswordReq request)
{
Repository.Update(u => u.Account == request.Account, user => new SysUser
Repository.Update(u => new SysUser
{
Password = request.Password
});
}, u => u.Account == request.Account);
}
/// <summary>
/// 获取指定角色包含的用户列表
@@ -254,15 +212,15 @@ namespace OpenAuth.App
/// <returns></returns>
public async Task<PagedDynamicDataResp> LoadByRole(QueryUserListByRoleReq request)
{
var users = from userRole in UnitWork.Find<Relevance>(u =>
u.SecondId == request.roleId && u.RelKey == Define.USERROLE)
join user in UnitWork.Find<SysUser>(null) on userRole.FirstId equals user.Id into temp
from c in temp.Where(u =>u.Id != null)
select c;
var users = SugarClient.Queryable<Relevance>()
.Where(u => u.SecondId == request.roleId && u.RelKey == Define.USERROLE)
.LeftJoin<SysUser>((userRole, user) => userRole.FirstId == user.Id)
.Where((userRole, user) => user.Id != null)
.Select((userRole, user) => user);
return new PagedDynamicDataResp
{
Count =await users.CountAsync(),
Data =await users.Skip((request.page - 1) * request.limit).Take(request.limit).ToListAsync()
Count = await users.CountAsync(),
Data = await users.Skip((request.page - 1) * request.limit).Take(request.limit).ToListAsync()
};
}
/// <summary>
@@ -272,15 +230,15 @@ namespace OpenAuth.App
/// <returns></returns>
public async Task<PagedDynamicDataResp> LoadByOrg(QueryUserListByOrgReq request)
{
var users = from userOrg in UnitWork.Find<Relevance>(u =>
u.SecondId == request.orgId && u.RelKey == Define.USERORG)
join user in UnitWork.Find<SysUser>(null) on userOrg.FirstId equals user.Id into temp
from c in temp.Where(u =>u.Id != null)
select c;
var users = SugarClient.Queryable<Relevance>()
.Where(u => u.SecondId == request.orgId && u.RelKey == Define.USERORG)
.LeftJoin<SysUser>((userOrg, user) => userOrg.FirstId == user.Id)
.Where((userOrg, user) => user.Id != null)
.Select((userOrg, user) => user);
return new PagedDynamicDataResp
{
Count =await users.CountAsync(),
Data =await users.Skip((request.page - 1) * request.limit).Take(request.limit).ToListAsync()
Count = await users.CountAsync(),
Data = await users.Skip((request.page - 1) * request.limit).Take(request.limit).ToListAsync()
};
}
/// <summary>
@@ -293,13 +251,13 @@ namespace OpenAuth.App
{
throw new Exception("不能修改超级管理员信息");
}
Repository.Update(u => u.Account == request.Account, user => new SysUser
{
Name = request.Name,
Sex = request.Sex
});
Repository.Update(u => new SysUser
{
Name = request.Name,
Sex = request.Sex
}, u => u.Account == request.Account);
}
/// <summary>
/// 获取用户的直属上级ID
/// </summary>
@@ -311,7 +269,7 @@ namespace OpenAuth.App
{
throw new Exception("超级管理员没有直属上级,请检查配置");
}
return Repository.FirstOrDefault(u => u.Id == userid).ParentId;
return SugarClient.Queryable<SysUser>().First(u => u.Id == userid).ParentId;
}
@@ -325,35 +283,31 @@ namespace OpenAuth.App
var sql = $@"
select u.*
from sysuser u
join (select distinct SecondId as UserId
from Relevance
where RelKey = '{Define.INSTANCE_NOTICE_USER}'
and FirstId = '{instanceId}'
union
select distinct FirstId as UserId
from Relevance a
inner join (select SecondId as RoleId
from Relevance
where RelKey = '{Define.INSTANCE_NOTICE_ROLE}'
and FirstId = '{instanceId}') b on a.SecondId = b.RoleId
where RelKey = 'UserRole') userids on u.Id = userids.UserId";
var users = UnitWork.FromSql<SysUser>(sql);
return users.Select(u=>u.Id).ToList();
join (select distinct SecondId as UserId
from Relevance
where RelKey = '{Define.INSTANCE_NOTICE_USER}'
and FirstId = '{instanceId}'
union
select distinct FirstId as UserId
from Relevance a
inner join (select SecondId as RoleId
from Relevance
where RelKey = '{Define.INSTANCE_NOTICE_ROLE}'
and FirstId = '{instanceId}') b on a.SecondId = b.RoleId
where RelKey = '{Define.USERROLE}') userids on u.Id = userids.UserId";
var users = SugarClient.Ado.SqlQuery<SysUser>(sql);
return users.Select(u => u.Id).ToList();
}
public List<UserView> LoadByIds(string[] ids)
{
var users = Repository.Find(u => ids.Contains(u.Id));
var users = SugarClient.Queryable<SysUser>().Where(u => ids.Contains(u.Id));
//获取用户及用户关联的机构
var userOrgs = from user in users
join relevance in UnitWork.Find<Relevance>(u => u.RelKey == "UserOrg")
on user.Id equals relevance.FirstId into temp
from r in temp.DefaultIfEmpty()
join org in UnitWork.Find<SysOrg>(null)
on r.SecondId equals org.Id into orgtmp
from o in orgtmp.DefaultIfEmpty()
select new
var userOrgs = users
.LeftJoin<Relevance>((user, r) => user.Id == r.FirstId && r.RelKey == Define.USERORG)
.LeftJoin<SysOrg>((user, r, o) => r.SecondId == o.Id)
.Select((user, r, o) => new
{
user.Id,
user.Account,
@@ -365,21 +319,22 @@ namespace OpenAuth.App
user.ParentId,
OrgId = o.Id,
OrgName = o.Name
};
});
var userViews = userOrgs.GroupBy(b => b.Account).Select(u => new UserView
var userOrgsList = userOrgs.ToList(); // 先执行ToList()
var userViews = userOrgsList.GroupBy(b => b.Account).Select(g => new UserView
{
Id = u.First().Id,
Account = u.Key,
Name = u.First().Name,
Sex = u.First().Sex,
Status = u.First().Status,
CreateTime = u.First().CreateTime,
CreateUser = u.First().CreateId,
ParentName = u.First().ParentId,
ParentId = u.First().ParentId,
OrganizationIds = string.Join(",", u.Select(x=>x.OrgId))
,Organizations = string.Join(",", u.Select(x=>x.OrgName))
Id = g.First().Id,
Account = g.Key,
Name = g.First().Name,
Sex = g.First().Sex,
Status = g.First().Status,
CreateTime = g.First().CreateTime,
CreateUser = g.First().CreateId,
ParentName = g.First().ParentId,
ParentId = g.First().ParentId,
OrganizationIds = string.Join(",", g.Select(x => x.OrgId)),
Organizations = string.Join(",", g.Select(x => x.OrgName))
});
return userViews.ToList();

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

@@ -2,7 +2,9 @@
using Infrastructure;
using Microsoft.AspNetCore.Mvc;
using OpenAuth.App;
using OpenAuth.App.Response;
using OpenAuth.Repository.Domain;
using System.Collections.Generic;
namespace OpenAuth.WebApi.Controllers
{
@@ -79,6 +81,37 @@ namespace OpenAuth.WebApi.Controllers
return result;
}
/// <summary>
/// 获取所有机构
/// </summary>
[HttpGet]
public Response<List<OrgView>> LoadAll()
{
var result = new Response<List<OrgView>>();
try
{
result.Data = _app.LoadAll();
}
catch (CommonException ex)
{
if (ex.Code == Define.INVALID_TOKEN)
{
result.Code = ex.Code;
result.Message = ex.Message;
}
else
{
result.Code = 500;
result.Message = ex.InnerException != null
? "OpenAuth.WebAPI数据库访问失败:" + ex.InnerException.Message
: "OpenAuth.WebAPI数据库访问失败:" + ex.Message;
}
}
return result;
}
/// <summary>
/// 删除选中的部门及所有的子部门

View File

@@ -70,6 +70,14 @@ namespace OpenAuth.WebApi.Controllers
return resp;
}
[HttpPost]
public Response<List<SysResource>> LoadByIds([FromBody]string[] ids)
{
var result = new Response<List<SysResource>>();
result.Data = _app.LoadByIds(ids);
return result;
}
[HttpPost]
public Response<string> Add([FromBody] AddOrUpdateResReq obj)
{

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

@@ -107,7 +107,7 @@ namespace OpenAuth.WebApi.Controllers
/// 获取当前登录用户可访问的一个部门及子部门全部用户
/// </summary>
[HttpGet]
public async Task<PagedDynamicDataResp> Load([FromQuery]QueryUserListReq request)
public async Task<PagedListDataResp<UserView>> Load([FromQuery]QueryUserListReq request)
{
return await _app.Load(request);
}

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 =>
@@ -187,6 +193,15 @@ namespace OpenAuth.WebApi
connectionConfigs.Add(config);
logger.LogInformation($"添加数据库连接: {conn.Key} / {(dbtypes.ContainsKey(conn.Key) ? dbtypes[conn.Key] : "")},连接字符串:{conn.Value}");
}
//通过ConfigId为空判断是否有默认的连接字符串
if(!connectionConfigs.Any(x => x.ConfigId == null))
{
throw new Exception($"没有找到默认的连接字符串:{Define.DEFAULT_TENANT_ID}");
}
//把connectionConfigs排序ConfigId为空的放在最前面即默认的连接字符串必须排最前面
connectionConfigs = connectionConfigs.OrderBy(x => x.ConfigId == null ? 0 : 1).ToList();
var sqlSugar = new SqlSugarClient(connectionConfigs);
@@ -232,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
@@ -251,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

@@ -4,7 +4,7 @@ createTime: 2025/04/23 21:03:10
permalink: /core/changesdk/
---
OpenAuth.Net最新版默认使用.Net SDK 9.0.100。如果你使用的是其他版本的sdk如.net 6.0/7.0等打开项目需要调整csproj项目文件的TargetFramework。用记事本等工具打开 `Infrastructure.csproj` `OpenAuth.Repository.csproj` `OpenAuth.App.csproj` `OpenAuth.Mvc.csproj` `OpenAuth.WebApi.csproj` `OpenAuth.IdentityServer.csproj`,将
OpenAuth.Net最新版默认使用.Net SDK 9.0.100。如果你使用的是其他版本的sdk如.net 6.0/7.0等打开项目需要调整csproj项目文件的TargetFramework。用记事本等工具打开 `Infrastructure.csproj` `OpenAuth.Repository.csproj` `OpenAuth.App.csproj` `OpenAuth.WebApi.csproj` `OpenAuth.IdentityServer.csproj`,将
```csharp
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>

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认证
```
@@ -32,19 +32,11 @@ OpenAuth.Net支持两种登录认证方式Token认证和==自己搭建=={.tip
不同于其他项目的统一登录(如微信登录、支付宝登录等),OpenAuth.Net的统一登录指的是自己搭建一套OAuth登录服务提供给其他项目使用。即OpenAuth.IdentityServer。启动后直接访问[http://localhost:12796](http://localhost:12796),效果如下:
![2025-03-18-23-12-18](http://img.openauth.net.cn/2025-03-18-23-12-18.png)
这时我们修改OpenAuth.WebApi/Mvc的IdentityServerUrl配置
这时我们修改OpenAuth.WebApi的IdentityServerUrl配置
```json
"IdentityServerUrl": "http://localhost:12796", //IdentityServer服务器地址。
```
### OpenAuth.Mvc效果
当启用了Identity时mvc启动后界面如下
![2025-04-24-00-24-28](http://img.openauth.net.cn/2025-04-24-00-24-28.png)
这时点击登录超链接会跳转到OpenAuth.Identity登录界面。效果如下
![2025-04-24-00-24-40](http://img.openauth.net.cn/2025-04-24-00-24-40.png)
### OpenAuth.WebApi效果
@@ -66,7 +58,8 @@ 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一样。
这时点击登录超链接,会跳转到OpenAuth.Identity登录界面。效果如下
![2025-04-24-00-24-40](http://img.openauth.net.cn/2025-04-24-00-24-40.png)
#### SwaggerUI

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

@@ -3,12 +3,17 @@ title: 多数据库
createTime: 2025/04/23 21:03:10
permalink: /core/multidbs/
---
::: tip 提示
* 多数据库指一个应用程序同时连接和操作多个不同的数据库。
* 多租户是架构设计模式,用户登录时选择租户信息,后面就只能访问该租户对应的数据库,即只访问一个数据库。
:::
框架支持同时访问多个数据库。具体操作如下:
## 添加新连接字符串
在配置文件appsettings.json中添加新的连接字符串`OpenAuthDBContext2`
在配置文件appsettings.json中添加新的连接字符串 `OpenAuthDBContext2`
```csharp
"ConnectionStrings": {
@@ -29,7 +34,8 @@ permalink: /core/multidbs/
### 新建数据库访问基类
在项目OpenAuth.App中新建数据库访问基类比如`SqlSugarApp2`
在项目OpenAuth.App中新建数据库访问基类比如 `SqlSugarApp2`
```csharp
namespace OpenAuth.App
{
@@ -60,7 +66,7 @@ namespace OpenAuth.App
obj.Application = Assembly.GetEntryAssembly().FullName.Split(',')[0];
Repository.Insert(obj);
}
public void Update(SysLog obj)
{
Repository.Update(u => new SysLog
@@ -82,7 +88,7 @@ namespace OpenAuth.App
### 添加数据上下文
在OpenAuth.Repository中添加新的数据库上下文比如`OpenAuthDBContext2`
在OpenAuth.Repository中添加新的数据库上下文比如 `OpenAuthDBContext2`
```csharp
public partial class OpenAuthDBContext2 : DbContext
@@ -119,7 +125,7 @@ namespace OpenAuth.App
### 注入新数据库
在项目OpenAuth.WebApi的启动代码`Startup.cs`中,把`ConfigureServices`方法中注入刚刚添加的数据库上下文
在项目OpenAuth.WebApi的启动代码 `Startup.cs`中,把 `ConfigureServices`方法中注入刚刚添加的数据库上下文
```csharp
services.AddDbContext<OpenAuthDBContext2>();

View File

@@ -4,6 +4,12 @@ createTime: 2025/04/23 21:03:10
permalink: /core/multitenant/
---
::: tip 提示
* 多数据库指一个应用程序同时连接和操作多个不同的数据库。
* 多租户是架构设计模式,用户登录时选择租户信息,后面就只能访问该租户对应的数据库,即只访问一个数据库。
:::
目前市面上主流的多租户方案有三种:
1. **独立数据库** 即为不同的租户提供独立的数据库。

View File

@@ -4,165 +4,181 @@ createTime: 2025/05/24 23:43:26
permalink: /pro/selectusercom/
---
SelectUsersCom是一个用于选择用户或角色的基础组件。如下图
SelectUsersCom是一个用于选择用户或角色的组件,提供了两种使用方式
1. 对话框模式 (index.vue)
2. 输入框触发模式 (indexwithinput.vue)
![2025-05-24-12-15-46](http://img.openauth.net.cn/2025-05-24-12-15-46.png)
![2025-07-24-00-07-13](http://img.openauth.net.cn/2025-07-24-00-07-13.png)
一般通过按钮触发弹框进行选择,如下:
## 基础对话框组件 (index.vue)
```vue
<el-button @click="selectDialog = true">选择用户</el-button>
<el-dialog :destroy-on-close="true" width="850px" title="选择用户" v-model="selectDialog">
<selectUsersCom v-if="selectDialog" :ignore-auth="ignoreAuth" v-model:show="selectDialog" :loginKey="'loginUser'"
v-model:users="selectUsers" v-model:userNames="names"></selectUsersCom>
</el-dialog>
### 组件功能
- 支持选择用户或角色
- 支持组织树结构浏览
- 支持搜索功能
- 支持分页显示
- 支持已选项目可视化管理
<script setup>
import { ref } from 'vue'
import SelectUsersCom from '@/components/SelectUsersCom/index.vue'
const selectDialog = ref(false)
const selectUsers = ref([])
const names = ref('')
const ignoreAuth = ref(false)
</script>
```
还有一种通过输入框触发弹框进行选择,这时需要回填数据到文本框中。如下:
![2025-05-24-11-44-26](http://img.openauth.net.cn/2025-05-24-11-44-26.png)
```vue
<el-input @click="selectDialog = true" readonly v-model="names" :placeholder="placeholder"></el-input>
<el-dialog :destroy-on-close="true" width="850px" title="选择用户" v-model="selectDialog">
<selectUsersCom v-if="selectDialog" :ignore-auth="ignoreAuth" v-model:show="selectDialog" :loginKey="'loginUser'"
v-model:users="selectUsers" v-model:userNames="names"></selectUsersCom>
</el-dialog>
<script setup>
import { ref } from 'vue'
import SelectUsersCom from '@/components/SelectUsersCom/index.vue'
const selectDialog = ref(false)
const selectUsers = ref([])
const names = ref('')
const ignoreAuth = ref(false)
</script>
```
为了方便使用,我们在`SelectUsersCom`组件的基础上封装了`/components/SelectUsersCom/dialog.vue`组件用于带文本框的场景。使用如下:
```vue
<select-users v-model:userNames="names" :users="users" :ignore-auth="true"></select-users>
<script setup>
import { ref } from 'vue'
import SelectUsersDialog from '@/components/SelectUsersCom/dialog.vue'
const names = ref('')
const roles = ref([])
</script>
```
## 属性
### 属性说明
| 属性名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| show | Boolean | false | 控制组件显示/隐藏 |
| loginKey | String | - | 选择类型,'loginUser'表示选择用户,其他值表示选择角色 |
| orgId | String | - | 组织ID如果为空则显示左树状结构 |
| ignoreAuth | Boolean | false | 是否忽略登录用户权限,直接获取全部数据,用于可以跨部门选择用户、角色的场景 |
| hiddenFooter | Boolean | false | 是否隐藏底部的确定/取消按钮 |
| userNames | String | - | 已选用户/角色名称,逗号分隔 |
| users | Array | [] | 已选用户/角色ID列表 |
| ignoreAuth | Boolean | false | 是否忽略登录用户权限,直接获取全部数据 |
| selectType | String | 'User' | 选择类型,'User'表示选择用户,'Role'表示选择角色 |
| orgId | String | - | 组织ID如果为空则显示左树状结构 |
| userNames | String | - | 用户名称(逗号分隔) |
| users | Object | - | 初始选中项ID列表或对象列表 |
| inType | String | 'id' | 父级传入的是id列表还是对象列表取值为'id'或'obj' |
| modelValue | Boolean | false | 控制对话框显示 |
## 事件
### 事件说明
| 事件名 | 说明 | 回调参数 |
| --- | --- | --- |
| update:show | 组件显示状态变化时触发 | (show: Boolean) |
| update:userNames | 选择的用户/角色名称变化时触发 | (userNames: String) |
| update:users | 选择的用户/角色ID变化时触发 | (users: Array) |
| 事件名 | 说明 |
| --- | --- |
| update:userNames | 更新用户名称 |
| update:users | 更新用户列表 |
| save | 保存选择结果 |
| update:modelValue | 更新对话框显示状态 |
## 方法
| 方法名 | 说明 | 参数 |
| --- | --- | --- |
| handleSaveUsers | 保存已选中的用户/角色可通过ref调用 | - |
## 更多示例
### 选择角色
### 使用示例
```vue
<el-input @click="selectDialog = true" readonly v-model="names" :placeholder="placeholder"></el-input>
<el-dialog :destroy-on-close="true" width="850px" title="选择角色" v-model="selectDialog">
<selectUsersCom v-if="selectDialog" :ignore-auth="ignoreAuth" v-model:show="selectDialog" :loginKey="'loginRole'"
v-model:users="selectRoles" v-model:userNames="names"></selectUsersCom>
</el-dialog>
<template>
<el-button @click="showDialog = true">选择用户</el-button>
<SelectUsersCom
v-model="showDialog"
:ignore-auth="true"
select-type="User"
v-model:users="selectedUsers"
v-model:userNames="selectedNames"
@save="handleSave"
/>
</template>
<script setup>
import { ref } from 'vue'
import SelectUsersCom from '@/components/SelectUsersCom/index.vue'
const selectDialog = ref(false)
const selectRoles = ref([])
const names = ref('')
const ignoreAuth = ref(false)
const showDialog = ref(false)
const selectedUsers = ref([])
const selectedNames = ref('')
const handleSave = () => {
console.log('已选用户ID:', selectedUsers.value)
console.log('已选用户名称:', selectedNames.value)
}
</script>
```
为了方便使用,我们在`SelectUsersCom`组件的基础上封装了`SelectRoles`组件用于选择角色。使用如下:
## 输入框触发模式 (indexwithinput.vue)
### 组件功能
- 默认显示为输入框
- 点击输入框弹出选择对话框
- 支持根据用户ID自动获取用户姓名
- 支持选择用户并回显名称
### 属性说明
| 属性名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| users | Array/String | - | 用户ID列表或单个用户ID |
| userNames | String | '' | 用户名称(逗号分隔) |
| placeholder | String | '' | 输入框占位符 |
| ignoreAuth | Boolean | false | 是否忽略权限限制 |
### 事件说明
| 事件名 | 说明 |
| --- | --- |
| users-change | 用户选择变更时触发,参数为('users', 选中的用户ID列表)或('Texts', 选中的用户名称) |
### 使用示例
```vue
<select-roles v-model:userNames="names" :roles="roles" :ignore-auth="true"></select-roles>
<template>
<SelectUsersComInput
:users="selectedUserIds"
:user-names="selectedUserNames"
placeholder="请选择用户"
:ignore-auth="true"
@users-change="handleUsersChange"
/>
</template>
<script setup>
import { ref } from 'vue'
import SelectRoles from '@/components/SelectRoles/index.vue'
import SelectUsersComInput from '@/components/SelectUsersCom/indexwithinput.vue'
const names = ref('')
const roles = ref([])
const selectedUserIds = ref(['user1', 'user2'])
const selectedUserNames = ref('张三,李四')
const handleUsersChange = (type, value) => {
if (type === 'users') {
selectedUserIds.value = value
} else if (type === 'Texts') {
selectedUserNames.value = value
}
}
</script>
```
### 不显示底部按钮通过ref调用方法
## 角色选择输入框 (SelectRoles/indexwithinput.vue)
### 组件功能
- 基于SelectUsersCom实现的角色选择器
- 默认显示为输入框
- 点击输入框弹出选择角色对话框
### 属性说明
| 属性名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| roles | Array | - | 角色ID列表 |
| userNames | String | - | 角色名称(逗号分隔) |
| placeholder | String | - | 输入框占位符 |
| ignoreAuth | Boolean | false | 是否忽略权限限制 |
### 事件说明
| 事件名 | 说明 |
| --- | --- |
| roles-change | 角色选择变更时触发,参数为('roles', 选中的角色ID列表)或('Texts', 选中的角色名称) |
### 使用示例
```vue
<el-dialog width="80%" draggable :title="'选择用户'" v-model="dialogSelectUser">
<selectUsersCom ref="selectUserRef" v-if="dialogSelectUser" :hiddenFooter="true" :loginKey="'loginUser'"
v-model:users="selectUsers"></selectUsersCom>
<template v-slot:footer>
<div style="text-align: right">
<el-button size="small" type="info" @click="dialogSelectUser = false">
取消
</el-button>
<el-button size="small" type="primary" @click="handleSaveUsers">
确定
</el-button>
</div>
</template>
</el-dialog>
<template>
<SelectRolesInput
:roles="selectedRoleIds"
:user-names="selectedRoleNames"
placeholder="请选择角色"
:ignore-auth="true"
@roles-change="handleRolesChange"
/>
</template>
<script setup>
import { ref } from 'vue'
import SelectUsersCom from '@/components/SelectUsersCom/index.vue'
import SelectRolesInput from '@/components/SelectRoles/indexwithinput.vue'
const selectUserRef = ref(null)
const dialogSelectUser = ref(false)
const selectUsers = ref([])
const selectedRoleIds = ref(['role1', 'role2'])
const selectedRoleNames = ref('管理员,操作员')
//确定添加的用户
const handleSaveUsers = () => {
selectUserRef.value.handleSaveUsers()
dialogSelectUser.value = false
const handleRolesChange = (type, value) => {
if (type === 'roles') {
selectedRoleIds.value = value
} else if (type === 'Texts') {
selectedRoleNames.value = value
}
}
</script>
```
## 注意事项
1.`ignoreAuth``true`时,将调用`loadAll`接口获取所有数据,否则调用`getList`接口获取有权限的数据
1. 使用indexwithinput.vue时如果在弹出框中使用建议设置`:destroy-on-close="true"`避免names记录上一次选中行的值
2. 选择用户时可以通过组织树进行筛选,也可以直接搜索
3. ignoreAuth属性可以控制是否忽略用户权限限制直接获取全部数据
4. 组件内部会自动处理用户ID和用户名称的映射关系

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"