Merge remote-tracking branch 'zrorigin/master' into net7.0

# Conflicts:
#	ZR.Admin.WebApi/Controllers/System/SysLoginController.cs
This commit is contained in:
文永达 2023-09-21 22:23:08 +08:00
commit de76f5872a
260 changed files with 4677 additions and 4142 deletions

View File

@ -1,5 +1,9 @@
using Microsoft.AspNetCore.Http;
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;
@ -7,19 +11,35 @@ namespace Infrastructure
{
public static class App
{
/// <summary>
/// 全局配置文件
/// </summary>
public static OptionsSetting OptionsSetting => CatchOrDefault(() => ServiceProvider?.GetService<IOptions<OptionsSetting>>()?.Value);
/// <summary>
/// 服务提供器
/// </summary>
public static IServiceProvider ServiceProvider => HttpContext?.RequestServices ?? InternalApp.ServiceProvider;
public static IServiceProvider ServiceProvider => InternalApp.ServiceProvider;
/// <summary>
/// 获取请求上下文
/// </summary>
public static HttpContext HttpContext => HttpContextLocal.Current();
public static HttpContext HttpContext => CatchOrDefault(() => ServiceProvider?.GetService<IHttpContextAccessor>()?.HttpContext);
/// <summary>
/// 获取请求上下文用户
/// </summary>
public static ClaimsPrincipal User => HttpContext?.User;
/// <summary>
/// 获取用户名
/// </summary>
public static string UserName => User?.Identity?.Name;
/// <summary>
/// 获取Web主机环境
/// </summary>
public static IWebHostEnvironment WebHostEnvironment => InternalApp.WebHostEnvironment;
/// <summary>
/// 获取全局配置
/// </summary>
public static IConfiguration Configuration => CatchOrDefault(() => InternalApp.Configuration, new ConfigurationBuilder().Build());
/// <summary>
/// 获取请求生命周期的服务
/// </summary>
@ -61,5 +81,25 @@ namespace Infrastructure
{
return ServiceProvider.GetRequiredService(type);
}
/// <summary>
/// 处理获取对象异常问题
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="action">获取对象委托</param>
/// <param name="defaultValue">默认值</param>
/// <returns>T</returns>
private static T CatchOrDefault<T>(Func<T> action, T defaultValue = null)
where T : class
{
try
{
return action();
}
catch
{
return defaultValue ?? null;
}
}
}
}

View File

@ -1,59 +0,0 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
namespace Microsoft.AspNetCore.Http
{
public static class HttpContextLocal
{
private static Func<object> _asyncLocalAccessor;
private static Func<object, object> _holderAccessor;
private static Func<object, HttpContext> _httpContextAccessor;
/// <summary>
/// 获取当前 HttpContext 对象
/// </summary>
/// <returns></returns>
public static HttpContext Current()
{
var asyncLocal = (_asyncLocalAccessor ??= CreateAsyncLocalAccessor())();
if (asyncLocal == null) return null;
var holder = (_holderAccessor ??= CreateHolderAccessor(asyncLocal))(asyncLocal);
if (holder == null) return null;
return (_httpContextAccessor ??= CreateHttpContextAccessor(holder))(holder);
// 创建异步本地访问器
static Func<object> CreateAsyncLocalAccessor()
{
var fieldInfo = typeof(HttpContextAccessor).GetField("_httpContextCurrent", BindingFlags.Static | BindingFlags.NonPublic);
var field = Expression.Field(null, fieldInfo);
return Expression.Lambda<Func<object>>(field).Compile();
}
// 创建常驻 HttpContext 访问器
static Func<object, object> CreateHolderAccessor(object asyncLocal)
{
var holderType = asyncLocal.GetType().GetGenericArguments()[0];
var method = typeof(AsyncLocal<>).MakeGenericType(holderType).GetProperty("Value").GetGetMethod();
var target = Expression.Parameter(typeof(object));
var convert = Expression.Convert(target, asyncLocal.GetType());
var getValue = Expression.Call(convert, method);
return Expression.Lambda<Func<object, object>>(getValue, target).Compile();
}
// 获取 HttpContext 访问器
static Func<object, HttpContext> CreateHttpContextAccessor(object holder)
{
var target = Expression.Parameter(typeof(object));
var convert = Expression.Convert(target, holder.GetType());
var field = Expression.Field(convert, "Context");
var convertAsResult = Expression.Convert(field, typeof(HttpContext));
return Expression.Lambda<Func<object, HttpContext>>(convertAsResult, target).Compile();
}
}
}
}

View File

@ -1,15 +1,20 @@
using Infrastructure;
using Infrastructure.Extensions;
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";
@ -58,10 +63,12 @@ namespace ZR.Admin.WebApi.Controllers
/// <returns></returns>
protected IActionResult ExportExcel(string path, string fileName)
{
//IWebHostEnvironment webHostEnvironment = (IWebHostEnvironment)App.ServiceProvider.GetService(typeof(IWebHostEnvironment));
//string fileDir = Path.Combine(webHostEnvironment.WebRootPath, path, fileName);
var stream = Io.File.OpenRead(path); //创建文件流
//var webHostEnvironment = App.WebHostEnvironment;
if (!Path.Exists(path))
{
throw new CustomException(fileName + "文件不存在");
}
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>
///
@ -169,23 +180,26 @@ namespace ZR.Admin.WebApi.Controllers
/// <summary>
/// 下载导入模板
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="stream"></param>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="list">空数据类型集合</param>
/// <param name="fileName">下载文件名</param>
/// <returns></returns>
protected string DownloadImportTemplate<T>(List<T> list, Stream stream, string fileName)
protected (string, string) DownloadImportTemplate<T>(List<T> list, string fileName)
{
IWebHostEnvironment webHostEnvironment = (IWebHostEnvironment)App.ServiceProvider.GetService(typeof(IWebHostEnvironment));
string sFileName = $"{fileName}模板.xlsx";
string newFileName = Path.Combine(webHostEnvironment.WebRootPath, "ImportTemplate", sFileName);
IWebHostEnvironment webHostEnvironment = App.WebHostEnvironment;
string sFileName = $"{fileName}.xlsx";
string fullPath = Path.Combine(webHostEnvironment.WebRootPath, "ImportTemplate", sFileName);
if (!Directory.Exists(newFileName))
//不存在模板创建模板
if (!Directory.Exists(fullPath))
{
Directory.CreateDirectory(Path.GetDirectoryName(newFileName));
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
}
MiniExcel.SaveAs(newFileName, list);
return sFileName;
if (!Path.Exists(fullPath))
{
MiniExcel.SaveAs(fullPath, list, overwriteFile: true);
}
return (sFileName, fullPath);
}
/// <summary>

View File

@ -2,7 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ZR.Admin.WebApi.Framework
namespace Infrastructure.Converter
{
public class JsonConverterUtil
{
@ -29,7 +29,7 @@ namespace ZR.Admin.WebApi.Framework
public static DateTime? ParseDateTime(string dateStr)
{
if (System.Text.RegularExpressions.Regex.IsMatch(dateStr, @"^\d{4}[/-]") && DateTime.TryParse(dateStr, null,System.Globalization.DateTimeStyles.AssumeLocal, out var dateVal))
if (System.Text.RegularExpressions.Regex.IsMatch(dateStr, @"^\d{4}[/-]") && DateTime.TryParse(dateStr, null, System.Globalization.DateTimeStyles.AssumeLocal, out var dateVal))
return dateVal;
return null;
}

View File

@ -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);
}
}
}

View File

@ -5,8 +5,18 @@ 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>

View File

@ -54,5 +54,10 @@
/// 清空数据
/// </summary>
CLEAN = 9,
/// <summary>
/// 下载
/// </summary>
DOWNLOAD = 10,
}
}

View File

@ -1,14 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Infrastructure
namespace Infrastructure
{
/// <summary>
/// 全局静态常量
/// </summary>
public class GlobalConstant
{
/// <summary>
/// 代码生成常量
/// </summary>
public static readonly string CodeGenDbConfig;
/// <summary>
/// 管理员权限
/// </summary>

View File

@ -91,7 +91,7 @@ namespace Infrastructure
#region unix时间戳
/// <summary>
/// 获取unix时间戳
/// 获取unix时间戳(毫秒)
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
@ -100,6 +100,12 @@ namespace Infrastructure
long unixTime = ((DateTimeOffset)dt).ToUnixTimeMilliseconds();
return unixTime;
}
public static long GetUnixTimeSeconds(DateTime dt)
{
long unixTime = ((DateTimeOffset)dt).ToUnixTimeSeconds();
return unixTime;
}
#endregion
#region

View File

@ -2,7 +2,7 @@
using System;
using System.IO;
namespace ZR.Common
namespace Infrastructure.Helper
{
public class JnHelper
{

View File

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Model\PagedInfo.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspectCore.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<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.2" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,5 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using System;
namespace Infrastructure
@ -15,12 +14,12 @@ namespace Infrastructure
/// <summary>
/// 全局配置构建器
/// </summary>
//public static IConfigurationBuilder ConfigurationBuilder;
public static IConfiguration Configuration;
/// <summary>
/// 获取Web主机环境
/// </summary>
//internal static IWebHostEnvironment WebHostEnvironment;
public static IWebHostEnvironment WebHostEnvironment;
/// <summary>
/// 获取泛型主机环境

View File

@ -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;
if (jwtSecurityToken == null) return null;
IEnumerable<Claim> claims = jwtSecurityToken?.Claims;
TokenModel loginUser = null;
var userData = jwtToken.FirstOrDefault(x => x.Type == ClaimTypes.UserData)?.Value;
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
Infrastructure/Log.cs Normal file
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();
}
}
}

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;
}
}

View File

@ -1,23 +1,31 @@

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; }
public JwtSettings JwtSettings { get; set; }
public Gen Gen { get; set; }
public List<DbConfigs> DbConfigs { get; set; }
public DbConfigs CodeGenDbConfig { get; set; }
}
/// <summary>
/// 发送邮件数据配置
@ -76,11 +84,24 @@ 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
{
public string Database { get; set; }
public bool ShowApp { get; set; }
public bool AutoPre { get; set; }
public string VuePath { get; set; }
public string Author { get; set; }
public DbConfigs GenDbConfig { get; set; }
public CsharpTypeArr CsharpTypeArr { get; set; }
}
public class DbConfigs
@ -89,10 +110,17 @@ namespace Infrastructure
public int DbType { get; set; }
public string ConfigId { get; set; }
public bool IsAutoCloseConnection { get; set; }
/// <summary>
/// 是否代码生成使用库
/// </summary>
public bool IsGenerateDb { get; set; }
public string DbName { get; set; }
}
public class CsharpTypeArr
{
public string[] String { get; set; }
public string[] Int { get; set; }
public string[] Long { get; set; }
public string[] DateTime { get; set; }
public string[] Float { get; set; }
public string[] Decimal { get; set; }
public string[] Bool { get; set; }
}
}

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; }
}
}

View File

@ -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服务注册

View File

@ -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>
@ -9,7 +16,7 @@
/// <param name="configuration"></param>
public static void AddCors(this IServiceCollection services, IConfiguration configuration)
{
var corsUrls = configuration["corsUrls"]?.Split(',', StringSplitOptions.RemoveEmptyEntries);
var corsUrls = configuration.GetSection("corsUrls").Get<string[]>();
//配置跨域
services.AddCors(c =>

View File

@ -0,0 +1,41 @@

using Infrastructure.Extensions;
using Microsoft.AspNetCore.Http;
using System;
using System.Reflection;
namespace Infrastructure
{
public static class EntityExtension
{
public static TSource ToCreate<TSource>(this TSource source, HttpContext? context = null)
{
var types = source?.GetType();
if (types == null || context == null) return source;
BindingFlags flag = BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance;
types.GetProperty("CreateTime", flag)?.SetValue(source, DateTime.Now, null);
types.GetProperty("AddTime", flag)?.SetValue(source, DateTime.Now, null);
types.GetProperty("CreateBy", flag)?.SetValue(source, context.GetName(), null);
types.GetProperty("Create_by", flag)?.SetValue(source, context.GetName(), null);
types.GetProperty("UserId", flag)?.SetValue(source, context.GetUId(), null);
return source;
}
public static TSource ToUpdate<TSource>(this TSource source, HttpContext? context = null)
{
var types = source?.GetType();
if (types == null || context == null) return source;
BindingFlags flag = BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance;
types.GetProperty("UpdateTime", flag)?.SetValue(source, DateTime.Now, null);
types.GetProperty("Update_time", flag)?.SetValue(source, DateTime.Now, null);
types.GetProperty("UpdateBy", flag)?.SetValue(source, context.GetName(), null);
types.GetProperty("Update_by", flag)?.SetValue(source, context.GetName(), null);
return source;
}
}
}

View File

@ -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,6 +134,55 @@ namespace ZR.Admin.WebApi.Extensions
return context.Request.Headers["Authorization"];
}
/// <summary>
/// 获取请求Url
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetRequestUrl(this HttpContext context)
{
return context != null ? context.Request.Path.Value : "";
}
/// <summary>
/// 获取请求参数
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetQueryString(this HttpContext context)
{
return context != null ? context.Request.QueryString.Value : "";
}
/// <summary>
/// 获取body请求参数
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetBody(this HttpContext context)
{
context.Request.EnableBuffering();
//context.Request.Body.Seek(0, SeekOrigin.Begin);
//using var reader = new StreamReader(context.Request.Body, Encoding.UTF8);
////需要使用异步方式才能获取
//return reader.ReadToEndAsync().Result;
string body = string.Empty;
var buffer = new MemoryStream();
context.Request.Body.Seek(0, SeekOrigin.Begin);
context.Request.Body.CopyToAsync(buffer);
buffer.Position = 0;
try
{
using StreamReader streamReader = new(buffer, Encoding.UTF8);
body = streamReader.ReadToEndAsync().Result;
}
finally
{
buffer?.Dispose();
}
return body;
}
/// <summary>
/// 获取浏览器信息
/// </summary>
@ -146,55 +198,36 @@ namespace ZR.Admin.WebApi.Extensions
}
/// <summary>
/// 获取请求Url
/// 根据IP获取地理位置
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string? GetRequestUrl(this HttpContext context)
public static string GetIpInfo(string IP)
{
return context != null ? context.Request.Path.Value : "";
var ipInfo = IpTool.Search(IP);
return ipInfo?.Province + "-" + ipInfo?.City + "-" + ipInfo?.NetworkOperator;
}
/// <summary>
/// 获取请求参数
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetQueryString(this HttpContext context)
{
return context != null ? context.Request.QueryString.Value : "";
}
/// <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 param = string.Empty;
if (HttpMethods.IsPost(reqMethod) || HttpMethods.IsPut(reqMethod) || HttpMethods.IsDelete(reqMethod))
{
context.Request.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8);
//需要使用异步方式才能获取
param = reader.ReadToEndAsync().Result;
if (param.IsEmpty())
{
param = context.GetQueryString();
}
param = PwdRep().Replace(param, "***");
param = context.GetBody();
string regex = "(?<=\"password\":\")[^\",]*";
param = Regex.Replace(param, regex, "***");
}
else
if (param.IsEmpty())
{
param = context.GetQueryString();
}
operLog.OperParam = param;
return param;
}
[GeneratedRegex("(?<=\"password\":\")[^\",]*")]
private static partial Regex PwdRep();
}
}

View File

@ -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
{

View File

@ -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;
},
};
});
}
}
}

View File

@ -0,0 +1,25 @@
using Infrastructure.Helper;
using JinianNet.JNTemplate;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Infrastructure
{
public static class LogoExtension
{
public static void AddLogo(this IServiceCollection services)
{
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/h5");
Console.WriteLine($"Swagger地址[后端启动地址]/swagger/index.html");
}
}
}

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>

199
README.en.md Normal file
View File

