diff --git a/Infrastructure/OptionsSetting.cs b/Infrastructure/OptionsSetting.cs index be68c71..c01060c 100644 --- a/Infrastructure/OptionsSetting.cs +++ b/Infrastructure/OptionsSetting.cs @@ -11,8 +11,6 @@ namespace Infrastructure public static string ConnBus = "conn_bus"; public static string ConnBusDbType = "conn_bus_type"; - public static string DbKey = "DbKey"; - public string Redis { get; set; } public string Database { get; set; } /// diff --git a/README.md b/README.md index d0342ff..b99e9fe 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ * 基于.NET5/.NET6实现的通用权限管理平台(RBAC模式)。整合最新技术高效快速开发,前后端分离模式,开箱即用。 * 代码量少、学习简单、通俗易懂、功能强大、易扩展、轻量级,让web开发更快速、简单高效(从此告别996),解决70%的重复工作,专注您的业务,轻松开发从现在开始! * 前端采用Vue2.0、Element UI。 -* 后端采用Net5/Net6、Sqlsugar、MySQL。 +* 后端采用Net5/Net6、Sqlsugar、MySQL等,可以自动分库分表。 * 权限认证使用Jwt,支持多终端认证系统。 * 支持加载动态权限菜单,多方式轻松权限控制 * 提供了技术栈(Ant Design Vue)版[Ant Design Vue](https://gitee.com/billzh/mc-dull.git) @@ -37,13 +37,13 @@ Vue版前端技术栈 :基于vue、vuex、vue-router 、vue-cli 、axios 和 element-ui,前端采用vscode工具开发 ## 🍀后端技术 -核心框架:.Net5.0/.Net6 + Web API + sqlsugar + swagger + signalR +核心框架:.Net5.0/.Net6 + Web API + sqlsugar + swagger + signalR + IpRateLimit -定时计划任务:Quartz.Net组件 +定时计划任务:Quartz.Net组件,支持执行程序集或者http网络请求 -安全支持:过滤器、Sql注入、请求伪造 +安全支持:过滤器(数据权限过滤)、Sql注入、请求伪造 -日志管理:NLog、登录日志、操作日志 +日志管理:NLog、登录日志、操作日志、定时任务日志 工具类:验证码、丰富公共功能、代码生成 @@ -63,9 +63,10 @@ Vue版前端技术栈 :基于vue、vuex、vue-router 、vue-cli 、axios 和 e 11. 任务系统:基于Quartz.NET,可以在线(添加、修改、删除、手动执行)任务调度包含执行结果日志。 12. 文章管理:可以写文章记录。 13. 代码生成:可以一键生成前后端代码(.cs、.vue、.js、SQL文件等),支持下载,自定义配置前端展示控件、让开发更快捷高效。 -14. 文件管理:可以上传文件 +14. 文件管理:可以进行上传文件管理,目前支持上传到本地、阿里云 15. 通知管理:系统通知公告信息发布维护,使用signalr实现对用户实时通知。 16. 参数管理:对系统动态配置常用参数。 +17. 发送邮件:可以对多个用户进行发送邮件 ## 🍻项目结构 @@ -104,7 +105,7 @@ Vue版前端技术栈 :基于vue、vuex、vue-router 、vue-cli 、axios 和 e - + @@ -115,9 +116,13 @@ Vue版前端技术栈 :基于vue、vuex、vue-router 、vue-cli 、axios 和 e - - + + + + + + ## 🎉优势 diff --git a/ZR.Admin.WebApi/Controllers/System/SysConfigController.cs b/ZR.Admin.WebApi/Controllers/System/SysConfigController.cs index 580b467..21962ea 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysConfigController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysConfigController.cs @@ -106,7 +106,7 @@ namespace ZR.Admin.WebApi.Controllers throw new CustomException("请求参数错误"); } //从 Dto 映射到 实体 - var model = parm.Adapt().ToCreate(); + var model = parm.Adapt().ToCreate(HttpContext); return SUCCESS(_SysConfigService.Insert(model, it => new { diff --git a/ZR.Admin.WebApi/Extensions/DbExtension.cs b/ZR.Admin.WebApi/Extensions/DbExtension.cs index acf8bbc..7228e83 100644 --- a/ZR.Admin.WebApi/Extensions/DbExtension.cs +++ b/ZR.Admin.WebApi/Extensions/DbExtension.cs @@ -33,28 +33,28 @@ namespace ZR.Admin.WebApi.Extensions int dbType_bus = Convert.ToInt32(Configuration[OptionsSetting.ConnBusDbType]); SugarIocServices.AddSqlSugar(new List() { - new IocConfig() { - ConfigId = "0", - ConnectionString = connStr, - DbType = (IocDbType)dbType, - IsAutoCloseConnection = true - }, new IocConfig() { - ConfigId = "1", - ConnectionString = connStrBus, - DbType = (IocDbType)dbType_bus, - IsAutoCloseConnection = true - } - }); + new IocConfig() { + ConfigId = "0", + ConnectionString = connStr, + DbType = (IocDbType)dbType, + IsAutoCloseConnection = true + }, new IocConfig() { + ConfigId = "1", + ConnectionString = connStrBus, + DbType = (IocDbType)dbType_bus, + IsAutoCloseConnection = true + } + }); SugarIocServices.ConfigurationSugar(db => { + FilterData(0); + //FilterData(1); #region db0 db.GetConnection(0).Aop.OnLogExecuting = (sql, pars) => { - var param = db.Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value)); + var param = db.GetConnection(0).Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value)); - FilterData(db.GetConnection(0)); - - logger.Info($"【sql语句】{sql},{param}"); + logger.Info($"【sql语句】{sql},{param}\n"); }; db.GetConnection(0).Aop.OnError = (e) => @@ -72,7 +72,7 @@ namespace ZR.Admin.WebApi.Extensions //Db1 db.GetConnection(1).Aop.OnLogExecuting = (sql, pars) => { - var param = DbScoped.SugarScope.Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value)); + var param = db.GetConnection(1).Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value)); logger.Info($"【sql语句】{sql}, {param}"); }; @@ -88,7 +88,8 @@ namespace ZR.Admin.WebApi.Extensions /// /// 分页获取count 不会追加sql /// - private static void FilterData(ISqlSugarClient sqlSugarClient) + /// 多库id + private static void FilterData(int configId) { var u = App.User; if (u == null) return; @@ -97,8 +98,8 @@ namespace ZR.Admin.WebApi.Extensions if (user == null) return; //管理员不过滤 if (user.RoleIds.Any(f => f.Equals("admin"))) return; - - foreach (var role in user.Roles) + var db = DbScoped.SugarScope.GetConnection(configId); + foreach (var role in user.Roles.OrderBy(f => f.DataScope)) { string dataScope = role.DataScope; if (DATA_SCOPE_ALL.Equals(dataScope))//所有权限 @@ -107,28 +108,30 @@ namespace ZR.Admin.WebApi.Extensions } else if (DATA_SCOPE_CUSTOM.Equals(dataScope))//自定数据权限 { - //有问题 - //var roleDepts = db0.Queryable() - //.Where(f => f.RoleId == role.RoleId).Select(f => f.DeptId).ToList(); - //var filter1 = new TableFilterItem(it => roleDepts.Contains(it.DeptId)); - //DbScoped.SugarScope.GetConnection(0).QueryFilter.Add(filter1); + //" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId())); + var filter1 = new TableFilterItem(it => SqlFunc.Subqueryable().Where(f => f.DeptId == it.DeptId && f.RoleId == role.RoleId).Any()); + db.QueryFilter.Add(filter1); } else if (DATA_SCOPE_DEPT.Equals(dataScope))//本部门数据 { - //有问题添加后的SQL 语句 是 AND deptId = @deptId - var exp = Expressionable.Create(); - exp.Or(it => it.DeptId == user.DeptId); - var filter1 = new TableFilterItem(exp.ToExpression()); - sqlSugarClient.QueryFilter.Add(filter1); + var filter1 = new TableFilterItem(it => it.DeptId == user.DeptId); + db.QueryFilter.Add(filter1); } else if (DATA_SCOPE_DEPT_AND_CHILD.Equals(dataScope))//本部门及以下数据 { //SQl OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) ) + var allChildDepts = db.Queryable().ToChildList(it => it.ParentId, user.DeptId); + + var filter1 = new TableFilterItem(it => allChildDepts.Select(f => f.DeptId).ToList().Contains(it.DeptId)); + db.QueryFilter.Add(filter1); + + var filter2 = new TableFilterItem(it => allChildDepts.Select(f => f.DeptId).ToList().Contains(it.DeptId)); + db.QueryFilter.Add(filter2); } else if (DATA_SCOPE_SELF.Equals(dataScope))//仅本人数据 { var filter1 = new TableFilterItem(it => it.UserId == user.UserId, true); - sqlSugarClient.QueryFilter.Add(filter1); + db.QueryFilter.Add(filter1); } } } diff --git a/ZR.Admin.WebApi/Middleware/GlobalExceptionMiddleware.cs b/ZR.Admin.WebApi/Middleware/GlobalExceptionMiddleware.cs index 9044a22..4fdf6d9 100644 --- a/ZR.Admin.WebApi/Middleware/GlobalExceptionMiddleware.cs +++ b/ZR.Admin.WebApi/Middleware/GlobalExceptionMiddleware.cs @@ -121,7 +121,7 @@ namespace ZR.Admin.WebApi.Middleware SysOperLogService.InsertOperlog(sysOperLog); } - public static Endpoint? GetEndpoint(HttpContext context) + public static Endpoint GetEndpoint(HttpContext context) { if (context == null) { diff --git a/ZR.Admin.WebApi/Middleware/RequestIPMiddleware.cs b/ZR.Admin.WebApi/Middleware/RequestIPMiddleware.cs deleted file mode 100644 index 14218c0..0000000 --- a/ZR.Admin.WebApi/Middleware/RequestIPMiddleware.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Common; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using NLog; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace ZR.Admin.WebApi.Middleware -{ - public class RequestIPMiddleware - { - private readonly RequestDelegate _next; - - static readonly Logger Logger = LogManager.GetCurrentClassLogger();//声明NLog变量 - - public RequestIPMiddleware(RequestDelegate next) - { - _next = next; - } - - public async Task Invoke(HttpContext context) - { - var url = context.Request.Path.ToString().ToLower(); - string userip = Tools.GetRealIP(); - //string agent = HttpContextExtension.GetUserAgent(context); - - string[] urls = new string[] { "/css", "/js", "/images", "/lib", "/home/error", "/api" }; - - var strRegex = "(.jpg|.png|.gif|.php|.cfg|.ico)$"; //用于验证图片扩展名的正则表达式 - var re = new Regex(strRegex); - - //阻止.php访问往下请求 - if (new Regex("(.php)$").IsMatch(url)) - { - await context.Response.WriteAsync("hello"); - return; - } - - //var ip_info = IpTool.Search(userip); - - ////bool flag = ((IList)urls).Contains(url); - //if (!re.IsMatch(url) ) - //{ - Logger.Debug($"IP中间件请求访问,IP[{userip}]"); - //} - - //两种方式传递下去 一是invoke 一个直接next - //await _next.Invoke(context); - - await _next(context); - } - } - - public static class RequestIPMiddlewareExtensions - { - /// - /// 用于引用请求IP中间件 - /// - /// 扩展类型 - /// IApplicationBuilder - public static IApplicationBuilder UseRequestIPMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } -} diff --git a/ZR.Admin.WebApi/NLog.config b/ZR.Admin.WebApi/NLog.config index dc78b69..f96caf9 100644 --- a/ZR.Admin.WebApi/NLog.config +++ b/ZR.Admin.WebApi/NLog.config @@ -57,7 +57,7 @@ - + diff --git a/ZR.Admin.WebApi/Startup.cs b/ZR.Admin.WebApi/Startup.cs index e60e549..abae759 100644 --- a/ZR.Admin.WebApi/Startup.cs +++ b/ZR.Admin.WebApi/Startup.cs @@ -123,7 +123,7 @@ namespace ZR.Admin.WebApi //2.ٿȨ app.UseAuthorization(); //session - app.UseSession(); + //app.UseSession(); // app.UseResponseCaching(); //ָ/ diff --git a/ZR.CodeGenerator/ZR.CodeGenerator.csproj b/ZR.CodeGenerator/ZR.CodeGenerator.csproj index 5f92abe..ce59e61 100644 --- a/ZR.CodeGenerator/ZR.CodeGenerator.csproj +++ b/ZR.CodeGenerator/ZR.CodeGenerator.csproj @@ -12,6 +12,6 @@ - + diff --git a/ZR.Model/ZR.Model.csproj b/ZR.Model/ZR.Model.csproj index 24b784b..9678f32 100644 --- a/ZR.Model/ZR.Model.csproj +++ b/ZR.Model/ZR.Model.csproj @@ -7,7 +7,7 @@ - + diff --git a/ZR.Repository/BaseRepository.cs b/ZR.Repository/BaseRepository.cs index 48a9f49..9da0605 100644 --- a/ZR.Repository/BaseRepository.cs +++ b/ZR.Repository/BaseRepository.cs @@ -1,12 +1,9 @@ -using Infrastructure.Model; -using SqlSugar; +using SqlSugar; using SqlSugar.IOC; using System; using System.Collections.Generic; using System.Data; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; using ZR.Model; namespace ZR.Repository @@ -18,18 +15,24 @@ namespace ZR.Repository public class BaseRepository : SimpleClient where T : class, new() { public ITenant itenant = null;//多租户事务 - public BaseRepository(ISqlSugarClient client = null) : base(client) + public BaseRepository(ISqlSugarClient context = null) : base(context) { //通过特性拿到ConfigId - var configId = typeof(T).GetCustomAttribute()?.configId; - if (configId != null) + //var configId = typeof(T).GetCustomAttribute()?.configId; + //if (configId != null) + //{ + // itenant = DbScoped.SugarScope;//设置租户接口 + // Context = DbScoped.SugarScope.GetConnection(configId); + //} + //else + //{ + // Context = context ?? DbScoped.SugarScope.GetConnection(1);//根据类传入的ConfigId自动选择 + //} + Context = DbScoped.SugarScope.GetConnectionWithAttr(); + itenant = DbScoped.SugarScope;//设置租户接口 + if (Context == null) { - Context = DbScoped.SugarScope.GetConnection(configId); - itenant = DbScoped.SugarScope;//设置租户接口 - } - else - { - Context = client ?? DbScoped.SugarScope.GetConnection(1);//根据类传入的ConfigId自动选择 + Context = DbScoped.SugarScope.GetConnection(1);//根据类传入的ConfigId自动选择 } } diff --git a/ZR.Repository/ZR.Repository.csproj b/ZR.Repository/ZR.Repository.csproj index 7f0fe65..05ba31b 100644 --- a/ZR.Repository/ZR.Repository.csproj +++ b/ZR.Repository/ZR.Repository.csproj @@ -13,7 +13,7 @@ - + diff --git a/ZR.Service/System/SysFileService.cs b/ZR.Service/System/SysFileService.cs index 0b455c4..5db5bf1 100644 --- a/ZR.Service/System/SysFileService.cs +++ b/ZR.Service/System/SysFileService.cs @@ -23,11 +23,11 @@ namespace ZR.Service.System public class SysFileService : BaseService, ISysFileService { private string domainUrl = AppSettings.GetConfig("ALIYUN_OSS:domainUrl"); - private readonly SysFileRepository SysFileRepository; + private readonly ISysConfigService SysConfigService; private OptionsSetting OptionsSetting; - public SysFileService(SysFileRepository repository, IOptions options) + public SysFileService(ISysConfigService sysConfigService, IOptions options) { - SysFileRepository = repository; + SysConfigService = sysConfigService; OptionsSetting = options.Value; } @@ -53,7 +53,8 @@ namespace ZR.Service.System { await formFile.CopyToAsync(stream);//await 不能少 } - string accessPath = string.Concat(OptionsSetting.Upload.UploadUrl, "/", filePath.Replace("\\", "/"), "/", fileName); + string uploadUrl = SysConfigService.GetSysConfigByKey("sys.file.uploadUrl")?.ConfigValue ?? OptionsSetting.Upload.UploadUrl; + string accessPath = string.Concat(uploadUrl, "/", filePath.Replace("\\", "/"), "/", fileName); SysFile file = new(formFile.FileName, fileName, fileExt, fileSize + "kb", filePath, userName) { StoreType = (int)Infrastructure.Enums.StoreType.LOCAL, diff --git a/ZR.Vue/src/views/system/role/index.vue b/ZR.Vue/src/views/system/role/index.vue index c6c1f79..5792ae9 100644 --- a/ZR.Vue/src/views/system/role/index.vue +++ b/ZR.Vue/src/views/system/role/index.vue @@ -203,18 +203,18 @@ export default { dictValue: '1', dictLabel: '全部' }, - // { - // dictValue: "2", - // dictLabel: "自定义", - // }, + { + dictValue: "2", + dictLabel: "自定义", + }, { dictValue: '3', dictLabel: '本部门' }, - // { - // dictValue: "4", - // dictLabel: "本部门及以下数据权限", - // }, + { + dictValue: "4", + dictLabel: "本部门及以下数据权限", + }, { dictValue: '5', dictLabel: '仅本人' diff --git a/document/admin-mysql.sql b/document/admin-mysql.sql index 9de1c29..31a599c 100644 --- a/document/admin-mysql.sql +++ b/document/admin-mysql.sql @@ -679,6 +679,7 @@ insert into sys_config values(1, '主框架页-默认皮肤样式名称', 's insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', null, '初始化密码 123456' ); insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' ); insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaOnOff', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(off、关闭,1、动态验证码 2、动态gif泡泡 3、泡泡 4、静态验证码)'); +INSERT INTO `sys_config`(`configId`, `configName`, `configKey`, `configValue`, `configType`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (5, '本地文件上传访问域名', 'sys.file.uploadurl', 'http://localhost:8888', 'Y', '', '2022-04-10 10:11:27', '', NULL, NULL); -- ---------------------------- -- 18、代码生成业务表 diff --git a/document/admin-sqlserver.sql b/document/admin-sqlserver.sql index 7175eb6..c116fd4 100644 --- a/document/admin-sqlserver.sql +++ b/document/admin-sqlserver.sql @@ -697,6 +697,8 @@ insert into sys_config values('主框架页-默认皮肤样式名称', 'sys. insert into sys_config values('用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', GETDATE(), '', null, '初始化密码 123456' ); insert into sys_config values('主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', GETDATE(), '', null, '深色主题theme-dark,浅色主题theme-light' ); insert into sys_config values('账号自助-验证码开关', 'sys.account.captchaOnOff', '1', 'Y', 'admin', GETDATE(), '', null, '开启验证码功能(off、关闭,1、动态验证码 2、动态gif泡泡 3、泡泡 4、静态验证码)'); +INSERT INTO sys_config VALUES('本地文件上传访问域名', 'sys.file.uploadurl', 'http://localhost:8888', 'Y', 'admin', GETDATE(), '', NULL, NULL); + GO -- ---------------------------- diff --git a/document/images/1.png b/document/images/1.png index d731ade..a78e80a 100644 Binary files a/document/images/1.png and b/document/images/1.png differ diff --git a/document/images/12.jpeg b/document/images/12.jpeg new file mode 100644 index 0000000..4d4a097 Binary files /dev/null and b/document/images/12.jpeg differ diff --git a/document/images/12.png b/document/images/12.png deleted file mode 100644 index b55aaca..0000000 Binary files a/document/images/12.png and /dev/null differ diff --git a/document/images/17.jpeg b/document/images/17.jpeg new file mode 100644 index 0000000..80a0d22 Binary files /dev/null and b/document/images/17.jpeg differ diff --git a/document/images/17.png b/document/images/17.png deleted file mode 100644 index c77cf17..0000000 Binary files a/document/images/17.png and /dev/null differ diff --git a/document/images/19.jpeg b/document/images/19.jpeg new file mode 100644 index 0000000..989a84e Binary files /dev/null and b/document/images/19.jpeg differ diff --git a/document/images/20.jpeg b/document/images/20.jpeg new file mode 100644 index 0000000..7d374ea Binary files /dev/null and b/document/images/20.jpeg differ diff --git a/document/images/21.jpeg b/document/images/21.jpeg new file mode 100644 index 0000000..cbfc18b Binary files /dev/null and b/document/images/21.jpeg differ