50 Commits
Author SHA1 Message Date
不做码农 714acc0749 :zap:优化启动打印 2023-09-18 21:09:40 +08:00
不做码农 1d32f4709b :pencil:更新数据库脚本 2023-09-18 17:59:48 +08:00
不做码农 b96a4adb9f 移动端代码生成表单优化 2023-09-15 19:55:07 +08:00
不做码农 0e08a7d102 :sparkles:字典组件(DictTag)显示翻译 2023-09-13 18:40:34 +08:00
不做码农 54e5b46dbf :arrow_up:升级包 2023-09-13 18:39:36 +08:00
不做码农 f266c9a718 :pencil:update doc 2023-09-13 11:18:55 +08:00
不做码农 b02cf986d3 :zap:树形代码生成新增布局选项 2023-09-08 18:40:29 +08:00
不做码农 44ef55be00 :recycle:重新设计返回格式,同Ruoyi保持一致 2023-09-08 18:39:40 +08:00
不做码农 70371e92b3 :zap:数据返回格式改为可灵活扩展,跟ruoyi保存一致 2023-09-08 15:18:14 +08:00
不做码农 803d862513 :zap:signalr返回数据格式改为驼峰 2023-09-08 15:15:55 +08:00
不做码农 4a093470d5 :bug:fix测试代码提交 2023-09-08 13:42:13 +08:00
不做码农 b01765360d :zap:优化Nlog日志记录,解决记录到文本IP、请求地址获取不到问题 2023-09-07 09:34:01 +08:00
不做码农 02ffc18a61 :zap:优化program,jwt注入移至WebExtensions目录 2023-09-07 07:29:04 +08:00
不做码农 0fb410159a :zap:优化移动端代码生成模板 2023-09-06 20:56:07 +08:00
不做码农 d2afa84c37 :truck:移动MessageHub 2023-09-06 20:56:07 +08:00
不做码农 672fb84871 :bug:fix种子数据重复menuId问题 2023-09-06 09:27:56 +08:00
不做码农 1e7a9c7abb :zap:邮件发送失败异常处理 2023-09-04 20:42:02 +08:00
不做码农 b83c005f28 :zap:代码生成样式调整 2023-09-04 20:40:51 +08:00
不做码农 b9a3cfe377 :pencil:更新文档 2023-09-04 20:26:07 +08:00
不做码农 be515339fe :recycle:代码重构 2023-09-02 17:55:34 +08:00
不做码农 c65952202a :zap:优化数据传输 2023-08-30 16:22:51 +08:00
不做码农 ab98cf30c1 :zap:强退功能优化 2023-08-30 08:36:45 +08:00
不做码农 4d914ead0e :zap:完善扫码登录(生成新的token) 2023-08-30 07:50:06 +08:00
不做码农 b67c59710b :sparkles:优化强退功能新增批量强退在线用户 2023-08-29 21:35:09 +08:00
不做码农 24c9ceefde :sparkles:种子数据新增菜单 2023-08-29 21:20:40 +08:00
不做码农 e713bcd9b7 :zap:优化新增演示模式条件判断 2023-08-29 21:20:09 +08:00
不做码农 400aa876df :zap:优化数据校验 2023-08-29 21:19:37 +08:00
不做码农 07b4b95732 :zap:优化功能、类目录 2023-08-29 16:56:07 +08:00
不做码农 94fa69e8ef :bug:fix查询用户角色缓存不起作用问题 2023-08-29 16:35:57 +08:00
不做码农 46c9563f33 :sparkles:刷新jwttoken 2023-08-29 07:40:56 +08:00
不做码农 52240ab6a8 :zap:优化登录 2023-08-28 22:16:57 +08:00
不做码农 ea3fa0e0d6 add SqlDiffLog.vue 2023-08-28 18:23:24 +08:00
不做码农 6bf4885f85 :zap:优化jwt(非管理员用户登录重启服务器后不用重新登录) 2023-08-28 18:22:36 +08:00
不做码农 839e400ed1 :sparkles:新增单点登录功能、在线时长 2023-08-28 08:28:12 +08:00
不做码农 55733f07e8 :zap:优化功能 2023-08-26 16:39:31 +08:00
不做码农 6533020ebc :sparkles:新增移动端扫码登录 2023-08-26 15:54:41 +08:00
不做码农 710966e098 ✨ 新增剔出在线用户 2023-08-26 15:51:12 +08:00
不做码农 9495589b28 ✨ 代码生成新增组件 2023-08-26 07:37:13 +08:00
不做码农 e8cbf35abb 🐛 fix部门管理列表显示异常 2023-08-25 19:43:05 +08:00
不做码农 dc38e97296 :sparkles:Treeselect树结构新增字段 2023-08-22 18:24:26 +08:00
不做码农 ba693d1e78 :zap:优化代码生成模板 2023-08-18 13:55:36 +08:00
不做码农 e8ab6d47eb :sparkles:swagger新增分组 2023-08-18 11:20:27 +08:00
不做码农 240a607875 :zap:新增数据库差异化日志记录 2023-08-18 07:24:02 +08:00
不做码农 09c45c4ba4 :zap:Action新增角色权限判断 2023-08-18 07:09:11 +08:00
不做码农 0257fb294a :arrow_up:升级包 2023-08-17 19:27:18 +08:00
不做码农 1ca05495d8 :zap:侧边栏路由新增isNew属性 2023-08-17 18:08:15 +08:00
不做码农 d1108700e0 :zap:添加用户改为返回实体类 2023-08-15 09:39:11 +08:00
不做码农 ff4e857128 :fire:移除仓储不常用方法 2023-08-15 09:38:39 +08:00
不做码农 31cf1a82f1 :zap:优化代码生成模板 2023-08-14 18:12:48 +08:00
不做码农 0531c2b330 :zap:db日志打印新增配置开关 2023-08-14 18:11:03 +08:00
217 changed files with 2025 additions and 1103 deletions
+2 -3
View File
@@ -1,12 +1,11 @@
using Microsoft.AspNetCore.Hosting;
using Infrastructure.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Security.Claims;
using System.Security.Principal;
using System.Xml.Linq;
namespace Infrastructure
{
@@ -1,13 +1,20 @@
using Infrastructure.Extensions;
using Infrastructure.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using MiniExcelLibs;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using Io = System.IO;
namespace ZR.Admin.WebApi.Controllers
namespace Infrastructure.Controllers
{
/// <summary>
/// web层通用数据处理
/// </summary>
public class BaseController : ControllerBase
{
public static string TIME_FORMAT_FULL = "yyyy-MM-dd HH:mm:ss";
@@ -61,7 +68,7 @@ namespace ZR.Admin.WebApi.Controllers
{
throw new CustomException(fileName + "文件不存在");
}
var stream = Io.File.OpenRead(path); //创建文件流
var stream = System.IO.File.OpenRead(path); //创建文件流
Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", HttpUtility.UrlEncode(fileName));
@@ -92,6 +99,10 @@ namespace ZR.Admin.WebApi.Controllers
return new ApiResult((int)resultCode, msg, data);
}
protected ApiResult Success()
{
return GetApiResult(ResultCode.SUCCESS);
}
/// <summary>
///
@@ -2,7 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Infrastructure
namespace Infrastructure.Converter
{
public class JsonConverterUtil
{
@@ -0,0 +1,48 @@
using System;
using System.Buffers;
using System.Linq;
using System.Text;
using System.Text.Json;
namespace Infrastructure.Converter
{
/// <summary>
/// Json任何类型读取到字符串属性
/// 因为 System.Text.Json 必须严格遵守类型一致,当非字符串读取到字符属性时报错:
/// The JSON value could not be converted to System.String.
/// </summary>
public class StringConverter : System.Text.Json.Serialization.JsonConverter<string>
{
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
return reader.GetString();
}
else
{
//非字符类型,返回原生内容
return GetRawPropertyValue(reader);
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
/// <summary>
/// 非字符类型,返回原生内容
/// </summary>
/// <param name="jsonReader"></param>
/// <returns></returns>
private static string GetRawPropertyValue(Utf8JsonReader jsonReader)
{
ReadOnlySpan<byte> utf8Bytes = jsonReader.HasValueSequence ?
jsonReader.ValueSequence.ToArray() :
jsonReader.ValueSpan;
return Encoding.UTF8.GetString(utf8Bytes);
}
}
}
@@ -5,9 +5,19 @@ namespace Infrastructure
public class CustomException : Exception
{
public int Code { get; set; }
/// <summary>
/// 前端提示语
/// </summary>
public string Msg { get; set; }
/// <summary>
/// 记录到日志的详细内容
/// </summary>
public string LogMsg { get; set; }
/// <summary>
/// 是否通知
/// </summary>
public bool Notice { get; set; } = true;
public CustomException(string msg) : base(msg)
{
}
@@ -17,9 +27,10 @@ namespace Infrastructure
Msg = msg;
}
public CustomException(ResultCode resultCode, string msg) : base(msg)
public CustomException(ResultCode resultCode, string msg, bool notice = true) : base(msg)
{
Code = (int)resultCode;
Notice = notice;
}
/// <summary>
+5
View File
@@ -54,5 +54,10 @@
/// 清空数据
/// </summary>
CLEAN = 9,
/// <summary>
/// 下载
/// </summary>
DOWNLOAD = 10,
}
}
+1 -1
View File
@@ -91,7 +91,7 @@ namespace Infrastructure
#region 获取unix时间戳
/// <summary>
/// 获取unix时间戳
/// 获取unix时间戳(毫秒)
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
@@ -2,7 +2,7 @@
using System;
using System.IO;
namespace ZR.Common
namespace Infrastructure.Helper
{
public class JnHelper
{
-19
View File
@@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<Compile Remove="Model\PagedInfo.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspectCore.Abstractions" Version="2.4.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
@@ -1,18 +1,19 @@
using Infrastructure;
using Infrastructure.Extensions;
using Infrastructure.Extensions;
using Infrastructure.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using ZR.Admin.WebApi.Extensions;
using ZR.Model.System.Dto;
using ZR.Service.System;
namespace ZR.Admin.WebApi.Framework
namespace Infrastructure
{
/// <summary>
/// 2020-11-20
/// 2023-8-29已从WebApi移至此
/// </summary>
public class JwtUtil
{
@@ -21,7 +22,7 @@ namespace ZR.Admin.WebApi.Framework
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public static LoginUser GetLoginUser(HttpContext httpContext)
public static TokenModel GetLoginUser(HttpContext httpContext)
{
string token = httpContext.GetToken();
@@ -36,10 +37,12 @@ namespace ZR.Admin.WebApi.Framework
/// 生成token
/// </summary>
/// <param name="claims"></param>
/// <param name="jwtSettings"></param>
/// <returns></returns>
public static string GenerateJwtToken(List<Claim> claims, JwtSettings jwtSettings)
public static string GenerateJwtToken(List<Claim> claims)
{
JwtSettings jwtSettings = new();
AppSettings.Bind("JwtSettings", jwtSettings);
var authTime = DateTime.Now;
var expiresAt = authTime.AddMinutes(jwtSettings.Expire);
var tokenHandler = new JwtSecurityTokenHandler();
@@ -55,7 +58,7 @@ namespace ZR.Admin.WebApi.Framework
IssuedAt = authTime,//token生成时间
Expires = expiresAt,
//NotBefore = authTime,
TokenType = "Bearer",
TokenType = jwtSettings.TokenType,
//对称秘钥,签名证书
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
@@ -96,7 +99,7 @@ namespace ZR.Admin.WebApi.Framework
/// </summary>
/// <param name="token">令牌</param>
/// <returns></returns>
public static IEnumerable<Claim>? ParseToken(string token)
public static JwtSecurityToken? ParseToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var validateParameter = ValidParameters();
@@ -105,8 +108,7 @@ namespace ZR.Admin.WebApi.Framework
{
tokenHandler.ValidateToken(token, validateParameter, out SecurityToken validatedToken);
var jwtToken = tokenHandler.ReadJwtToken(token);
return jwtToken.Claims;
return tokenHandler.ReadJwtToken(token);
}
catch (Exception ex)
{
@@ -119,25 +121,21 @@ namespace ZR.Admin.WebApi.Framework
/// <summary>
/// jwt token校验
/// </summary>
/// <param name="jwtToken"></param>
/// <param name="jwtSecurityToken"></param>
/// <returns></returns>
public static LoginUser? ValidateJwtToken(IEnumerable<Claim> jwtToken)
public static TokenModel? ValidateJwtToken(JwtSecurityToken jwtSecurityToken)
{
try
{
LoginUser loginUser = null;
var userData = jwtToken.FirstOrDefault(x => x.Type == ClaimTypes.UserData)?.Value;
if (jwtSecurityToken == null) return null;
IEnumerable<Claim> claims = jwtSecurityToken?.Claims;
TokenModel loginUser = null;
var userData = claims.FirstOrDefault(x => x.Type == ClaimTypes.UserData)?.Value;
if (userData != null)
{
loginUser = JsonConvert.DeserializeObject<LoginUser>(userData);
var permissions = CacheService.GetUserPerms(GlobalConstant.UserPermKEY + loginUser?.UserId);
if (loginUser?.UserName == GlobalConstant.AdminRole)
{
permissions = new List<string>() { GlobalConstant.AdminPerm };
}
if (permissions == null) return null;
loginUser.Permissions = permissions;
loginUser = JsonConvert.DeserializeObject<TokenModel>(userData);
loginUser.ExpireTime = jwtSecurityToken.ValidTo;
}
return loginUser;
}
@@ -153,7 +151,7 @@ namespace ZR.Admin.WebApi.Framework
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public static List<Claim> AddClaims(LoginUser user)
public static List<Claim> AddClaims(TokenModel user)
{
var claims = new List<Claim>()
{
+14
View File
@@ -0,0 +1,14 @@
using System;
namespace Infrastructure
{
public class Log
{
public static void WriteLine(ConsoleColor color = ConsoleColor.Black, string msg = "")
{
Console.ForegroundColor = color;
Console.WriteLine($"{DateTime.Now} {msg}");
Console.ResetColor();
}
}
}
+49 -40
View File
@@ -1,17 +1,26 @@
using Infrastructure.Constant;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Infrastructure.Model
{
public class ApiResult
public class ApiResult : Dictionary<string, object>
{
public int Code { get; set; }
public string Msg { get; set; }
/** 状态码 */
public static readonly string CODE_TAG = "code";
/** 返回内容 */
public static readonly string MSG_TAG = "msg";
/** 数据对象 */
public static readonly string DATA_TAG = "data";
//public int Code { get; set; }
//public string Msg { get; set; }
/// <summary>
/// 如果data值为null,则忽略序列化将不会返回data字段
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public object Data { get; set; }
//[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
//public object Data { get; set; }
/// <summary>
/// 初始化一个新创建的APIResult对象,使其表示一个空消息
@@ -27,8 +36,8 @@ namespace Infrastructure.Model
/// <param name="msg"></param>
public ApiResult(int code, string msg)
{
Code = code;
Msg = msg;
Add(CODE_TAG, code);
Add(MSG_TAG, msg);
}
/// <summary>
@@ -36,33 +45,28 @@ namespace Infrastructure.Model
/// </summary>
/// <param name="code"></param>
/// <param name="msg"></param>
/// <param name="data"></param>
public ApiResult(int code, string msg, object data)
{
Code = code;
Msg = msg;
Add(CODE_TAG, code);
Add(MSG_TAG, msg);
if (data != null)
{
Data = data;
Add(DATA_TAG, data);
}
}
/// <summary>
/// 返回成功消息
/// </summary>
/// < returns > 成功消息 </ returns >
public static ApiResult Success() { return new ApiResult(HttpStatus.SUCCESS, "success"); }
/// <summary>
/// 返回成功消息
/// </summary>
/// <returns></returns>
public ApiResult Success()
{
Code = (int)ResultCode.SUCCESS;
Msg = "success";
return this;
}
///// <summary>
///// 返回成功消息
///// </summary>
///// <param name = "data" > 数据对象 </ param >
///// < returns > 成功消息 </ returns >
//public static ApiResult Success(object data) { return new ApiResult(HttpStatus.SUCCESS, "success", data); }
/// <param name="data"></param>
/// <returns> 成功消息 </returns >
public static ApiResult Success(object data) { return new ApiResult(HttpStatus.SUCCESS, "success", data); }
/// <summary>
/// 返回成功消息
@@ -79,21 +83,9 @@ namespace Infrastructure.Model
/// <returns>成功消息</returns>
public static ApiResult Success(string msg, object data) { return new ApiResult(HttpStatus.SUCCESS, msg, data); }
/// <summary>
/// 访问被拒
/// </summary>
/// <returns></returns>
public ApiResult On401()
public static ApiResult Error(ResultCode code, string msg = "")
{
Code = (int)ResultCode.DENY;
Msg = "access denyed";
return this;
}
public ApiResult Error(ResultCode resultCode, string msg = "")
{
Code = (int)resultCode;
Msg = msg;
return this;
return Error((int)code, msg);
}
/// <summary>
@@ -111,9 +103,26 @@ namespace Infrastructure.Model
/// <returns></returns>
public static ApiResult Error(string msg) { return new ApiResult((int)ResultCode.CUSTOM_ERROR, msg); }
public override string ToString()
/// <summary>
/// 是否为成功消息
/// </summary>
/// <returns></returns>
public bool IsSuccess()
{
return $"msg={Msg},data={Data}";
return HttpStatus.SUCCESS == (int)this[CODE_TAG];
}
/// <summary>
/// 方便链式调用
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public ApiResult Put(string key, object value)
{
Add(key, value);
return this;
}
}
@@ -1,18 +1,24 @@

using System;
using System.Collections.Generic;
using System.Collections.Generic;
namespace Infrastructure
namespace Infrastructure.Model
{
/// <summary>
/// 获取配置文件POCO实体类
/// </summary>
public class OptionsSetting
{
/// <summary>
/// 是否单点登录
/// </summary>
public bool SingleLogin { get; set; }
/// <summary>
/// 是否演示模式
/// </summary>
public bool DemoMode { get; set; }
/// <summary>
/// 初始化db
/// </summary>
public bool InitDb { get; set; }
public MailOptions MailOptions { get; set; }
public Upload Upload { get; set; }
public ALIYUN_OSS ALIYUN_OSS { get; set; }
@@ -78,6 +84,14 @@ namespace Infrastructure
/// token时间(分)
/// </summary>
public int Expire { get; set; } = 1440;
/// <summary>
/// 刷新token时长
/// </summary>
public int RefreshTokenTime { get; set; }
/// <summary>
/// token类型
/// </summary>
public string TokenType { get; set; } = "Bearer";
}
public class Gen
+48
View File
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Infrastructure.Model
{
public class TokenModel
{
public long UserId { get; set; }
public long DeptId { get; set; }
public string UserName { get; set; }
/// <summary>
/// 角色集合
/// </summary>
public List<string> RoleIds { get; set; }
/// <summary>
/// 角色集合(数据权限过滤使用)
/// </summary>
public List<Roles> Roles { get; set; }
/// <summary>
/// Jwt过期时间
/// </summary>
public DateTime ExpireTime { get; set; }
/// <summary>
/// 权限集合
/// </summary>
//public List<string> Permissions { get; set; } = new List<string>();
public TokenModel()
{
}
public TokenModel(TokenModel info, List<Roles> roles)
{
UserId = info.UserId;
UserName = info.UserName;
DeptId = info.DeptId;
Roles = roles;
RoleIds = roles.Select(f => f.RoleKey).ToList();
}
}
public class Roles
{
public long RoleId { get; set; }
public string RoleKey { get; set; }
public int DataScope { get; set; }
}
}
@@ -1,8 +1,10 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Attribute;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Reflection;
namespace ZR.Admin.WebApi.Extensions
namespace Infrastructure
{
/// <summary>
/// App服务注册
@@ -1,5 +1,12 @@
namespace ZR.Admin.WebApi.Extensions
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Infrastructure
{
/// <summary>
/// 跨域扩展
/// </summary>
public static class CorsExtension
{
/// <summary>
@@ -1,14 +1,17 @@

using Infrastructure.Extensions;
using Microsoft.AspNetCore.Http;
using System;
using System.Reflection;
namespace ZR.Admin.WebApi.Extensions
namespace Infrastructure
{
public static class EntityExtension
{
public static TSource ToCreate<TSource>(this TSource source, HttpContext? context = null)
{
var types = source?.GetType();
if (types == null) return source;
if (types == null || context == null) return source;
BindingFlags flag = BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance;
types.GetProperty("CreateTime", flag)?.SetValue(source, DateTime.Now, null);
@@ -23,7 +26,7 @@ namespace ZR.Admin.WebApi.Extensions
public static TSource ToUpdate<TSource>(this TSource source, HttpContext? context = null)
{
var types = source?.GetType();
if (types == null) return source;
if (types == null || context == null) return source;
BindingFlags flag = BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance;
types.GetProperty("UpdateTime", flag)?.SetValue(source, DateTime.Now, null);
@@ -1,12 +1,15 @@
using Infrastructure;
using Infrastructure.Extensions;
using IPTools.Core;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Text.RegularExpressions;
using UAParser;
using ZR.Model.System;
namespace ZR.Admin.WebApi.Extensions
namespace Infrastructure.Extensions
{
/// <summary>
/// HttpContext扩展类
@@ -50,7 +53,7 @@ namespace ZR.Admin.WebApi.Extensions
if (result.Contains("::1"))
result = "127.0.0.1";
result = result.Replace("::ffff:", "127.0.0.1");
result = result.Replace("::ffff:", "");
result = result.Split(':')?.FirstOrDefault() ?? "127.0.0.1";
result = IsIP(result) ? result : "127.0.0.1";
return result;
@@ -82,7 +85,7 @@ namespace ZR.Admin.WebApi.Extensions
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string? GetName(this HttpContext context)
public static string GetName(this HttpContext context)
{
var uid = context.User?.Identity?.Name;
@@ -105,7 +108,7 @@ namespace ZR.Admin.WebApi.Extensions
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static IEnumerable<ClaimsIdentity>? GetClaims(this HttpContext context)
public static IEnumerable<ClaimsIdentity> GetClaims(this HttpContext context)
{
return context.User?.Identities;
}
@@ -131,26 +134,12 @@ namespace ZR.Admin.WebApi.Extensions
return context.Request.Headers["Authorization"];
}
/// <summary>
/// 获取浏览器信息
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static ClientInfo GetClientInfo(this HttpContext context)
{
var str = context.GetUserAgent();
var uaParser = Parser.GetDefault();
ClientInfo c = uaParser.Parse(str);
return c;
}
/// <summary>
/// 获取请求Url
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string? GetRequestUrl(this HttpContext context)
public static string GetRequestUrl(this HttpContext context)
{
return context != null ? context.Request.Path.Value : "";
}
@@ -194,30 +183,51 @@ namespace ZR.Admin.WebApi.Extensions
return body;
}
/// <summary>
/// 获取浏览器信息
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static ClientInfo GetClientInfo(this HttpContext context)
{
var str = context.GetUserAgent();
var uaParser = Parser.GetDefault();
ClientInfo c = uaParser.Parse(str);
return c;
}
/// <summary>
/// 根据IP获取地理位置
/// </summary>
/// <returns></returns>
public static string GetIpInfo(string IP)
{
var ipInfo = IpTool.Search(IP);
return ipInfo?.Province + "-" + ipInfo?.City + "-" + ipInfo?.NetworkOperator;
}
/// <summary>
/// 设置请求参数
/// </summary>
/// <param name="operLog"></param>
/// <param name="reqMethod"></param>
/// <param name="context"></param>
public static void GetRequestValue(this HttpContext context, SysOperLog operLog)
public static string GetRequestValue(this HttpContext context, string reqMethod)
{
string reqMethod = operLog.RequestMethod;
string param= string.Empty;
string param = string.Empty;
if (HttpMethods.IsPost(reqMethod) || HttpMethods.IsPut(reqMethod) || HttpMethods.IsDelete(reqMethod))
{
param = context.GetBody();
param = PwdRep().Replace(param, "***");
string regex = "(?<=\"password\":\")[^\",]*";
param = Regex.Replace(param, regex, "***");
}
if (param.IsEmpty())
{
param = context.GetQueryString();
}
operLog.OperParam = param;
return param;
}
[GeneratedRegex("(?<=\"password\":\")[^\",]*")]
private static partial Regex PwdRep();
}
}
@@ -1,6 +1,9 @@
using AspNetCoreRateLimit;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace ZR.Admin.WebApi.Extensions
namespace ZR.Infrastructure.WebExtensions
{
public static class IPRateExtension
{
@@ -0,0 +1,39 @@
using Infrastructure;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Threading.Tasks;
namespace ZR.Infrastructure.WebExtensions
{
public static class JwtExtension
{
public static void AddJwt(this IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddCookie()
.AddJwtBearer(o =>
{
o.TokenValidationParameters = JwtUtil.ValidParameters();
o.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// 如果过期,把过期信息添加到头部
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
Console.WriteLine("jwt过期了");
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
},
};
});
}
}
}
@@ -1,7 +1,9 @@
using JinianNet.JNTemplate;
using ZR.Common;
using Infrastructure.Helper;
using JinianNet.JNTemplate;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace ZR.Admin.WebApi.Extensions
namespace Infrastructure
{
public static class LogoExtension
{
@@ -10,13 +12,14 @@ namespace ZR.Admin.WebApi.Extensions
Console.ForegroundColor = ConsoleColor.Blue;
var contentTpl = JnHelper.ReadTemplate("", "logo.txt");
var content = contentTpl?.Render();
var context = App.HttpContext;
Console.WriteLine(content);
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine("🎉源码地址: https://gitee.com/izory/ZrAdminNetCore");
Console.WriteLine("📖官方文档:http://www.izhaorui.cn/doc");
Console.WriteLine("🤑打赏作者:http://www.izhaorui.cn/doc/support.html");
Console.WriteLine("💰打赏作者:http://www.izhaorui.cn/doc/support.html");
Console.WriteLine("📱移动端体验:http://www.izhaorui.cn/h5");
Console.WriteLine($"Swagger地址:[后端启动地址]/swagger/index.html");
}
}
}
+26
View File
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>8632</NoWarn>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspectCore.Abstractions" Version="2.4.0" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="IPTools.China" Version="1.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.7" />
<PackageReference Include="JinianNet.JNTemplate" Version="2.3.3" />
<PackageReference Include="MiniExcel" Version="1.31.2" />
<PackageReference Include="CSRedisCore" Version="3.8.670" />
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
</ItemGroup>
</Project>
+4 -7
View File
@@ -22,10 +22,6 @@
-Based on. NET5/. A common rights management platform (RBAC model) implemented by NET7. Integrate the latest technology for efficient and rapid development, front-end and back-end separation mode, out of the box.
- Less code, simple to learn, easy to understand, powerful, easy to extend, lightweight, make web development faster, simpler and more efficient (say goodbye to 996), solve 70% of repetitive work, focus on your business, easy development from now on!
- 提供了技术栈(Ant Design Vue)版[Ant Design Vue](https://gitee.com/billzh/mc-dull.git)
- 七牛云通用云产品优惠券:[点我进入](https://s.qiniu.com/FzEfay)。
- 腾讯云秒杀场:[☛☛ 点我进入 ☚☚](https://curl.qcloud.com/4yEoRquq)。
- 腾讯云优惠券:[☛☛ 点我领取 ☚☚](https://curl.qcloud.com/5J4nag8D)。
- 阿里云特惠专区:[☛☛ 点我进入 ☚☚](https://www.aliyun.com/minisite/goods?userCode=uotn5vt1&share_source=copy_link)
```
If it helps you, you can click "Star" in the upper right corner to collect it, so that the author has the motivation to continue to go on for free, thank you! ~
@@ -52,7 +48,7 @@ Since it is a personal project, the funds are limited, and the experience server
| repository | Github | Gitee |
| -------------- | ------------------------------------------------------ | ---------------------------------------------------------------- |
| Net7 | [Clone/Download](https://github.com/izhaorui/Zr.Admin.NET) | [Clone/Download](https://gitee.com/izory/ZrAdminNetCore/tree/net7.0/) |
| Net7 | [Clone/Download](https://github.com/izhaorui/Zr.Admin.NET) | [Clone/Download](https://gitee.com/izory/ZrAdminNetCore) |
| Vue3(Hot) | [Clone/Download](https://github.com/izhaorui/ZR.Admin.Vue3) | [Clone/Download](https://gitee.com/izory/ZRAdmin-vue) |
## 🍁 Front-end technology
@@ -61,7 +57,7 @@ Vue Front-end technology stack: Based on Vue2.x/Vue3.x/UniApp, Vue, Vue-router,
## 🍀 Back-end technology
- Core Framework: . Net5.0/. Net7.0 + Web API + sqlsugar + swagger + signalR + IpRateLimit + Quartz.net + Redis
- Core Framework: . Net7.0 + Web API + sqlsugar + swagger + signalR + IpRateLimit + Quartz.net + Redis
- Scheduled tasks: Quartz.Net component that supports the execution of assemblies or HTTP network requests
- Security support: filters (data permission filtering), SQL injection, request forgery
- Log management: NLog, login log, operation log, scheduled task log
@@ -99,7 +95,8 @@ Vue Front-end technology stack: Based on Vue2.x/Vue3.x/UniApp, Vue, Vue-router,
## 🍻 Project structure
```
├─ZR.Service ->[服务层类库]:提供WebApi接口调用;
├─ZR.Service ->[你的业务服务层类库]:提供WebApi接口调用;
├─ZR.ServiceCore ->[系统服务层类库]:提供WebApi接口调用;
├─ZR.Repository ->[仓库层类库]:方便提供有执行存储过程的操作;
├─ZR.Model ->[实体层类库]:提供项目中的数据库表、数据传输对象;
├─ZR.Admin.WebApi ->[webapi接口]:为Vue版或其他三方系统提供接口服务。
+14 -6
View File
@@ -5,7 +5,7 @@
<a href="https://gitee.com/izory/ZrAdminNetCore"><img src="https://gitee.com/izory/ZrAdminNetCore/badge/star.svg?theme=dark"></a>
<a href='https://gitee.com/izory/ZrAdminNetCore/members'><img src='https://gitee.com/izory/ZrAdminNetCore/badge/fork.svg?theme=dark' alt='fork'></img></a>
<a href="https://gitee.com/izory/ZrAdminNetCore/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
<a href="http://www.izhaorui.cn/doc/changelog.html"><img src="https://img.shields.io/badge/更新日志-20230713-yellow"/></a>
<a href="http://www.izhaorui.cn/doc/changelog.html"><img src="https://img.shields.io/badge/更新日志-20230811-yellow"/></a>
</p>
---
@@ -28,7 +28,7 @@
- 阿里云特惠专区:[☛☛ 点我进入 ☚☚](https://www.aliyun.com/minisite/goods?userCode=uotn5vt1&share_source=copy_link)
```
如果对您有帮助,您可以点右上角 “Star” 收藏一下 ,这样作者才有继续免费下去的动力,谢谢!~
如果对您有帮助,您可以点右上角 “Star” 收藏一下 ,谢谢!~
```
## 🍿 在线体验
@@ -38,7 +38,7 @@
- Vue3.x 版本体验:[http://www.izhaorui.cn/vue3](http://www.izhaorui.cn/vue3)
- Vue2.x 版本体验:[http://www.izhaorui.cn/admin](http://www.izhaorui.cn/admin)
- Uniapp 版本体验:[http://www.izhaorui.cn/h5](http://www.izhaorui.cn/h5)
- 账号密码:admin/123456
- 账号密码:admin/123456,普通用户 user/123456
| H5 | 微信小程序 |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
@@ -52,7 +52,7 @@
| 仓库 | Github | Gitee |
| -------------- | ------------------------------------------------------ | ---------------------------------------------------------------- |
| Net7 | [克隆/下载](https://github.com/izhaorui/Zr.Admin.NET) | [克隆/下载](https://gitee.com/izory/ZrAdminNetCore/tree/net7.0/) |
| Net7 | [克隆/下载](https://github.com/izhaorui/Zr.Admin.NET) | [克隆/下载](https://gitee.com/izory/ZrAdminNetCore) |
| Vue3(推荐使用) | [克隆/下载](https://github.com/izhaorui/ZR.Admin.Vue3) | [克隆/下载](https://gitee.com/izory/ZRAdmin-vue) |
## 🍁 前端技术
@@ -72,6 +72,7 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x/uniapp、vuex、vue-router 、vue
- 分库分表:使用 orm sqlsugar 可以很轻松的实现分库分库性能优越
- 多 租 户:支持多租户功能
- 缓存数据:内置内存缓存和 Redis
- signalR:使用signalr管理用户在线状态
## 🍖 内置功能
@@ -88,18 +89,24 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x/uniapp、vuex、vue-router 、vue
11. 在线构建器:拖动表单元素生成相应的 VUE 代码(仅支持 vue2)。
12. 任务系统:基于 Quartz.NET,可以在线(添加、修改、删除、手动执行)任务调度包含执行结果日志。
13. 文章管理:可以写文章记录。
14. 代码生成:可以一键生成前后端代码(.cs、.vue、.js、.sql 等)支持下载,自定义配置前端展示控件、让开发更快捷高效(史上最强)。
14. 代码生成:可以一键生成前后端代码(.cs、.vue、.js、.sql、uniapp 等)支持下载,自定义配置前端展示控件、让开发更快捷高效(史上最强)。
15. 参数管理:对系统动态配置常用参数。
16. 发送邮件:可以对多个用户进行发送邮件。
17. 文件管理:可以进行上传文件管理,目前支持上传到本地、阿里云。
18. 通知管理:系统通知公告信息发布维护,使用 signalr 实现对用户实时通知。
19. 账号注册:可以注册账号登录系统。
20. 多语言管理:支持静态、后端动态配置国际化。目前只支持中、英、繁体(仅支持 vue3)
21. 在线用户:可以查看正在登录使用的用户,可以对其踢出、通知操作
22. db 审计日志:数据库审计功能
23. 三方登录:提供三方登录实现逻辑
## 🍻 项目结构
![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/kj.png)
```
├─ZR.Service ->[服务层类库]:提供WebApi接口调用;
├─ZR.Service ->[你的业务服务层类库]:提供WebApi接口调用;
├─ZR.ServiceCore ->[系统服务层类库]:提供WebApi接口调用;
├─ZR.Repository ->[仓库层类库]:方便提供有执行存储过程的操作;
├─ZR.Model ->[实体层类库]:提供项目中的数据库表、数据传输对象;
├─ZR.Admin.WebApi ->[webapi接口]:为Vue版或其他三方系统提供接口服务。
@@ -195,6 +202,7 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x/uniapp、vuex、vue-router 、vue
- 👉SqlSugar:[SqlSugar](https://gitee.com/dotnetchina/SqlSugar)
- 👉vue-element-admin:[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
- 👉Meiam.System:[Meiam.System](https://github.com/91270/Meiam.System)
- 👉Furion:[Furion](https://gitee.com/dotnetchina/Furion)
## 🎀 捐赠
@@ -1,10 +1,9 @@
using Infrastructure.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model.System;
using ZR.Service.IService;
using ZR.Service.System;
using ZR.Service.System.IService;
@@ -14,6 +13,8 @@ namespace ZR.Admin.WebApi.Controllers
/// 公共模块
/// </summary>
[Route("[controller]/[action]")]
[ApiExplorerSettings(GroupName = "sys")]
//[Produces("application/json")]
public class CommonController : BaseController
{
private OptionsSetting OptionsSetting;
@@ -21,18 +22,22 @@ namespace ZR.Admin.WebApi.Controllers
private IWebHostEnvironment WebHostEnvironment;
private ISysFileService SysFileService;
private IHelloService HelloService;
public CommonController(
IOptions<OptionsSetting> options,
IWebHostEnvironment webHostEnvironment,
ISysFileService fileService)
ISysFileService fileService,
IHelloService helloService)
{
WebHostEnvironment = webHostEnvironment;
SysFileService = fileService;
OptionsSetting = options.Value;
HelloService = helloService;
}
/// <summary>
/// hello
/// home
/// </summary>
/// <returns></returns>
[Route("/")]
@@ -43,6 +48,18 @@ namespace ZR.Admin.WebApi.Controllers
"如果觉得项目有用,打赏作者喝杯咖啡作为奖励\n☛☛http://www.izhaorui.cn/doc/support.html\n");
}
/// <summary>
/// hello
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Route("/hello")]
[HttpGet]
public IActionResult Hello(string name)
{
return Ok(HelloService.SayHello(name));
}
/// <summary>
/// 企业消息测试
/// </summary>
@@ -11,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers
/// 文章目录Controller
/// </summary>
[Route("article/ArticleCategory")]
[ApiExplorerSettings(GroupName = "article")]
public class ArticleCategoryController : BaseController
{
/// <summary>
@@ -59,7 +60,7 @@ namespace ZR.Admin.WebApi.Controllers
public IActionResult GetArticleCategory(int CategoryId)
{
var response = _ArticleCategoryService.GetFirst(x => x.CategoryId == CategoryId);
return SUCCESS(response);
}
@@ -13,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("article")]
[ApiExplorerSettings(GroupName = "article")]
public class ArticleController : BaseController
{
/// <summary>
@@ -32,7 +33,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
/// <returns></returns>
[HttpGet("list")]
[ActionPermissionFilter(Permission = "system:article:list")]
[ActionPermissionFilter(RolePermi = "admin")]
public IActionResult Query([FromQuery] ArticleQueryDto parm)
{
var response = _ArticleService.GetList(parm);
@@ -87,7 +88,7 @@ namespace ZR.Admin.WebApi.Controllers
var model = response.Adapt<ArticleDto>();
if (model.IsPublic == 0 && userId != model.UserId)
{
return ToResponse(Infrastructure.ResultCode.CUSTOM_ERROR, "访问失败");
return ToResponse(ResultCode.CUSTOM_ERROR, "访问失败");
}
if (model != null)
{
@@ -1,16 +1,10 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Extensions;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.CodeGenerator;
using ZR.CodeGenerator.Model;
using ZR.CodeGenerator.Service;
using ZR.Common;
using ZR.Model;
using ZR.Model.System.Dto;
using ZR.Model.System.Generate;
@@ -23,6 +17,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("tool/gen")]
[ApiExplorerSettings(GroupName = "sys")]
public class CodeGeneratorController : BaseController
{
private readonly CodeGeneraterService _CodeGeneraterService = new CodeGeneraterService();
@@ -14,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("system/CommonLang")]
[ApiExplorerSettings(GroupName = "sys")]
public class CommonLangController : BaseController
{
/// <summary>
@@ -14,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("system/config")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysConfigController : BaseController
{
/// <summary>
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model.System;
using ZR.Model.System.Dto;
@@ -13,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/dept")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysDeptController : BaseController
{
public ISysDeptService DeptService;
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model;
using ZR.Model.System;
@@ -13,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/dict/data")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysDictDataController : BaseController
{
private readonly ISysDictDataService SysDictDataService;
@@ -13,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/dict/type")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysDictTypeController : BaseController
{
private readonly ISysDictService SysDictService;
@@ -1,13 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using ZR.Admin.WebApi.Filters;
using ZR.Common;
using ZR.Model.System;
using ZR.Service.System.IService;
using ZR.Model.System.Dto;
using ZR.Service.System.IService;
namespace ZR.Admin.WebApi.Controllers
{
@@ -16,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("tool/file")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysFileController : BaseController
{
/// <summary>
@@ -37,15 +34,13 @@ namespace ZR.Admin.WebApi.Controllers
[ActionPermissionFilter(Permission = "tool:file:list")]
public IActionResult QuerySysFile([FromQuery] SysFileQueryDto parm)
{
//开始拼装查询条件
var predicate = Expressionable.Create<SysFile>();
//搜索条件查询语法参考Sqlsugar
predicate = predicate.AndIF(parm.BeginCreate_time != null, it => it.Create_time >= parm.BeginCreate_time);
predicate = predicate.AndIF(parm.EndCreate_time != null, it => it.Create_time <= parm.EndCreate_time);
predicate = predicate.AndIF(parm.StoreType != null, m => m.StoreType == parm.StoreType);
predicate = predicate.AndIF(parm.FileId != null, m => m.Id == parm.FileId);
//搜索条件查询语法参考Sqlsugar
var response = _SysFileService.GetPages(predicate.ToExpression(), parm, x => x.Id, OrderByType.Desc);
return SUCCESS(response);
}
@@ -1,11 +1,7 @@
using IPTools.Core;
using Lazy.Captcha.Core;
using Lazy.Captcha.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using UAParser;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Admin.WebApi.Framework;
using ZR.Model.System;
using ZR.Model.System.Dto;
using ZR.Service.System;
@@ -16,9 +12,10 @@ namespace ZR.Admin.WebApi.Controllers.System
/// <summary>
/// 登录
/// </summary>
[ApiExplorerSettings(GroupName = "sys")]
public class SysLoginController : BaseController
{
static readonly NLog.Logger logger = NLog.LogManager.GetLogger("LoginController");
//static readonly NLog.Logger logger = NLog.LogManager.GetLogger("LoginController");
private readonly IHttpContextAccessor httpContextAccessor;
private readonly ISysUserService sysUserService;
private readonly ISysMenuService sysMenuService;
@@ -27,7 +24,7 @@ namespace ZR.Admin.WebApi.Controllers.System
private readonly ICaptcha SecurityCodeHelper;
private readonly ISysConfigService sysConfigService;
private readonly ISysRoleService roleService;
private readonly OptionsSetting jwtSettings;
private readonly OptionsSetting optionSettings;
public SysLoginController(
IHttpContextAccessor contextAccessor,
@@ -38,7 +35,7 @@ namespace ZR.Admin.WebApi.Controllers.System
ISysConfigService configService,
ISysRoleService sysRoleService,
ICaptcha captcha,
IOptions<OptionsSetting> jwtSettings)
IOptions<OptionsSetting> optionSettings)
{
httpContextAccessor = contextAccessor;
SecurityCodeHelper = captcha;
@@ -48,7 +45,7 @@ namespace ZR.Admin.WebApi.Controllers.System
this.permissionService = permissionService;
this.sysConfigService = configService;
roleService = sysRoleService;
this.jwtSettings = jwtSettings.Value;
this.optionSettings = optionSettings.Value;
}
@@ -70,15 +67,17 @@ namespace ZR.Admin.WebApi.Controllers.System
return ToResponse(ResultCode.CAPTCHA_ERROR, "验证码错误");
}
var user = sysLoginService.Login(loginBody, RecordLogInfo(httpContextAccessor.HttpContext));
sysLoginService.CheckLockUser(loginBody.Username);
string location = HttpContextExtension.GetIpInfo(loginBody.LoginIP);
var user = sysLoginService.Login(loginBody, new SysLogininfor() { LoginLocation = location });
List<SysRole> roles = roleService.SelectUserRoleListByUserId(user.UserId);
//权限集合 eg *:*:*,system:user:list
List<string> permissions = permissionService.GetMenuPermission(user);
LoginUser loginUser = new(user, roles, permissions);
TokenModel loginUser = new(user.Adapt<TokenModel>(), roles.Adapt<List<Roles>>());
CacheService.SetUserPerms(GlobalConstant.UserPermKEY + user.UserId, permissions);
return SUCCESS(JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(loginUser), jwtSettings.JwtSettings));
return SUCCESS(JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(loginUser)));
}
/// <summary>
@@ -118,6 +117,7 @@ namespace ZR.Admin.WebApi.Controllers.System
//权限集合 eg *:*:*,system:user:list
List<string> permissions = permissionService.GetMenuPermission(user);
user.WelcomeContent = GlobalConstant.WelcomeMessages[new Random().Next(0, GlobalConstant.WelcomeMessages.Length)];
return SUCCESS(new { user, roles, permissions });
}
@@ -152,28 +152,6 @@ namespace ZR.Admin.WebApi.Controllers.System
return SUCCESS(obj);
}
/// <summary>
/// 记录用户登陆信息
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public SysLogininfor RecordLogInfo(HttpContext context)
{
var ipAddr = context.GetClientUserIp();
var ip_info = IpTool.Search(ipAddr);
ClientInfo clientInfo = context.GetClientInfo();
SysLogininfor sysLogininfor = new()
{
Browser = clientInfo.ToString(),
Os = clientInfo.OS.ToString(),
Ipaddr = ipAddr,
UserName = context.GetName(),
LoginLocation = ip_info?.Province + "-" + ip_info?.City
};
return sysLogininfor;
}
/// <summary>
/// 注册
/// </summary>
@@ -181,7 +159,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// <returns></returns>
[HttpPost("/register")]
[AllowAnonymous]
[Log(Title = "注册", BusinessType = Infrastructure.Enums.BusinessType.INSERT)]
[Log(Title = "注册", BusinessType = BusinessType.INSERT)]
public IActionResult Register([FromBody] RegisterDto dto)
{
SysConfig config = sysConfigService.GetSysConfigByKey("sys.account.register");
@@ -194,7 +172,7 @@ namespace ZR.Admin.WebApi.Controllers.System
{
return ToResponse(ResultCode.CAPTCHA_ERROR, "验证码错误");
}
SysUser user = sysUserService.Register(dto);
if (user.UserId > 0)
{
@@ -202,5 +180,86 @@ namespace ZR.Admin.WebApi.Controllers.System
}
return ToResponse(ResultCode.CUSTOM_ERROR, "注册失败,请联系管理员");
}
#region 二维码登录
/// <summary>
/// 生成二维码
/// </summary>
/// <param name="uuid"></param>
/// <param name="deviceId"></param>
/// <returns></returns>
[HttpGet("/GenerateQrcode")]
public IActionResult GenerateQrcode(string uuid, string deviceId)
{
var state = Guid.NewGuid().ToString();
var dict = new Dictionary<string, object>
{
{ "state", state }
};
CacheService.SetScanLogin(uuid, dict);
return SUCCESS(new
{
status = 1,
state,
uuid,
codeContent = new { uuid, deviceId }// "https://qm.qq.com/cgi-bin/qm/qr?k=kgt4HsckdljU0VM-0kxND6d_igmfuPlL&authKey=r55YUbruiKQ5iwC/folG7KLCmZ++Y4rQVgNlvLbUniUMkbk24Y9+zNuOmOnjAjRc&noverify=0"
});
}
/// <summary>
/// 轮询判断扫码状态
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("/VerifyScan")]
[AllowAnonymous]
public IActionResult VerifyScan([FromBody] ScanDto dto)
{
int status = -1;
object token = string.Empty;
if (CacheService.GetScanLogin(dto.Uuid) is Dictionary<string, object> str)
{
status = 0;
str.TryGetValue("token", out token);
if (str.ContainsKey("status") && (string)str.GetValueOrDefault("status") == "success")
{
status = 2;//扫码成功
CacheService.RemoveScanLogin(dto.Uuid);
}
}
return SUCCESS(new { status, token });
}
/// <summary>
/// 移动端扫码登录
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("/ScanLogin")]
[Log(Title = "扫码登录")]
[Verify]
public IActionResult ScanLogin([FromBody] ScanDto dto)
{
if (dto == null) { return ToResponse(ResultCode.CUSTOM_ERROR, "扫码失败"); }
var name = App.HttpContext.GetName();
sysLoginService.CheckLockUser(name);
TokenModel tokenModel = JwtUtil.GetLoginUser(HttpContext);
if (CacheService.GetScanLogin(dto.Uuid) is not null)
{
Dictionary<string, object> dict = new() { };
dict.Add("status", "success");
dict.Add("token", JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(tokenModel)));
CacheService.SetScanLogin(dto.Uuid, dict);
return SUCCESS(1);
}
return ToResponse(ResultCode.FAIL, "二维码已失效");
}
#endregion
}
}
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model.System;
using ZR.Model.System.Dto;
@@ -12,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("/system/menu")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysMenuController : BaseController
{
private readonly ISysRoleService sysRoleService;
@@ -1,14 +1,12 @@
using Infrastructure.Constant;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using SqlSugar;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Admin.WebApi.Hubs;
using ZR.Model;
using ZR.Model.System;
using ZR.Model.System.Dto;
using ZR.Service.System.IService;
using ZR.ServiceCore.Signalr;
namespace ZR.Admin.WebApi.Controllers.System
{
@@ -17,6 +15,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/notice")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysNoticeController : BaseController
{
/// <summary>
@@ -1,7 +1,5 @@
using Infrastructure.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model;
using ZR.Model.System;
@@ -14,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/post")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysPostController : BaseController
{
private readonly ISysPostService PostService;
@@ -68,7 +67,7 @@ namespace ZR.Admin.WebApi.Controllers.System
throw new CustomException($"修改岗位{post.PostName}失败,岗位编码已存在");
}
post.ToCreate(HttpContext);
return ToResponse(PostService.Add(post));
}
@@ -122,7 +121,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// 岗位导出
/// </summary>
/// <returns></returns>
[Log(BusinessType = BusinessType.EXPORT, IsSaveResponseData = false, Title= "岗位导出")]
[Log(BusinessType = BusinessType.EXPORT, IsSaveResponseData = false, Title = "岗位导出")]
[HttpGet("export")]
[ActionPermissionFilter(Permission = "system:post:export")]
public IActionResult Export()
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model.System;
using ZR.Model.System.Dto;
@@ -12,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/user/profile")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysProfileController : BaseController
{
private readonly ISysUserService UserService;
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model;
using ZR.Model.System;
@@ -13,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/role")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysRoleController : BaseController
{
private readonly ISysRoleService sysRoleService;
@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using MiniExcelLibs;
using SqlSugar;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model;
using ZR.Model.System;
@@ -15,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/user")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysUserController : BaseController
{
private readonly ISysUserService UserService;
@@ -93,9 +93,10 @@ namespace ZR.Admin.WebApi.Controllers.System
}
user.Create_by = HttpContext.GetName();
user.Create_time = DateTime.Now;
user.Password = NETCore.Encrypt.EncryptProvider.Md5(user.Password);
return ToResponse(UserService.InsertUser(user));
return SUCCESS(UserService.InsertUser(user));
}
/// <summary>
@@ -143,7 +144,7 @@ namespace ZR.Admin.WebApi.Controllers.System
public IActionResult Remove(int userid = 0)
{
if (userid <= 0) { return ToResponse(ApiResult.Error(101, "请求参数错误")); }
if (userid == 1) return ToResponse(Infrastructure.ResultCode.FAIL, "不能删除管理员账号");
if (userid == 1) return ToResponse(ResultCode.FAIL, "不能删除管理员账号");
int result = UserService.DeleteUser(userid);
return ToResponse(result);
@@ -156,7 +157,7 @@ namespace ZR.Admin.WebApi.Controllers.System
[HttpPut("resetPwd")]
[Log(Title = "重置密码", BusinessType = BusinessType.UPDATE)]
[ActionPermissionFilter(Permission = "system:user:resetPwd")]
public IActionResult ResetPwd([FromBody] SysUser sysUser)
public IActionResult ResetPwd([FromBody] SysUserDto sysUser)
{
//密码md5
sysUser.Password = NETCore.Encrypt.EncryptProvider.Md5(sysUser.Password);
@@ -10,6 +10,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/userRole")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysUserRoleController : BaseController
{
private readonly ISysUserRoleService SysUserRoleService;
@@ -43,7 +44,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// <returns></returns>
[HttpPost("create")]
[ActionPermissionFilter(Permission = "system:roleusers:add")]
[Log(Title = "添加角色用户", BusinessType = Infrastructure.Enums.BusinessType.INSERT)]
[Log(Title = "添加角色用户", BusinessType = BusinessType.INSERT)]
public IActionResult Create([FromBody] RoleUsersCreateDto roleUsersCreateDto)
{
var response = SysUserRoleService.InsertRoleUser(roleUsersCreateDto);
@@ -58,7 +59,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// <returns></returns>
[HttpPost("delete")]
[ActionPermissionFilter(Permission = "system:roleusers:remove")]
[Log(Title = "删除角色用户", BusinessType = Infrastructure.Enums.BusinessType.DELETE)]
[Log(Title = "删除角色用户", BusinessType = BusinessType.DELETE)]
public IActionResult Delete([FromBody] RoleUsersCreateDto roleUsersCreateDto)
{
return SUCCESS(SysUserRoleService.DeleteRoleUserByUserIds(roleUsersCreateDto.RoleId, roleUsersCreateDto.UserIds));
@@ -1,8 +1,6 @@
using Infrastructure.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Quartz;
using SqlSugar;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model.System;
using ZR.Model.System.Dto;
@@ -16,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("system/Tasks")]
[ApiExplorerSettings(GroupName = "sys")]
public class TasksController : BaseController
{
private ISysTasksQzService _tasksQzService;
@@ -87,7 +86,7 @@ namespace ZR.Admin.WebApi.Controllers
throw new CustomException($"程序集或者类名不能为空");
}
//从 Dto 映射到 实体
var tasksQz = parm.Adapt<SysTasks>().ToCreate();
var tasksQz = parm.Adapt<SysTasks>().ToCreate(HttpContext);
tasksQz.Create_by = HttpContext.GetName();
tasksQz.ID = SnowFlakeSingle.Instance.NextId().ToString();
@@ -159,7 +158,7 @@ namespace ZR.Admin.WebApi.Controllers
var tasksQz = _tasksQzService.GetFirst(m => m.ID == id);
var taskResult = await _schedulerServer.DeleteTaskScheduleAsync(tasksQz);
if (taskResult.Code == 200)
if (taskResult.IsSuccess())
{
_tasksQzService.Delete(id);
}
@@ -188,7 +187,7 @@ namespace ZR.Admin.WebApi.Controllers
var tasksQz = _tasksQzService.GetFirst(m => m.ID == id);
var taskResult = await _schedulerServer.AddTaskScheduleAsync(tasksQz);
if (taskResult.Code == 200)
if (taskResult.IsSuccess())
{
tasksQz.IsStart = 1;
_tasksQzService.Update(tasksQz);
@@ -219,7 +218,7 @@ namespace ZR.Admin.WebApi.Controllers
var tasksQz = _tasksQzService.GetFirst(m => m.ID == id);
var taskResult = await _schedulerServer.DeleteTaskScheduleAsync(tasksQz);//await _schedulerServer.PauseTaskScheduleAsync(tasksQz);
if (taskResult.Code == 200)
if (taskResult.IsSuccess())
{
tasksQz.IsStart = 0;
_tasksQzService.Update(tasksQz);
@@ -1,11 +1,6 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using ZR.Admin.WebApi.Filters;
using ZR.Common;
using ZR.Model;
using ZR.Model.System;
using ZR.Model.System.Dto;
@@ -18,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("/monitor/jobLog")]
[ApiExplorerSettings(GroupName = "sys")]
public class TasksLogController : BaseController
{
private readonly ISysTasksLogService tasksLogService;
@@ -1,11 +1,6 @@
using Infrastructure;
using Infrastructure.Extensions;
using Infrastructure.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
@@ -14,6 +9,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor
/// <summary>
/// 系统监控
/// </summary>
[ApiExplorerSettings(GroupName = "sys")]
public class MonitorController : BaseController
{
private OptionsSetting Options;
@@ -0,0 +1,77 @@
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Filters;
using ZR.Model.System.Dto;
using ZR.Service.System.IService;
//创建时间:2023-08-17
namespace ZR.Admin.WebApi.Controllers
{
/// <summary>
/// 数据差异日志
/// </summary>
[Verify]
[Route("monitor/SqlDiffLog")]
[ApiExplorerSettings(GroupName = "sys")]
public class SqlDiffLogController : BaseController
{
/// <summary>
/// 数据差异日志接口
/// </summary>
private readonly ISqlDiffLogService _SqlDiffLogService;
public SqlDiffLogController(ISqlDiffLogService SqlDiffLogService)
{
_SqlDiffLogService = SqlDiffLogService;
}
/// <summary>
/// 查询数据差异日志列表
/// </summary>
/// <param name="parm"></param>
/// <returns></returns>
[HttpGet("list")]
[ActionPermissionFilter(Permission = "sqldifflog:list")]
public IActionResult QuerySqlDiffLog([FromQuery] SqlDiffLogQueryDto parm)
{
var response = _SqlDiffLogService.GetList(parm);
return SUCCESS(response);
}
/// <summary>
/// 删除数据差异日志
/// </summary>
/// <returns></returns>
[HttpDelete("{ids}")]
[ActionPermissionFilter(Permission = "sqldifflog:delete")]
[Log(Title = "数据差异日志", BusinessType = BusinessType.DELETE)]
public IActionResult DeleteSqlDiffLog(string ids)
{
long[] idsArr = Tools.SpitLongArrary(ids);
if (idsArr.Length <= 0) { return ToResponse(ApiResult.Error($"删除失败Id 不能为空")); }
var response = _SqlDiffLogService.Delete(idsArr);
return ToResponse(response);
}
/// <summary>
/// 导出数据差异日志
/// </summary>
/// <returns></returns>
[Log(Title = "数据差异日志", BusinessType = BusinessType.EXPORT, IsSaveResponseData = false)]
[HttpGet("export")]
[ActionPermissionFilter(Permission = "sqldifflog:export")]
public IActionResult Export([FromQuery] SqlDiffLogQueryDto parm)
{
parm.PageNum = 1;
parm.PageSize = 100000;
var list = _SqlDiffLogService.GetList(parm).Result;
if (list == null || list.Count <= 0)
{
return ToResponse(ResultCode.FAIL, "没有要导出的数据");
}
var result = ExportExcelMini(list, "数据差异日志", "数据差异日志");
return ExportExcel(result.Item2, result.Item1);
}
}
}
@@ -13,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor
/// </summary>
[Verify]
[Route("/monitor/logininfor")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysLogininforController : BaseController
{
private ISysLoginService sysLoginService;
@@ -11,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor
/// </summary>
[Verify]
[Route("/monitor/operlog")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysOperlogController : BaseController
{
private ISysOperLogService sysOperLogService;
@@ -65,7 +66,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor
{
if (!HttpContextExtension.IsAdmin(HttpContext))
{
return ToResponse(Infrastructure.ResultCode.CUSTOM_ERROR,"操作失败");
return ToResponse(ResultCode.CUSTOM_ERROR,"操作失败");
}
sysOperLogService.CleanOperLog();
@@ -1,18 +1,23 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using ZR.Admin.WebApi.Filters;
using ZR.Admin.WebApi.Hubs;
using ZR.Model;
using ZR.Model.System.Dto;
using ZR.ServiceCore.Signalr;
namespace ZR.Admin.WebApi.Controllers.monitor
{
/// <summary>
/// 在线用户
/// </summary>
[Verify]
[Route("monitor/online")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysUserOnlineController : BaseController
{
private IHubContext<Hub> HubContext;
private readonly IHubContext<MessageHub> HubContext;
public SysUserOnlineController(IHubContext<Hub> hubContext)
public SysUserOnlineController(IHubContext<MessageHub> hubContext)
{
HubContext = hubContext;
}
@@ -25,11 +30,47 @@ namespace ZR.Admin.WebApi.Controllers.monitor
[HttpGet("list")]
public IActionResult Index([FromQuery] PagerInfo parm)
{
var result = MessageHub.clientUsers
var result = MessageHub.onlineClients
.OrderByDescending(f => f.LoginTime)
.Skip(parm.PageNum - 1).Take(parm.PageSize);
return SUCCESS(new { result, totalNum = MessageHub.clientUsers.Count });
return SUCCESS(new { result, totalNum = MessageHub.onlineClients.Count });
}
/// <summary>
/// 单个强退
/// </summary>
/// <returns></returns>
[HttpDelete("force")]
[Log(Title = "强退", BusinessType = BusinessType.FORCE)]
[ActionPermissionFilter(Permission = "monitor:online:forceLogout")]
public async Task<IActionResult> Force([FromBody] LockUserDto dto)
{
if (dto == null) { return ToResponse(ResultCode.PARAM_ERROR); }
await HubContext.Clients.Client(dto.ConnnectionId)
.SendAsync(HubsConstant.ForceUser, new { dto.Reason, dto.Time });
//var expirTime = DateTimeHelper.GetUnixTimeSeconds(DateTime.Now.AddMinutes(dto.Time));
////PC 端采用设备 + 用户名的方式进行封锁
//CacheService.SetLockUser(dto.ClientId + dto.Name, expirTime, dto.Time);
return SUCCESS(1);
}
/// <summary>
/// 批量强退
/// </summary>
/// <returns></returns>
[HttpDelete("batchForce")]
[Log(Title = "强退", BusinessType = BusinessType.FORCE)]
[ActionPermissionFilter(Permission = "monitor:online:batchLogout")]
public async Task<IActionResult> BatchforceLogout([FromBody] LockUserDto dto)
{
if (dto == null) { return ToResponse(ResultCode.PARAM_ERROR); }
await HubContext.Clients.All.SendAsync(HubsConstant.ForceUser, new { dto.Reason });
return SUCCESS(1);
}
}
}
@@ -1,5 +1,4 @@
using Infrastructure.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using System.Web;
namespace ZR.Admin.WebApi.Controllers
@@ -11,9 +10,6 @@ namespace ZR.Admin.WebApi.Controllers
[AllowAnonymous]
public class WxOpenController : BaseController
{
private NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
public WxOpenController() { }
/// <summary>
/// 获取签名
/// </summary>
+40 -3
View File
@@ -1,7 +1,7 @@
using Infrastructure;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using Swashbuckle.AspNetCore.SwaggerUI;
using System.Reflection;
namespace ZR.Admin.WebApi.Extensions
@@ -33,7 +33,13 @@ namespace ZR.Admin.WebApi.Extensions
};
});
});
app.UseSwaggerUI(c => c.SwaggerEndpoint("v1/swagger.json", "ZrAdmin v1"));
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("sys/swagger.json", "系统管理");
c.SwaggerEndpoint("article/swagger.json", "文章管理");
c.SwaggerEndpoint("v1/swagger.json", "business");
c.DocExpansion(DocExpansion.None); //->修改界面打开时自动折叠
});
}
public static void AddSwaggerConfig(this IServiceCollection services)
@@ -43,6 +49,20 @@ namespace ZR.Admin.WebApi.Extensions
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("sys", new OpenApiInfo
{
Title = "ZrAdmin.NET Api",
Version = "v1",
Description = "系统管理",
Contact = new OpenApiContact { Name = "ZRAdmin doc", Url = new Uri("https://www.izhaorui.cn/doc") }
});
c.SwaggerDoc("article", new OpenApiInfo
{
Title = "ZrAdmin.NET Api",
Version = "v1",
Description = "文章管理",
Contact = new OpenApiContact { Name = "ZRAdmin doc", Url = new Uri("https://www.izhaorui.cn/doc") }
});
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "ZrAdmin.NET Api",
@@ -55,6 +75,9 @@ namespace ZR.Admin.WebApi.Extensions
//添加文档注释
var baseDir = AppContext.BaseDirectory;
c.IncludeXmlComments(Path.Combine(baseDir, "ZR.Model.xml"), true);
c.IncludeXmlComments(Path.Combine(baseDir, "ZR.ServiceCore.xml"), true);
c.IncludeXmlComments(Path.Combine(baseDir, "ZR.Service.xml"), true);
c.IncludeXmlComments(Path.Combine(baseDir, "ZR.Admin.WebApi.xml"), true);
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(baseDir, xmlFile);
@@ -94,6 +117,20 @@ namespace ZR.Admin.WebApi.Extensions
new List<string>()
}
});
//判断接口归于哪个分组
c.DocInclusionPredicate((docName, apiDescription) =>
{
if (docName == "v1")
{
//当分组为NoGroup时,只要没加特性的都属于这个组
return string.IsNullOrEmpty(apiDescription.GroupName);
}
else
{
return apiDescription.GroupName == docName;
}
});
});
}
}
+1 -1
View File
@@ -40,7 +40,7 @@ namespace ZR.Admin.WebApi.Extensions
foreach (var task in tasks.Result)
{
var result = _schedulerServer.AddTaskScheduleAsync(task);
if (result.Result.Code == 200)
if (result.Result.IsSuccess())
{
Console.WriteLine($"注册任务[{task.Name}]ID:{task.ID}成功");
}
+4 -1
View File
@@ -4,4 +4,7 @@ global using Infrastructure;
global using Infrastructure.Attribute;
global using Infrastructure.Enums;
global using Infrastructure.Model;
global using Mapster;
global using Mapster;
global using Infrastructure.Extensions;
global using Infrastructure.Controllers;
global using ZR.ServiceCore.Middleware;
-107
View File
@@ -1,107 +0,0 @@
using Infrastructure;
using Infrastructure.Constant;
using Infrastructure.Model;
using IPTools.Core;
using Microsoft.AspNetCore.SignalR;
using UAParser;
using ZR.Admin.WebApi.Extensions;
using ZR.Service.System.IService;
namespace ZR.Admin.WebApi.Hubs
{
/// <summary>
/// msghub
/// </summary>
public class MessageHub : Hub
{
//创建用户集合,用于存储所有链接的用户数据
public static readonly List<OnlineUsers> clientUsers = new();
private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly ISysNoticeService SysNoticeService;
public MessageHub(ISysNoticeService noticeService)
{
SysNoticeService = noticeService;
}
private ApiResult SendNotice()
{
var result = SysNoticeService.GetSysNotices();
return new ApiResult(200, "success", result);
}
#region 客户端连接
/// <summary>
/// 客户端连接的时候调用
/// </summary>
/// <returns></returns>
public override Task OnConnectedAsync()
{
var name = HttpContextExtension.GetName(App.HttpContext);
var ip = HttpContextExtension.GetClientUserIp(App.HttpContext);
var ip_info = IpTool.Search(ip);
ClientInfo clientInfo = HttpContextExtension.GetClientInfo(App.HttpContext);
string device = clientInfo.ToString();
var userid = HttpContextExtension.GetUId(App.HttpContext);
string uuid = device + userid + ip;
var user = clientUsers.Any(u => u.ConnnectionId == Context.ConnectionId);
var user2 = clientUsers.Any(u => u.Uuid == uuid);
//判断用户是否存在,否则添加集合!user2 && !user &&
if (!user2 && !user && Context.User.Identity.IsAuthenticated)
{
OnlineUsers users = new(Context.ConnectionId, name, userid, ip, device)
{
Location = ip_info.City,
Uuid = uuid
};
clientUsers.Add(users);
Console.WriteLine($"{DateTime.Now}:{name},{Context.ConnectionId}连接服务端success,当前已连接{clientUsers.Count}个");
//Clients.All.SendAsync("welcome", $"欢迎您:{name},当前时间:{DateTime.Now}");
Clients.All.SendAsync(HubsConstant.MoreNotice, SendNotice());
}
Clients.All.SendAsync(HubsConstant.OnlineNum, clientUsers.Count);
return base.OnConnectedAsync();
}
/// <summary>
/// 连接终止时调用。
/// </summary>
/// <returns></returns>
public override Task OnDisconnectedAsync(Exception? exception)
{
var user = clientUsers.Where(p => p.ConnnectionId == Context.ConnectionId).FirstOrDefault();
//判断用户是否存在,否则添加集合
if (user != null)
{
clientUsers.Remove(user);
Clients.All.SendAsync(HubsConstant.OnlineNum, clientUsers.Count);
Console.WriteLine($"用户{user?.Name}离开了,当前已连接{clientUsers.Count}个");
}
return base.OnDisconnectedAsync(exception);
}
#endregion
/// <summary>
/// 注册信息
/// </summary>
/// <param name="connectId"></param>
/// <param name="userName"></param>
/// <param name="message"></param>
/// <returns></returns>
[HubMethodName("SendMessage")]
public async Task SendMessage(string connectId, string userName, string message)
{
Console.WriteLine($"{connectId},message={message}");
await Clients.Client(connectId).SendAsync("receiveChat", new { userName, message });
}
}
}
-40
View File
@@ -1,40 +0,0 @@
namespace ZR.Admin.WebApi.Hubs
{
public class OnlineUsers
{
/// <summary>
/// 客户端连接Id
/// </summary>
public string ConnnectionId { get; set; }
/// <summary>
/// 用户id
/// </summary>
public long? Userid { get; set; }
public string Name { get; set; }
public DateTime LoginTime { get; set; }
public string UserIP { get; set; }
/// <summary>
/// 登录地点
/// </summary>
public string? Location { get; set; }
/// <summary>
/// 判断用户唯一
/// </summary>
public string? Uuid{ get; set; }
/// <summary>
/// 浏览器
/// </summary>
public string Browser { get; set; }
public OnlineUsers(string clientid, string name, long? userid, string userip, string browser)
{
ConnnectionId = clientid;
Name = name;
LoginTime = DateTime.Now;
Userid = userid;
UserIP = userip;
Browser = browser;
}
}
}
+13 -15
View File
@@ -24,29 +24,29 @@
<!--${basedir}表示当前应用程序域所在的根目录-->
<target name="allfile" xsi:type="File"
fileName="${basedir}/adminlogs/all.txt"
archiveFileName="${basedir}/adminlogs/bak/all.{###}.txt"
archiveFileName="${basedir}/adminlogs/bak/all/all.{###}.txt"
archiveEvery="Day"
archiveNumbering="DateAndSequence"
archiveAboveSize="20000000"
maxArchiveFiles="30"
keepFileOpen="true"
layout="${longdate} | ${event-properties:item=EventId_Id} | ${uppercase:${level}} | ${logger} | ${aspnet-request-iP:CheckForwardedForHeader=true} | ${event-properties:item=user} | ${aspnet-request-url} | ${message} | ${event-properties:item=requestParam} | ${event-properties:item=jsonResult} | ${onexception:${exception:format=tostring}"/>
layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${aspnet-request-connection-id}|${uppercase:${level}}|${logger}|${aspnet-request-iP:CheckForwardedForHeader=true}|${event-properties:item=user:whenEmpty=-}|url: ${aspnet-request-url}|${message:whenEmpty=-}|${event-properties:item=requestParam}|${event-properties:item=jsonResult}"/>
<!--错误日志-->
<target name="errorfile" xsi:type="File"
fileName="${basedir}/adminlogs/error.txt"
archiveFileName="${basedir}/adminlogs/bak/error.{###}.txt"
archiveFileName="${basedir}/adminlogs/bak/error/error.{###}.txt"
archiveEvery="Day"
archiveNumbering="DateAndSequence"
archiveAboveSize="20000000"
maxArchiveFiles="30"
keepFileOpen="true"
layout="${longdate} | ${event-properties:item=EventId_Id} | ${uppercase:${level}} | ${logger} | ${aspnet-request-iP:CheckForwardedForHeader=true} | ${event-properties:item=user} | ${aspnet-request-url} | ${message} | ${event-properties:item=requestParam} | ${event-properties:item=jsonResult} | ${onexception:${exception:format=tostring}"/>
layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${uppercase:${level}}|${logger}${newline}用户IP:${aspnet-request-iP:CheckForwardedForHeader=true}|${event-properties:item=user}${newline}请求地址:${aspnet-request-url}${newline}错误消息:${message}${newline}请求参数:${event-properties:item=requestParam}${newline}请求结果:${event-properties:item=jsonResult}${newline}${onexception:${exception:format=tostring}"/>
<!--SQL文件-->
<target name="sqlfile" xsi:type="File"
fileName="${basedir}/adminlogs/admin-sql.txt"
archiveFileName="${basedir}/adminlogs/bak/admin-sql{###}.txt"
fileName="${basedir}/adminlogs/sql.txt"
archiveFileName="${basedir}/adminlogs/bak/sql/sql{###}.txt"
archiveEvery="Day"
archiveNumbering="DateAndSequence"
archiveAboveSize="20000000"
@@ -56,18 +56,17 @@
<!--写入彩色控制台-->
<target name="consoleSql" xsi:type="ColoredConsole" useDefaultRowHighlightingRules="false"
layout="${date:format=MM-dd HH\:mm\:ss} | ${aspnet-request-iP} | ${aspnet-request-url} ${newline}${message}">
layout="${date:format=MM-dd HH\:mm\:ss}|${aspnet-request-iP}|${aspnet-request-url}${newline}${message}">
<highlight-row condition="level == LogLevel.Debug" foregroundColor="DarkGray" />
<highlight-row condition="level == LogLevel.Info" foregroundColor="Gray" />
<highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" />
<highlight-row condition="level == LogLevel.Error" foregroundColor="Red" />
<highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" backgroundColor="White" />
<highlight-word regex="SQL语句" foregroundColor="Blue" />
<highlight-word regex="SQL" foregroundColor="Blue" />
<highlight-word regex="【" foregroundColor="Blue" />
<highlight-word regex="】" foregroundColor="Blue" />
</target>
<target name="console" xsi:type="ColoredConsole"
layout="${date:format=MM-dd HH\:mm\:ss} | ${aspnet-request-url} ${newline} ${message}"/>
<!--写入黑洞-->
<target name="blackhole" xsi:type="Null" />
</targets>
@@ -80,11 +79,10 @@
<!--<logger name="System.*" writeTo="blackhole" final="true" />-->
<!-- Quartz -->
<logger name="Quartz*" minlevel="Trace" maxlevel="Info" final="true" />
<logger name="ZR.Admin.WebApi.Middleware.GlobalExceptionMiddleware" final="true" writeTo="console,errorfile"/>
<logger name="ZR.Admin.WebApi.Extensions.DbExtension" final="true" writeTo="consoleSql,sqlfile"/>
<logger name="*" writeTo="console"/>
<logger name="*.SqlSugar.SqlsugarSetup" final="true" writeTo="consoleSql,sqlfile"/>
<logger name="*" minLevel="Trace" writeTo="allfile" />
<logger name="*.GlobalExceptionMiddleware" final="true" writeTo="consoleSql,errorfile"/>
<!--Skip non-critical Microsoft logs and so log only own logs-->
<logger name="Microsoft.*,Quartz.Core.QuartzSchedulerThread" maxlevel="Info" final="true" />
</rules>
+31 -36
View File
@@ -1,17 +1,19 @@
using AspNetCoreRateLimit;
using Infrastructure;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Infrastructure.Converter;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.IdentityModel.Tokens;
using NLog.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Admin.WebApi.Framework;
using ZR.Admin.WebApi.Hubs;
using ZR.Admin.WebApi.Middleware;
using ZR.Common.Cache;
using ZR.Infrastructure.WebExtensions;
using ZR.ServiceCore.Signalr;
using ZR.ServiceCore.SqlSugar;
var builder = WebApplication.CreateBuilder(args);
// NLog: Setup NLog for Dependency injection
//builder.Logging.ClearProviders();
builder.Host.UseNLog();
// Add services to the container.
builder.Services.AddControllers();
@@ -24,8 +26,6 @@ builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddCors(builder.Configuration);
// 显示logo
builder.Services.AddLogo();
//注入SignalR实时通讯,默认用json传输
builder.Services.AddSignalR();
//消除Error unprotecting the session cookie警告
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "DataProtection"));
@@ -39,30 +39,10 @@ builder.Services.AddHttpContextAccessor();
builder.Services.Configure<OptionsSetting>(builder.Configuration);
//jwt 认证
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddCookie()
.AddJwtBearer(o =>
{
o.TokenValidationParameters = JwtUtil.ValidParameters();
o.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// 如果过期,把过期信息添加到头部
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
builder.Services.AddJwt();
//配置文件
builder.Services.AddSingleton(new AppSettings(builder.Configuration));
//app服务注册
builder.Services.AddAppService();
//开启计划任务
builder.Services.AddTaskSchedulers();
@@ -80,12 +60,21 @@ builder.Services.AddMvc(options =>
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString;
//options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString;
options.JsonSerializerOptions.WriteIndented = true;
options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeConverter());
options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeNullConverter());
options.JsonSerializerOptions.Converters.Add(new StringConverter());
//PropertyNamingPolicy属性用于前端传过来的属性的格式策略,目前内置的仅有一种策略CamelCase
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
//options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;//属性可以忽略大小写格式,开启后性能会降低
});
//注入SignalR实时通讯,默认用json传输
builder.Services.AddSignalR()
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
builder.Services.AddSwaggerConfig();
var app = builder.Build();
@@ -93,11 +82,18 @@ InternalApp.ServiceProvider = app.Services;
InternalApp.Configuration = builder.Configuration;
InternalApp.WebHostEnvironment = app.Environment;
//初始化db
builder.Services.AddDb(builder.Configuration, app.Environment);
builder.Services.AddDb(app.Environment);
//使用全局异常中间件
app.UseMiddleware<GlobalExceptionMiddleware>();
//请求头转发
//ForwardedHeaders中间件会自动把反向代理服务器转发过来的X-Forwarded-For(客户端真实IP)以及X-Forwarded-Proto(客户端请求的协议)自动填充到HttpContext.Connection.RemoteIPAddress和HttpContext.Request.Scheme中,这样应用代码中读取到的就是真实的IP和真实的协议了,不需要应用做特殊处理。
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor | Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto
});
app.Use((context, next) =>
{
//设置可以多次获取body内容
@@ -138,5 +134,4 @@ app.MapControllerRoute(
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapControllers();
app.Run();
+9 -8
View File
@@ -6,7 +6,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;1591,8603,8602,8604,8600</NoWarn>
<NoWarn>1701;1702;1591,8603,8602,8604,8600,8618</NoWarn>
</PropertyGroup>
<ItemGroup>
@@ -15,17 +15,12 @@
<ProjectReference Include="..\ZR.Tasks\ZR.Tasks.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="Lazy.Captcha.Core" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.6" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="IPTools.China" Version="1.6.0" />
<PackageReference Include="NLog" Version="5.2.0" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.3" />
<PackageReference Include="Mapster" Version="7.3.0" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.5" />
</ItemGroup>
<ItemGroup>
@@ -36,5 +31,11 @@
<None Update="ip2region.db">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Content Update="wwwroot\Generatecode\**\*">
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\export\**\*">
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>
+9 -10
View File
@@ -21,7 +21,7 @@
"Conn": "Data Source=LAPTOP-STKF2M8H\\SQLEXPRESS;User ID=admin;Password=admin123;Initial Catalog={dbName};",
"DbType": 1,
"IsAutoCloseConnection": true,
"DbName": "ZrAdmin" //代码生成默认连接数据库
"DbName": "ZrAdmin" //代码生成默认连接数据库,Oracle库是实例的名称
},
"urls": "http://localhost:8888", //项目启动url,如果改动端口前端对应devServer也需要进行修改
"corsUrls": [ "http://localhost:8887", "http://localhost:8886" ], //跨域地址(前端启动项目,前后端分离单独部署需要设置),多个用","隔开
@@ -29,11 +29,15 @@
"Issuer": "ZRAdmin.NET", //即token的签发者。
"Audience": "ZRAdmin.NET", //指该token是服务于哪个群体的(群体范围)
"SecretKey": "SecretKey-ZRADMIN.NET-20210101",
"Expire": 1440 //jwt登录过期时间(分)
"Expire": 1440, //jwt登录过期时间(分)
"RefreshTokenTime": 5,//分钟
"TokenType": "Bearer"
},
"InjectClass": [ "ZR.Repository", "ZR.Service", "ZR.Tasks" ], //自动注入类
"InjectClass": [ "ZR.Repository", "ZR.Service", "ZR.Tasks", "ZR.ServiceCore" ], //自动注入类
"ShowDbLog": true, //是否打印db日志
"InitDb": false, //是否初始化db
"DemoMode": false, //是否演示模式
"SingleLogin": false,//是否单点登录
"Upload": {
"uploadUrl": "http://localhost:8888", //本地存储资源访问路径
"localSavePath": "", //本地上传默认文件存储目录 wwwroot
@@ -108,7 +112,7 @@
"RealIpHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"EndpointWhitelist": [ "post:/system/dict/data/types", "*:/msghub/negotiate", "*:/LogOut", "*:/common/uploadfile" ],
"EndpointWhitelist": [ "post:/system/dict/data/types", "*:/msghub/negotiate", "*:/LogOut", "*:/common/uploadfile", "*:/VerifyScan" ],
"QuotaExceededResponse": {
"Content": "{{\"code\":429,\"msg\":\"访问过于频繁,请稍后重试\"}}",
"ContentType": "application/json",
@@ -127,12 +131,7 @@
"Period": "3s",
"Limit": 1
}
],
"IpRateLimitPolicies": {
//ip规则
"IpRules": [
]
}
]
},
//验证码配置
"CaptchaOptions": {
@@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Mvc;
using ${options.DtosNamespace};
using ${options.ModelsNamespace}.${options.SubNamespace};
using ${options.IServicsNamespace}.${options.SubNamespace}.I${options.SubNamespace}Service;
using ${options.ApiControllerNamespace}.Extensions;
using ${options.ApiControllerNamespace}.Filters;
$if(replaceDto.ShowBtnImport)
using MiniExcelLibs;
@@ -117,7 +117,7 @@ $end
$if(null != genTable.SubTableName && "" != genTable.SubTableName)
return Context.InsertNav(model).Include(s1 => s1.${genTable.SubTable.ClassName}Nav).ExecuteReturnEntity();
$else
return Insertable(model).IgnoreColumns(true).ExecuteReturnEntity();
return Context.Insertable(model).ExecuteReturnEntity();
$end
}
@@ -69,12 +69,14 @@ $end
</u--form>
<view class="btn-group">
<view class="btn-item">
<u-button text="取消" shape="circle" icon="close" type="info" @click="handleCancel"></u-button>
</view>
<view class="btn-item" v-if="opertype != 3">
<u-button text="确定" shape="circle" icon="checkmark" type="primary" @click="submit"></u-button>
<view class="form-footer">
<view class="btn_wrap">
<view class="btn-item">
<u-button text="取消" shape="circle" type="info" @click="handleCancel"></u-button>
</view>
<view class="btn-item" v-if="opertype != 3">
<u-button text="确定" shape="circle" type="primary" @click="submit"></u-button>
</view>
</view>
</view>
</view>
@@ -110,7 +112,6 @@ $end
return {
form: {},
rules: {
xxx: {},
$foreach(column in genTable.Columns)
$if(column.IsRequired && column.IsIncrement == false)
${column.CsharpFieldFl}: [{
@@ -185,12 +186,10 @@ $end
update${genTable.BusinessName}(this.form).then((res) => {
this.${modal}modal.msgSuccess("修改成功")
})
.catch(() => {})
} else {
add${genTable.BusinessName}(this.form).then((res) => {
this.${modal}modal.msgSuccess("新增成功")
})
.catch(() => {})
}
}).catch(errors => {
this.${modal}modal.msg('表单校验失败')
@@ -197,7 +197,7 @@ $elseif(tool.CheckTree(genTable ,column.CsharpField))
</el-form-item>
</el-col>
$elseif(column.IsPK || column.IsIncrement)
<el-col :lg="12">
<el-col :lg="${options.ColNum}">
<el-form-item label="${labelName}" prop="${columnName}">
$if(column.IsIncrement == false)
<el-input-number v-model.number="form.${columnName}" controls-position="right" placeholder="请输入${labelName}" :disabled="title=='修改数据'"/>
@@ -208,13 +208,13 @@ $end
</el-col>
$else
$if(column.HtmlType == "inputNumber")
<el-col :lg="12">
<el-col :lg="${options.ColNum}">
<el-form-item label="${labelName}" prop="${columnName}">
<el-input-number v-model.number="form.${columnName}" controls-position="right" placeholder="请输入${labelName}" ${labelDisabled}/>
</el-form-item>
</el-col>
$elseif(column.HtmlType == "datetime")
<el-col :lg="12">
<el-col :lg="${options.ColNum}">
<el-form-item label="${labelName}" prop="${columnName}">
<el-date-picker v-model="form.${columnName}" type="datetime" placeholder="选择日期时间"></el-date-picker>
</el-form-item>
@@ -232,7 +232,7 @@ $elseif(column.HtmlType == "fileUpload")
</el-form-item>
</el-col>
$elseif(column.HtmlType == "radio")
<el-col :lg="12">
<el-col :lg="${options.ColNum}">
<el-form-item label="${labelName}" prop="${columnName}">
<el-radio-group v-model="form.${columnName}">
<el-radio v-for="item in $if(column.DictType != "") options.${column.DictType} $else options.${column.CsharpFieldFl}Options$end" :key="item.dictValue" :label="${value}">{{item.dictLabel}}</el-radio>
@@ -252,7 +252,7 @@ $elseif(column.HtmlType == "editor")
</el-form-item>
</el-col>
$elseif(column.HtmlType == "select" || column.HtmlType == "selectMulti")
<el-col :lg="12">
<el-col :lg="${options.ColNum}">
<el-form-item label="${labelName}" prop="${columnName}">
<el-select v-model="form.${columnName}" placeholder="请选择${labelName}"${column.DisabledStr}>
<el-option v-for="item in $if(column.DictType != "") options.${column.DictType} $else options.${column.CsharpFieldFl}Options$end" :key="item.dictValue" :label="item.dictLabel" :value="${value}"></el-option>
@@ -268,7 +268,7 @@ $elseif(column.HtmlType == "checkbox")
</el-form-item>
</el-col>
$else
<el-col :lg="12">
<el-col :lg="${options.ColNum}">
<el-form-item label="${labelName}" prop="${columnName}">
<el-input v-model="form.${columnName}" placeholder="请输入${labelName}" ${labelDisabled}/>
</el-form-item>
@@ -301,10 +301,8 @@ import Editor from '@/components/Editor'
$end
const { proxy } = getCurrentInstance()
// 是否展开,默认全部折叠
const isExpandAll = ref(false)
const refreshTable = ref(true)
// 展开/折叠操作
function toggleExpandAll() {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
@@ -53,7 +53,7 @@ $elseif(column.HtmlType == "datePicker")
:default-time="defaultTime">
</el-date-picker>
</el-form-item>
$elseif(column.HtmlType.Contains("select") || column.HtmlType == "radio")
$elseif(column.HtmlType.Contains("select"))
<el-form-item label="${labelName}" prop="${columnName}">
<el-select clearable $if(column.HtmlType == "selectMulti")multiple$end v-model="queryParams.${columnName}" placeholder="请选择${labelName}">
<el-option v-for="item in $if(column.DictType != "") options.${column.DictType} $else options.${column.CsharpFieldFl}Options$end" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue">
@@ -65,6 +65,7 @@ $elseif(column.HtmlType.Contains("select") || column.HtmlType == "radio")
$elseif(column.HtmlType == "radio")
<el-form-item label="${labelName}" prop="${columnName}">
<el-radio-group v-model="queryParams.${columnName}">
<el-radio>全部</el-radio>
<el-radio v-for="item in $if(column.DictType != "") options.${column.DictType} $else options.${column.CsharpFieldFl}Options$end" :key="item.dictValue" :label="item.dictValue">{{item.dictLabel}}</el-radio>
</el-radio-group>
</el-form-item>
@@ -81,7 +82,7 @@ $end
</el-form-item>
</el-form>
<!-- 工具区域 -->
<el-row :gutter="10" class="mb8">
<el-row :gutter="15" class="mb10">
$if(replaceDto.ShowBtnAdd)
<el-col :span="1.5">
<el-button type="primary" v-hasPermi="['${replaceDto.PermissionPrefix}:add']" plain icon="plus" @click="handleAdd">
@@ -187,7 +188,7 @@ $elseif(column.HtmlType == "imageUpload")
$elseif(column.HtmlType == "checkbox" || column.HtmlType.Contains("select") || column.HtmlType == "radio")
<el-table-column prop="${columnName}" label="${labelName}" align="center"${column.sortStr} v-if="columns.showColumn('${columnName}')">
<template #default="scope">
<dict-tag :options="$if(column.DictType != "") options.${column.DictType} $else options.${column.CsharpFieldFl}Options$end" :value="scope.row.${columnName}" $if(column.HtmlType == "checkbox")split=","$end />
<dict-tag :options="$if(column.DictType != "") options.${column.DictType} $else options.${column.CsharpFieldFl}Options$end" :value="scope.row.${columnName}" $if(column.HtmlType == "checkbox" || column.HtmlType == "selectMulti")split=","$end />
</template>
</el-table-column>
$elseif(column.HtmlType == "datetime")
@@ -213,13 +214,13 @@ $end
</el-button-group>
$else
$if(replaceDto.ShowBtnView)
<el-button type="primary" icon="view" title="详情" @click="handlePreview(scope.row)"></el-button>
<el-button type="primary" size="small" icon="view" title="详情" @click="handlePreview(scope.row)"></el-button>
$end
$if(replaceDto.ShowBtnEdit)
<el-button v-hasPermi="['${replaceDto.PermissionPrefix}:edit']" type="success" icon="edit" title="编辑" @click="handleUpdate(scope.row)"></el-button>
<el-button type="success" size="small" icon="edit" title="编辑" v-hasPermi="['${replaceDto.PermissionPrefix}:edit']" @click="handleUpdate(scope.row)"></el-button>
$end
$if(replaceDto.ShowBtnDelete)
<el-button v-hasPermi="['${replaceDto.PermissionPrefix}:delete']" type="danger" icon="delete" title="删除" @click="handleDelete(scope.row)"></el-button>
<el-button type="danger" size="small" icon="delete" title="删除" v-hasPermi="['${replaceDto.PermissionPrefix}:delete']" @click="handleDelete(scope.row)"></el-button>
$end
$end
</template>
@@ -326,10 +327,16 @@ $elseif(column.HtmlType == "editor")
<editor v-model="form.${columnName}" :min-height="200" />
</el-form-item>
</el-col>
$elseif(column.HtmlType == "slider")
<el-col :lg="24">
<el-form-item label="${labelName}" prop="${columnName}">
<el-slider v-model="form.${columnName}" />
</el-form-item>
</el-col>
$elseif(column.HtmlType == "select" || column.HtmlType == "selectMulti")
<el-col :lg="${options.ColNum}">
<el-form-item label="${labelName}" prop="${columnName}">
<el-select v-model="form.${columnName}" placeholder="请选择${labelName}"${column.DisabledStr}>
<el-select v-model="form.${columnName}$if(column.HtmlType == "selectMulti")Checked$end" $if(column.HtmlType == "selectMulti")multiple$end placeholder="请选择${labelName}"${column.DisabledStr}>
<el-option
v-for="item in $if(column.DictType != "")options.${column.DictType}${else}options.${column.CsharpFieldFl}Options$end"
:key="item.dictValue"
@@ -442,7 +449,9 @@ $end
</el-form>
<template #footer v-if="opertype != 3">
<el-button text @click="cancel">{{ ${t}t('btn.cancel') }}</el-button>
$if(replaceDto.ShowBtnEdit || replaceDto.ShowBtnAdd)
<el-button type="primary" @click="submitForm">{{ ${t}t('btn.submit') }}</el-button>
$end
</template>
</el-dialog>
</div>
@@ -654,7 +663,7 @@ function cancel(){
function reset() {
form.value = {
$foreach(item in genTable.Columns)
$if((item.HtmlType == "checkbox"))
$if(item.HtmlType == "checkbox" || item.HtmlType == "selectMulti")
${item.CsharpFieldFl}Checked: [],
$else
$item.CsharpFieldFl: null,
@@ -666,7 +675,7 @@ $if(sub)
$end
proxy.resetForm("formRef")
}
$if(replaceDto.ShowBtnAdd)
// 添加按钮操作
function handleAdd() {
reset();
@@ -674,7 +683,8 @@ function handleAdd() {
title.value = '添加${genTable.functionName}'
opertype.value = 1
}
$end
$if(replaceDto.ShowBtnEdit)
// 修改按钮操作
function handleUpdate(row) {
reset()
@@ -689,7 +699,7 @@ function handleUpdate(row) {
form.value = {
...data,
$foreach(item in genTable.Columns)
$if(item.HtmlType == "checkbox")
$if(item.HtmlType == "checkbox" || item.HtmlType == "selectMulti")
${item.CsharpFieldFl}Checked: data.${item.CsharpFieldFl} ? data.${item.CsharpFieldFl}.split(',') : [],
$end
$end
@@ -700,13 +710,14 @@ $end
}
})
}
$end
// 添加&修改 表单提交
function submitForm() {
proxy.${refs}refs["formRef"].validate((valid) => {
if (valid) {
$foreach(item in genTable.Columns)
$if(item.HtmlType == "checkbox")
$if(item.HtmlType == "checkbox" || item.HtmlType == "selectMulti")
form.value.${item.CsharpFieldFl} = form.value.${item.CsharpFieldFl}Checked.toString();
$end
$end
@@ -715,24 +726,27 @@ $if(sub)
form.value.${tool.FirstLowerCase(genTable.SubTable.ClassName)}Nav = ${tool.FirstLowerCase(genTable.SubTable.ClassName)}List.value
$end
if (form.value.${replaceDto.FistLowerPk} != undefined && opertype.value === 2) {
$if(replaceDto.ShowBtnEdit)
update${genTable.BusinessName}(form.value).then((res) => {
proxy.${modal}modal.msgSuccess("修改成功")
open.value = false
getList()
})
.catch(() => {})
$end
} else {
$if(replaceDto.ShowBtnAdd)
add${genTable.BusinessName}(form.value).then((res) => {
proxy.${modal}modal.msgSuccess("新增成功")
open.value = false
getList()
})
.catch(() => {})
$end
}
}
})
}
$if(replaceDto.ShowBtnMultiDel || replaceDto.ShowBtnDelete)
// 删除按钮操作
function handleDelete(row) {
const Ids = row.${replaceDto.FistLowerPk} || ids.value
@@ -746,8 +760,8 @@ function handleDelete(row) {
getList()
proxy.${modal}modal.msgSuccess("删除成功")
})
.catch(() => {})
}
$end
$if(replaceDto.ShowBtnTruncate)
// 清空
@@ -778,7 +792,7 @@ function handlePreview(row) {
open.value = true
title.value = '查看'
opertype.value = 3
form.value = row
form.value = { ...row }
}
$end
Binary file not shown.
+2 -1
View File
@@ -1,5 +1,7 @@
using Infrastructure;
using Infrastructure.Extensions;
using Infrastructure.Helper;
using Infrastructure.Model;
using JinianNet.JNTemplate;
using SqlSugar;
using System;
@@ -7,7 +9,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using ZR.CodeGenerator.Model;
using ZR.Common;
using ZR.Model.System.Generate;
namespace ZR.CodeGenerator
+1
View File
@@ -1,4 +1,5 @@
using Infrastructure;
using Infrastructure.Model;
using SqlSugar;
using System;
using System.Collections.Generic;
@@ -1,4 +1,5 @@
using Infrastructure;
using Infrastructure.Model;
using SqlSugar;
using System.Collections.Generic;
using System.Linq;
+3 -3
View File
@@ -5,13 +5,13 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
<ProjectReference Include="..\Infrastructure\ZR.Infrastructure.csproj" />
<ProjectReference Include="..\ZR.Common\ZR.Common.csproj" />
<ProjectReference Include="..\ZR.Model\ZR.Model.csproj" />
<ProjectReference Include="..\ZR.ServiceCore\ZR.ServiceCore.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JinianNet.JNTemplate" Version="2.3.3" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.94" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.105" />
</ItemGroup>
</Project>
+16 -6
View File
@@ -1,4 +1,5 @@
using Infrastructure;
using Infrastructure.Model;
using MailKit.Net.Smtp;
using MimeKit;
using MimeKit.Text;
@@ -19,7 +20,7 @@ namespace ZR.Common
public MailHelper()
{
AppSettings.Bind("MailOptions", mailOptions);
FromEmail= mailOptions.FromEmail;
FromEmail = mailOptions.FromEmail;
}
public MailHelper(MailOptions _mailOptions)
{
@@ -132,11 +133,20 @@ namespace ZR.Common
//特别说明,对于服务器端的中文相应,Exception中有编码问题,显示乱码了
client.Authenticate(System.Text.Encoding.UTF8, mailOptions.FromEmail, mailOptions.Password);
var result = client.Send(message);
//断开
client.Disconnect(true);
Console.WriteLine($"【{DateTime.Now}】发送邮件结果{result}");
return result;
try
{
var result = client.Send(message);
//断开
client.Disconnect(true);
Console.WriteLine($"【{DateTime.Now}】发送邮件结果{result}");
return result;
}
catch (Exception ex)
{
client.Disconnect(true);
Log.WriteLine(ConsoleColor.Red, "发送邮件失败" + ex.Message);
return "fail";
}
}
}
}
+1 -5
View File
@@ -6,14 +6,10 @@
<ItemGroup>
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.13.0" />
<PackageReference Include="CSRedisCore" Version="3.8.670" />
<PackageReference Include="JinianNet.JNTemplate" Version="2.3.3" />
<PackageReference Include="MailKit" Version="4.1.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="MiniExcel" Version="1.31.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
<ProjectReference Include="..\Infrastructure\ZR.Infrastructure.csproj" />
</ItemGroup>
</Project>
-1
View File
@@ -1 +0,0 @@
此文件夹用于存放业务代码数据库实体类
-9
View File
@@ -1,9 +0,0 @@
using System;
namespace ZR.Model
{
public enum ProteryConstant
{
NOTNULL = 0
}
}
-40
View File
@@ -1,40 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace ZR.Model.System.Dto
{
/// <summary>
/// 登录用户信息存储
/// </summary>
public class LoginUser
{
public long UserId { get; set; }
public long DeptId { get; set; }
public string UserName { get; set; }
/// <summary>
/// 角色集合
/// </summary>
public List<string> RoleIds { get; set; }
/// <summary>
/// 角色集合(数据权限过滤使用)
/// </summary>
public List<SysRole> Roles { get; set; }
/// <summary>
/// 权限集合
/// </summary>
public List<string> Permissions { get; set; } = new List<string>();
public LoginUser()
{
}
public LoginUser(SysUser user, List<SysRole> roles, List<string> permissions)
{
UserId = user.UserId;
UserName = user.UserName;
DeptId = user.DeptId;
Roles = roles;
RoleIds = roles.Select(f => f.RoleKey).ToList();
Permissions = permissions;
}
}
}
+2 -2
View File
@@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MiniExcel" Version="1.31.0" />
<PackageReference Include="MiniExcel" Version="1.31.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.94" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.105" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
</Project>
+8 -46
View File
@@ -48,7 +48,7 @@ namespace ZR.Repository
public int Insert(List<T> t)
{
return Context.Insertable(t).ExecuteCommand();
return InsertRange(t) ? 1 : 0;
}
public int Insert(T parm, Expression<Func<T, object>> iClumns = null, bool ignoreNull = true)
{
@@ -61,10 +61,10 @@ namespace ZR.Repository
#endregion add
#region update
public IUpdateable<T> Updateable(T entity)
{
return Context.Updateable(entity);
}
//public IUpdateable<T> Updateable(T entity)
//{
// return Context.Updateable(entity);
//}
/// <summary>
/// 实体根据主键更新
@@ -72,9 +72,10 @@ namespace ZR.Repository
/// <param name="entity"></param>
/// <param name="ignoreNullColumns"></param>
/// <returns></returns>
public int Update(T entity, bool ignoreNullColumns = false)
public int Update(T entity, bool ignoreNullColumns = false, object data = null)
{
return Context.Updateable(entity).IgnoreColumns(ignoreNullColumns).ExecuteCommand();
return Context.Updateable(entity).IgnoreColumns(ignoreNullColumns)
.EnableDiffLogEventIF(data.IsNotEmpty(), data).ExecuteCommand();
}
/// <summary>
@@ -103,37 +104,6 @@ namespace ZR.Repository
return Context.Updateable(entity).UpdateColumns(expression).Where(where).ExecuteCommand();
}
public int Update(SqlSugarClient client, T entity, Expression<Func<T, object>> expression, Expression<Func<T, bool>> where)
{
return client.Updateable(entity).UpdateColumns(expression).Where(where).ExecuteCommand();
}
/// <summary>
///
/// </summary>
/// <param name="entity"></param>
/// <param name="list"></param>
/// <param name="isNull">默认为true</param>
/// <returns></returns>
public int Update(T entity, List<string> list = null, bool isNull = true)
{
list ??= new List<string>()
{
"Create_By",
"Create_time"
};
return Context.Updateable(entity).IgnoreColumns(isNull).IgnoreColumns(list.ToArray()).ExecuteCommand();
}
//public bool Update(List<T> entity)
//{
// var result = base.Context.Ado.UseTran(() =>
// {
// base.Context.Updateable(entity).ExecuteCommand();
// });
// return result.IsSuccess;
//}
/// <summary>
/// 更新指定列 eg:Update(w => w.NoticeId == model.NoticeId, it => new SysNotice(){ Update_time = DateTime.Now, Title = "通知标题" });
/// </summary>
@@ -160,14 +130,6 @@ namespace ZR.Repository
throw;
}
}
public IStorageable<T> Storageable(T t)
{
return Context.Storageable<T>(t);
}
public IStorageable<T> Storageable(List<T> t)
{
return Context.Storageable(t);
}
/// <summary>
///
+1 -6
View File
@@ -19,8 +19,7 @@ namespace ZR.Repository
#endregion add
#region update
IUpdateable<T> Updateable(T entity);
int Update(T entity, bool ignoreNullColumns = false);
int Update(T entity, bool ignoreNullColumns = false, object data = null);
/// <summary>
/// 只更新表达式的值
@@ -32,13 +31,9 @@ namespace ZR.Repository
int Update(T entity, Expression<Func<T, object>> expression, Expression<Func<T, bool>> where);
int Update(SqlSugarClient client, T entity, Expression<Func<T, object>> expression, Expression<Func<T, bool>> where);
int Update(Expression<Func<T, bool>> where, Expression<Func<T, T>> columns);
#endregion update
IStorageable<T> Storageable(T t);
IStorageable<T> Storageable(List<T> t);
DbResult<bool> UseTran(Action action);
DbResult<bool> UseTran(SqlSugarClient client, Action action);
+2 -4
View File
@@ -5,16 +5,14 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
<ProjectReference Include="..\Infrastructure\ZR.Infrastructure.csproj" />
<ProjectReference Include="..\ZR.Model\ZR.Model.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Mapster" Version="7.3.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
<PackageReference Include="MySqlConnector" Version="2.2.6" />
<PackageReference Include="NETCore.Encrypt" Version="2.1.1" />
<PackageReference Include="SqlSugar.IOC" Version="2.0.0" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.94" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.105" />
</ItemGroup>
</Project>
+4 -1
View File
@@ -1 +1,4 @@
global using System.Collections.Generic;
global using System.Collections.Generic;
global using System;
global using SqlSugar;
global using Newtonsoft.Json;
+59
View File
@@ -0,0 +1,59 @@
using Infrastructure;
using Infrastructure.Attribute;
using SqlSugar.IOC;
using ZR.Model.System;
using ZR.Repository;
using ZR.Service.IService;
using ZR.Service.System.IService;
namespace ZR.Service
{
/// <summary>
/// 注意:下面的AppService不要漏了
/// </summary>
[AppService(ServiceType = typeof(IHelloService), ServiceLifetime = LifeTime.Transient)]
public class HelloService : BaseService<ArticleCategory>, IHelloService
{
/// <summary>
/// 引用User服务
/// </summary>
private readonly ISysUserService userService;
/// <summary>
///
/// </summary>
/// <param name="userService"></param>
public HelloService(ISysUserService userService)
{
this.userService = userService;
}
/// <summary>
/// 数据库使用案例
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public string SayHello(string name)
{
//构造函数式使用
var user = JsonConvert.SerializeObject(userService.GetFirst(f => f.UserId == 1));
Console.WriteLine(user);
var postService = App.GetRequiredService<ISysPostService>();
Console.WriteLine(JsonConvert.SerializeObject(postService.GetId(1)));
BaseRepository<SysDept> deptRepo = new();
Console.WriteLine(JsonConvert.SerializeObject(deptRepo.GetId(1)));
var result = DbScoped.SugarScope.Queryable<SysDictType>().Where(f => f.DictId == 1).First();
Console.WriteLine(JsonConvert.SerializeObject(result));
//切换库
//DbScoped.SugarScope.GetConnectionScope(2);
GetFirst(x => x.CategoryId == 1);
Context.Queryable<SysUser>().First(f => f.UserId == 1);
return "Hello:" + name;
}
}
}
+17
View File
@@ -0,0 +1,17 @@
using ZR.Model.System;
namespace ZR.Service.IService
{
/// <summary>
/// Hello接口
/// </summary>
public interface IHelloService : IBaseService<ArticleCategory>
{
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
string SayHello(string name);
}
}
-29
View File
@@ -1,29 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using ZR.Common;
using ZR.Common.Cache;
namespace ZR.Service.System
{
public class CacheService
{
#region 用户权限 缓存
public static List<string> GetUserPerms(string key)
{
return (List<string>)CacheHelper.GetCache(key);
//return RedisServer.Cache.Get<List<string>>(key).ToList();
}
public static void SetUserPerms(string key, object data)
{
CacheHelper.SetCache(key, data);
//RedisServer.Cache.Set(key, data);
}
public static void RemoveUserPerms(string key)
{
CacheHelper.Remove(key);
//RedisServer.Cache.Del(key);
}
#endregion
}
}
+6 -3
View File
@@ -2,11 +2,14 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ZR.Common\ZR.Common.csproj" />
<ProjectReference Include="..\ZR.Repository\ZR.Repository.csproj" />
<ProjectReference Include="..\ZR.ServiceCore\ZR.ServiceCore.csproj" />
</ItemGroup>
</Project>
@@ -8,11 +8,5 @@ namespace ZR.Service
/// <typeparam name="T"></typeparam>
public class BaseService<T> : BaseRepository<T> where T : class, new()
{
//public IBaseRepository<T> baseRepository;
//public BaseService(IBaseRepository<T> repository)
//{
// this.baseRepository = repository ?? throw new ArgumentNullException(nameof(repository));
//}
}
}
@@ -2,9 +2,11 @@
using Infrastructure.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using ZR.Model.System.Dto;
using ZR.Model.System;
using ZR.Service.System;
using ZR.Service.System.IService;
namespace ZR.Admin.WebApi.Filters
namespace ZR.ServiceCore.Middleware
{
/// <summary>
/// API授权判断
@@ -17,8 +19,11 @@ namespace ZR.Admin.WebApi.Filters
/// 权限字符串,例如 system:user:view
/// </summary>
public string Permission { get; set; } = string.Empty;
/// <summary>
/// 角色字符串,例如 common,admin
/// </summary>
public string RolePermi { get; set; } = string.Empty;
private bool HasPermi { get; set; }
private bool HasRole { get; set; }
public ActionPermissionFilter() { }
public ActionPermissionFilter(string permission)
{
@@ -34,12 +39,22 @@ namespace ZR.Admin.WebApi.Filters
/// <returns></returns>
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
LoginUser info = Framework.JwtUtil.GetLoginUser(context.HttpContext);
TokenModel info = JwtUtil.GetLoginUser(context.HttpContext);
if (info != null && info?.UserId > 0)
{
List<string> perms = info.Permissions;
long userId = info.UserId;
List<string> perms = CacheService.GetUserPerms(GlobalConstant.UserPermKEY + userId);
List<string> rolePerms = info.RoleIds;
if (perms == null)
{
var sysPermissionService = App.GetService<ISysPermissionService>();
perms = sysPermissionService.GetMenuPermission(new SysUser() { UserId = userId });
CacheService.SetUserPerms(GlobalConstant.UserPermKEY + userId, perms);
}
if (perms.Exists(f => f.Equals(GlobalConstant.AdminPerm)))
{
HasPermi = true;
@@ -52,24 +67,22 @@ namespace ZR.Admin.WebApi.Filters
{
HasPermi = perms.Exists(f => f.ToLower() == Permission.ToLower());
}
if (!HasPermi && !string.IsNullOrEmpty(RolePermi))
{
HasPermi = info.RoleIds.Contains(RolePermi);
}
bool isDemoMode = AppSettings.GetAppConfig("DemoMode", false);
var url = context.HttpContext.Request.Path;
//演示公开环境屏蔽权限
string[] denyPerms = new string[] { "update", "add", "remove", "add", "edit", "delete", "import", "run", "start", "stop", "clear", "send", "export", "upload", "common", "gencode", "reset" };
if (isDemoMode && denyPerms.Any(f => Permission.ToLower().Contains(f)))
string[] denyPerms = new string[] { "update", "add", "remove", "add", "edit", "delete", "import", "run", "start", "stop", "clear", "send", "export", "upload", "common", "gencode", "reset", "forceLogout", "batchLogout" };
if (isDemoMode && denyPerms.Any(f => Permission.Contains(f, StringComparison.OrdinalIgnoreCase)))
{
context.Result = new JsonResult(new { code = ResultCode.FORBIDDEN, msg = "演示模式 , 不允许操作" });
context.Result = new JsonResult(new { code = (int)ResultCode.FORBIDDEN, msg = "演示模式 , 不允许操作" });
}
if (!HasPermi && !Permission.Equals("common"))
{
logger.Info($"用户{info.UserName}没有权限访问{url},当前权限[{Permission}]");
JsonResult result = new(new ApiResult()
{
Code = (int)ResultCode.FORBIDDEN,
Msg = $"你当前没有权限[{Permission}]访问,请联系管理员",
Data = url
})
JsonResult result = new(new ApiResult((int)ResultCode.FORBIDDEN, $"你当前没有权限[{Permission}]访问,请联系管理员", url))
{
ContentType = "application/json",
};
@@ -2,20 +2,20 @@
using Infrastructure.Attribute;
using Infrastructure.Model;
using IPTools.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using NLog;
using ZR.Admin.WebApi.Extensions;
using ZR.Model.System;
using ZR.Service.System.IService;
namespace ZR.Admin.WebApi.Filters
namespace ZR.ServiceCore.Middleware
{
public class GlobalActionMonitor : ActionFilterAttribute
{
static readonly Logger logger = LogManager.GetCurrentClassLogger();
private ISysOperLogService OperLogService;
private readonly ISysOperLogService OperLogService;
public GlobalActionMonitor(ISysOperLogService operLogService)
{
OperLogService = operLogService;
@@ -29,29 +29,25 @@ namespace ZR.Admin.WebApi.Filters
/// <returns></returns>
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
ApiResult response = new();
response.Code = (int)ResultCode.PARAM_ERROR;
string msg = string.Empty;
var values = context.ModelState.Values;
foreach (var item in values)
{
foreach (var err in item.Errors)
{
if (err.ErrorMessage.Contains("JSON"))
if (!string.IsNullOrEmpty(msg))
{
return next();
}
if (!string.IsNullOrEmpty(response.Msg))
{
response.Msg += " | ";
msg += " | ";
}
response.Msg += err.ErrorMessage;
msg += err.ErrorMessage;
}
}
if (!string.IsNullOrEmpty(response.Msg))
if (!string.IsNullOrEmpty(msg))
{
logger.Info($"请求参数错误,{response.Msg}");
logger.Info($"请求参数错误,{msg}");
ApiResult response = new((int)ResultCode.PARAM_ERROR, msg);
context.Result = new JsonResult(response);
}
return base.OnActionExecutionAsync(context, next);
@@ -101,12 +97,12 @@ namespace ZR.Admin.WebApi.Filters
OperUrl = HttpContextExtension.GetRequestUrl(context.HttpContext),
RequestMethod = method,
JsonResult = jsonResult,
OperLocation = ip_info.Province + " " + ip_info.City,
OperLocation = HttpContextExtension.GetIpInfo(ip),
Method = controller + "." + action + "()",
//Elapsed = _stopwatch.ElapsedMilliseconds,
OperTime = DateTime.Now
OperTime = DateTime.Now,
OperParam = HttpContextExtension.GetRequestValue(context.HttpContext, method)
};
HttpContextExtension.GetRequestValue(context.HttpContext, sysOperLog);
if (logAttribute != null)
{
@@ -117,7 +113,7 @@ namespace ZR.Admin.WebApi.Filters
}
LogEventInfo ei = new(NLog.LogLevel.Info, "GlobalActionMonitor", "");
ei.Properties["jsonResult"] = !HttpMethods.IsGet(method) ? jsonResult : "";
ei.Properties["requestParam"] = sysOperLog.OperParam;
ei.Properties["user"] = userName;
@@ -131,7 +127,7 @@ namespace ZR.Admin.WebApi.Filters
}
}
private LogAttribute? GetLogAttribute(ControllerActionDescriptor controllerActionDescriptor)
private LogAttribute GetLogAttribute(ControllerActionDescriptor controllerActionDescriptor)
{
var attribute = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true)
.FirstOrDefault(a => a.GetType().Equals(typeof(LogAttribute)));
@@ -4,23 +4,18 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using NLog;
using System;
using System.Linq;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Framework;
using ZR.Model.System.Dto;
using ZR.Common;
//本命名空间暂时先不改,改动比较大2023年9月2日
namespace ZR.Admin.WebApi.Filters
{
/// <summary>
/// 授权校验访问
/// 如果跳过授权登录在Action 或controller加上 AllowAnonymousAttribute
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public class VerifyAttribute : Attribute, IAuthorizationFilter
{
static readonly Logger logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// 只判断token是否正确,不判断权限
/// 如果需要判断权限的在Action上加上ApiActionPermission属性标识权限类别,ActionPermissionFilter作权限处理
@@ -40,16 +35,35 @@ namespace ZR.Admin.WebApi.Filters
string ip = HttpContextExtension.GetClientUserIp(context.HttpContext);
string url = context.HttpContext.Request.Path;
var isAuthed = context.HttpContext.User.Identity.IsAuthenticated;
string osType = context.HttpContext.Request.Headers["os"];
//使用jwt token校验2020-11-21
LoginUser info = JwtUtil.GetLoginUser(context.HttpContext);
TokenModel loginUser = JwtUtil.GetLoginUser(context.HttpContext);
if (loginUser != null)
{
var nowTime = DateTime.UtcNow;
TimeSpan ts = loginUser.ExpireTime - nowTime;
if (info == null || !isAuthed)
//Console.WriteLine($"jwt到期剩余:{ts.TotalMinutes}分,{ts.TotalSeconds}秒");
var CK = "token_" + loginUser.UserId;
if (!CacheHelper.Exists(CK) && ts.TotalMinutes < 5)
{
var newToken = JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(loginUser));
CacheHelper.SetCache(CK, CK, 1);
//移动端不加下面这个获取不到自定义Header
if (osType != null)
{
context.HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "X-Refresh-Token");
}
context.HttpContext.Response.Headers.Add("X-Refresh-Token", newToken);
}
}
if (loginUser == null || !isAuthed)
{
string msg = $"请求访问[{url}]失败,无法访问系统资源";
logger.Info($"{msg}");
context.Result = new JsonResult(new ApiResult((int)ResultCode.DENY, msg));
//logger.Info(msg);
context.Result = new JsonResult(ApiResult.Error(ResultCode.DENY, msg));
}
}
}
+5
View File
@@ -0,0 +1,5 @@
global using Newtonsoft.Json;
global using SqlSugar;
global using System;
global using System.Collections.Generic;
global using Infrastructure.Extensions;
@@ -2,17 +2,16 @@
using Infrastructure.Attribute;
using Infrastructure.Model;
using IPTools.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using NLog;
using System.Diagnostics;
using System.Text.Encodings.Web;
using System.Text.Json;
using ZR.Admin.WebApi.Extensions;
using ZR.Common;
using ZR.Model.System;
using ZR.Service.System.IService;
using textJson = System.Text.Json;
namespace ZR.Admin.WebApi.Middleware
namespace ZR.ServiceCore.Middleware
{
/// <summary>
/// 全局异常处理中间件
@@ -45,16 +44,18 @@ namespace ZR.Admin.WebApi.Middleware
private async Task HandleExceptionAsync(HttpContext context, Exception ex)
{
NLog.LogLevel logLevel = NLog.LogLevel.Info;
LogLevel logLevel = LogLevel.Info;
int code = (int)ResultCode.GLOBAL_ERROR;
string msg;
string error = string.Empty;
bool notice = true;
//自定义异常
if (ex is CustomException customException)
{
code = customException.Code;
msg = customException.Message;
error = customException.LogMsg;
notice = customException.Notice;
}
else if (ex is ArgumentException)//参数异常
{
@@ -65,18 +66,18 @@ namespace ZR.Admin.WebApi.Middleware
{
msg = "服务器好像出了点问题,请联系系统管理员...";
error = $"{ex.Message}";
logLevel = NLog.LogLevel.Error;
logLevel = LogLevel.Error;
context.Response.StatusCode = 500;
}
var options = new JsonSerializerOptions
var options = new textJson.JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNamingPolicy = textJson.JsonNamingPolicy.CamelCase,
WriteIndented = true
};
ApiResult apiResult = new(code, msg);
string responseResult = JsonSerializer.Serialize(apiResult, options).ToLower();
string responseResult = textJson.JsonSerializer.Serialize(apiResult, options);
string ip = HttpContextExtension.GetClientUserIp(context);
var ip_info = IpTool.Search(ip);
@@ -90,9 +91,9 @@ namespace ZR.Admin.WebApi.Middleware
ErrorMsg = string.IsNullOrEmpty(error) ? msg : error,
OperName = HttpContextExtension.GetName(context),
OperLocation = ip_info.Province + " " + ip_info.City,
OperTime = DateTime.Now
OperTime = DateTime.Now,
OperParam = HttpContextExtension.GetRequestValue(context, context.Request.Method)
};
HttpContextExtension.GetRequestValue(context, sysOperLog);
var endpoint = GetEndpoint(context);
if (endpoint != null)
{
@@ -118,15 +119,16 @@ namespace ZR.Admin.WebApi.Middleware
Logger.Log(ei);
context.Response.ContentType = "text/json;charset=utf-8";
await context.Response.WriteAsync(responseResult, System.Text.Encoding.UTF8);
string errorMsg = $"> 操作人:{sysOperLog.OperName}" +
$"\n> 操作地区:{sysOperLog.OperIp}({sysOperLog.OperLocation})" +
$"\n> 操作模块:{sysOperLog.Title}" +
$"\n> 操作地址:{sysOperLog.OperUrl}" +
$"\n> 错误信息:{msg}\n\n> {error}";
WxNoticeHelper.SendMsg("系统出错", errorMsg, "", WxNoticeHelper.MsgType.markdown);
SysOperLogService.InsertOperlog(sysOperLog);
if (!notice) return;
WxNoticeHelper.SendMsg("系统异常", errorMsg, msgType: WxNoticeHelper.MsgType.markdown);
}
public static Endpoint GetEndpoint(HttpContext context)
+11
View File
@@ -0,0 +1,11 @@
namespace ZR.Model.System.Dto
{
public class LockUserDto
{
public string Name { get; set; }
public string ClientId { get; set; }
public string ConnnectionId { get; set; }
public string Reason { get; set; }
public int Time { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More