From 4ed8c0819b48470e0de57c2e210e6cbdbcb4862d Mon Sep 17 00:00:00 2001 From: yubaolee Date: Sat, 2 Aug 2025 18:53:10 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=90=9Bfix:=20#ICGHSO=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=9B=A0=E7=BB=9F=E4=B8=80Sqlsugar=E5=BC=95=E8=B5=B7?= =?UTF-8?q?=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenAuth.App/Test/TestBase.cs | 9 +++++ OpenAuth.App/UserManager/UserManagerApp.cs | 47 ++++++++++++++-------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/OpenAuth.App/Test/TestBase.cs b/OpenAuth.App/Test/TestBase.cs index 9d949e24..b6ce879f 100644 --- a/OpenAuth.App/Test/TestBase.cs +++ b/OpenAuth.App/Test/TestBase.cs @@ -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数据库处理 diff --git a/OpenAuth.App/UserManager/UserManagerApp.cs b/OpenAuth.App/UserManager/UserManagerApp.cs index 92bafb74..4661e4e0 100644 --- a/OpenAuth.App/UserManager/UserManagerApp.cs +++ b/OpenAuth.App/UserManager/UserManagerApp.cs @@ -42,29 +42,42 @@ namespace OpenAuth.App var query = SugarClient.Queryable(); if (!string.IsNullOrEmpty(request.key)) { - query = SugarClient.Queryable().Where(u => u.Name.Contains(request.key) || u.Account.Contains(request.key)); + query = query.Where(u => u.Name.Contains(request.key) || u.Account.Contains(request.key)); } + + var orgs = SugarClient.Queryable(); + 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().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().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().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((user, u) => user.ParentId == u.Id) .LeftJoin((user, u, r) => user.Id == r.FirstId && r.RelKey == Define.USERORG) .LeftJoin((user, u, r, o) => r.SecondId == o.Id); - //如果请求的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((user, u, r, o) => r.RelKey == Define.USERORG && orgIds.Contains(o.Id)); - } - else if (!ignoreAuth) //如果请求的orgId为空,即为跟节点,如果不忽略权限,只能获取到用户可以看到的机构及未分配机构的用户 - { - var orgIds = loginUser.Orgs.Select(u => u.Id).ToArray(); - //获取用户可以访问的机构的用户和没有任何机构关联的用户(机构被删除后,没有删除这里面的关联关系) - userOrgs = userOrgs.Where((user, u, r, o) => (r.RelKey == Define.USERORG && orgIds.Contains(o.Id)) || (o == null)); - } - var userOrgsResult = userOrgs.Select((user, u, r, o) => new { Account = user.Account, From c084e2409f8689fc79dca0511e7012d0540daafe Mon Sep 17 00:00:00 2001 From: yubaolee Date: Sat, 2 Aug 2025 19:06:48 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=94=A7chore:=20=E5=A2=9E=E5=8A=A0curs?= =?UTF-8?q?or=E9=A1=B9=E7=9B=AErules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/openauth.mdc | 237 +++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 .cursor/rules/openauth.mdc diff --git a/.cursor/rules/openauth.mdc b/.cursor/rules/openauth.mdc new file mode 100644 index 00000000..ec6d564e --- /dev/null +++ b/.cursor/rules/openauth.mdc @@ -0,0 +1,237 @@ +--- +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注释 +- 复杂业务逻辑必须添加行内注释 +- 使用/// 标记方法说明 +- 参数使用/// 说明 +- 返回值使用/// 说明 + +## 分层架构规则 + +### Infrastructure层 (基础设施层) +- **职责**: 提供通用工具类、扩展方法、帮助类、常量定义 +- **规则**: + - 不依赖其他业务层 + - 提供可复用的基础功能 + - 包含缓存、配置、工具类等 +- **文件组织**: + - `/Cache` - 缓存相关 + - `/Extensions` - 扩展方法 + - `/Helpers` - 帮助类 + - `/Const` - 常量定义 + +### Repository层 (数据访问层) +- **职责**: 数据访问、实体定义、数据库操作 +- **规则**: + - 继承自`BaseRepository` + - 实体类继承自`StringEntity`、`LongEntity`或`IntAutoGenEntity` + - 使用SqlSugar或EF Core进行数据访问 + - 包含DbContext配置 +- **基类使用**: + - `StringEntity`: 字符串主键实体 + - `LongEntity`: 长整型主键实体 + - `IntAutoGenEntity`: 自增整型主键实体 + +### App层 (应用服务层) +- **职责**: 业务逻辑处理、数据传输对象、业务规则 +- **规则**: + - 继承自`SqlSugarBaseApp`、`BaseStringApp`、`BaseLongApp`等基类 + - 通过构造函数注入依赖 + - 实现业务逻辑,不直接操作数据库 + - 使用Request/Response模式 +- **依赖注入**: + ```csharp + public UserManagerApp(ISqlSugarClient client, RevelanceManagerApp app, IAuth auth) + : base(client, auth) + ``` + +### WebApi层 (表示层) +- **职责**: HTTP请求处理、参数验证、响应格式化 +- **规则**: + - 继承自`ControllerBase` + - 使用`[ApiController]`特性 + - 统一返回`Response`格式 + - 进行参数验证 + - 添加Swagger文档注释 + +## 数据库操作规范 + +### SqlSugar使用规范 +```csharp +// 查询 +var users = SugarClient.Queryable() + .Where(u => u.Status == 1) + .ToList(); + +// 分页查询 +var result = new PagedListDataResp(); +var objs = SugarClient.Queryable(); +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() + .LeftJoin((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; + } + + /// + /// 获取用户列表 + /// + [HttpGet] + public async Task>> 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`: 生产环境配置 From 04142acaaa5fa7b27c3df1ecd1bf207a17f4d124 Mon Sep 17 00:00:00 2001 From: yubaolee Date: Sat, 2 Aug 2025 19:42:42 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E2=9A=A0=EF=B8=8Ffeat:=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=94=A8=E6=88=B7=E9=80=89=E6=8B=A9=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenAuth.App/OrgManager/OrgManagerApp.cs | 19 ++++++----- OpenAuth.WebApi/Controllers/OrgsController.cs | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/OpenAuth.App/OrgManager/OrgManagerApp.cs b/OpenAuth.App/OrgManager/OrgManagerApp.cs index 711d76d9..8f76c897 100644 --- a/OpenAuth.App/OrgManager/OrgManagerApp.cs +++ b/OpenAuth.App/OrgManager/OrgManagerApp.cs @@ -4,6 +4,7 @@ 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; @@ -76,16 +77,18 @@ namespace OpenAuth.App } /// - /// 加载特定用户的部门 + /// 获取所有机构 /// - /// The user unique identifier. - public List LoadForUser(string userId) + /// + public List LoadAll() { - var result = SugarClient.Queryable() - .LeftJoin((u, o) => u.SecondId == o.Id) - .Where((u, o) => u.FirstId == userId && u.RelKey == Define.USERORG) - .Select((u, o) => o); - return result.ToList(); + return SugarClient.Queryable() + .LeftJoin((org, user) => org.ChairmanId ==user.Id) + .Select((org,user)=>new OrgView + { + Id = org.Id.SelectAll(), + ChairmanName = user.Name + }).ToList(); } public OrgManagerApp(ISqlSugarClient client, IAuth auth, diff --git a/OpenAuth.WebApi/Controllers/OrgsController.cs b/OpenAuth.WebApi/Controllers/OrgsController.cs index 3d63d414..7022513b 100644 --- a/OpenAuth.WebApi/Controllers/OrgsController.cs +++ b/OpenAuth.WebApi/Controllers/OrgsController.cs @@ -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; } + /// + /// 获取所有机构 + /// + [HttpGet] + public Response> LoadAll() + { + var result = new Response>(); + 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; + } + /// /// 删除选中的部门及所有的子部门 From 628aa65938abbaaf5dc10c1169c229cadef12195 Mon Sep 17 00:00:00 2001 From: yubaolee Date: Mon, 18 Aug 2025 21:59:40 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=94=A7chore:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- newdocs/docs/notes/core/identity.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 06599916..31be1434 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,6 @@ 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"] diff --git a/newdocs/docs/notes/core/identity.md b/newdocs/docs/notes/core/identity.md index 50678668..e7cb30b5 100644 --- a/newdocs/docs/notes/core/identity.md +++ b/newdocs/docs/notes/core/identity.md @@ -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认证 ``` From e3f6374ac133fe0383c0452e39524acf4e4f99c6 Mon Sep 17 00:00:00 2001 From: yubaolee Date: Tue, 19 Aug 2025 21:43:18 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9A=A0=EF=B8=8Ffeat:=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=B8=AA=E4=BA=BA=E4=B8=AD=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Vue2/src/views/usermanager/profile.vue | 447 ++++++++++++++++++++----- 1 file changed, 366 insertions(+), 81 deletions(-) diff --git a/Vue2/src/views/usermanager/profile.vue b/Vue2/src/views/usermanager/profile.vue index 303259ce..db1996ba 100644 --- a/Vue2/src/views/usermanager/profile.vue +++ b/Vue2/src/views/usermanager/profile.vue @@ -1,81 +1,129 @@ From 6f0c09d8d5a25c45473a8e33ac1cedb557a69d58 Mon Sep 17 00:00:00 2001 From: yubaolee Date: Sat, 30 Aug 2025 19:01:06 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=90=9Bfix:=E4=BF=AE=E5=A4=8Dapp?= =?UTF-8?q?=E5=B1=82=E6=B3=A8=E9=87=8A=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenAuth.App/OpenAuth.App.csproj | 6 +++++- OpenAuth.App/SSO/PassportLoginRequest.cs | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/OpenAuth.App/OpenAuth.App.csproj b/OpenAuth.App/OpenAuth.App.csproj index ffbdaa2d..c05fd1a8 100644 --- a/OpenAuth.App/OpenAuth.App.csproj +++ b/OpenAuth.App/OpenAuth.App.csproj @@ -5,10 +5,14 @@ - bin\Debug\net5.0\OpenAuth.App.xml + bin\Debug\net9.0\OpenAuth.App.xml 1701;1702;1591;1573;1572;1570 + + bin\Release\net9.0\OpenAuth.App.xml + + diff --git a/OpenAuth.App/SSO/PassportLoginRequest.cs b/OpenAuth.App/SSO/PassportLoginRequest.cs index b982fe47..392a6609 100644 --- a/OpenAuth.App/SSO/PassportLoginRequest.cs +++ b/OpenAuth.App/SSO/PassportLoginRequest.cs @@ -5,9 +5,15 @@ namespace OpenAuth.App.SSO public class PassportLoginRequest { + /// + /// 登录账号 + /// /// System public string Account { get; set; } + /// + /// 登录密码 + /// /// 123456 public string Password { get; set; } From 56602c67f39d94701b0eee05e25ee087e2348bc6 Mon Sep 17 00:00:00 2001 From: yubaolee Date: Sat, 30 Aug 2025 20:56:53 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=90=9Bfix:=E4=BF=AE=E5=A4=8D=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Infrastructure/Infrastructure.csproj | 2 +- OpenAuth.Repository/OpenAuth.Repository.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Infrastructure/Infrastructure.csproj b/Infrastructure/Infrastructure.csproj index f8b852a5..d43eb432 100644 --- a/Infrastructure/Infrastructure.csproj +++ b/Infrastructure/Infrastructure.csproj @@ -5,7 +5,7 @@ - bin\Debug\net5.0\Infrastructure.xml + bin\Debug\net9.0\Infrastructure.xml 1701;1702;1591;1573;1572;1570 diff --git a/OpenAuth.Repository/OpenAuth.Repository.csproj b/OpenAuth.Repository/OpenAuth.Repository.csproj index 73125d25..70d47d3c 100644 --- a/OpenAuth.Repository/OpenAuth.Repository.csproj +++ b/OpenAuth.Repository/OpenAuth.Repository.csproj @@ -5,7 +5,7 @@ - bin\Debug\net5.0\OpenAuth.Repository.xml + bin\Debug\net9.0\OpenAuth.Repository.xml 1701;1702;1591;1573;1572;1570