# Easy.SmartNet_多租户后台管理系统脚手架 **Repository Path**: youcanhuang/easy-smartnet ## Basic Information - **Project Name**: Easy.SmartNet_多租户后台管理系统脚手架 - **Description**: 基于 .NET 10 的多租户后台管理系统脚手架:六层架构 + RBAC 权限闭环(前端控显示 / 后端控行为)+ JWT 双令牌 + 缓存、限流、日志、数据字典,CodeFirst 开箱即跑,中文文档齐全,MIT 开源。 - **Primary Language**: C# - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 2 - **Created**: 2026-09-25 - **Last Updated**: 2026-10-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: 多租户, 脚手架, Csharp, net, Vue ## README # 🚀 Easy.SmartNet [![star](https://gitee.com/youcanhuang/easy-smartnet/badge/star.svg?theme=dark)](https://gitee.com/youcanhuang/easy-smartnet/stargazers) [![fork](https://gitee.com/youcanhuang/easy-smartnet/badge/fork.svg?theme=dark)](https://gitee.com/youcanhuang/easy-smartnet/members) > 🧭 **分支与开发指引**:**`main` 是唯一主线**——插件化架构(PR !1)已合入 `main`,包含框架装配层(`Easy.SmartNet.Framework`,宿主一行装配)、**插件模块机制**(`ISmartNetModule`)、数据访问增强(`IUnitOfWork` / 原子更新 / 乐观锁 / 悲观锁 / 树形仓储 / 软删恢复 / 批量操作 / 写入校验 / 查询投影)、缓存降级保护与业务编号生成器。日常开发请从 `main` 拉取新的功能分支(如 `feature/xxx`、`fix/xxx`),完成后以 PR 合回 `main`;历史分支 `feature/plugin-modular` 已完成使命,仅作留存。 > > ⚠️ **使用提示**:插件模块机制目前只在集成测试的示例插件(`tests/…/TestPlugins/TestModule`)中验证过,**尚未经真实业务插件完整验证**;新增数据访问能力(UoW / 批量 / 唯一码 / 悲观锁等)在业务层尚无真实调用。首次在生产插件中使用这些能力时,建议同步补齐对应集成测试。 通用后台管理系统脚手架 —— 基于 **.NET 10 + SqlSugar + Redis** 的多层架构快速开发框架,内置 **多租户隔离、RBAC 权限、软删除、审计字段、JWT 认证** 等企业级通用能力,开箱即用。 ## 📚 文档导航 | 文档 | 内容 | 适合谁 | | --- | --- | --- | | [README.md](./README.md)(本文) | 快速上手、配置说明、API 一览、Roadmap | 所有人 | | 🏛️ [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) | 总体架构:分层设计、请求处理流程、横切能力、数据模型、扩展指南 | 想了解项目全貌、准备二次开发的使用者 | | 📦 [docs/MODULES.md](./docs/MODULES.md) | 模块详解:每个模块的职责与接口,以及模块之间如何协作(含典型时序) | 想理解功能边界与协作关系的开发者 | | 🧬 [docs/ENTITIES.md](./docs/ENTITIES.md) | 实体继承指南:基类与行为接口的作用、多租户与否的选型决策、自定义组合 | 准备新建业务实体/模块的开发者 | ## 💡 项目简介 Easy.SmartNet 旨在提供一套结构清晰、职责分明的后台管理系统基础框架,把业务开发中反复出现的横切能力(认证、多租户、权限、审计、分页、软删除、统一响应)下沉到框架层,让上层业务只需关心领域逻辑本身。 目前已实现的功能模块: - **认证模块**:租户编码 + 用户名 + 密码登录,签发 JWT(含角色声明),刷新令牌轮换、退出吊销、当前用户信息与菜单树接口 - **权限控制(RBAC 闭环)**:`/me` + `/me/menus` 前端控显示,`[HasPermission]` 后端控行为 - **用户管理**:分页查询、详情、新增、更新、重置密码、软删除、分配角色 - **角色管理**:分页查询、列表、详情、新增、更新、删除、分配菜单 - **菜单管理**:菜单树(目录/菜单/按钮三级)、新增、更新、删除、角色绑定菜单 - **租户管理**:分页查询、详情、新增、更新、删除 - **日志审计**:登录日志、操作日志(请求体脱敏)与查询接口 - **数据字典**:字典类型/项两级管理,`/api/Dict/{code}` 供下拉与状态渲染,缓存 + 变更失效 - **部门与数据权限**:组织架构树,角色数据范围(本租户全部/本部门及以下/仅本部门/仅本人) - **系统参数**:键值配置管理,`/api/SysConfig/{key}` 按键读取带缓存,改动无需发版 - **文件存储**:`IFileStorage` 抽象(本地磁盘实现,可扩展 OSS),上传/下载/管理,白名单 + 大小上限 - **通知公告**:草稿/发布/撤回状态机,阅读端仅可见已发布(置顶优先) - **登录验证码**:零依赖 SVG 图形验证码,一次性缓存校验(可配置开关) - **Excel 导入导出**:用户列表导出 + 批量导入(行级校验与失败明细),MiniExcel 流式读写 - **定时任务**:Hangfire 周期作业 + /hangfire 超管专属管理面板 - **在线用户**:在线会话列表与强制下线(jti 黑名单即时生效) ## 🖼️ 系统预览 | 工作台 | 用户管理 | | --- | --- | | ![工作台](docs/images/dashboard.png) | ![用户管理](docs/images/user-management.png) | | 角色管理 | 菜单管理 | | --- | --- | | ![角色管理](docs/images/role-management.png) | ![菜单管理](docs/images/menu-management.png) | | 租户管理 | 部门管理 | | --- | --- | | ![租户管理](docs/images/tenant-management.png) | ![部门管理](docs/images/dept-management.png) | | 字典管理 | 系统配置 | | --- | --- | | ![字典管理](docs/images/dict-management.png) | ![系统配置](docs/images/config-management.png) | | 文件管理 | 通知公告 | | --- | --- | | ![文件管理](docs/images/file-management.png) | ![通知公告](docs/images/notice-management.png) | | 在线用户 | 日志查询 | | --- | --- | | ![在线用户](docs/images/online-users.png) | ![日志查询](docs/images/log-query.png) | ## 🧰 技术栈 | 分类 | 技术 | | --- | --- | | 运行时 | .NET 10(ASP.NET Core Web API) | | ORM | SqlSugar 5.1(CodeFirst + 雪花 Id) | | 数据库 | MySQL(可按配置切换 SqlSugar 支持的其他库) | | 缓存 | Redis(StackExchange.Redis 分布式缓存,未配置自动回退内存缓存) | | 认证 | JWT Bearer(HmacSha256)+ 动态权限策略 | | 文档 | Swashbuckle 10(Swagger UI,支持 XML 注释) | | 密码 | PBKDF2-SHA256(hash.salt 存储,固定时间比较) | | 前端 | Vue 3 + TypeScript + Vite + Element Plus + Pinia + Vue Router(`web/` 目录) | ## 🏗️ 解决方案结构 解决方案采用经典分层(Clean Architecture 风格)+ 框架装配层 + 插件模块机制: ```mermaid graph TD Api["Easy.SmartNet.Api
薄宿主(启动入口 + 控制器)"] Module["Easy.SmartNet.Module.*
插件模块(垂直切片)"] Framework["Easy.SmartNet.Framework
框架装配 + 插件加载器"] Service["Easy.SmartNet.Service
业务服务实现"] IService["Easy.SmartNet.IService
服务接口 + DTO"] Infrastructure["Easy.SmartNet.Infrastructure
JWT / Redis / 授权组件"] Repository["Easy.SmartNet.Repository
SqlSugar 仓储 / 数据库初始化"] Core["Easy.SmartNet.Core
实体 / 公共组件 / 抽象接口"] Api --> Framework Module --> Framework Module --> Core Framework --> Service Framework --> Infrastructure Framework --> Repository Service --> IService Service --> Repository Service --> Infrastructure IService --> Core Infrastructure --> Core Repository --> Core ``` | 项目 | 职责 | 关键内容 | | --- | --- | --- | | `Easy.SmartNet.Core` | 最底层共享库,不依赖任何其他项目 | 实体基类、枚举、`ApiResult` / `PageInput` / `PageResult`、`PasswordHasher`、`RateLimiter`、`IRepository` 等抽象接口 | | `Easy.SmartNet.IService` | 应用服务契约层 | `IAuthService` / `IUserService` / `ITenantService` 及输入输出 DTO | | `Easy.SmartNet.Repository` | 数据访问层 | `SqlSugarContext`(单例 SqlSugarScope)、泛型 `Repository`、`DatabaseInitializer` | | `Easy.SmartNet.Infrastructure` | 基础设施层 | `JwtTokenService`、`CurrentUserContext`、缓存、`[HasPermission]` / `[RateLimit]` 组件、配置选项 | | `Easy.SmartNet.Service` | 框架内置业务服务 | 认证、用户/角色/菜单/租户/字典/日志等服务与 DI 注册 | | `Easy.SmartNet.Framework` | 框架装配层 | `AddSmartNetFramework` / `UseSmartNetFrameworkAsync` 一行装配全部能力;`ISmartNetModule` 契约与模块加载器 | | `Easy.SmartNet.Api` | 薄宿主入口 | 启动代码数行 + 各控制器(控制器也可下沉到模块) | | `Easy.SmartNet.Module.*`(规划中) | 插件模块 | 业务模块以独立项目垂直切片(实体+服务+控制器),实现 `ISmartNetModule` 即被自动装载 | ## 🧩 插件模块机制 业务模块可以做成独立项目(垂直切片),实现 `ISmartNetModule` 契约后由框架自动装载,宿主零感知: ```csharp public class WmsModule : ISmartNetModule { public string Name => "wms"; public void ConfigureServices(IServiceCollection services, IConfiguration configuration) => services.AddScoped(); // 模块服务 public IEnumerable GetEntityTypes() => new[] { typeof(Warehouse), typeof(StorageLocation) }; // 并入 CodeFirst 建表 public Task SeedAsync(ISqlSugarClient db, IServiceProvider services) => /* 模块菜单/权限/字典种子(幂等) */ Task.CompletedTask; } ``` 装载管线:宿主引用模块项目 → `AddSmartNetModules()` 扫描发现 → 模块服务注册 → 控制器自动发现(ApplicationPart)→ 实体并入建表 → 种子数据执行。模块内接口的权限校验、操作日志、限流、多租户隔离等框架能力全部自动生效。 ## ✨ 核心特性 ### 🏢 1. 多租户隔离 - 租户信息由 `Tenant` 实体维护(租户编码全局唯一,用于登录识别)。 - 业务实体继承 `FullAuditedEntity` 自动携带 `TenantId`。 - 仓储层 `Repository.BuildQuery()` 统一注入过滤:**非超管** 的所有查询自动追加 `TenantId = 当前租户`,插入时自动填充当前租户。 - 超级管理员(`IsSuperAdmin=true`,JWT 中无租户声明)可跨租户查询与指定租户写入。 - 登录属于"未登录场景",通过 `IUserRepository.FindForLoginAsync()` 显式绕过多租户过滤。 ### 🗑️ 2. 软删除 + 审计字段 - 实体实现 `ISoftDelete` 后,查询自动过滤 `IsDeleted = 0`,删除操作自动转为软删除。 - 插入/更新时由仓储统一填充 `CreatedTime / UpdatedTime / CreatedBy / UpdatedBy`,无需业务代码关心。 - 主键统一为 `Id`(long 雪花 Id),由 SqlSugar 自动生成。 ### 🔐 3. 认证与安全 - 登录流程:校验租户(存在性/状态/有效期)→ 查询用户 → PBKDF2 验证密码 → 签发 JWT(claims:`uid`/`name`/`tid`/`sadmin`)→ 更新最后登录时间。 - `ICurrentUserContext` 抽象当前用户上下文,由 Infrastructure 层从 `HttpContext` 的 JWT claims 实现,供仓储/服务注入使用。 - 密码使用 PBKDF2-SHA256(1 万次迭代),存储格式 `base64(hash).base64(salt)`,比较使用固定时间算法。 - 密码强度策略 `PasswordPolicy`:8~64 位且必须同时包含字母和数字,应用于创建用户/重置密码/修改密码三个入口;个人修改密码成功后自动吊销当前令牌强制重新登录。 - 登录验证码:`GET /api/Auth/captcha` 获取 SVG 图形(一次性,5 分钟有效),登录时随 `CaptchaId/CaptchaCode` 提交校验;`Captcha:Enabled` 开关控制(默认启用,开发配置已关闭便于联调)。 - 登录接口叠加 `[RateLimit(10, 60)]` 限流(同 IP 每分钟 10 次),与失败锁定(15 分钟 5 次)构成双层防爆破。 ### 🔑 4. 角色权限(RBAC) - `Role`(租户内隔离)+ `Menu`(全局菜单树:目录/菜单/按钮三级)+ `UserRole` / `RoleMenu` 关系表。 - 登录时聚合用户角色编码写入 JWT `roles` 声明,`ICurrentUserContext.Roles` 可在任意层读取。 - **前端控显示**:`GET /api/Auth/me` 返回角色编码与权限编码集合(按钮显隐);`GET /api/Auth/me/menus` 返回按角色过滤的菜单树(自动补全父级链,超管返回全部)。 - **后端控行为**:接口标注 `[HasPermission("system:user:create")]`,经动态授权策略(`PermissionPolicyProvider` + `PermissionAuthorizationHandler`)校验权限编码,超管放行;无权限返回 403 JSON(与 `ApiResult` 同构)。 - 权限集合带 5 分钟缓存,角色/菜单/用户角色变更时主动失效(`IPermissionService`)。 - **数据范围(数据权限)**:`Role.DataScope` 在租户隔离上进一步收敛可见行——本租户全部/本部门及以下/仅本部门/仅本人;仓储层对实现 `IDeptEntity` 的实体自动追加过滤,`IDataScopeProvider` 每请求解析即时生效。 - 删除角色/菜单时自动清理绑定关系;关系表为纯主键表,物理删除。 ### 🔄 5. 刷新令牌与退出登录 - 登录同时签发**访问令牌**(JWT,含 `jti` 唯一标识)与**刷新令牌**(随机 64 字节,有效期 7 天)。 - `POST /api/Auth/refresh`:以刷新令牌换取新令牌对,旧刷新令牌**一次一换立即失效**(轮换防重放)。 - `POST /api/Auth/logout`:当前访问令牌的 `jti` 进入缓存黑名单(JWT 中间件每次请求校验),刷新令牌同步删除。 ### ⚡ 6. Redis 缓存接入 - 统一缓存抽象 `ICacheService`(Core 层定义),Infrastructure 层基于 `IDistributedCache` 实现:配置了 Redis 用 Redis,未配置自动回退进程内内存缓存。 - **登录失败锁定**:同一租户+用户名 15 分钟窗口内密码错误 5 次,锁定拒绝登录;登录成功自动清零。 - **热点数据缓存**:登录时"租户编码 → 租户"查询缓存 10 分钟,降低数据库压力。 - **接口限流**:`[RateLimit]` 特性 + 滑动窗口计数算法,登录 10 次/分钟/IP、刷新令牌 30 次/分钟/IP,超限返回 429 JSON + Retry-After。 - 缓存键统一在 `CacheKeys` 中约定,实际存储自动叠加 `Redis:InstanceName` 前缀。 ### 🌱 7. 数据库自动初始化 `Database:AutoInit=true` 时,应用启动即自动完成: 1. 建库(`CREATE DATABASE IF NOT EXISTS`,utf8mb4) 2. CodeFirst 建表(`Tenant`、`User`、`Role`、`Menu`、`UserRole`、`RoleMenu`、`LoginLog`、`OperationLog`) 3. 种子数据:默认租户 `default` + 超级管理员 `admin / Admin@123` + 系统管理菜单树(含按钮权限)+ `superadmin`(全局)/ `tenant-admin`(默认租户)角色及菜单绑定 + 内置字典(`gender`/`user_status`/`tenant_status`) ### 🚨 8. 全局异常处理与统一错误码 - 业务异常统一抛出 `ApiException`(Core 层定义),由 Api 层 `ExceptionHandlingMiddleware` 在管道最外层捕获。 - 业务异常返回 HTTP 200 + `ApiResult`(Code=业务错误码,默认 1);未处理异常记录错误日志后返回 HTTP 500 + `ApiResult(Code=500)`,不向前端泄露堆栈细节。 - Controller 不再需要手写 try/catch,Service 层抛出 `ApiException` 即可中断业务并返回友好错误。 ### 📄 9. 统一响应与分页 - 所有接口返回 `ApiResult` / `ApiResult`(`Code=0` 表示成功)。 - 分页统一走 `PageInput`(页码/条数/排序字段/升降序)→ `PageResult`(含总页数计算)。 - 分页参数在仓储层统一收敛:页码 ≥1、每页条数 1~`PageInput.MaxPageSize`(200),防止外部传大值一次拉取过量数据。 - 分页排序字段经实体属性名白名单校验(反射缓存),非法字段自动回退主键 `Id`,杜绝 `OrderBy` 拼接 SQL 注入。 ### 📋 10. 操作日志与登录日志 - **登录日志**:`AuthService` 记录每次登录尝试(无论成败)的用户名、租户、结果、IP 与 User-Agent。 - **操作日志**:Api 层 `OperationLogFilter` 自动记录 POST/PUT/DELETE 请求的方法、路径、请求体(敏感字段如 password 脱敏、超长截断)、状态码、耗时与异常信息;写入失败不影响业务响应。 - 日志查询接口:`/api/Log/loginPage`、`/api/Log/operationPage`,非超管自动按租户过滤。 ### 🧱 11. 事务与数据访问增强 - **IUnitOfWork 事务**:Service 层 `BeginAsync()` 开启事务边界,跨多表写入同成功同失败;支持嵌套(引用计数,只有最外层提交生效),未显式提交即 Dispose 自动回滚。 - **仓储原子更新**:`DecrementAsync` / `IncrementAsync` 数据库侧原子递增递减(`SET 列=列-@value WHERE 列>=@value`),条件不满足返回 false,防并发超卖;列名经数值属性白名单校验,软删除与租户过滤自动附加(原子操作不绕过隔离边界)。 - **ISqlQueryExecutor 受控查询口**:报表/复杂聚合的参数化原生查询,只读守卫强制仅放行 SELECT/WITH;多租户/软删除条件需在 SQL 中自行携带。 ## 🌐 API 一览 | 模块 | 方法 | 路径 | 说明 | 认证 | | --- | --- | --- | --- | --- | | 认证 | POST | `/api/Auth/login` | 登录获取访问令牌 + 刷新令牌 | 匿名 | | 认证 | POST | `/api/Auth/refresh` | 刷新令牌(旧刷新令牌一次一换) | 匿名 | | 认证 | POST | `/api/Auth/logout` | 退出登录(访问令牌进 jti 黑名单) | ✅ | | 认证 | GET | `/api/Auth/me` | 当前用户信息(角色 + 权限编码) | ✅ | | 认证 | GET | `/api/Auth/me/menus` | 当前用户菜单树(导航渲染) | ✅ | | 认证 | POST | `/api/Auth/change-password` | 修改自己密码(验原密码,成功后吊销当前令牌) | ✅ | | 认证 | PUT | `/api/Auth/profile` | 修改自己资料(姓名/邮箱/手机号/性别) | ✅ | | 用户 | GET | `/api/User/page` | 分页查询(关键字/租户/状态过滤) | ✅ | | 用户 | GET | `/api/User/{id}` | 用户详情 | ✅ | | 用户 | POST | `/api/User` | 新增用户 | ✅ | | 用户 | PUT | `/api/User` | 更新用户(不含密码) | ✅ | | 用户 | POST | `/api/User/{id}/reset-password` | 重置密码 | ✅ | | 用户 | DELETE | `/api/User/{id}` | 删除用户(软删除) | ✅ | | 用户 | GET | `/api/User/{id}/roles` | 用户已分配角色 | ✅ | | 用户 | PUT | `/api/User/{id}/roles` | 分配角色(全量覆盖) | ✅ | | 角色 | GET | `/api/Role/page` | 分页查询(关键字过滤) | ✅ | | 角色 | GET | `/api/Role/list` | 角色列表(下拉选择) | ✅ | | 角色 | GET | `/api/Role/{id}` | 角色详情 | ✅ | | 角色 | POST | `/api/Role` | 新增角色(编码租户内唯一) | ✅ | | 角色 | PUT | `/api/Role` | 更新角色 | ✅ | | 角色 | DELETE | `/api/Role/{id}` | 删除角色(清理绑定) | ✅ | | 角色 | GET | `/api/Role/{id}/menus` | 角色已绑定菜单 Id | ✅ | | 角色 | PUT | `/api/Role/{id}/menus` | 分配菜单(全量覆盖) | ✅ | | 菜单 | GET | `/api/Menu/tree` | 完整菜单树(目录/菜单/按钮) | ✅ | | 菜单 | GET | `/api/Menu/{id}` | 菜单详情 | ✅ | | 菜单 | POST | `/api/Menu` | 新增菜单 | ✅ | | 菜单 | PUT | `/api/Menu` | 更新菜单 | ✅ | | 菜单 | DELETE | `/api/Menu/{id}` | 删除菜单(有子级禁止) | ✅ | | 日志 | GET | `/api/Log/loginPage` | 登录日志分页(用户名/结果过滤) | ✅ | | 日志 | GET | `/api/Log/operationPage` | 操作日志分页(用户名/路径过滤) | ✅ | | 字典 | GET | `/api/Dict/{dictCode}` | 字典启用项列表(下拉/状态渲染,缓存) | 登录 | | 字典 | GET | `/api/Dict/type/page` | 字典类型分页(关键字过滤) | ✅ | | 字典 | GET | `/api/Dict/type/{id}` | 字典类型详情 | ✅ | | 字典 | POST | `/api/Dict/type` | 新增字典类型(编码全局唯一) | ✅ | | 字典 | PUT | `/api/Dict/type` | 更新字典类型(编码不可改) | ✅ | | 字典 | DELETE | `/api/Dict/type/{id}` | 删除字典类型(级联删项) | ✅ | | 字典 | GET | `/api/Dict/data/page` | 字典项分页(按字典编码过滤) | ✅ | | 字典 | POST | `/api/Dict/data` | 新增字典项(值同字典下唯一) | ✅ | | 字典 | PUT | `/api/Dict/data` | 更新字典项 | ✅ | | 字典 | DELETE | `/api/Dict/data/{id}` | 删除字典项(内置项禁删) | ✅ | | 部门 | GET | `/api/Dept/tree` | 部门树(组织架构) | ✅ | | 部门 | GET | `/api/Dept/{id}` | 部门详情 | ✅ | | 部门 | POST | `/api/Dept` | 新增部门(同父级名称唯一) | ✅ | | 部门 | PUT | `/api/Dept` | 更新部门 | ✅ | | 部门 | DELETE | `/api/Dept/{id}` | 删除部门(有子级/用户禁止) | ✅ | | 配置 | GET | `/api/SysConfig/{key}` | 按键读取参数值(缓存) | 登录 | | 配置 | GET | `/api/SysConfig/page` | 参数分页(关键字过滤) | ✅ | | 配置 | GET | `/api/SysConfig/detail/{id}` | 参数详情 | ✅ | | 配置 | POST | `/api/SysConfig` | 新增参数(键全局唯一) | ✅ | | 配置 | PUT | `/api/SysConfig` | 更新参数(键不可改) | ✅ | | 配置 | DELETE | `/api/SysConfig/{id}` | 删除参数(内置禁删) | ✅ | | 文件 | POST | `/api/File/upload` | 上传文件(白名单 + 大小上限,限流 30 次/分钟) | 登录 | | 文件 | GET | `/api/File/{id}/download` | 下载文件(还原原始文件名) | 登录 | | 文件 | GET | `/api/File/page` | 文件记录分页(关键字/业务分类过滤) | ✅ | | 文件 | DELETE | `/api/File/{id}` | 删除文件(物理文件 + 记录) | ✅ | | 公告 | GET | `/api/Notice/current` | 已发布公告分页(置顶优先,阅读端) | 登录 | | 公告 | GET | `/api/Notice/detail/{id}` | 已发布公告详情(阅读端) | 登录 | | 公告 | GET | `/api/Notice/page` | 公告分页(全部状态,管理端) | ✅ | | 公告 | GET | `/api/Notice/{id}` | 公告详情(含草稿,管理端) | ✅ | | 公告 | POST | `/api/Notice` | 新增公告(草稿) | ✅ | | 公告 | PUT | `/api/Notice` | 更新公告 | ✅ | | 公告 | DELETE | `/api/Notice/{id}` | 删除公告 | ✅ | | 公告 | POST | `/api/Notice/{id}/publish` | 发布公告 | ✅ | | 公告 | POST | `/api/Notice/{id}/withdraw` | 撤回公告 | ✅ | | 认证 | GET | `/api/Auth/captcha` | 获取登录验证码(SVG,一次性) | 匿名 | | 用户 | GET | `/api/User/export` | 导出用户列表 xlsx(同分页过滤条件) | ✅ | | 用户 | GET | `/api/User/import-template` | 下载导入模板(含示例行) | ✅ | | 用户 | POST | `/api/User/import` | 批量导入(行级校验 + 失败明细) | ✅ | | 在线 | GET | `/api/Online/page` | 在线会话分页(非超管限本租户) | ✅ | | 在线 | POST | `/api/Online/{userId}/kick` | 强制下线(吊销全部会话) | ✅ | | 租户 | GET | `/api/Tenant/page` | 分页查询(关键字过滤) | ✅ | | 租户 | GET | `/api/Tenant/{id}` | 租户详情 | ✅ | | 租户 | POST | `/api/Tenant` | 新增租户(编码唯一校验) | ✅ | | 租户 | PUT | `/api/Tenant` | 更新租户 | ✅ | | 租户 | DELETE | `/api/Tenant/{id}` | 删除租户 | ✅ | | 健康 | GET | `/health` | 健康检查 | 匿名 | ## ⚙️ 配置说明(appsettings.json) | 配置节 | 说明 | 默认值 | | --- | --- | --- | | `ConnectionStrings:DefaultConnection` | MySQL 连接串 | `127.0.0.1:3306` 库名 `EasySmartNet` | | `SqlSugar:DbType` | 数据库类型 | `MySql` | | `Database:AutoInit` | 启动时自动建库/建表/种子数据 | `true`(生产环境建议关闭) | | `Jwt:SecretKey` | JWT 密钥(≥ 32 字符) | 开发占位值,**上线必须更换** | | `Jwt:ExpireMinutes` | Token 有效期(分钟) | `720` | | `Redis:ConnectionString` | Redis 连接串 | `127.0.0.1:6379` | | `Redis:InstanceName` | 缓存键前缀 | `EasySmartNet` | | `Log:AutoCleanupEnabled` | 日志自动清理开关(每日一次) | `true` | | `Log:RetentionDays` | 日志保留天数(≤0 表示不清理;`/api/Log/cleanup` 未传参时也取此值) | `90` | | `File:StorageType` / `LocalRoot` / `MaxSizeMB` | 文件存储类型(Local)/本地根目录/单文件大小上限 | `Local` / `uploads` / `20` | | `Captcha:Enabled` / `ExpireSeconds` | 登录验证码开关(生产建议开启)与有效期 | `false` / `300` | ## ▶️ 快速开始 环境要求:.NET 10 SDK、MySQL 8.x(Redis 可选,未配置时缓存自动回退为进程内内存缓存)。 ```bash # 1. 按需修改 src/Easy.SmartNet.Api/appsettings.json 中的数据库连接串 # 2. 启动(首次启动自动建库建表并写入种子数据) dotnet run --project src/Easy.SmartNet.Api # 3. 打开 Swagger(开发环境根路径即 Swagger UI) # https://localhost:7258 或 http://localhost:5260 # 运行全部测试(集成测试在 Docker 不可用时自动跳过) dotnet test Easy.SmartNet.slnx # 只跑单元测试 / 只跑集成测试(需 Docker) dotnet test Easy.SmartNet.slnx --filter ProjectName!=Easy.SmartNet.IntegrationTests dotnet test Easy.SmartNet.slnx --filter ProjectName=Easy.SmartNet.IntegrationTests ``` 默认超级管理员账号:**admin / Admin@123**(超管登录时租户编码留空;租户用户登录需填对应 `TenantCode`)。 ## 🖥️ 前端(web/) `web/` 目录是基于 **Vue 3 + TypeScript + Vite + Element Plus + Pinia** 的单页管理端,覆盖全部 13 个后端模块的页面。 ```bash cd web npm install npm run dev # http://localhost:5173(/api 代理到后端 5260) npm run build # 类型检查 + 产物构建 ``` 前端要点: - **动态菜单**:侧边栏与可导航路径来自 `/api/Auth/me/menus`(前端控显示); - **按钮权限**:`v-permission="'system:user:create'"` 指令,权限编码与后端 `[HasPermission]` 同源(`/api/Auth/me`); - **令牌续期**:401 自动用刷新令牌轮换并重放请求;后端控行为,403 统一提示; - **开发期验证码**:后端 `Captcha:Enabled=false` 时登录无需验证码,开启后登录页自动展示图形验证码。 ## 🐳 Docker 部署 仓库自带一键编排(MySQL 8.4 + Redis 7 + API): ```bash docker compose up -d --build # API 地址:http://localhost:8080(Development 环境根路径即 Swagger UI) # 首次启动自动建库建表并写入种子数据;上传文件持久化在 file-data 卷 # 生产部署前请按 docker-compose.yml 顶部注释更换 JWT 密钥、关闭 AutoInit、开启验证码 ``` ## 👷 CI/CD `.github/workflows/ci.yml` 在 push/PR 时自动执行:`dotnet restore → build → test`(含依赖 Docker 的集成测试)→ `docker build` 验证镜像可构建。 ## 📐 设计约定 - 实体主键统一命名为 `Id`,SqlSugar 的 `EntityService` 按约定自动识别为主键,避免在 Core 层引入 ORM 特性。 - 实体属性的可空标注(`string?`、`DateTime?` 等)自动映射为数据库可空列(NRT 兼容)。 - 审计与租户填充集中在仓储层完成,Service/Controller 无需重复处理。 - 宿主通过 `AddSmartNetFramework` / `UseSmartNetFrameworkAsync` 一行完成框架装配;业务模块实现 `ISmartNetModule` 即被自动装载。 ## ✍️ 提交规范 Commit Message 采用 emoji + 类型前缀,格式:` : <描述>` | emoji | 类型 | 用途 | | --- | --- | --- | | 🎉 | `init` | 初始化项目 | | ✨ | `feat` | 新功能 | | 🐛 | `fix` | 缺陷修复 | | 📝 | `docs` | 文档变更 | | ♻️ | `refactor` | 重构 | | ⚡ | `perf` | 性能优化 | | ✅ | `test` | 测试相关 | | 🔧 | `chore` | 构建/配置/杂项 | | 👷 | `ci` | CI/CD 流水线变更 | ## 📂 目录结构 ``` Easy.SmartNet/ ├── Easy.SmartNet.slnx ├── Dockerfile / docker-compose.yml # 一键容器化部署(MySQL + Redis + API) ├── .github/workflows/ci.yml # CI:构建 + 测试 + 镜像验证 ├── README.md # 快速上手 + API 一览(文档导航入口) ├── docs/ │ ├── ARCHITECTURE.md # 总体架构文档 ├── ENTITIES.md # 实体继承指南(基类选择与行为接口) │ └── MODULES.md # 模块功能与协作文档 ├── src/ │ ├── Easy.SmartNet.Api/ # 薄宿主:启动入口 + 控制器、配置文件 │ ├── Easy.SmartNet.Framework/ # 框架装配 + 插件模块契约与加载器 │ ├── Easy.SmartNet.Core/ # 实体、枚举、公共组件、抽象接口 │ ├── Easy.SmartNet.IService/ # 服务接口、DTO │ ├── Easy.SmartNet.Infrastructure/ # JWT、缓存、当前用户上下文、客户端信息、授权组件 │ ├── Easy.SmartNet.Repository/ # SqlSugar 上下文、仓储、数据库初始化 │ └── Easy.SmartNet.Service/ # 业务服务实现、DI 注册 ├── web/ # 前端(Vue3 + TS + Element Plus,见上方"前端"章节) └── tests/ ├── Easy.SmartNet.Tests/ # xUnit 单元测试(公共组件、DTO、授权策略、缓存键约定) └── Easy.SmartNet.IntegrationTests/ # 集成测试(Testcontainers MySQL + WebApplicationFactory,覆盖多租户/数据范围/权限闭环) ``` ## 🗺️ Roadmap - [x] 角色权限(RBAC)与菜单管理 - [x] Redis 缓存业务接入(登录失败锁定、热点数据缓存) - [x] 全局异常处理中间件与统一错误码 - [x] `PageInput.OrderField` 排序字段白名单校验(防 SQL 注入) - [x] `TenantController` 改为返回 DTO(TenantDto 输入输出,不再直接暴露实体) - [x] 刷新 Token / 退出登录(Token 吊销) - [x] 操作日志 / 登录日志 - [x] 单元测试与集成测试项目(xUnit 单元测试,覆盖公共组件与菜单树组装) - [x] 数据字典模块(类型/项两级管理、业务侧查询接口、缓存与变更失效、内置项保护) - [x] 接口限流(`[RateLimit]` 特性 + 滑动窗口计数,IP/用户/全局三种维度,fail-open) - [x] 安全与一致性加固(个人中心接口、分页上限、密码强度策略、租户删除拦截、日志保留清理) - [x] 部门/组织架构与数据权限(角色 DataScope:全部/本部门及以下/仅本部门/仅本人) - [x] 系统参数配置模块(键值配置、按键读取缓存、内置参数保护) - [x] 文件上传存储(IFileStorage 抽象 + 本地实现 + 白名单/大小上限) - [x] 通知公告(草稿/发布/撤回 + 阅读端已发布查询) - [x] 登录验证码(SVG 图形、一次性缓存校验、可配置开关) - [x] Excel 导入导出(用户批量导入/列表导出,MiniExcel) - [x] 定时任务(Hangfire + /hangfire 管理面板,日志清理与过期会话清理) - [x] 在线用户管理与强制下线 - [x] 插件模块机制(ISmartNetModule 契约 + 自动装载:服务注册/控制器发现/建表/种子) - [x] Swagger 按模块分组(插件模块接口独立文档组,程序集归属自动判定) - [x] 插件开发完整指南(MODULES 第 21 节:脚手架/契约实现/接入六步/下线调试) - [x] 生产表结构演进策略文档(ARCHITECTURE 第 11 节:CodeFirst 边界/演进方案/破坏性变更规范) - [x] 实体能力扩展(流水基类/批量操作/树形仓储/软删恢复/乐观锁/悲观锁读取/数据范围泛化/缓存失效钩子/软删唯一键辅助) - [x] 仓储写入校验管道(EntityValidator:插入/更新前自动执行 DataAnnotations 校验) - [x] 通用查询投影(分页/查询/首条的表达式投影,避免列表页全字段拉取) - [x] 模块健康检查(ISmartNetModule.RegisterHealthChecks,/health 与 HealthCheckService 汇总) - [x] 数据访问增强(IUnitOfWork 事务、仓储原子更新防超卖、ISqlQueryExecutor 受控只读查询口) ## 📜 开源协议 本项目基于 [MIT License](./LICENSE) 开源。 - 允许自由使用、复制、修改、合并、发布与商用,唯一要求是在软件或其副本中**保留原版权声明与本许可文本** - 软件按"现状"提供,不附带任何担保;二次开发后的维护责任由使用者自行承担 - 如果本项目对你有帮助,欢迎 Star 支持,也欢迎提交 Issue 反馈问题或 PR 共建