@ -0,0 +1,199 @@
<h2 align="center"> ZR.Admin.NET Back-end management system</h2>
<h4 align="center">base .Net7 + vue2.x/vue3.x/uniapp Front-end and back-end separation of .NET rapid development framework</h4>
<p align="center">
<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/更新日志-20230920-yellow"/></a>
</p>
---
<div align="center">
<p><strong><a href="README.md">简体中文</a> | <a href="README.en.md">English</a></strong></p>
</div>
---
## 🍟 overview
- This project is suitable for developers with some NetCore and vue foundation
-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)
```
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! ~
```
## 🍿 Online experience
- Official documentationhttp://www.izhaorui.cn/doc
- Join a group chat[立即加入](http://www.izhaorui.cn/doc/contact.html)
- Vue3.x experience[http://www.izhaorui.cn/vue3](http://www.izhaorui.cn/vue3)
- Vue2.x experience[http://www.izhaorui.cn/admin](http://www.izhaorui.cn/admin)
- Uniapp experience[http://www.izhaorui.cn/h5](http://www.izhaorui.cn/h5)
- account/passwordadmin/123456
| H5 | WeChat mini program |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/qrcodeH5.png) | ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/qrcode.jpg) |
```
Since it is a personal project, the funds are limited, and the experience server is low-fied, please cherish it, poke it lightly, and appreciate it!!
```
## 💒 Code repository
| repository | Github | Gitee |
| -------------- | ------------------------------------------------------ | ---------------------------------------------------------------- |
| 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
Vue Front-end technology stack: Based on Vue2.x/Vue3.x/UniApp, Vue, Vue-router, Vue-CLI, AXIOS, Element-UI, Echats, i18N Internationalization, etc., the front-end adopts VSCODE tool development
## 🍀 Back-end technology
- 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
- Tools: Captcha, rich public functions
- Interface throttling: Supports interface throttling to avoid excessive pressure on the service layer caused by malicious requests
- Code generation: efficient development, the code generator can generate all front-end and back-end code with one click
- Data dictionary: Support data dictionary, which can facilitate the management of some states
- Sharding and sharding: Using ORM SQLSUGAR, you can easily achieve superior sharding and sharding performance
- Multi-tenant: Support multi-tenancy function
- Cache data: Built-in memory cache and Redis
## 🍖 Built-in features
1. User management: The user is the system operator, and this function mainly completes the system user configuration.
2. Department management: configure the system organization (company, department, group), tree structure display.
3. Job management: configure the position of the system user.
4. Menu management: configure system menus, operation permissions, button permission identification, etc.
5. Role Management: Role menu permission assignment.
6. Dictionary management: maintain some relatively fixed data that is often used in the system.
7. Operation log: system normal operation log records and queries; System exception information logging and querying.
8. Logon logon: The system logon log record query contains logon exceptions.
9. System Interface: Use Swagger to generate relevant API interface documentation.
10. Service monitoring: Monitor the current system CPU, memory, disk, stack, and other related information.
11. Online Builder: Drag form elements to generate the corresponding VUE code (only VUE2 supported).
12. Task system: Based on the Quartz.NET, you can schedule tasks online (add, modify, delete, manually execute) including execution result logs.
13. Article management: You can write article records.
14. Code generation: You can generate front-end and back-end code (.cs, .vue, .js, .sql, etc.) with one click, support download, customize the configuration of front-end display controls, and make development faster and more efficient (the strongest in history).
15. Parameter management: dynamically configure common parameters for the system.
16. Send Mail: You can send mail to multiple users.
17. File management: You can manage uploaded files, which currently supports uploading to on-premises and Alibaba Cloud.
18. Notification management: The system notifies and announces information release and maintenance, and uses SignalR to realize real-time notification to users.
19. Account Registration: You can register an account to log in to the system.
20. Multi-language management: support static and back-end dynamic configuration internationalization. Currently only supports Chinese, English, and Traditional characters (only VUE3 is supported)
## 🍻 Project structure
```
├─ZR.Service ->[你的业务服务层类库]提供WebApi接口调用
├─ZR.ServiceCore ->[系统服务层类库]提供WebApi接口调用
├─ZR.Repository ->[仓库层类库]:方便提供有执行存储过程的操作;
├─ZR.Model ->[实体层类库]:提供项目中的数据库表、数据传输对象;
├─ZR.Admin.WebApi ->[webapi接口]为Vue版或其他三方系统提供接口服务。
├─ZR.Tasks ->[定时任务类库]:提供项目定时任务实现功能;
├─ZR.CodeGenerator ->[代码生成功能]:包含代码生成的模板、方法、代码生成的下载。
├─ZR.Vue ->[前端UI]vue2.0版本UI层(已经不再更新推荐使用vue3)。
├─document ->[文档]:数据库脚本
```
## 🍎 Storyplate
<table>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/1.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/2.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/3.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/4.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/5.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/6.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/7.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/8.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/9.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/10.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/11.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/12.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/13.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/14.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/15.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/16.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/17.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/18.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/19.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/20.png"/></td>
</tr>
</table>
## Mobile Storyplate
<table>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a1.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a2.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a8.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a4.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a5.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a6.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a7.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a9.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a10.png"/></td>
</tr>
</table>
## 🎉 Advantages
1. The front-end system does not need to write login, authorization, and authentication modules; Just write the business module
2. The background system can be used directly after release without any secondary development
3. The front-end and back-end systems are separated, and they are separate systems (domain names can be independent)
4. Unified handling of global exceptions
5. Custom code generation features
6. Less dependence, easy to get started
7. Comprehensive documentation
## 💐 Special thanks
- 👉Ruoyi.vue[Ruoyi](http://www.ruoyi.vip/)
- 👉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)
## 🎀 donation
If you feel that the project has helped you, you can ask the author for a cup of coffee as a sign of encouragement ☕️
<img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/pay.jpg"/>

View File

@ -1,13 +1,21 @@
<h2 align="center"> ZR.Admin.NET后台管理系统</h2>
<h4 align="center">基于.NET5/.Net7 + vue2.x/vue3.x/uniapp前后端分离的.net快速开发框架</h4>
<h4 align="center">基于.Net7 + vue2.x/vue3.x/uniapp前后端分离的.net快速开发框架</h4>
<p align="center">
<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/change-更新日志-yellow"/></a>
<a href="http://www.izhaorui.cn/doc/changelog.html"><img src="https://img.shields.io/badge/更新日志-20230920-yellow"/></a>
</p>
---
<div align="center">
<p><strong><a href="README.md">简体中文</a> | <a href="README.en.md">English</a></strong></p>
</div>
---
## 🍟 概述
- 本项目适合有一定 NetCore 和 vue 基础的开发人员
@ -20,7 +28,7 @@
- 阿里云特惠专区:[☛☛ 点我进入 ☚☚](https://www.aliyun.com/minisite/goods?userCode=uotn5vt1&share_source=copy_link)
```
如果对您有帮助,您可以点右上角 “Star” 收藏一下 这样作者才有继续免费下去的动力,谢谢!~
如果对您有帮助,您可以点右上角 “Star” 收藏一下 ,谢谢!~
```
## 🍿 在线体验
@ -30,26 +38,26 @@
- 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 | 微信小程序 |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/net7.0/document/images/qrcodeH5.png) | ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/net7.0/document/images/qrcode.jpg) |
| ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/qrcodeH5.png) | ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/qrcode.jpg) |
```
由于是个人项目,资金有限,体验服是低配,请大家爱惜,轻戳,不胜感激!!!
由于是个人项目,资金有限,体验服务器是低配,请大家爱惜,轻戳,不胜感激!!!
```
## 💒 代码仓库
| 仓库 | Github | Gitee |
| ---- | ------------------------------------------------------ | ---------------------------------------------------------------- |
| Net7 | [克隆/下载](https://github.com/izhaorui/Zr.Admin.NET) | [克隆/下载](https://gitee.com/izory/ZrAdminNetCore/tree/net7.0/) |
| Vue3 | [克隆/下载](https://github.com/izhaorui/ZR.Admin.Vue3) | [克隆/下载](https://gitee.com/izory/ZRAdmin-vue) |
| 仓库 | Github | Gitee |
| -------------- | ------------------------------------------------------ | --------------------------------------------------- |
| 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) |
## 🍁 前端技术
Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli 、axios、 element-ui、echats、i18n 国际化等,前端采用 vscode 工具开发
Vue 版前端技术栈 :基于 vue2.x/vue3.x/uniapp、vuex、vue-router 、vue-cli 、axios、 element-ui、echats、i18n 国际化等,前端采用 vscode 工具开发
## 🍀 后端技术
@ -64,6 +72,7 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli
- 分库分表:使用 orm sqlsugar 可以很轻松的实现分库分库性能优越
- 多 租 户:支持多租户功能
- 缓存数据:内置内存缓存和 Redis
- signalR使用 signalr 管理用户在线状态
## 🍖 内置功能
@ -80,24 +89,30 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli
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.Repository ->[仓库层类库]:方便提供有执行存储过程的操作;
├─ZR.Model ->[实体层类库]:提供项目中的数据库表、数据传输对象;
├─ZR.Admin.WebApi ->[webapi接口]为Vue版或其他三方系统提供接口服务。
├─ZR.Service ->[你的业务服务层类库]提供WebApi接口调用
├─ZR.ServiceCore ->[系统服务层类库]提供WebApi接口调用
├─ZR.Repository ->[仓库层类库]:方便提供有执行存储过程的操作;
├─ZR.Model ->[实体层类库]:提供项目中的数据库表、数据传输对象;
├─ZR.Admin.WebApi ->[webapi接口]为Vue版或其他三方系统提供接口服务。
├─ZR.Tasks ->[定时任务类库]:提供项目定时任务实现功能;
├─ZR.CodeGenerator ->[代码生成功能]:包含代码生成的模板、方法、代码生成的下载。
├─ZR.Vue ->[前端UI]vue2.0版本UI层。
├─ZR.CodeGenerator ->[代码生成功能]:包含代码生成的模板、方法、代码生成的下载。
├─ZR.Vue ->[前端UI]vue2.0版本UI层(已经不再更新推荐使用vue3)
├─document ->[文档]:数据库脚本
```
@ -138,12 +153,15 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/17.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/18.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/18.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/19.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/20.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/21.png"/></td>
</tr>
</table>
## 移动端演示图
@ -160,6 +178,13 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a5.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a6.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a7.png"/></td>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a9.png"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/a10.png"/></td>
</tr>
</table>
@ -173,6 +198,7 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli
5. 自定义的代码生成功能
6. 依赖少,上手容易
7. 文档全面
8. 支持中文表头导入数据
## 💐 特别鸣谢
@ -180,8 +206,9 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli
- 👉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)
## 🎀 捐赠
如果你觉得这个项目帮助到了你,你可以请作者喝杯咖啡表示鼓励 ☕️
<img src="https://gitee.com/izory/ZrAdminNetCore/raw/net7.0/document/images/pay.jpg"/>
<img src="https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/pay.jpg"/>

View File

@ -1,15 +1,9 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Extensions;
using Infrastructure.Model;
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.Common;
using ZR.Model.System;
using ZR.Service.IService;
using ZR.Service.System;
using ZR.Service.System.IService;
@ -19,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;
@ -26,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("/")]
@ -48,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>

View File

@ -1,15 +1,9 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using ZR.Model.Dto;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Common;
using ZR.Service.System.IService;
using ZR.Model.Dto;
using ZR.Model.System;
using ZR.Service.System.IService;
namespace ZR.Admin.WebApi.Controllers
{
@ -17,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers
/// 文章目录Controller
/// </summary>
[Route("article/ArticleCategory")]
[ApiExplorerSettings(GroupName = "article")]
public class ArticleCategoryController : BaseController
{
/// <summary>

View File

@ -1,8 +1,4 @@
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
@ -17,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("article")]
[ApiExplorerSettings(GroupName = "article")]
public class ArticleController : BaseController
{
/// <summary>
@ -36,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);
@ -91,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)
{

View File

@ -1,19 +1,11 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Extensions;
using IP2Region.Ex.Models;
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;
using ZR.Model.System.Dto;
using ZR.Model.System.Generate;
using ZR.Service.System.IService;
@ -25,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();
@ -153,7 +146,7 @@ namespace ZR.Admin.WebApi.Controllers
{
throw new CustomException("表不能为空");
}
var dbConfig = AppSettings.Get<List<DbConfigs>>("dbConfigs").FirstOrDefault(f => f.IsGenerateDb);
DbConfigs dbConfig = AppSettings.Get<DbConfigs>(nameof(GlobalConstant.CodeGenDbConfig));
string[] tableNames = tables.Split(',', StringSplitOptions.RemoveEmptyEntries);
int result = 0;
foreach (var tableName in tableNames)
@ -218,29 +211,25 @@ namespace ZR.Admin.WebApi.Controllers
/// <summary>
/// 预览代码
/// </summary>
/// <param name="dto"></param>
/// <param name="tableId"></param>
/// <param name="VueVersion"></param>
/// <returns></returns>
[HttpPost("preview/{tableId}")]
[ActionPermissionFilter(Permission = "tool:gen:preview")]
public IActionResult Preview(long tableId = 0, int VueVersion = 0)
public IActionResult Preview([FromQuery] GenerateDto dto, [FromRoute] int tableId = 0)
{
GenerateDto dto = new()
{
TableId = tableId,
VueVersion = VueVersion
};
dto.TableId = tableId;
if (dto == null || dto.TableId <= 0)
{
throw new CustomException(ResultCode.CUSTOM_ERROR, "请求参数为空");
}
var genTableInfo = GenTableService.GetGenTableInfo(dto.TableId);
var dbConfig = AppSettings.Get<List<DbConfigs>>("dbConfigs").FirstOrDefault(f => f.IsGenerateDb);
var dbConfig = AppSettings.Get<DbConfigs>(nameof(GlobalConstant.CodeGenDbConfig));
dto.DbType = dbConfig.DbType;
dto.GenTable = genTableInfo;
dto.IsPreview = true;
//生成代码
CodeGeneratorTool.Generate(dto);
return SUCCESS(dto.GenCodes);
@ -261,7 +250,7 @@ namespace ZR.Admin.WebApi.Controllers
throw new CustomException(ResultCode.CUSTOM_ERROR, "请求参数为空");
}
var genTableInfo = GenTableService.GetGenTableInfo(dto.TableId);
var dbConfig = AppSettings.Get<List<DbConfigs>>("dbConfigs").FirstOrDefault(f => f.IsGenerateDb);
var dbConfig = AppSettings.Get<DbConfigs>(nameof(GlobalConstant.CodeGenDbConfig));
dto.DbType = dbConfig.DbType;
dto.GenTable = genTableInfo;
@ -284,12 +273,20 @@ namespace ZR.Admin.WebApi.Controllers
//生成代码到指定文件夹
CodeGeneratorTool.Generate(dto);
//下载文件
FileUtil.ZipGenCode(dto.ZipPath, dto.GenCodePath, zipReturnFileName);
if (genTableInfo.Options.GenerateMenu)
{
SysMenuService.AddSysMenu(genTableInfo, dto.ReplaceDto.PermissionPrefix, dto.ReplaceDto.ShowBtnEdit, dto.ReplaceDto.ShowBtnExport);
SysMenuService.AddSysMenu(genTableInfo, dto.ReplaceDto.PermissionPrefix, dto.ReplaceDto.ShowBtnEdit, dto.ReplaceDto.ShowBtnExport, dto.ReplaceDto.ShowBtnImport);
}
foreach (var item in dto.GenCodes)
{
item.Path = Path.Combine(dto.GenCodePath, item.Path);
FileUtil.WriteAndSave(item.Path, item.Content);
}
//下载文件
FileUtil.ZipGenCode(dto.ZipPath, dto.GenCodePath, zipReturnFileName);
return SUCCESS(new { path = "/Generatecode/" + zipReturnFileName, fileName = dto.ZipFileName });
}

View File

@ -1,15 +1,10 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Extensions;
using Infrastructure.Model;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MiniExcelLibs;
using ZR.Admin.WebApi.Filters;
using ZR.Common;
using ZR.Model;
using ZR.Model.Dto;
using ZR.Model.Models;
using ZR.Service.System.IService;
namespace ZR.Admin.WebApi.Controllers
@ -19,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("system/CommonLang")]
[ApiExplorerSettings(GroupName = "sys")]
public class CommonLangController : BaseController
{
/// <summary>
@ -135,6 +131,25 @@ namespace ZR.Admin.WebApi.Controllers
return ToResponse(response);
}
/// <summary>
/// 删除多语言配置
/// </summary>
/// <returns></returns>
[HttpDelete("ByKey")]
[ActionPermissionFilter(Permission = "system:lang:delete")]
[Log(Title = "多语言配置", BusinessType = BusinessType.DELETE)]
public IActionResult DeleteCommonLangByKey(string langkey)
{
if (langkey.IsEmpty()) { return ToResponse(ApiResult.Error($"删除失败Id 不能为空")); }
var response = _CommonLangService
.Deleteable()
.EnableDiffLogEvent()
.Where(f => f.LangKey == langkey)
.ExecuteCommand();
return ToResponse(response);
}
/// <summary>
/// 导出多语言配置
/// </summary>
@ -145,11 +160,50 @@ namespace ZR.Admin.WebApi.Controllers
public IActionResult Export([FromQuery] CommonLangQueryDto parm)
{
parm.PageSize = 10000;
var list = _CommonLangService.GetList(parm).Result;
var list = _CommonLangService.GetListToPivot(parm);
string sFileName = ExportExcel(list, "CommonLang", "多语言配置");
return SUCCESS(new { path = "/export/" + sFileName, fileName = sFileName });
}
/// <summary>
/// 导入
/// </summary>
/// <param name="formFile"></param>
/// <returns></returns>
[HttpPost("importData")]
[Log(Title = "多语言设置导入", BusinessType = BusinessType.IMPORT, IsSaveRequestData = false, IsSaveResponseData = true)]
[ActionPermissionFilter(Permission = "system:lang:import")]
public IActionResult ImportData([FromForm(Name = "file")] IFormFile formFile)
{
List<CommonLang> list = new();
var nowTime = DateTime.Now;
using (var stream = formFile.OpenReadStream())
{
var rows = stream.Query(startCell: "A2").ToList();
foreach (var item in rows)
{
list.Add(new CommonLang() { LangCode = "zh-cn", LangKey = item.A, LangName = item.B, Addtime = nowTime });
list.Add(new CommonLang() { LangCode = "en", LangKey = item.A, LangName = item.C, Addtime = nowTime });
list.Add(new CommonLang() { LangCode = "zh-tw", LangKey = item.A, LangName = item.D, Addtime = nowTime });
}
}
return SUCCESS(_CommonLangService.ImportCommonLang(list));
}
/// <summary>
/// 多语言设置导入模板下载
/// </summary>
/// <returns></returns>
[HttpGet("importTemplate")]
[Log(Title = "多语言设置模板", BusinessType = BusinessType.EXPORT, IsSaveRequestData = true, IsSaveResponseData = false)]
[AllowAnonymous]
public IActionResult ImportTemplateExcel()
{
var result = DownloadImportTemplate(new List<CommonLang>() { }, "lang");
return ExportExcel(result.Item2, result.Item1);
}
}
}

View File

@ -1,15 +1,8 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Extensions;
using Infrastructure.Model;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Common;
using ZR.Model.System;
using ZR.Model.System.Dto;
using ZR.Service.System.IService;
@ -21,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("system/config")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysConfigController : BaseController
{
/// <summary>

View File

@ -1,12 +1,8 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using System.Collections;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Common;
using ZR.Model.System;
using ZR.Model.System.Dto;
using ZR.Service.System.IService;
namespace ZR.Admin.WebApi.Controllers.System
@ -16,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/dept")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysDeptController : BaseController
{
public ISysDeptService DeptService;
@ -33,7 +30,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// <returns></returns>
[ActionPermissionFilter(Permission = "system:dept:list")]
[HttpGet("list")]
public IActionResult List([FromQuery] SysDept dept)
public IActionResult List([FromQuery] SysDeptQueryDto dept)
{
return SUCCESS(DeptService.GetSysDepts(dept), TIME_FORMAT_FULL);
}
@ -46,7 +43,7 @@ namespace ZR.Admin.WebApi.Controllers.System
[HttpGet("list/exclude/{deptId}")]
public IActionResult ExcludeChild(long deptId)
{
var depts = DeptService.GetSysDepts(new SysDept());
var depts = DeptService.GetSysDepts(new SysDeptQueryDto());
for (int i = 0; i < depts.Count; i++)
{
@ -66,7 +63,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// <param name="dept"></param>
/// <returns></returns>
[HttpGet("treeselect")]
public IActionResult TreeSelect(SysDept dept)
public IActionResult TreeSelect(SysDeptQueryDto dept)
{
var depts = DeptService.GetSysDepts(dept);
@ -82,7 +79,7 @@ namespace ZR.Admin.WebApi.Controllers.System
[HttpGet("roleDeptTreeselect/{roleId}")]
public IActionResult RoleMenuTreeselect(int roleId)
{
var depts = DeptService.GetSysDepts(new SysDept());
var depts = DeptService.GetSysDepts(new SysDeptQueryDto());
var checkedKeys = DeptService.SelectRoleDepts(roleId);
return SUCCESS(new
{
@ -152,7 +149,7 @@ namespace ZR.Admin.WebApi.Controllers.System
[Log(Title = "部门管理", BusinessType = BusinessType.DELETE)]
public IActionResult Remove(long deptId)
{
if (DeptService.Queryable().Count(it => it.ParentId == deptId && it.DelFlag == "0") > 0)
if (DeptService.Queryable().Count(it => it.ParentId == deptId && it.DelFlag == 0) > 0)
{
return ToResponse(ResultCode.CUSTOM_ERROR, $"存在下级部门,不允许删除");
}

View File

@ -1,8 +1,4 @@
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Extensions;
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Filters;
using ZR.Model;
using ZR.Model.System;
@ -16,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;

View File

@ -1,11 +1,6 @@
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Extensions;
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("system/dict/type")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysDictTypeController : BaseController
{
private readonly ISysDictService SysDictService;

View File

@ -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);
}

View File

@ -1,20 +1,7 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Model;
using IPTools.Core;
using Lazy.Captcha.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Lazy.Captcha.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using NETCore.Encrypt;
using UAParser;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Admin.WebApi.Framework;
using ZR.Common;
using ZR.Model.System;
using ZR.Model.System.Dto;
using ZR.Service.System;
@ -25,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;
@ -36,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,
@ -47,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;
@ -57,11 +45,9 @@ namespace ZR.Admin.WebApi.Controllers.System
this.permissionService = permissionService;
this.sysConfigService = configService;
roleService = sysRoleService;
this.jwtSettings = jwtSettings.Value;
this.optionSettings = optionSettings.Value;
}
// RSA私钥
private static string _privateKey;
/// <summary>
/// 登录
@ -80,17 +66,18 @@ namespace ZR.Admin.WebApi.Controllers.System
{
return ToResponse(ResultCode.CAPTCHA_ERROR, "验证码错误");
}
// RSA解密
loginBody.Password = EncryptProvider.RSADecryptWithPem(_privateKey, loginBody.Password);
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>
@ -130,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 });
}
@ -164,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>
@ -193,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");
@ -215,18 +181,85 @@ namespace ZR.Admin.WebApi.Controllers.System
return ToResponse(ResultCode.CUSTOM_ERROR, "注册失败,请联系管理员");
}
#region
/// <summary>
/// 获取RSA公钥和密钥
/// 生成二维码
/// </summary>
/// <param name="uuid"></param>
/// <param name="deviceId"></param>
/// <returns></returns>
[HttpGet("getRsaKey")]
public IActionResult GetRsaKey()
[HttpGet("/GenerateQrcode")]
public IActionResult GenerateQrcode(string uuid, string deviceId)
{
var (publicPem, privatePem) = EncryptProvider.RSAToPem(true);
_privateKey = privatePem;
return SUCCESS(new {
PublicKey = publicPem
});
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
}
}

View File

@ -1,10 +1,4 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Extensions;
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Filters;
using ZR.Model.System;
using ZR.Model.System.Dto;
@ -17,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;

View File

@ -1,19 +1,12 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Constant;
using Infrastructure.Enums;
using Infrastructure.Model;
using Mapster;
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.Common;
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
{
@ -22,6 +15,7 @@ namespace ZR.Admin.WebApi.Controllers.System
/// </summary>
[Verify]
[Route("system/notice")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysNoticeController : BaseController
{
/// <summary>
@ -58,13 +52,7 @@ namespace ZR.Admin.WebApi.Controllers.System
[ActionPermissionFilter(Permission = "system:notice:list")]
public IActionResult QuerySysNotice([FromQuery] SysNoticeQueryDto parm)
{
var predicate = Expressionable.Create<SysNotice>();
predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.NoticeTitle), m => m.NoticeTitle.Contains(parm.NoticeTitle));
predicate = predicate.AndIF(parm.NoticeType != null, m => m.NoticeType == parm.NoticeType);
predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.CreateBy), m => m.Create_by.Contains(parm.CreateBy) || m.Update_by.Contains(parm.CreateBy));
predicate = predicate.AndIF(parm.Status != null, m => m.Status == parm.Status);
var response = _SysNoticeService.GetPages(predicate.ToExpression(), parm);
PagedInfo<SysNotice> response = _SysNoticeService.GetPageList(parm);
return SUCCESS(response);
}
@ -74,7 +62,6 @@ namespace ZR.Admin.WebApi.Controllers.System
/// <param name="NoticeId"></param>
/// <returns></returns>
[HttpGet("{NoticeId}")]
[ActionPermissionFilter(Permission = "system:notice:query")]
public IActionResult GetSysNotice(int NoticeId)
{
var response = _SysNoticeService.GetFirst(x => x.NoticeId == NoticeId);
@ -92,8 +79,6 @@ namespace ZR.Admin.WebApi.Controllers.System
public IActionResult AddSysNotice([FromBody] SysNoticeDto parm)
{
var modal = parm.Adapt<SysNotice>().ToCreate(HttpContext);
modal.Create_by = HttpContext.GetName();
modal.Create_time = DateTime.Now;
int result = _SysNoticeService.Insert(modal, it => new
{

View File

@ -1,12 +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.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Common;
using ZR.Model;
using ZR.Model.System;
using ZR.Service.System.IService;
@ -18,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;
@ -71,8 +66,8 @@ namespace ZR.Admin.WebApi.Controllers.System
{
throw new CustomException($"修改岗位{post.PostName}失败,岗位编码已存在");
}
post.ToCreate(HttpContext);
post.Create_by = HttpContext.GetName();
return ToResponse(PostService.Add(post));
}
@ -94,7 +89,7 @@ namespace ZR.Admin.WebApi.Controllers.System
{
throw new CustomException($"修改岗位{post.PostName}失败,岗位编码已存在");
}
post.Update_by = HttpContext.GetName();
post.ToUpdate(HttpContext);
return ToResponse(PostService.Update(post));
}
@ -126,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()

View File

@ -1,10 +1,4 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Model.System;
using ZR.Model.System.Dto;
@ -17,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;

View File

@ -1,21 +1,9 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using Microsoft.AspNetCore.Mvc;
using ZR.Common;
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Filters;
using ZR.Model;
using ZR.Model.System;
using ZR.Service.System.IService;
using ZR.Admin.WebApi.Extensions;
using ZR.Model.System.Dto;
using Mapster;
using ZR.Service;
using Microsoft.AspNetCore.Authorization;
using Aliyun.OSS;
using MiniExcelLibs.OpenXml;
using MiniExcelLibs;
using ZR.Service.System.IService;
namespace ZR.Admin.WebApi.Controllers.System
{
@ -24,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;

View File

@ -1,11 +1,6 @@
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using Microsoft.AspNetCore.Authorization;
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;
@ -19,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;
@ -49,7 +45,7 @@ namespace ZR.Admin.WebApi.Controllers.System
{
var list = UserService.SelectUserList(user, pager);
return SUCCESS(list, TIME_FORMAT_FULL);
return SUCCESS(list);
}
/// <summary>
@ -97,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>
@ -147,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);
@ -160,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);

View File

@ -1,6 +1,4 @@
using Infrastructure;
using Infrastructure.Attribute;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Filters;
using ZR.Model.System.Dto;
using ZR.Service.System.IService;
@ -12,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;
@ -45,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);
@ -60,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));

View File

@ -1,14 +1,7 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Extensions;
using Mapster;
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;
using ZR.Model.System;
using ZR.Model.System.Dto;
using ZR.Service.System.IService;
@ -21,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers
/// </summary>
[Verify]
[Route("system/Tasks")]
[ApiExplorerSettings(GroupName = "sys")]
public class TasksController : BaseController
{
private ISysTasksQzService _tasksQzService;
@ -40,19 +34,9 @@ namespace ZR.Admin.WebApi.Controllers
/// <returns></returns>
[HttpGet("list")]
[ActionPermissionFilter(Permission = "monitor:job:list")]
public IActionResult ListTask([FromQuery] TasksQueryDto parm, [FromQuery] PagerInfo pager)
public IActionResult ListTask([FromQuery] TasksQueryDto parm)
{
//开始拼装查询条件
var predicate = Expressionable.Create<SysTasks>();
predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.QueryText),
m => m.Name.Contains(parm.QueryText) ||
m.JobGroup.Contains(parm.QueryText) ||
m.AssemblyName.Contains(parm.QueryText));
predicate.AndIF(parm.TaskType != null, m => m.TaskType == parm.TaskType);
var response = _tasksQzService.GetPages(predicate.ToExpression(), pager);
var response = _tasksQzService.SelectTaskList(parm);
return SUCCESS(response, TIME_FORMAT_FULL);
}
@ -102,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();
@ -174,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);
}
@ -203,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);
@ -234,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);
@ -253,11 +237,12 @@ namespace ZR.Admin.WebApi.Controllers
[Log(Title = "执行任务", BusinessType = BusinessType.OTHER)]
public async Task<IActionResult> Run(string id)
{
if (!_tasksQzService.Any(m => m.ID == id))
var result = await _tasksQzService.IsAnyAsync(m => m.ID == id);
if (!result)
{
throw new CustomException("任务不存在");
}
var tasksQz = _tasksQzService.GetFirst(m => m.ID == id);
var tasksQz = await _tasksQzService.GetFirstAsync(m => m.ID == id);
var taskResult = await _schedulerServer.RunTaskScheduleAsync(tasksQz);
return ToResponse(taskResult);

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -1,12 +1,7 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Common;
using ZR.Model;
using ZR.Model.System;
using ZR.Service.System.IService;
@ -18,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor
/// </summary>
[Verify]
[Route("/monitor/logininfor")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysLogininforController : BaseController
{
private ISysLoginService sysLoginService;

View File

@ -1,10 +1,6 @@
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Common;
using ZR.Model.System.Dto;
using ZR.Service.System.IService;
@ -15,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor
/// </summary>
[Verify]
[Route("/monitor/operlog")]
[ApiExplorerSettings(GroupName = "sys")]
public class SysOperlogController : BaseController
{
private ISysOperLogService sysOperLogService;
@ -69,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();

View File

@ -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);
}
}
}

View File

@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using System.Web;
namespace ZR.Admin.WebApi.Controllers
{
/// <summary>
/// 微信公众号
/// </summary>
[Route("[controller]/[action]")]
[AllowAnonymous]
public class WxOpenController : BaseController
{
/// <summary>
/// 获取签名
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
[Log(Title = "获取微信签名")]
[HttpGet]
public IActionResult GetSignature(string url = "")
{
url = HttpUtility.UrlDecode(url);
var appId = AppSettings.App(new string[] { "WxOpen", "AppID" });
var noncestr = Guid.NewGuid().ToString().Replace("-", "");
var timestamp = DateTimeHelper.GetUnixTimeSeconds(DateTime.Now);
var ticketResult = WxHelper.GetTicket();
if (appId.IsEmpty()) return ToResponse(ResultCode.CUSTOM_ERROR, "appId未配置");
if (ticketResult?.errcode != 0)
{
return ToResponse(ResultCode.CUSTOM_ERROR, "获取配置失败");
}
var signature = WxHelper.GetSignature(ticketResult.ticket, timestamp.ToString(), noncestr, url);
return SUCCESS(new { appId, signature, noncestr, timestamp, url });
}
}
}

View File

@ -1,268 +0,0 @@
using Infrastructure;
using Infrastructure.Extensions;
using SqlSugar;
using SqlSugar.IOC;
using ZR.Admin.WebApi.Framework;
using ZR.Model;
using ZR.Model.System;
namespace ZR.Admin.WebApi.Extensions
{
/// <summary>
/// sqlsugar 数据处理
/// </summary>
public static class DbExtension
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
//全部数据权限
public static long DATA_SCOPE_ALL = 1;
//自定数据权限
public static long DATA_SCOPE_CUSTOM = 2;
//部门数据权限
public static long DATA_SCOPE_DEPT = 3;
//部门及以下数据权限
public static long DATA_SCOPE_DEPT_AND_CHILD = 4;
//仅本人数据权限
public static long DATA_SCOPE_SELF = 5;
/// <summary>
/// 初始化db
/// </summary>
/// <param name="services"></param>
/// <param name="Configuration"></param>
/// <param name="environment"></param>
public static void AddDb(this IServiceCollection services, IConfiguration Configuration, IWebHostEnvironment environment)
{
List<DbConfigs> dbConfigs = Configuration.GetSection("DbConfigs").Get<List<DbConfigs>>();
var iocList = new List<IocConfig>();
foreach (var item in dbConfigs.FindAll(f => !f.IsGenerateDb))
{
iocList.Add(new IocConfig()
{
ConfigId = item.ConfigId,
ConnectionString = item.Conn,
DbType = (IocDbType)item.DbType,
IsAutoCloseConnection = item.IsAutoCloseConnection
});
}
SugarIocServices.AddSqlSugar(iocList);
ICacheService cache = new SqlSugarCache();
SugarIocServices.ConfigurationSugar(db =>
{
var u = App.User;
if (u != null)
{
FilterData(0);
//ConfigId = 1的数据权限过滤
//FilterData1(1);
}
iocList.ForEach(iocConfig =>
{
SetSugarAop(db, iocConfig, cache);
});
});
if (Configuration["InitDb"].ParseToBool() == true && environment.IsDevelopment())
{
InitTable.InitDb();
}
}
/// <summary>
/// 数据库Aop设置
/// </summary>
/// <param name="db"></param>
/// <param name="iocConfig"></param>
/// <param name="cache"></param>
private static void SetSugarAop(SqlSugarClient db, IocConfig iocConfig, ICacheService cache)
{
var config = db.GetConnectionScope(iocConfig.ConfigId).CurrentConnectionConfig;
string configId = config.ConfigId;
db.GetConnectionScope(configId).Aop.OnLogExecuting = (sql, pars) =>
{
string log = $"【db{configId} SQL语句】{UtilMethods.GetSqlString(config.DbType, sql, pars)}\n";
if (sql.TrimStart().StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
{
logger.Info(log);
}
else if (sql.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase) || sql.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase))
{
logger.Warn(log);
}
else if (sql.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase) || sql.StartsWith("TRUNCATE", StringComparison.OrdinalIgnoreCase))
{
logger.Error(log);
}
else
{
log = $"【db{configId} SQL语句】dbo.{sql} {string.Join(", ", pars.Select(x => x.ParameterName + " = " + GetParsValue(x)))};\n";
logger.Info(log);
}
};
db.GetConnectionScope(configId).Aop.OnError = (ex) =>
{
var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value));
string sql = "【错误SQL】" + UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres) + "\r\n";
logger.Error(ex, $"{sql}\r\n{ex.Message}\r\n{ex.StackTrace}");
};
db.GetConnectionScope(configId).Aop.DataExecuting = (oldValue, entiyInfo) =>
{
};
db.GetConnectionScope(configId).CurrentConnectionConfig.MoreSettings = new ConnMoreSettings()
{
IsAutoRemoveDataCache = true
};
db.GetConnectionScope(configId).CurrentConnectionConfig.ConfigureExternalServices = new ConfigureExternalServices()
{
DataInfoCacheService = cache,
EntityService = (c, p) =>
{
if (p.IsPrimarykey == true)//主键不能为null
{
p.IsNullable = false;
}
else if (p.ExtendedAttribute?.ToString() == ProteryConstant.NOTNULL.ToString())
{
p.IsNullable = false;
}
else//则否默认为null
{
p.IsNullable = true;
}
if (config.DbType == DbType.PostgreSQL)
{
if (c.Name == nameof(SysMenu.IsCache) || c.Name == nameof(SysMenu.IsFrame))
{
p.DataType = "char(1)";
}
}
#region Oracle
if (config.DbType == DbType.Oracle)
{
if (p.IsIdentity == true)
{
if (p.EntityName == nameof(SysUser))
{
p.OracleSequenceName = "SEQ_SYS_USER_USERID";
}
else if (p.EntityName == nameof(SysRole))
{
p.OracleSequenceName = "SEQ_SYS_ROLE_ROLEID";
}
else if (p.EntityName == nameof(SysDept))
{
p.OracleSequenceName = "SEQ_SYS_DEPT_DEPTID";
}
else if (p.EntityName == nameof(SysMenu))
{
p.OracleSequenceName = "SEQ_SYS_MENU_MENUID";
}
else
{
p.OracleSequenceName = "SEQ_ID";
}
}
}
#endregion
}
};
}
private static object GetParsValue(SugarParameter x)
{
if (x.DbType == System.Data.DbType.String || x.DbType == System.Data.DbType.DateTime || x.DbType == System.Data.DbType.String)
{
return "'" + x.Value + "'";
}
return x.Value;
}
/// <summary>
/// 数据过滤
/// </summary>
/// <param name="configId">多库id</param>
private static void FilterData(int configId)
{
//获取当前用户的信息
var user = JwtUtil.GetLoginUser(App.HttpContext);
if (user == null) return;
//管理员不过滤
if (user.RoleIds.Any(f => f.Equals(GlobalConstant.AdminRole))) return;
var db = DbScoped.SugarScope.GetConnectionScope(configId);
var expUser = Expressionable.Create<SysUser>().Or(it => 1 == 1);
var expRole = Expressionable.Create<SysRole>().Or(it => 1 == 1);
var expLoginlog = Expressionable.Create<SysLogininfor>();
foreach (var role in user.Roles.OrderBy(f => f.DataScope))
{
long dataScope = role.DataScope;
if (DATA_SCOPE_ALL.Equals(dataScope))//所有权限
{
break;
}
else if (DATA_SCOPE_CUSTOM.Equals(dataScope))//自定数据权限
{
//" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId()));
expUser.Or(it => SqlFunc.Subqueryable<SysRoleDept>().Where(f => f.DeptId == it.DeptId && f.RoleId == role.RoleId).Any());
}
else if (DATA_SCOPE_DEPT.Equals(dataScope))//本部门数据
{
expUser.Or(it => it.DeptId == user.DeptId);
}
else if (DATA_SCOPE_DEPT_AND_CHILD.Equals(dataScope))//本部门及以下数据
{
//SQl OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )
var allChildDepts = db.Queryable<SysDept>().ToChildList(it => it.ParentId, user.DeptId);
expUser.Or(it => allChildDepts.Select(f => f.DeptId).ToList().Contains(it.DeptId));
}
else if (DATA_SCOPE_SELF.Equals(dataScope))//仅本人数据
{
expUser.Or(it => it.UserId == user.UserId);
expRole.Or(it => user.RoleIds.Contains(it.RoleKey));
expLoginlog.And(it => it.UserName == user.UserName);
}
db.QueryFilter.AddTableFilter(expUser.ToExpression());
db.QueryFilter.AddTableFilter(expRole.ToExpression());
db.QueryFilter.AddTableFilter(expLoginlog.ToExpression());
}
}
private static void FilterData1(int configId)
{
//获取当前用户的信息
var user = JwtUtil.GetLoginUser(App.HttpContext);
if (user == null) return;
var db = DbScoped.SugarScope.GetConnectionScope(configId);
foreach (var role in user.Roles.OrderBy(f => f.DataScope))
{
long dataScope = role.DataScope;
if (DATA_SCOPE_ALL.Equals(dataScope))//所有权限
{
break;
}
else if (DATA_SCOPE_CUSTOM.Equals(dataScope))//自定数据权限
{
}
else if (DATA_SCOPE_DEPT.Equals(dataScope))//本部门数据
{
}
else if (DATA_SCOPE_DEPT_AND_CHILD.Equals(dataScope))//本部门及以下数据
{
}
else if (DATA_SCOPE_SELF.Equals(dataScope))//仅本人数据
{
}
}
}
}
}

View File

@ -1,48 +0,0 @@

namespace ZR.Admin.WebApi.Extensions
{
public static class EntityExtension
{
public static TSource ToCreate<TSource>(this TSource source, HttpContext? context = null)
{
var types = source?.GetType();
if (types == null) return source;
types.GetProperty("CreateTime")?.SetValue(source, DateTime.Now, null);
types.GetProperty("AddTime")?.SetValue(source, DateTime.Now, null);
types.GetProperty("UpdateTime")?.SetValue(source, DateTime.Now, null);
if (types.GetProperty("Create_by") != null && context != null)
{
types.GetProperty("Create_by")?.SetValue(source, context.GetName(), null);
}
if (types.GetProperty("Create_By") != null && context != null)
{
types.GetProperty("Create_By")?.SetValue(source, context.GetName(), null);
}
if (types.GetProperty("CreateBy") != null && context != null)
{
types.GetProperty("CreateBy")?.SetValue(source, context.GetName(), null);
}
if (types.GetProperty("UserId") != null && context != null)
{
types.GetProperty("UserId")?.SetValue(source, context.GetUId(), null);
}
return source;
}
public static TSource ToUpdate<TSource>(this TSource source, HttpContext? context = null)
{
var types = source?.GetType();
if (types == null) return source;
types.GetProperty("UpdateTime")?.SetValue(source, DateTime.Now, null);
types.GetProperty("Update_time")?.SetValue(source, DateTime.Now, null);
types.GetProperty("UpdateBy")?.SetValue(source,context.GetName(), null);
types.GetProperty("Update_by")?.SetValue(source, context.GetName(), null);
return source;
}
}
}

View File

@ -1,21 +0,0 @@
using JinianNet.JNTemplate;
using ZR.Common;
namespace ZR.Admin.WebApi.Extensions
{
public static class LogoExtension
{
public static void AddLogo(this IServiceCollection services)
{
Console.ForegroundColor = ConsoleColor.Blue;
var contentTpl = JnHelper.ReadTemplate("", "logo.txt");
var content = contentTpl?.Render();
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");
}
}
}

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;
}
});
});
}
}

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}成功");
}

View File

@ -1,34 +0,0 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace ZR.Admin.WebApi.Framework
{
public class CookieUtil
{
public static void WhiteCookie(HttpContext context, List<Claim> claims)
{
//2.创建声明主题 指定认证方式 这里使用cookie
var claimsIdentity = new ClaimsIdentity(claims, "Login");
Task.Run(async () =>
{
await context.SignInAsync(
JwtBearerDefaults.AuthenticationScheme,//这里要注意的是HttpContext.SignInAsync(AuthenticationType,…) 所设置的Scheme一定要与前面的配置一样这样对应的登录授权才会生效。
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties()
{
IsPersistent = true,
AllowRefresh = true,
ExpiresUtc = DateTimeOffset.Now.AddDays(1),//有效时间
});
}).Wait();
}
}
}

View File

@ -0,0 +1,10 @@
global using ZR.Common;
global using Microsoft.AspNetCore.Authorization;
global using Infrastructure;
global using Infrastructure.Attribute;
global using Infrastructure.Enums;
global using Infrastructure.Model;
global using Mapster;
global using Infrastructure.Extensions;
global using Infrastructure.Controllers;
global using ZR.ServiceCore.Middleware;

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 });
}
}
}

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;
}
}
}

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>

View File

@ -1,19 +1,21 @@
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();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
@ -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,31 +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;
}
};
});
//InternalApp.InternalServices = builder.Services;
builder.Services.AddJwt();
//配置文件
builder.Services.AddSingleton(new AppSettings(builder.Configuration));
//app服务注册
builder.Services.AddAppService();
//开启计划任务
builder.Services.AddTaskSchedulers();
@ -81,23 +60,43 @@ builder.Services.AddMvc(options =>
})
.AddJsonOptions(options =>
{
//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();
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>();
//使可以多次多去body内容
//请求头转发
//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内容
context.Request.EnableBuffering();
if (context.Request.Query.TryGetValue("access_token", out var token))
{
@ -117,8 +116,11 @@ app.UseAuthorization();
//开启缓存
app.UseResponseCaching();
//恢复/启动任务
app.UseAddTaskSchedulers();
if (builder.Environment.IsProduction())
{
//恢复/启动任务
app.UseAddTaskSchedulers();
}
//使用swagger
app.UseSwagger();
//启用客户端IP限制速率
@ -132,5 +134,4 @@ app.MapControllerRoute(
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapControllers();
app.Run();

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,21 +15,15 @@
<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.5" />
<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.1.4" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.2.3" />
<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>
<Folder Include="Controllers\" />
<Folder Include="Properties\PublishProfiles\" />
</ItemGroup>
@ -37,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>

View File

@ -12,32 +12,35 @@
"DbType": 0, // MySql = 0, SqlServer = 1, Oracle = 3PgSql = 4
"ConfigId": "0", //
"IsAutoCloseConnection": true
},
{
//{dbName}
"Conn": "Data Source=LAPTOP-STKF2M8H\\SQLEXPRESS;User ID=admin;Password=admin123;Initial Catalog={dbName};",
"DbType": 1,
"ConfigId": "0",
"IsAutoCloseConnection": true,
"DbName": "ZrAdmin", //
"IsGenerateDb": true //使
}
//...
],
//
"CodeGenDbConfig": {
//{dbName}
"Conn": "Data Source=LAPTOP-STKF2M8H\\SQLEXPRESS;User ID=admin;Password=admin123;Initial Catalog={dbName};",
"DbType": 1,
"IsAutoCloseConnection": true,
"DbName": "ZrAdmin" //,Oracle
},
"urls": "http://localhost:8888", //urldevServer
"corsUrls": "http://localhost:8887", //","
"corsUrls": [ "http://localhost:8887", "http://localhost:8886" ], //","
"JwtSettings": {
"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": "uploads", // wwwroot/uploads, saveType= 2 使, /home/resource)
"localSavePath": "", // wwwroot
"maxSize": 15, // 15M
"notAllowedExt": [ ".bat", ".exe", ".jar", ".js" ]
},
@ -57,12 +60,30 @@
"CorpSecret": "",
"SendUser": "@all"
},
//
"WxOpen": {
"AppID": "",
"AppSecret": ""
},
//
"gen": {
"autoPre": true, //
//
"showApp": false,
//
"autoPre": true,
"author": "admin",
"tablePrefix": "sys_", //"表前缀(生成类名不会包含表前缀,多个用逗号分隔)",
"vuePath": "" //egD:\Work\ZRAdmin-Vue3
"vuePath": "", //egD:\Work\ZRAdmin-Vue3
"csharpTypeArr": {
"string": [ "varchar", "nvarchar", "text", "longtext" ],
"int": [ "int", "integer", "smallint", "int4", "int8", "int2" ],
"long": [ "bigint", "number" ],
"float": [ "numeric", "real", "float" ],
"decimal": [ "money", "decimal", "smallmoney" ],
"dateTime": [ "date", "datetime", "datetime2", "smalldatetime", "timestamp" ],
"byte": [ "tinyint" ],
"bool": [ "bit" ]
}
},
//
"MailOptions": {
@ -71,11 +92,12 @@
//
"FromEmail": "", //egxxxx@qq.com
//
"Password": "123456",
"Password": "",
//
"Smtp": "smtp.qq.com",
"Port": 587,
"Signature": "系统邮件,请勿回复!"
"Signature": "系统邮件,请勿回复!",
"UseSsl": true
},
//redis
"RedisServer": {
@ -90,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",
@ -109,12 +131,7 @@
"Period": "3s",
"Limit": 1
}
],
"IpRateLimitPolicies": {
//ip
"IpRules": [
]
}
]
},
//
"CaptchaOptions": {

View File

@ -1,15 +1,11 @@
using Infrastructure;
using Infrastructure.Attribute;
using Infrastructure.Enums;
using Infrastructure.Model;
using Mapster;
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;
using ${options.BaseNamespace}Common;
$if(replaceDto.ShowBtnImport)
using MiniExcelLibs;
$end
//创建时间:${replaceDto.AddTime}
namespace ${options.ApiControllerNamespace}.Controllers
@ -68,7 +64,7 @@ $end
[ActionPermissionFilter(Permission = "${replaceDto.PermissionPrefix}:query")]
public IActionResult Get${replaceDto.ModelTypeName}(${replaceDto.PKType} ${replaceDto.PKName})
{
var response = _${replaceDto.ModelTypeName}Service.GetFirst(x => x.${replaceDto.PKName} == ${replaceDto.PKName});
var response = _${replaceDto.ModelTypeName}Service.GetInfo(${replaceDto.PKName});
var info = response.Adapt<${replaceDto.ModelTypeName}>();
return SUCCESS(info);
@ -88,7 +84,7 @@ $if(replaceDto.ShowBtnAdd)
var response = _${replaceDto.ModelTypeName}Service.Add${replaceDto.ModelTypeName}(modal);
return ToResponse(response);
return SUCCESS(response);
}
$end
@ -168,6 +164,40 @@ $if(replaceDto.ShowBtnTruncate)
}
$end
$if(replaceDto.ShowBtnImport)
/// <summary>
/// 导入
/// </summary>
/// <param name="formFile"></param>
/// <returns></returns>
[HttpPost("importData")]
[Log(Title = "${genTable.FunctionName}导入", BusinessType = BusinessType.IMPORT, IsSaveRequestData = false)]
[ActionPermissionFilter(Permission = "${replaceDto.PermissionPrefix}:import")]
public IActionResult ImportData([FromForm(Name = "file")] IFormFile formFile)
{
List<${replaceDto.ModelTypeName}Dto> list = new();
using (var stream = formFile.OpenReadStream())
{
list = stream.Query<${replaceDto.ModelTypeName}Dto>(startCell: "A1").ToList();
}
return SUCCESS(_${replaceDto.ModelTypeName}Service.Import${replaceDto.ModelTypeName}(list.Adapt<List<${replaceDto.ModelTypeName}>>()));
}
/// <summary>
/// ${genTable.FunctionName}导入模板下载
/// </summary>
/// <returns></returns>
[HttpGet("importTemplate")]
[Log(Title = "${genTable.FunctionName}模板", BusinessType = BusinessType.EXPORT, IsSaveResponseData = false)]
[AllowAnonymous]
public IActionResult ImportTemplateExcel()
{
var result = DownloadImportTemplate(new List<${replaceDto.ModelTypeName}Dto>() { }, "${replaceDto.ModelTypeName}");
return ExportExcel(result.Item2, result.Item1);
}
$end
$if(showCustomInput)
/// <summary>
/// 保存排序

View File

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
//using ${options.ModelsNamespace}.${options.SubNamespace};
$if(replaceDto.ShowBtnExport)
using MiniExcelLibs.Attributes;
$end
@ -42,6 +39,7 @@ $end
$if(replaceDto.ShowBtnExport)
$if(item.IsExport)
[ExcelColumn(Name = "$if(item.ColumnComment == "")${item.CsharpField}${else}${item.ColumnComment}${end}"$if(item.CsharpType == "DateTime"), Format = "yyyy-MM-dd HH:mm:ss"$end)]
[ExcelColumnName("$if(item.ColumnComment == "")${item.CsharpField}${else}${item.ColumnComment}${end}")]
$else
[ExcelIgnore]
$end
@ -57,14 +55,14 @@ $if(genTable.TplCategory == "subNav" && genTable.SubTable != null)
$if(replaceDto.ShowBtnExport)
[ExcelIgnore]
$end
public ${genTable.SubTable.ClassName} ${genTable.SubTable.ClassName}Nav { get; set; }
public ${genTable.SubTable.ClassName}Dto ${genTable.SubTable.ClassName}Nav { get; set; }
$end
$if(genTable.TplCategory == "subNavMore" && genTable.SubTable != null)
$if(replaceDto.ShowBtnExport)
[ExcelIgnore]
$end
public List<${genTable.SubTable.ClassName}> ${genTable.SubTable.ClassName}Nav { get; set; }
public List<${genTable.SubTable.ClassName}Dto> ${genTable.SubTable.ClassName}Nav { get; set; }
$end
}
}

View File

@ -13,14 +13,20 @@ namespace ${options.IServicsNamespace}.${options.SubNamespace}.I${options.SubNam
{
PagedInfo<${replaceDto.ModelTypeName}Dto> GetList(${replaceDto.ModelTypeName}QueryDto parm);
${replaceDto.ModelTypeName} GetInfo(${replaceDto.PKType} ${replaceDto.PKName});
$if(genTable.TplCategory == "tree")
List<${replaceDto.ModelTypeName}> GetTreeList(${replaceDto.ModelTypeName}QueryDto parm);
$end
int Add${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} parm);
${replaceDto.ModelTypeName} Add${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} parm);
int Update${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} parm);
$if(replaceDto.ShowBtnTruncate)
bool Truncate${replaceDto.ModelTypeName}();
$end
$if(replaceDto.ShowBtnImport)
(string, object, object) Import${replaceDto.ModelTypeName}(List<${replaceDto.ModelTypeName}> list);
$end
}
}

View File

@ -1,7 +1,4 @@
using System;
using SqlSugar;
using System.Collections.Generic;
$if(genTable.TplCategory.Contains("subNav") && genTable.SubTable != null)
$if(genTable.TplCategory.Contains("subNav") && genTable.SubTable != null)
using ${subTableOptions.ModelsNamespace}.${subTableOptions.SubNamespace};
$end
@ -37,7 +34,7 @@ $if(genTable.TplCategory == "tree")
[SugarColumn(IsIgnore = true)]
public List<${replaceDto.ModelTypeName}> Children { get; set; }
$end
$if(genTable.TplCategory == "subNav" && genTable.SubTable != null)
$if((genTable.TplCategory == "subNav" || genTable.TplCategory == "sub") && genTable.SubTable != null)
[Navigate(NavigateType.OneToOne, nameof(${replaceDto.PKName}))] //自定义关系映射
public ${genTable.SubTable.ClassName} ${genTable.SubTable.ClassName}Nav { get; set; }
$end

View File

@ -51,7 +51,7 @@ $end
$end
var response = Queryable()
$if(null != genTable.SubTableName && "" != genTable.SubTableName)
.Includes(x => x.${genTable.SubTable.ClassName}Nav) //填充子对象
//.Includes(x => x.${genTable.SubTable.ClassName}Nav) //填充子对象
$end
$if(genTable.Options.SortField != "" && genTable.Options.SortField != null)
//.OrderBy("${genTable.Options.SortField} ${genTable.Options.SortType}")
@ -90,14 +90,35 @@ $end
}
$end
/// <summary>
/// 获取详情
/// </summary>
/// <param name="${replaceDto.PKName}"></param>
/// <returns></returns>
public ${replaceDto.ModelTypeName} GetInfo(${replaceDto.PKType} ${replaceDto.PKName})
{
var response = Queryable()
$if(null != genTable.SubTableName && "" != genTable.SubTableName)
.Includes(x => x.${genTable.SubTable.ClassName}Nav) //填充子对象
$end
.Where(x => x.${replaceDto.PKName} == ${replaceDto.PKName})
.First();
return response;
}
/// <summary>
/// 添加${genTable.FunctionName}
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public int Add${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} model)
public ${replaceDto.ModelTypeName} Add${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} model)
{
return Add(model, true);
$if(null != genTable.SubTableName && "" != genTable.SubTableName)
return Context.InsertNav(model).Include(s1 => s1.${genTable.SubTable.ClassName}Nav).ExecuteReturnEntity();
$else
return Context.Insertable(model).ExecuteReturnEntity();
$end
}
/// <summary>
@ -116,7 +137,11 @@ $end
${end}
//});
//return response;
$if(null != genTable.SubTableName && "" != genTable.SubTableName)
return Context.UpdateNav(model).Include(z1 => z1.${genTable.SubTable.ClassName}Nav).ExecuteCommand() ? 1 : 0;
$else
return Update(model, true);
$end
}
$if(replaceDto.ShowBtnTruncate)
/// <summary>
@ -134,5 +159,40 @@ $if(replaceDto.ShowBtnTruncate)
return Truncate();
}
$end
$if(replaceDto.ShowBtnImport)
/// <summary>
/// 导入${genTable.FunctionName}
/// </summary>
/// <returns></returns>
public (string, object, object) Import${replaceDto.ModelTypeName}(List<${replaceDto.ModelTypeName}> list)
{
var x = Context.Storageable(list)
.SplitInsert(it => !it.Any())
$foreach(column in genTable.Columns)
$if(column.IsRequired && column.IsIncrement == false)
.SplitError(x => x.Item.${column.CsharpField}.IsEmpty(), "${column.ColumnComment}不能为空")
$end
$end
//.WhereColumns(it => it.UserName)//如果不是主键可以这样实现多字段it=>new{it.x1,it.x2}
.ToStorage();
var result = x.AsInsertable.ExecuteCommand();//插入可插入部分;
string msg = $"插入{x.InsertList.Count} 更新{x.UpdateList.Count} 错误数据{x.ErrorList.Count} 不计算数据{x.IgnoreList.Count} 删除数据{x.DeleteList.Count} 总共{x.TotalList.Count}";
Console.WriteLine(msg);
//输出错误信息
foreach (var item in x.ErrorList)
{
Console.WriteLine("错误" + item.StorageMessage);
}
foreach (var item in x.IgnoreList)
{
Console.WriteLine("忽略" + item.StorageMessage);
}
return (msg, x.ErrorList, x.IgnoreList);
}
$end
}
}

View File

@ -0,0 +1,78 @@
import request from '@/utils/request'
/**
* ${genTable.functionName}分页查询
* @param {查询条件} data
*/
export function list${genTable.BusinessName}(query) {
return request({
url: '/${genTable.ModuleName}/${genTable.BusinessName}/list',
method: 'get',
data: query,
})
}
$if(replaceDto.ShowBtnAdd)
/**
* 新增${genTable.functionName}
* @param data
*/
export function add${genTable.BusinessName}(data) {
return request({
url: '/${genTable.ModuleName}/${genTable.BusinessName}',
method: 'post',
data: data,
})
}
$end
$if(replaceDto.ShowBtnEdit)
/**
* 修改${genTable.functionName}
* @param data
*/
export function update${genTable.BusinessName}(data) {
return request({
url: '/${genTable.ModuleName}/${genTable.BusinessName}',
method: 'PUT',
data: data,
})
}
$end
/**
* 获取${genTable.functionName}详情
* @param {Id}
*/
export function get${genTable.BusinessName}(id) {
return request({
url: '/${genTable.ModuleName}/${genTable.BusinessName}/' + id,
method: 'get'
})
}
$if(replaceDto.ShowBtnDelete || replaceDto.ShowBtnMultiDel)
/**
* 删除${genTable.functionName}
* @param {主键} pid
*/
export function del${genTable.BusinessName}(pid) {
return request({
url: '/${genTable.ModuleName}/${genTable.BusinessName}/' + pid,
method: 'delete'
})
}
$end
$if(replaceDto.ShowBtnTruncate)
// 清空${genTable.functionName}
export function clear${genTable.BusinessName}() {
return request({
url: '/${genTable.ModuleName}/${genTable.BusinessName}/clean',
method: 'delete'
})
}
$end
$if(replaceDto.ShowBtnExport)
// 导出${genTable.functionName}
export async function export${genTable.BusinessName}(query) {
await downFile('$/{genTable.ModuleName}/${genTable.BusinessName}/export', { ...query })
}
$end

View File

@ -0,0 +1,210 @@
<template>
<view class="edit-form">
<u--form labelPosition="left" :model="form" labelWidth="90px" :rules="rules" ref="uForm">
$foreach(column in genTable.Columns)
$set(columnName = column.CsharpFieldFl)
$set(value = "item.value")
$set(number = "")
$set(labelName = column.ColumnComment)
$if(column.CsharpType == "int" || column.CsharpType == "long")
$set(value = "parseInt(item.value)")
$set(number = ".number")
$end
$if(column.IsPK || column.IsIncrement)
$if(column.IsPK && column.IsIncrement == false)
<u-form-item label="${labelName}" prop="${columnName}">
<u--input type="number" v-model.number="form.${columnName}" placeholder="请输入${labelName}" :disabled="opertype != 1"></u--input>
</u-form-item>
$else
<u-form-item label="${labelName}" prop="${columnName}" v-if="opertype != 1">
<u--input type="number" v-model.number="form.${columnName}" placeholder="请输入${labelName}" :disabled="true"/>
</u-form-item>
$end
$else
$if(column.HtmlType == "radio" || column.HtmlType == "selectRadio")
<u-form-item label="${labelName}" prop="${columnName}">
<u-radio-group v-model="form.${columnName}">
<u-radio v-for="item in ${if(column.DictType != "")}dict.type.${column.DictType}${else}${column.CsharpFieldFl}Options$end" :name="${value}" class="margin-right-xl">{{item.label}}</u-radio>
</u-radio-group>
</u-form-item>
$elseif(column.HtmlType == "checkbox")
<u-form-item label="${labelName}" prop="${columnName}">
<view class="">
<u-checkbox-group v-model="form.${columnName}Checked">
<u-checkbox :customStyle="{marginRight: '20px', marginBottom: '15px'}" v-for="(item, index) in ${if(column.DictType != "")}dict.type.${column.DictType}${else}${column.CsharpFieldFl}Options$end" :key="index"
:label="item.label" :name="${value}">
</u-checkbox>
</u-checkbox-group>
</view>
</u-form-item>
$elseif(column.HtmlType == "inputNumber" || column.HtmlType == "customInput")
<u-form-item label="${labelName}" prop="${columnName}">
<u-number-box v-model="form.${columnName}"></u-number-box>
</u-form-item>
$elseif(column.HtmlType == "datetime" || column.HtmlType == "month")
<u-form-item label="${labelName}" prop="${columnName}">
<uni-datetime-picker v-model="form.${columnName}" />
</u-form-item>
$elseif(column.HtmlType == "textarea")
<u-form-item label="${labelName}" prop="${columnName}">
<u--textarea v-model="form.${columnName}" placeholder="请输入内容" count ></u--textarea>
</u-form-item>
$elseif(column.HtmlType == "imageUpload" || column.HtmlType == "fileUpload")
<u-form-item label="${labelName}" prop="${columnName}">
<uploadImage v-model="form.${columnName}"></uploadImage>
</u-form-item>
$elseif(column.HtmlType == "select" || column.HtmlType == "selectMulti")
<u-form-item label="${labelName}" prop="${columnName}">
<uni-data-select v-model="form.${columnName}" :clear="true" :localdata="${if(column.DictType != "")}dict.type.${column.DictType}${else}${column.CsharpFieldFl}Options$end"
format="{label} - {value}"></uni-data-select>
</u-form-item>
$else
<u-form-item label="${labelName}" prop="${columnName}">
<u--input v-model${number}="form.${columnName}" placeholder="请输入${labelName}" ${column.DisabledStr}/>
</u-form-item>
$end
$end
$end
</u--form>
<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>
</template>
<script>
import "@/static/scss/page.scss";
import {
get${genTable.BusinessName},
$if(replaceDto.ShowBtnAdd)
add${genTable.BusinessName},
$end
$if(replaceDto.ShowBtnEdit)
update${genTable.BusinessName},
$end
}
from '@/api/${tool.FirstLowerCase(genTable.ModuleName)}/${genTable.BusinessName.ToLower()}.js'
export default {
$if(replaceDto.UploadFile == 1)
components: {
UploadImage
},
$end
dicts: [
$foreach(item in genTable.Columns)
$if((item.HtmlType == "radio" || item.HtmlType.Contains("select") || item.HtmlType == "checkbox") && item.DictType != "")
"${item.DictType}",
$end
$end
],
data() {
return {
form: {},
rules: {
$foreach(column in genTable.Columns)
$if(column.IsRequired && column.IsIncrement == false)
${column.CsharpFieldFl}: [{
required: true,
message: "${column.ColumnComment}不能为空",
trigger: [ 'change', 'blur' ],
$if(column.CsharpType == "int" || column.CsharpType == "long") type: "number"$end
}],
$end
$end
},
opertype: 0,
$foreach(item in genTable.Columns)
$if((item.HtmlType == "radio" || item.HtmlType == "select" || item.HtmlType == "checkbox") && item.DictType == "")
// ${item.ColumnComment}选项列表 格式 eg:{ label: '标签', value: '0'}
${item.CsharpFieldFl}Options: [],
$end
$end
}
},
onReady() {
// 需要在onReady中设置规则
setTimeout(() => {
this.${refs}refs.uForm.setRules(this.rules)
}, 300)
},
onLoad(e) {
this.opertype = e.opertype
if (e.id) {
get${genTable.BusinessName}(e.id).then(res => {
const {
code,
data
} = res
if (code == 200) {
this.form = {
...data,
$foreach(item in genTable.Columns)
$if(item.HtmlType == "checkbox")
${item.CsharpFieldFl}Checked: data.${item.CsharpFieldFl} ? data.${item.CsharpFieldFl}.split(',') : [],
$end
$end
}
}
})
} else {
this.reset()
}
},
methods: {
reset(){
this.form = {
$foreach(item in genTable.Columns)
$if((item.HtmlType == "checkbox"))
${item.CsharpFieldFl}Checked: [],
$else
$item.CsharpFieldFl: undefined,
$end
$end
};
},
submit() {
this.${refs}refs.uForm.validate().then(res => {
this.${modal}modal.msg('表单校验通过')
$foreach(item in genTable.Columns)
$if(item.HtmlType == "checkbox")
this.form.${item.CsharpFieldFl} = this.form.${item.CsharpFieldFl}Checked.toString();
$end
$end
if (this.form.${replaceDto.FistLowerPk} != undefined && this.opertype == 2) {
update${genTable.BusinessName}(this.form).then((res) => {
this.${modal}modal.msgSuccess("修改成功")
})
} else {
add${genTable.BusinessName}(this.form).then((res) => {
this.${modal}modal.msgSuccess("新增成功")
})
}
}).catch(errors => {
this.${modal}modal.msg('表单校验失败')
})
},
handleCancel() {
uni.navigateBack()
}
}
}
</script>
<style scoped>
.btn-wrap {
margin: 150rpx auto 0 auto;
width: 80%
}
</style>

View File

@ -0,0 +1,288 @@
<template>
<view class="container">
<view class="search-bar">
$if(replaceDto.ShowBtnAdd)
<u-button type="primary" size="small" shape="circle" icon="plus" v-if="checkPermi(['${replaceDto.PermissionPrefix}:add'])"
@click="handleAdd" :customStyle="{'width': '80px', 'margin': '10px'}">新增</u-button>
$end
<u-search :disabled="true" placeholder="请输入要搜索的内容" @click="show=true"></u-search>
</view>
<u-line dashed></u-line>
<view class="info-item" v-for="(item,index) in dataList" :key="index">
$foreach(column in genTable.Columns)
$set(labelName = column.ColumnComment)
$set(columnName = column.CsharpFieldFl)
$if(column.IsList == true)
$if(column.HtmlType == "imageUpload")
<view class="info-line">
<text class="label-name">${labelName}</text>
<imagePreview :src="item.${columnName}"></imagePreview>
</view>
$elseif(column.HtmlType == "checkbox" || column.HtmlType == "select" || column.HtmlType == "radio")
<view class="info-line">
<text class="label-name">${labelName}</text>
$if(column.HtmlType == "checkbox")
<dict-tag :options="$if(column.DictType != "") dict.type.${column.DictType} $else ${column.CsharpFieldFl}Options$end" :value="item.${columnName} ? item.${columnName}.split(',') : []" />
$else
<dict-tag :options="$if(column.DictType != "") dict.type.${column.DictType} $else ${column.CsharpFieldFl}Options$end" :value="item.${columnName}" />
$end
</view>
$else
<view class="info-line">
<text class="label-name">${labelName}</text>
<text>{{item.${columnName}}}</text>
</view>
$end
$end
$end
<view class="info-btn-wrap justify-end">
$if(replaceDto.ShowBtnView)
<view class="tag-item">
<u-tag text="详情" plain shape="circle" type="info" icon="eye" @click="handleView(item)"
v-if="checkPermi(['${replaceDto.PermissionPrefix}:query'])"></u-tag>
</view>
$end
$if(replaceDto.ShowBtnEdit)
<view class="tag-item">
<u-tag text="编辑" plain shape="circle" icon="edit-pen" @click="handleEdit(item)"
v-if="checkPermi(['${replaceDto.PermissionPrefix}:edit'])"></u-tag>
</view>
$end
$if(replaceDto.ShowBtnDelete || replaceDto.ShowBtnMultiDel)
<view class="tag-item">
<u-tag text="删除" class="tag-item" plain shape="circle" type="error" icon="trash"
v-if="checkPermi(['userinfo:delete'])" @click="handleDelete(item)"></u-tag>
</view>
$end
</view>
<u-line dashed></u-line>
</view>
<view class="page-footer">
<u-empty mode="list" :marginTop="140" v-if="total == 0"></u-empty>
<uni-pagination v-else show-icon="true" :total="total" :pageSize="queryParams.pageSize"
:current="queryParams.pageNum" @change="getData"></uni-pagination>
<view class="text-grey padding text-center"> 共 {{ total }}条数据 </view>
</view>
<u-popup :show="show" mode="bottom" @close="show = false" @open="show = true">
<view class="search-form">
<view class="search-title">搜索</view>
<u--form labelPosition="left" :model="queryParams" labelWidth="100px" ref="uForm">
<u-form-item label="排序字段" prop="sort" borderBottom>
<uni-data-select v-model="queryParams.sort" :clear="true" :localdata="sortOptions"
format="{label}"></uni-data-select>
</u-form-item>
<u-form-item label="排序方式" prop="sortType" borderBottom ref="item1">
<u-radio-group v-model="queryParams.sortType">
<u-radio label="升序" name="asc" :customStyle="{marginRight: '20px'}"></u-radio>
<u-radio label="倒序" name="desc"></u-radio>
</u-radio-group>
</u-form-item>
<u-gap height="30"></u-gap>
$foreach(column in genTable.Columns)
$set(labelName = "")
$set(columnName = "")
$if(column.IsQuery == true)
$set(columnName = column.CsharpFieldFl)
$if(column.ColumnComment != "")
$set(labelName = column.ColumnComment)
$else
$set(labelName = column.CsharpFieldFl)
$end
$if(column.HtmlType == "datetime")
<u-form-item label="时间查询">
<uni-datetime-picker :border="false" v-model="dateRange${column.CsharpField}" type="daterange" @change="${column.CsharpFieldFl}Select" />
</u-form-item>
$elseif(column.HtmlType == "radio" || column.HtmlType == "select")
<u-form-item label="${labelName}" prop="${column.CsharpFieldFl}" borderBottom ref="item2">
<view class="tag-wrap">
<view class="tag-item">
<u-tag text="全部" :plain="null != queryParams.${column.CsharpFieldFl}" name="" @click="queryParams.${column.CsharpFieldFl} = null">
</u-tag>
</view>
<view class="tag-item" v-for="(item, index) in $if(column.DictType != "") dict.type.${column.DictType} $else ${column.CsharpFieldFl}Options$end" :key="index">
<u-tag :text="item.label" :plain="item.value != queryParams.${column.CsharpFieldFl}" :name="item.value"
@click="${column.CsharpFieldFl}Select(item)">
</u-tag>
</view>
</view>
</u-form-item>
$else
<u-form-item label="${labelName}" prop="${column.CsharpFieldFl}" borderBottom ref="item1">
<u--input v-model="queryParams.${column.CsharpFieldFl}" border="none" placeholder="请输入${labelName}"></u--input>
</u-form-item>
$end
$end
$end
</u--form>
<view class="btn-group">
<u-button text="重置" icon="reload" :customStyle="{marginRight: '10px'}" shape="circle" type="success"
@click="resetQuery"></u-button>
<u-button text="搜索" icon="search" shape="circle" type="primary" @click="handleQuery"></u-button>
</view>
</view>
</u-popup>
<u-back-top :scroll-top="scrollTop" :bottom="180"></u-back-top>
</view>
</template>
<script>
import {
checkPermi
} from '@/utils/permission.js'
import "@/static/scss/page.scss";
import {
list${genTable.BusinessName},
$if(replaceDto.ShowBtnDelete)
del${genTable.BusinessName},
$end
}
from '@/api/${tool.FirstLowerCase(genTable.ModuleName)}/${genTable.BusinessName.ToLower()}.js'
import {
getDate
} from '@/utils/common.js'
export default {
dicts: [
$foreach(item in genTable.Columns)
$if((item.HtmlType == "radio" || item.HtmlType.Contains("select") || item.HtmlType == "checkbox") && item.DictType != "")
"${item.DictType}",
$end
$end
],
data() {
return {
scrollTop: 0,
dataList: [],
queryParams: {
pageNum: 1,
pageSize: 20,
sortType: 'desc',
sort: undefined,
$foreach(item in genTable.Columns)
$if(item.IsQuery == true)
${item.CsharpFieldFl}: undefined,
$end
$end
},
total: 0,
show: false,
loading: false,
$foreach(item in genTable.Columns)
$if((item.HtmlType == "radio" || item.HtmlType == "select" || item.HtmlType == "checkbox") && item.DictType == "")
// ${item.ColumnComment}选项列表 格式 eg:{ label: '标签', value: '0'}
${item.CsharpFieldFl}Options: [],
$elseif(item.HtmlType == "datetime" && item.IsQuery == true)
//${item.ColumnComment}时间范围
dateRange${item.CsharpField}: [],
$end
$end
// 排序集合 格式 eg:{ label: '名称', value: 'userId'}
sortOptions: [
$foreach(column in genTable.Columns)
$if(column.IsSort)
{
label: '${column.ColumnComment}',
value: '${column.CsharpFieldFl}'
},
$end
$end
]
}
},
onLoad() {
this.getList()
},
methods: {
checkPermi,
getList() {
uni.showLoading({
title: 'loading...'
});
$foreach(item in genTable.Columns)
$if(item.HtmlType == "datetime" && item.IsQuery == true)
this.addDateRange(this.queryParams, this.dateRange${item.CsharpField}, '${item.CsharpField}');
$end
$end
list${genTable.BusinessName}(this.queryParams).then(res => {
if (res.code == 200) {
this.dataList = [...this.dataList, ...res.data.result]
this.total = res.data.totalNum;
}
})
},
$if(replaceDto.ShowBtnAdd)
handleAdd() {
this.${tab}tab.navigateTo('./edit?opertype=1')
},
$end
$if(replaceDto.ShowBtnEdit)
handleEdit(e) {
this.${tab}tab.navigateTo('./edit?opertype=2&${replaceDto.FistLowerPk}=' + e.id)
},
$end
$if(replaceDto.ShowBtnView)
handleView(e) {
this.${tab}tab.navigateTo('./edit?opertype=3&${replaceDto.FistLowerPk}=' + e.id)
},
$end
$if(replaceDto.ShowBtnDelete || replaceDto.ShowBtnMultiDel)
handleDelete(row) {
const Ids = row.${replaceDto.FistLowerPk};
this.${modal}modal.confirm('你确定要删除吗?').then(() => {
return del${genTable.BusinessName}(Ids);
})
.then(() => {
this.handleQuery();
this.${modal}modal.msgSuccess("删除成功");
})
.catch(() => {});
},
$end
resetQuery() {
this.${refs}refs.uForm.resetFields()
$foreach(column in genTable.Columns)
$if(column.HtmlType == "datetime" && column.IsQuery == true)
this.dateRange${column.CsharpField} = []
$end
$end
},
handleQuery() {
this.queryParams.pageNum = 1;
this.dataList = []
uni.startPullDownRefresh();
this.getList()
this.show = false
},
getData(e) {
this.queryParams.pageNum = e.current
this.getList()
},
onPullDownRefresh() {
uni.stopPullDownRefresh()
this.handleQuery()
},
$foreach(column in genTable.Columns)
$if(column.IsQuery == true)
$if(column.HtmlType == "datetime")
${column.CsharpFieldFl}Select(e) {
this.${column.CsharpFieldFl} = e
},
$elseif(column.HtmlType == "radio" || column.HtmlType == "select")
${column.CsharpFieldFl}Select(e) {
this.queryParams.${column.CsharpFieldFl} = e.value
},
$end
$end
$end
onPageScroll(e) {
this.scrollTop = e.scrollTop;
}
}
}
</script>

View File

@ -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

View File

@ -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">
@ -108,6 +109,25 @@ $if(replaceDto.ShowBtnTruncate)
</el-button>
</el-col>
$end
$if(replaceDto.ShowBtnImport)
<el-col :span="1.5">
<el-dropdown trigger="click" v-hasPermi="['${replaceDto.PermissionPrefix}:import']">
<el-button type="primary" plain icon="Upload">
{{ ${t}t('btn.import') }}<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="upload">
<importData
templateUrl="${genTable.ModuleName}/${genTable.BusinessName}/importTemplate"
importUrl="/${genTable.ModuleName}/${genTable.BusinessName}/importData"
@success="handleFileSuccess"></importData>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
$end
$if(replaceDto.ShowBtnExport)
<el-col :span="1.5">
<el-button type="warning" plain icon="download" @click="handleExport" v-hasPermi="['${replaceDto.PermissionPrefix}:export']">
@ -118,12 +138,12 @@ $end
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<!-- 数据区域 -->
<el-table
:data="dataList"
v-loading="loading"
ref="table"
border
header-cell-class-name="el-table-header-cell"
highlight-current-row
@sort-change="sortChange"
$if(replaceDto.ShowBtnMultiDel)
@ -133,27 +153,10 @@ $end
$if(replaceDto.ShowBtnMultiDel)
<el-table-column type="selection" width="50" align="center"/>
$end
$if(null != genTable.SubTableName && "" != genTable.SubTableName && genTable.TplCategory == "subNav")
<el-table-column type="expand">
<template #default="props">
<el-descriptions border>
$foreach(subColumn in genSubTable.Columns)
<el-descriptions-item label="${subColumn.ColumnComment}">{{ props.row.$tool.FirstLowerCase(genTable.SubTable.ClassName)Nav.${subColumn.CsharpFieldFl} }}</el-descriptions-item>
$end
</el-descriptions>
</template>
</el-table-column>
$end
$if(null != genTable.SubTableName && "" != genTable.SubTableName && genTable.TplCategory == "subNavMore")
<el-table-column type="expand">
<template #default="props">
<el-table :data="props.row.$tool.FirstLowerCase(genTable.SubTable.ClassName)Nav" header-row-class-name="text-navy">
$foreach(subColumn in genSubTable.Columns)
$if(subColumn.IsList == true)
<el-table-column prop="${subColumn.CsharpFieldFl}" label="${subColumn.ColumnComment}" align="center"/>
$end
$end
</el-table>
$if(sub)
<el-table-column align="center" width="90">
<template #default="scope">
<el-button text @click="rowClick(scope.row)">{{ ${t}t('btn.details') }}</el-button>
</template>
</el-table-column>
$end
@ -185,11 +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">
$if(column.HtmlType == "checkbox")
<dict-tag :options="$if(column.DictType != "") options.${column.DictType} $else options.${column.CsharpFieldFl}Options$end" :value="scope.row.${columnName} ? scope.row.${columnName}.split(',') : []" />
$else
<dict-tag :options="$if(column.DictType != "") options.${column.DictType} $else options.${column.CsharpFieldFl}Options$end" :value="scope.row.${columnName}" />
$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")
@ -199,24 +198,61 @@ $else
$end
$end
$end
<el-table-column label="操作" align="center" width="160">
<el-table-column label="操作" width="160">
<template #default="scope">
$if(replaceDto.OperBtnStyle == 2)
<el-button-group>
$if(replaceDto.ShowBtnView)
<el-button type="primary" icon="view" @click="handlePreview(scope.row)"></el-button>
<el-button text type="primary" icon="view" @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 text v-hasPermi="['${replaceDto.PermissionPrefix}:edit']" type="success" icon="edit" title="编辑" @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 text v-hasPermi="['${replaceDto.PermissionPrefix}:delete']" type="danger" icon="delete" title="删除" @click="handleDelete(scope.row)"></el-button>
$end
</el-button-group>
$else
$if(replaceDto.ShowBtnView)
<el-button type="primary" size="small" icon="view" title="详情" @click="handlePreview(scope.row)"></el-button>
$end
$if(replaceDto.ShowBtnEdit)
<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 type="danger" size="small" icon="delete" title="删除" v-hasPermi="['${replaceDto.PermissionPrefix}:delete']" @click="handleDelete(scope.row)"></el-button>
$end
$end
</template>
</el-table-column>
</el-table>
<pagination class="mt10" background :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
$* 一对一/一对多列表显示详情 *$
$if(sub)
<el-drawer v-model="drawer" :with-header="false" direction="btt">
<el-table :data="$tool.FirstLowerCase(genTable.SubTable.ClassName)List" header-row-class-name="text-navy">
<el-table-column label="序号" type="index" width="80" />
$foreach(column in genSubTable.Columns)
$set(columnName = column.CsharpFieldFl)
$if(column.IsList == true)
$if(column.HtmlType == "checkbox" || column.HtmlType.Contains("select") || column.HtmlType == "radio")
<el-table-column prop="${columnName}" label="${column.ColumnComment}">
<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 />
</template>
</el-table-column>
$else
<el-table-column prop="${column.CsharpFieldFl}" label="${column.ColumnComment}"/>
$end
$end
$end
</el-table>
</el-drawer>
$end
<!-- 添加或修改${genTable.functionName}对话框 -->
<el-dialog :title="title" :lock-scroll="false" v-model="open" >
<el-dialog :title="title" :lock-scroll="false" v-model="open" ${if(sub)}:fullscreen="fullScreen"$end>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
$foreach(column in genTable.Columns)
@ -227,6 +263,7 @@ $set(labelName = column.ColumnComment)
$if(column.CsharpType == "int" || column.CsharpType == "long")
$set(value = "parseInt(item.dictValue)")
$set(number = ".number")
$set(switchType = ":active-value='1' :inactive-value='0'")
$end
$if(column.IsPK || column.IsIncrement)
@ -272,7 +309,9 @@ $elseif(column.HtmlType == "radio" || column.HtmlType == "selectRadio")
<el-col :lg="${options.ColNum}">
<el-form-item label="${labelName}" prop="${columnName}">
<el-radio-group v-model="form.${columnName}"${column.DisabledStr}>
<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>
<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>
</el-radio-group>
</el-form-item>
</el-col>
@ -288,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"
@ -316,6 +361,12 @@ $elseif(column.HtmlType == "colorPicker")
<el-color-picker v-model="form.${columnName}" />
</el-form-item>
</el-col>
$elseif(column.HtmlType == "switch")
<el-col :lg="12">
<el-form-item label="${labelName}" prop="${columnName}">
<el-switch v-model="form.${columnName}" ${switchType} />
</el-form-item>
</el-col>
$else
<el-col :lg="${options.ColNum}">
<el-form-item label="${labelName}" prop="${columnName}">
@ -326,10 +377,81 @@ $end
$end
$end
</el-row>
$* 子表信息 *$
$if(sub)
<el-divider content-position="center">${genTable.SubTable.FunctionName}信息</el-divider>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="Plus" @click="handleAdd${genTable.SubTable.ClassName}">添加</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" icon="Delete" @click="handleDelete${genTable.SubTable.ClassName}">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" icon="FullScreen" @click="fullScreen = !fullScreen">{{ fullScreen ? '退出全屏' : '全屏' }}</el-button>
</el-col>
</el-row>
<el-table :data="${tool.FirstLowerCase(genTable.SubTable.ClassName)}List" :row-class-name="row${genTable.SubTable.ClassName}Index" @selection-change="handle${genTable.SubTable.ClassName}SelectionChange" ref="${genTable.SubTable.ClassName}Ref">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" align="center" prop="index" width="50"/>
$foreach(column in genTable.SubTable.Columns)
$set(labelName = column.ColumnComment)
$set(columnName = column.CsharpFieldFl)
$set(value = "item.dictValue")
$if(column.CsharpType == "int" || column.CsharpType == "long")
$set(value = "parseInt(item.dictValue)")
$set(number = ".number")
$set(switchType = ":active-value='1' :inactive-value='0'")
$end
$if(column.IsList == true)
$if(column.IsPk || column.CsharpField == genTable.SubTableFkName)
$elseif(column.HtmlType == "inputNumber" || column.HtmlType == "customInput")
<el-table-column label="${labelName}" align="center" prop="${columnName}" width="140">
<template #default="scope">
<el-input-number v-model="scope.row.${columnName}" controls-position="right" placeholder="请输入${labelName}" />
</template>
</el-table-column>
$elseif(column.HtmlType == "datetime" || column.HtmlType == "month")
<el-table-column label="${labelName}" align="center" prop="${columnName}">
<template #default="scope">
<el-date-picker clearable v-model="scope.row.${columnName}" type="date" placeholder="选择日期时间"></el-date-picker>
</template>
</el-table-column>
$elseif(column.HtmlType == "radio" || column.HtmlType == "selectRadio" || column.HtmlType == "select" || column.HtmlType == "selectMulti")
<el-table-column label="${labelName}" prop="${columnName}">
<template #default="scope">
<el-select v-model="scope.row.${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>
</el-select>
</template>
</el-table-column>
$elseif(column.HtmlType == "switch")
<el-table-column label="${labelName}" prop="${columnName}">
<template #default="scope">
<el-switch v-model="scope.row.${columnName}" ${switchType} />
</template>
</el-table-column>
$else
<el-table-column label="${labelName}" align="center" prop="${columnName}">
<template #default="scope">
<el-input v-model="scope.row.${columnName}" placeholder="请输入${labelName}" />
</template>
</el-table-column>
$end
$end
$end
</el-table>
$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>
@ -347,7 +469,9 @@ from '@/api/${tool.FirstLowerCase(genTable.ModuleName)}/${genTable.BusinessName.
$if(replaceDto.ShowEditor == 1)
import Editor from '@/components/Editor'
$end
$if(replaceDto.ShowBtnImport)
import importData from '@/components/ImportData'
$end
const { proxy } = getCurrentInstance()
const ids = ref([])
const loading = ref(false)
@ -419,8 +543,8 @@ $end
$set(index = 0)
var dictParams = [
$foreach(item in genTable.Columns)
$if((item.HtmlType == "radio" || item.HtmlType.Contains("select") || item.HtmlType == "checkbox") && item.DictType != "")
$foreach(item in dicts)
$if(item.DictType != "")
{ dictType: "${item.DictType}" },
$set(index = index + 1)
$end
@ -518,7 +642,7 @@ $end
$end
},
options: {
$foreach(column in genTable.Columns)
$foreach(column in dicts)
$if(column.HtmlType == "radio" || column.HtmlType.Contains("select") || column.HtmlType == "checkbox")
//$if(column.ColumnComment != "") ${column.ColumnComment} $else ${column.CsharpFieldFl}$end选项列表 格式 eg:{ dictLabel: '标签', dictValue: '0'}
$if(column.DictType != "")${column.DictType}$else${column.CsharpFieldFl}Options$end: [],
@ -539,24 +663,28 @@ 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: undefined,
$item.CsharpFieldFl: null,
$end
$end
};
$if(sub)
${tool.FirstLowerCase(genTable.SubTable.ClassName)}List.value = []
$end
proxy.resetForm("formRef")
}
$if(replaceDto.ShowBtnAdd)
// 添加按钮操作
function handleAdd() {
reset();
open.value = true
title.value = '添加'
title.value = '添加${genTable.functionName}'
opertype.value = 1
}
$end
$if(replaceDto.ShowBtnEdit)
// 修改按钮操作
function handleUpdate(row) {
reset()
@ -565,49 +693,60 @@ function handleUpdate(row) {
const { code, data } = res
if (code == 200) {
open.value = true
title.value = "修改数据"
title.value = '修改${genTable.functionName}'
opertype.value = 2
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
}
$if(sub)
${tool.FirstLowerCase(genTable.SubTable.ClassName)}List.value = res.data.${tool.FirstLowerCase(genTable.SubTable.ClassName)}Nav
$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
$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
@ -621,8 +760,8 @@ function handleDelete(row) {
getList()
proxy.${modal}modal.msgSuccess("删除成功")
})
.catch(() => {})
}
$end
$if(replaceDto.ShowBtnTruncate)
// 清空
@ -653,7 +792,22 @@ function handlePreview(row) {
open.value = true
title.value = '查看'
opertype.value = 3
form.value = row
form.value = { ...row }
}
$end
$if(replaceDto.ShowBtnImport)
// 导入数据成功处理
const handleFileSuccess = (response) => {
const { item1, item2 } = response.data
var error = ''
item2.forEach((item) => {
error += item.storageMessage + ','
})
proxy.${alert}alert(item1 + '<p>' + error + '</p>', '导入结果', {
dangerouslyUseHTMLString: true
})
getList()
}
$end
@ -672,5 +826,61 @@ function handleExport() {
}
$end
$if(sub)
/*********************${genTable.SubTable.FunctionName}子表信息*************************/
const ${tool.FirstLowerCase(genTable.SubTable.ClassName)}List = ref([])
const checked${genTable.SubTable.ClassName} = ref([])
const fullScreen = ref(false)
const drawer = ref(false)
/** ${genTable.SubTable.FunctionName}序号 */
function row${genTable.SubTable.ClassName}Index({ row, rowIndex }) {
row.index = rowIndex + 1;
}
/** ${genTable.SubTable.FunctionName}添加按钮操作 */
function handleAdd${genTable.SubTable.ClassName}() {
let obj = {};
$foreach(column in genTable.SubTable.Columns)
$if(column.IsPK || column.CsharpField == genTable.SubTableFkName)
$elseif(column.IsList == true && "" != column.CsharpField)
//obj.${column.CsharpFieldFl} = null;
$end
$end
${tool.FirstLowerCase(genTable.SubTable.ClassName)}List.value.push(obj);
}
/** 复选框选中数据 */
function handle${genTable.SubTable.ClassName}SelectionChange(selection) {
checked${genTable.SubTable.ClassName}.value = selection.map(item => item.index)
}
/** ${genTable.SubTable.FunctionName}删除按钮操作 */
function handleDelete${genTable.SubTable.ClassName}() {
if(checked${genTable.SubTable.ClassName}.value.length == 0){
proxy.${modal}modal.msgError('请先选择要删除的${genTable.SubTable.FunctionName}数据')
} else {
const ${genTable.SubTable.ClassName}s = ${tool.FirstLowerCase(genTable.SubTable.ClassName)}List.value;
const checked${genTable.SubTable.ClassName}s = checked${genTable.SubTable.ClassName}.value;
${tool.FirstLowerCase(genTable.SubTable.ClassName)}List.value = ${genTable.SubTable.ClassName}s.filter(function(item) {
return checked${genTable.SubTable.ClassName}s.indexOf(item.index) == -1
});
}
}
/** ${genTable.SubTable.FunctionName}详情 */
function rowClick(row) {
const id = row.${replaceDto.FistLowerPk} || ids.value
get${genTable.BusinessName}(id).then((res) => {
const { code, data } = res
if (code == 200) {
drawer.value = true
${tool.FirstLowerCase(genTable.SubTable.ClassName)}List.value = data.${tool.FirstLowerCase(genTable.SubTable.ClassName)}Nav
}
})
}
$end
handleQuery()
</script>

Binary file not shown.

Binary file not shown.

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
@ -26,11 +27,11 @@ namespace ZR.CodeGenerator
/// <param name="dto"></param>
public static void Generate(GenerateDto dto)
{
var vuePath = AppSettings.GetConfig("gen:vuePath");
var genOptions = AppSettings.Get<Gen>("gen");
dto.VueParentPath = dto.VueVersion == 3 ? "ZRAdmin-vue" : "ZR.Vue";
if (!vuePath.IsEmpty())
if (!genOptions.VuePath.IsEmpty())
{
dto.VueParentPath = vuePath;
dto.VueParentPath = genOptions.VuePath;
}
dto.GenOptions = GenerateOption(dto.GenTable);
if (dto.GenTable.SubTable != null)
@ -50,7 +51,9 @@ namespace ZR.CodeGenerator
ShowBtnView = dto.GenTable.Options.CheckedBtn.Any(f => f == 5),
ShowBtnTruncate = dto.GenTable.Options.CheckedBtn.Any(f => f == 6),
ShowBtnMultiDel = dto.GenTable.Options.CheckedBtn.Any(f => f == 7),
ViewFileName = dto.GenTable.BusinessName.FirstUpperCase()
ShowBtnImport = dto.GenTable.Options.CheckedBtn.Any(f => f == 8),
ViewFileName = dto.GenTable.BusinessName.FirstUpperCase(),
OperBtnStyle = dto.GenTable.Options.OperBtnStyle
};
var columns = dto.GenTable.Columns;
@ -80,14 +83,13 @@ namespace ZR.CodeGenerator
}
GenerateVueJs(dto);
GenerateSql(dto);
dto.ReplaceDto = replaceDto;
if (dto.IsPreview) return;
foreach (var item in dto.GenCodes)
if (genOptions.ShowApp)
{
item.Path = Path.Combine(dto.GenCodePath, item.Path);
FileUtil.WriteAndSave(item.Path, item.Content);
GenerateAppVueViews(replaceDto, dto);
GenerateAppVueFormViews(replaceDto, dto);
GenerateAppJs(dto);
}
dto.ReplaceDto = replaceDto;
}
private static CodeGenerateOption GenerateOption(GenTable genTable)
@ -284,6 +286,52 @@ namespace ZR.CodeGenerator
}
#endregion
#region app页面
/// <summary>
/// 列表页面
/// </summary>
/// <param name="generateDto"></param>
private static void GenerateAppVueViews(ReplaceDto replaceDto, GenerateDto generateDto)
{
var fileName = Path.Combine("app", "vue2.txt");
var tpl = JnHelper.ReadTemplate(CodeTemplateDir, fileName);
tpl.Set("options", generateDto.GenTable?.Options);
var result = tpl.Render();
var fullPath = Path.Combine(generateDto.AppVuePath, "pages", generateDto.GenTable.ModuleName.FirstLowerCase(), $"{replaceDto.ViewFileName.FirstLowerCase()}", "index.vue");
generateDto.GenCodes.Add(new GenCode(20, "uniapp页面", fullPath, result));
}
private static void GenerateAppVueFormViews(ReplaceDto replaceDto, GenerateDto generateDto)
{
var fileName = Path.Combine("app", "form.txt");
var tpl = JnHelper.ReadTemplate(CodeTemplateDir, fileName);
tpl.Set("options", generateDto.GenTable?.Options);
var result = tpl.Render();
var fullPath = Path.Combine(generateDto.AppVuePath, "pages", generateDto.GenTable.ModuleName.FirstLowerCase(), $"{replaceDto.ViewFileName.FirstLowerCase()}", "edit.vue");
generateDto.GenCodes.Add(new GenCode(20, "uniapp表单", fullPath, result));
}
/// <summary>
/// 生成vue页面api
/// </summary>
/// <param name="generateDto"></param>
/// <returns></returns>
public static void GenerateAppJs(GenerateDto generateDto)
{
var filePath = Path.Combine("app", "api.txt");
var tpl = JnHelper.ReadTemplate(CodeTemplateDir, filePath);
var result = tpl.Render();
string fileName = generateDto.GenTable.BusinessName.ToLower() + ".js";
string fullPath = Path.Combine(generateDto.AppVuePath, "api", generateDto.GenTable.ModuleName.FirstLowerCase(), fileName);
generateDto.GenCodes.Add(new GenCode(21, "uniapp Api", fullPath, result));
}
#endregion
#region
/// <summary>
@ -334,23 +382,36 @@ namespace ZR.CodeGenerator
/// 获取C# 类型
/// </summary>
/// <param name="sDatatype"></param>
/// <param name="csharpType"></param>
/// <returns></returns>
public static string GetCSharpDatatype(string sDatatype)
public static CSharpDataType GetCSharpDatatype(string sDatatype, CsharpTypeArr csharpType)
{
sDatatype = sDatatype.ToLower();
string sTempDatatype = sDatatype switch
if (csharpType.Int.Contains(sDatatype))
{
"int" or "integer" or "smallint" or "int4" or "int8" or "int2" => "int",
"bigint" or "number" => "long",
"tinyint" => "byte",
"numeric" or "real" or "float" => "float",
"decimal" or "numer(8,2)" or "numeric" => "decimal",
"bit" => "bool",
"date" or "datetime" or "datetime2" or "smalldatetime" or "timestamp" => "DateTime",
"money" or "smallmoney" => "decimal",
_ => "string",
};
return sTempDatatype;
return CSharpDataType.@int;
}
else if (csharpType.Long.Contains(sDatatype))
{
return CSharpDataType.@long;
}
else if (csharpType.Float.Contains(sDatatype))
{
return CSharpDataType.@float;
}
else if (csharpType.Decimal.Contains(sDatatype))
{
return CSharpDataType.@decimal;
}
else if (csharpType.DateTime.Contains(sDatatype))
{
return CSharpDataType.DateTime;
}
else if (csharpType.Bool.Contains(sDatatype))
{
return CSharpDataType.@bool;
}
return CSharpDataType.@string;
}
#endregion
@ -398,10 +459,18 @@ namespace ZR.CodeGenerator
/// <param name="seqs"></param>
public static List<GenTableColumn> InitGenTableColumn(GenTable genTable, List<DbColumnInfo> dbColumnInfos, List<OracleSeq> seqs = null)
{
OptionsSetting optionsSetting = new();
var gen = AppSettings.Get<Gen>("gen");
var dbConfig = AppSettings.Get<DbConfigs>("CodeGenDbConfig");
optionsSetting.CodeGenDbConfig = dbConfig;
optionsSetting.Gen = gen ?? throw new CustomException("代码生成节点配置异常");
optionsSetting.Gen.GenDbConfig = dbConfig ?? throw new CustomException("代码生成节点数据配置异常");
List<GenTableColumn> genTableColumns = new();
foreach (var column in dbColumnInfos)
{
genTableColumns.Add(InitColumnField(genTable, column, seqs));
genTableColumns.Add(InitColumnField(genTable, column, seqs, optionsSetting));
}
return genTableColumns;
}
@ -411,12 +480,13 @@ namespace ZR.CodeGenerator
/// </summary>
/// <param name="genTable"></param>
/// <param name="column"></param>
/// <param name="optionsSetting"></param>
/// <param name="seqs">oracle 序列</param>
/// <returns></returns>
private static GenTableColumn InitColumnField(GenTable genTable, DbColumnInfo column, List<OracleSeq> seqs)
private static GenTableColumn InitColumnField(GenTable genTable, DbColumnInfo column, List<OracleSeq> seqs, OptionsSetting optionsSetting)
{
var dbConfig = AppSettings.Get<List<DbConfigs>>("dbConfigs").FirstOrDefault(f => f.IsGenerateDb);
var dataType = column.DataType;
if (dbConfig.DbType == 3)
if (optionsSetting.Gen.GenDbConfig.DbType == 3)
{
dataType = column.OracleDataType;
var seqName = $"SEQ_{genTable.TableName}_{column.DbColumnName}";
@ -431,7 +501,7 @@ namespace ZR.CodeGenerator
ColumnType = dataType,
TableId = genTable.TableId,
TableName = genTable.TableName,
CsharpType = GetCSharpDatatype(dataType),
CsharpType = GetCSharpDatatype(dataType, optionsSetting.Gen.CsharpTypeArr).ToString(),
CsharpField = column.DbColumnName.ConvertToPascal("_"),
IsRequired = !column.IsNullable,
IsIncrement = column.IsIdentity,
@ -494,9 +564,22 @@ namespace ZR.CodeGenerator
/// <param name="replaceDto"></param>
private static void InitJntTemplate(GenerateDto dto, ReplaceDto replaceDto)
{
#if DEBUG
Engine.Current.Clean();
#endif
dto.GenTable.Columns = dto.GenTable.Columns.OrderBy(x => x.Sort).ToList();
bool showCustomInput = dto.GenTable.Columns.Any(f => f.HtmlType.Equals(GenConstants.HTML_CUSTOM_INPUT, StringComparison.OrdinalIgnoreCase));
#region
var dictHtml = new string[] { GenConstants.HTML_CHECKBOX, GenConstants.HTML_RADIO, GenConstants.HTML_SELECT, GenConstants.HTML_SELECT_MULTI };
var dicts = new List<GenTableColumn>();
dicts.AddRange(dto.GenTable.Columns.FindAll(f => dictHtml.Contains(f.HtmlType)));
if (dto.GenTable.SubTable != null && dto.GenTable.SubTableName.IsNotEmpty())
{
dicts.AddRange(dto.GenTable?.SubTable?.Columns?.FindAll(f => dictHtml.Contains(f.HtmlType)));
}
#endregion
//jnt模板引擎全局变量
Engine.Configure((options) =>
{
@ -508,9 +591,11 @@ namespace ZR.CodeGenerator
options.Data.Set("refs", "$");//特殊标签替换
options.Data.Set("t", "$");//特殊标签替换
options.Data.Set("modal", "$");//特殊标签替换
options.Data.Set("alert", "$");//特殊标签替换
options.Data.Set("index", "$");//特殊标签替换
options.Data.Set("confirm", "$");//特殊标签替换
options.Data.Set("nextTick", "$");
options.Data.Set("tab", "$");
options.Data.Set("replaceDto", replaceDto);
options.Data.Set("options", dto.GenOptions);
options.Data.Set("subTableOptions", dto.SubTableOptions);
@ -519,6 +604,8 @@ namespace ZR.CodeGenerator
options.Data.Set("showCustomInput", showCustomInput);
options.Data.Set("tool", new CodeGeneratorTool());
options.Data.Set("codeTool", new CodeGenerateTemplate());
options.Data.Set("dicts", dicts);
options.Data.Set("sub", dto.GenTable.SubTable != null && dto.GenTable.SubTableName.IsNotEmpty());
options.EnableCache = true;
//...其它数据
});

View File

@ -1,4 +1,5 @@
using Infrastructure;
using Infrastructure.Model;
using SqlSugar;
using System;
using System.Collections.Generic;
@ -20,9 +21,8 @@ namespace ZR.CodeGenerator
/// <returns></returns>
public SqlSugarClient GetSugarDbContext(string dbName = "")
{
List<DbConfigs> dbConfigs = AppSettings.Get<List<DbConfigs>>("dbConfigs");
DbConfigs configs = AppSettings.Get<DbConfigs>("CodeGenDbConfig");
DbConfigs configs = dbConfigs.Find(f => f.IsGenerateDb == true);
string connStr = configs.Conn;
if (!string.IsNullOrEmpty(dbName))

View File

@ -28,7 +28,6 @@ namespace ZR.CodeGenerator
/// </summary>
public static readonly string[] radioFiled = new string[] { "status", "state", "is" };
/// <summary>
/// 单表(增删改查)
/// </summary>
@ -69,38 +68,60 @@ namespace ZR.CodeGenerator
/// </summary>
public static string PARENT_MENU_NAME = "parentMenuName";
/** 数据库字符串类型 */
/// <summary>
/// 数据库字符串类型
/// </summary>
public static string[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };
/** 数据库文本类型 */
/// <summary>
/// 数据库文本类型
/// </summary>
public static string[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" };
/** 数据库时间类型 */
/// <summary>
/// 数据库时间类型
/// </summary>
public static string[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" };
/** 页面不需要编辑字段 */
/// <summary>
/// 页面不需要编辑字段
/// </summary>
public static string[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "delFlag" };
/** 页面不需要显示的列表字段 */
/// <summary>
/// 页面不需要显示的列表字段
/// </summary>
public static string[] COLUMNNAME_NOT_LIST = { "create_by", "create_time", "delFlag", "update_by",
"update_time" , "password"};
/** 页面不需要查询字段 */
/// <summary>
/// 页面不需要查询字段
/// </summary>
public static string[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "delFlag", "update_by",
"update_time", "remark" };
/** Entity基类字段 */
/// <summary>
/// Entity基类字段
/// </summary>
public static string[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" };
/** Tree基类字段 */
/// <summary>
/// Tree基类字段
/// </summary>
public static string[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" };
/** 文本框 */
/// <summary>
/// 文本框
/// </summary>
public static string HTML_INPUT = "input";
/** 数字框 */
/// <summary>
/// 数字框
/// </summary>
public static string HTML_INPUT_NUMBER = "inputNumber";
/** 文本域 */
/// <summary>
/// 文本域
/// </summary>
public static string HTML_TEXTAREA = "textarea";
/** 下拉框 */
@ -110,53 +131,56 @@ namespace ZR.CodeGenerator
/// </summary>
public static string HTML_SELECT_MULTI = "selectMulti";
/** 单选框 */
/// <summary>
/// 单选框
/// </summary>
public static string HTML_RADIO = "radio";
/** 复选框 */
/// <summary>
/// 复选框
/// </summary>
public static string HTML_CHECKBOX = "checkbox";
/** 日期控件 */
/// <summary>
/// 日期控件
/// </summary>
public static string HTML_DATETIME = "datetime";
/** 图片上传控件 */
/// <summary>
/// 图片上传控件
/// </summary>
public static string HTML_IMAGE_UPLOAD = "imageUpload";
/** 文件上传控件 */
/// <summary>
/// 文件上传控件
/// </summary>
public static string HTML_FILE_UPLOAD = "fileUpload";
/** 富文本控件 */
/// <summary>
/// 富文本控件
/// </summary>
public static string HTML_EDITOR = "editor";
// 自定义排序
public static string HTML_SORT = "sort";
/// <summary>
/// 自定义输入框
/// </summary>
public static string HTML_CUSTOM_INPUT = "customInput";
//颜色选择器
/// <summary>
/// 颜色选择器
/// </summary>
public static string HTML_COLORPICKER = "colorPicker";
//switch开关
public static string HTML_SWITCH { get; set; }
/** 字符串类型 */
public static string TYPE_STRING = "string";
/** 整型 */
public static string TYPE_INT = "int";
/** 长整型 */
public static string TYPE_LONG = "long";
/** 浮点型 */
public static string TYPE_DOUBLE = "Double";
/** 时间类型 */
public static string TYPE_DATE = "DateTime";
/** 模糊查询 */
/// <summary>
/// 模糊查询
/// </summary>
public static string QUERY_LIKE = "LIKE";
/** 需要 */
/// <summary>
/// 需要
/// </summary>
public static string REQUIRE = "1";
/// <summary>
/// 时间类型
/// </summary>
public static string TYPE_DATE = "DateTime";
}
}

View File

@ -57,6 +57,10 @@ namespace ZR.CodeGenerator.Model
/// vue代码路径
/// </summary>
public string VueParentPath { get; set; }
/// <summary>
/// uniapp存储路径
/// </summary>
public string AppVuePath { get; set; } = "ZRAdminn-app";
#endregion
public ReplaceDto ReplaceDto { get; set; }
}

View File

@ -50,6 +50,7 @@ namespace ZR.CodeGenerator.Model
public bool ShowBtnView { get; set; }
public bool ShowBtnTruncate { get; set; }
public bool ShowBtnMultiDel { get; set; }
public bool ShowBtnImport { get; set; }
/// <summary>
/// 上传URL data
/// </summary>
@ -69,5 +70,9 @@ namespace ZR.CodeGenerator.Model
/// vue页面文件名
/// </summary>
public string ViewFileName { get; set; }
/// <summary>
/// 操作按钮样式
/// </summary>
public int OperBtnStyle { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using Infrastructure;
using Infrastructure.Model;
using SqlSugar;
using System.Collections.Generic;
using System.Linq;
@ -17,8 +18,7 @@ namespace ZR.CodeGenerator.Service
{
var db = GetSugarDbContext();
//Oracle库特殊处理
List<DbConfigs> dbConfigs = AppSettings.Get<List<DbConfigs>>("dbConfigs");
DbConfigs configs = dbConfigs.Find(f => f.IsGenerateDb == true);
DbConfigs configs = AppSettings.Get<DbConfigs>(nameof(GlobalConstant.CodeGenDbConfig));
if (configs.DbType == 3)
{
return new List<string>() { configs?.DbName };

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.84-preview10" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.105" />
</ItemGroup>
</Project>

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";
}
}
}
}

View File

@ -0,0 +1,13 @@
namespace ZR.Common.Model
{
public class WxTokenResult
{
/// <summary>
/// 0、正常
/// </summary>
public int errcode { get; set; }
public string errmsg { get; set; }
public string access_token { get; set; }
public string ticket { get; set; }
}
}

119
ZR.Common/WxHelper.cs Normal file
View File

@ -0,0 +1,119 @@
using Infrastructure;
using Infrastructure.Extensions;
using Newtonsoft.Json;
using System;
using System.Security.Cryptography;
using System.Text;
using ZR.Common.Model;
namespace ZR.Common
{
public class WxHelper
{
private static readonly string GetTokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
private static readonly string GetTicketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
private static readonly string AppID = AppSettings.App(new string[] { "WxOpen", "AppID" });
private static readonly string AppSECRET = AppSettings.App(new string[] { "WxOpen", "AppSecret" });
/// <summary>
/// 获取访问token
/// </summary>
/// <returns>
/// {"errcode":0,"errmsg":"ok","access_token":"iCbcfE1OjfRhV0_io-CzqTNC0lnrudeW3oF5rhJKfmINaxLClLa1FoqAY_wEXtodYh_DTnrtAwZfzeb-NRXvwiOoqUTHx3i6QKLYcfBtF8y-xd5mvaeaf3e9mvTAPhmX0lkm1cLTwRLmoa1IwzgQ-QZEZcuIcntWdEMGseVYok3BwCGpC87bt6nNdgnekZdFVRp1uuaxoctDGlXpoQlQsA","expires_in":7200}
/// </returns>
private static WxTokenResult GetAccessToken()
{
if (AppID.IsEmpty() || AppSECRET.IsEmpty())
{
Console.WriteLine("公众号配置错误");
throw new ArgumentException("公众号配置错误");
};
var Ck = "wx_token";
string getTokenUrl = $"{GetTokenUrl}?grant_type=client_credential&appid={AppID}&secret={AppSECRET}";
if (CacheHelper.Get(Ck) is WxTokenResult tokenResult)
{
return tokenResult;
}
else
{
string result = HttpHelper.HttpGet(getTokenUrl);
tokenResult = JsonConvert.DeserializeObject<WxTokenResult>(result);
if (tokenResult?.errcode == 0)
{
CacheHelper.SetCache(Ck, tokenResult, 110);
}
else
{
Console.WriteLine("GetAccessToken失败,结果=" + result);
throw new Exception("获取AccessToken失败");
}
}
return tokenResult;
}
/// <summary>
/// 获取ticket
/// </summary>
/// <returns></returns>
public static WxTokenResult GetTicket()
{
WxTokenResult token = GetAccessToken();
string ticket = token?.access_token;
var Ck = "wx_ticket";
string getTokenUrl = $"{GetTicketUrl}?access_token={ticket}&type=jsapi";
if (CacheHelper.Get(Ck) is WxTokenResult tokenResult)
{
return tokenResult;
}
else
{
string result = HttpHelper.HttpGet(getTokenUrl);
tokenResult = JsonConvert.DeserializeObject<WxTokenResult>(result);
if (tokenResult?.errcode == 0)
{
CacheHelper.SetCache(Ck, tokenResult, 110);
}
else
{
Console.WriteLine("GetTicket结果=" + result);
throw new Exception("获取ticket失败");
}
}
return tokenResult;
}
/// <summary>
/// 返回正确的签名
/// </summary>
/// <param name="jsapi_ticket"></param>
/// <param name="timestamp"></param>
/// <param name="noncestr"></param>
/// <param name="url"></param>
/// <returns></returns>
public static string GetSignature(string jsapi_ticket, string timestamp, string noncestr, string url = null)
{
if (string.IsNullOrEmpty(jsapi_ticket) || string.IsNullOrEmpty(noncestr) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(url))
return null;
//将字段添加到列表中。
string[] arr = new[]
{
string.Format("jsapi_ticket={0}",jsapi_ticket),
string.Format("noncestr={0}",noncestr),
string.Format("timestamp={0}",timestamp),
string.Format("url={0}",url)
};
//字典排序
Array.Sort(arr);
//使用URL键值对的格式拼接成字符串
var temp = string.Join("&", arr);
var sha1Arr = SHA1.HashData(Encoding.UTF8.GetBytes(temp));
return BitConverter.ToString(sha1Arr).Replace("-", "").ToLower();
}
}
}

View File

@ -1,6 +1,7 @@
using Infrastructure;
using System.Collections.Generic;
using System.Text.Json;
using ZR.Common.Model;
namespace ZR.Common
{
@ -44,7 +45,7 @@ namespace ZR.Common
System.Console.WriteLine("请完成企业微信配置");
return (0, "请完成企业微信通知配置");
}
GetTokenResult tokenResult = GetAccessToken();
WxTokenResult tokenResult = GetAccessToken();
if (tokenResult == null || tokenResult.errcode != 0)
{
@ -74,7 +75,7 @@ namespace ZR.Common
//返回结果
//{"errcode":0,"errmsg":"ok","invaliduser":""}
string msgResult = HttpHelper.HttpPost(msgUrl, postData, "contentType/json");
GetTokenResult getTokenResult = JsonSerializer.Deserialize<GetTokenResult>(msgResult);
WxTokenResult getTokenResult = JsonSerializer.Deserialize<WxTokenResult>(msgResult);
System.Console.WriteLine(msgResult);
return (getTokenResult?.errcode == 0 ? 100 : 0, getTokenResult?.errmsg);
}
@ -89,12 +90,12 @@ namespace ZR.Common
/// <returns>
/// {"errcode":0,"errmsg":"ok","access_token":"iCbcfE1OjfRhV0_io-CzqTNC0lnrudeW3oF5rhJKfmINaxLClLa1FoqAY_wEXtodYh_DTnrtAwZfzeb-NRXvwiOoqUTHx3i6QKLYcfBtF8y-xd5mvaeaf3e9mvTAPhmX0lkm1cLTwRLmoa1IwzgQ-QZEZcuIcntWdEMGseVYok3BwCGpC87bt6nNdgnekZdFVRp1uuaxoctDGlXpoQlQsA","expires_in":7200}
/// </returns>
private static GetTokenResult GetAccessToken()
private static WxTokenResult GetAccessToken()
{
string getTokenUrl = $"{GetTokenUrl}?corpid={CORPID}&corpsecret={CORPSECRET}";
string getTokenResult = HttpHelper.HttpGet(getTokenUrl);
System.Console.WriteLine(getTokenResult);
GetTokenResult tokenResult = JsonSerializer.Deserialize<GetTokenResult>(getTokenResult);
WxTokenResult tokenResult = JsonSerializer.Deserialize<WxTokenResult>(getTokenResult);
return tokenResult;
}
@ -146,15 +147,5 @@ namespace ZR.Common
};
return dic;
}
public class GetTokenResult
{
/// <summary>
/// 0、正常
/// </summary>
public int errcode { get; set; }
public string errmsg { get; set; }
public string access_token { get; set; }
}
}
}

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.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="MiniExcel" Version="1.30.3" />
<PackageReference Include="MailKit" Version="4.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
<ProjectReference Include="..\Infrastructure\ZR.Infrastructure.csproj" />
</ItemGroup>
</Project>

4
ZR.Model/GlobalUsing.cs Normal file
View File

@ -0,0 +1,4 @@
global using System.Collections.Generic;
global using System;
global using SqlSugar;
global using Newtonsoft.Json;

View File

@ -1 +0,0 @@
此文件夹用于存放业务代码数据库实体类

View File

@ -1,9 +0,0 @@
using System;
namespace ZR.Model
{
public enum ProteryConstant
{
NOTNULL = 0
}
}

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;
}
}
}

View File

@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MiniExcel" Version="1.30.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.84-preview10" />
<PackageReference Include="MiniExcel" Version="1.31.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.105" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
</Project>

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)
{
@ -56,15 +56,15 @@ namespace ZR.Repository
}
public IInsertable<T> Insertable(T t)
{
return Context.Insertable<T>(t);
return Context.Insertable(t);
}
#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>
/// 更新指定列 egUpdate(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>
///
@ -212,13 +174,13 @@ namespace ZR.Repository
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int Delete(object[] obj)
public int Delete(object[] obj, string title = "")
{
return Context.Deleteable<T>().In(obj).ExecuteCommand();
return Context.Deleteable<T>().In(obj).EnableDiffLogEventIF(title.IsNotEmpty(), title).ExecuteCommand();
}
public int Delete(object id)
public int Delete(object id, string title = "")
{
return Context.Deleteable<T>(id).ExecuteCommand();
return Context.Deleteable<T>(id).EnableDiffLogEventIF(title.IsNotEmpty(), title).ExecuteCommand();
}
public int DeleteTable()
{
@ -351,8 +313,12 @@ namespace ZR.Repository
var total = 0;
page.PageSize = parm.PageSize;
page.PageIndex = parm.PageNum;
page.Result = source.OrderByIF(parm.Sort.IsNotEmpty(), $"{parm.Sort.ToSqlFilter()} {(!string.IsNullOrWhiteSpace(parm.SortType) && parm.SortType.Contains("desc") ? "desc" : "asc")}")
if (parm.Sort.IsNotEmpty())
{
source.OrderByPropertyName(parm.Sort, parm.SortType.Contains("desc") ? OrderByType.Desc : OrderByType.Asc);
}
page.Result = source
//.OrderByIF(parm.Sort.IsNotEmpty(), $"{parm.Sort.ToSqlFilter()} {(!string.IsNullOrWhiteSpace(parm.SortType) && parm.SortType.Contains("desc") ? "desc" : "asc")}")
.ToPageList(parm.PageNum, parm.PageSize, ref total);
page.TotalNum = total;
return page;
@ -372,9 +338,12 @@ namespace ZR.Repository
var total = 0;
page.PageSize = parm.PageSize;
page.PageIndex = parm.PageNum;
if (parm.Sort.IsNotEmpty())
{
source.OrderByPropertyName(parm.Sort, parm.SortType.Contains("desc") ? OrderByType.Desc : OrderByType.Asc);
}
var result = source
.OrderByIF(parm.Sort.IsNotEmpty(), $"{parm.Sort.ToSqlFilter()} {(!string.IsNullOrWhiteSpace(parm.SortType) && parm.SortType.Contains("desc") ? "desc" : "asc")}")
//.OrderByIF(parm.Sort.IsNotEmpty(), $"{parm.Sort.ToSqlFilter()} {(!string.IsNullOrWhiteSpace(parm.SortType) && parm.SortType.Contains("desc") ? "desc" : "asc")}")
.ToPageList(parm.PageNum, parm.PageSize, ref total);
page.TotalNum = total;

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);
@ -47,8 +42,8 @@ namespace ZR.Repository
#region delete
IDeleteable<T> Deleteable();
int Delete(object[] obj);
int Delete(object id);
int Delete(object[] obj, string title = "");
int Delete(object id, string title = "");
int DeleteTable();
bool Truncate();

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.84-preview10" />
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.105" />
</ItemGroup>
</Project>

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