staging #31
|
|
@ -3,10 +3,16 @@ using Learn.Archives.API.Controllers.Dto;
|
|||
using Learn.Archives.API.Expand;
|
||||
using Learn.Archives.Core.Common;
|
||||
using Learn.Archives.Core.Model;
|
||||
using Learn.Archives.Core.Model.Dto;
|
||||
using Learn.Archives.Core.Model.Enum;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MiniExcelLibs;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using System.Text.RegularExpressions;
|
||||
using UserCenter.Model.Common;
|
||||
|
||||
namespace Learn.Archives.API.Controllers
|
||||
{
|
||||
|
|
@ -15,11 +21,17 @@ namespace Learn.Archives.API.Controllers
|
|||
readonly Repository<Admin> baseService;
|
||||
readonly Repository<MenuRelation> menuRelationDB;
|
||||
readonly Repository<Menu> menuDB;
|
||||
public AdminController(Repository<Admin> baseService, Repository<MenuRelation> menuRelationDB, Repository<Menu> menuDB) : base(baseService)
|
||||
readonly Repository<AdminRole> roleDB;
|
||||
readonly LiveUserInfo userInfo;
|
||||
readonly IHttpContextAccessor accessor;
|
||||
public AdminController(Repository<Admin> baseService, Repository<MenuRelation> menuRelationDB, Repository<Menu> menuDB, IHttpContextAccessor accessor, Repository<AdminRole> roleDB, LiveUserInfo userInfo = null) : base(baseService)
|
||||
{
|
||||
this.baseService = baseService;
|
||||
this.menuRelationDB = menuRelationDB;
|
||||
this.menuDB = menuDB;
|
||||
this.accessor = accessor;
|
||||
this.roleDB = roleDB;
|
||||
this.userInfo = userInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// 管理员登录
|
||||
|
|
@ -73,12 +85,107 @@ namespace Learn.Archives.API.Controllers
|
|||
}
|
||||
|
||||
|
||||
public override Task<bool> Edit([FromBody] Admin model)
|
||||
public override async Task<bool> Edit([FromBody] Admin model)
|
||||
{
|
||||
//创建用户时 密码加密
|
||||
if (model.Id == 0)
|
||||
model.Password = model.Password.GetMD5();
|
||||
return base.Edit(model);
|
||||
if (string.IsNullOrEmpty(model.Account) || model.Account.Length < 2 ||
|
||||
string.IsNullOrEmpty(model.Phone) || model.Phone.Length < 11 ||
|
||||
string.IsNullOrEmpty(model.Name) || model.Phone.Length < 2)
|
||||
{
|
||||
Oh.ModelError("账号/手机号/名称 不合法");
|
||||
}
|
||||
if (await baseService.IsAnyAsync(s => s.Account == model.Account && s.Id != model.Id))
|
||||
Oh.ModelError($"账号 {model.Account} 已被使用!");
|
||||
return await base.Edit(model);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 下载导入模板
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet, ResultIgnore, AllowAnonymous]
|
||||
public IActionResult DwImportTemplate()
|
||||
{
|
||||
var resultList = new List<AdminImport>() { new AdminImport()
|
||||
{
|
||||
Account = "登录账号[建议使用手机号]",
|
||||
Name = "必填:用户名称",
|
||||
Phone = "联系方式",
|
||||
Role = "必填:与系统的角色名称匹配\r\n普通成员 管理员",
|
||||
Password = "必填: 登录密码",
|
||||
} };
|
||||
return File(resultList.ExportExcel(), "application/ms-excel",
|
||||
$"导入管理员模板{DateTime.Now.ToString("MMddHHmm")}.xlsx");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入考试信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost, ResultIgnore]
|
||||
[HttpLogEnable]
|
||||
public async Task<IActionResult> Import(IFormFile? file)
|
||||
{
|
||||
if(!userInfo.IsSa)
|
||||
Oh.ModelError("只允许管理员使用本功能!");
|
||||
var fl = file != null ? file : accessor.HttpContext?.Request.Form.Files[0];
|
||||
if (fl == null) Oh.ModelError("传入无效的数据");
|
||||
if (!Path.GetExtension(fl.FileName).Equals(".xlsx", StringComparison.OrdinalIgnoreCase))
|
||||
Oh.ModelError("请选择导入文件为.xlsx的后缀名!");
|
||||
//分析excel
|
||||
IEnumerable<AdminImportError> dataList;
|
||||
using var stream = new MemoryStream();
|
||||
{
|
||||
await fl.CopyToAsync(stream);
|
||||
dataList = stream.Query<AdminImportError>()
|
||||
.Where(s => !string.IsNullOrEmpty(s.Account));
|
||||
}
|
||||
if (dataList == null || dataList.Count() == 0)
|
||||
Oh.ModelError("导入失败:无有效数据");
|
||||
|
||||
//处理数据
|
||||
var accountArr = await baseService.AsQueryable()
|
||||
.Select(s => s.Account).Distinct()
|
||||
.ToArrayAsync();
|
||||
var accountH = accountArr.ToHashSet();
|
||||
var roleDic = await roleDB.AsQueryable()
|
||||
.ToDictionaryAsync(s => s.Name, s => s.Id);
|
||||
var errorExcelInfo = new List<AdminImportError>();
|
||||
var insertInfo = new List<Admin>();
|
||||
foreach (var imp in dataList)
|
||||
{
|
||||
imp.Account = imp.Account.Trim();
|
||||
imp.Phone = imp.Phone.Trim();
|
||||
imp.Name = imp.Name.Trim();
|
||||
imp.Role = imp.Role.Trim();
|
||||
if (accountH.Contains(imp.Account))
|
||||
{
|
||||
imp.Error = $"导入失败:账号已被使用!";
|
||||
errorExcelInfo.Add(imp);
|
||||
continue;
|
||||
}
|
||||
else if (!roleDic.ContainsKey(imp.Role))
|
||||
{
|
||||
imp.Error = $"导入失败:无效的 角色名称!";
|
||||
errorExcelInfo.Add(imp);
|
||||
continue;
|
||||
}
|
||||
var admin = imp.Adapt<Admin>();
|
||||
admin.Enable = true;
|
||||
admin.RoleId = (long)roleDic[imp.Role];
|
||||
admin.Password = imp.Password.Trim().GetMD5();
|
||||
insertInfo.Add(admin);
|
||||
}
|
||||
|
||||
if (errorExcelInfo.Count != 0)
|
||||
return File(errorExcelInfo.ExportExcel(), "application/ms-excel"
|
||||
, $"错误管理员信息{DateTime.Now.ToString("MMddHHmm")}.xlsx");
|
||||
//写入数据库
|
||||
await baseService.InsertRangeAsync(insertInfo);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
using UserCenter.Model;
|
||||
using UserCenter.Model.Common;
|
||||
|
||||
namespace Learn.Archives.API.Controllers.Dto
|
||||
{
|
||||
public class ClassDto:Classes
|
||||
{
|
||||
public string Grade => GradeHelper.GetGrade(this.GradeLevel, GraduationYear);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,37 @@
|
|||
using UserCenter.Model;
|
||||
using MiniExcelLibs.Attributes;
|
||||
using UserCenter.Model;
|
||||
using UserCenter.Model.Common;
|
||||
|
||||
namespace Learn.Archives.API.Controllers.Dto
|
||||
{
|
||||
public class ClassDto:Classes
|
||||
|
||||
|
||||
public class AdminImportError : AdminImport
|
||||
{
|
||||
public string Grade => GradeHelper.GetGrade(this.GradeLevel, GraduationYear);
|
||||
|
||||
[ExcelColumn(Name = "错误原因", Width = 25)]
|
||||
public string Error { get; set; }
|
||||
}
|
||||
public class AdminImport
|
||||
{
|
||||
|
||||
[ExcelColumn(Name = "账号", Width = 30)]
|
||||
public string Account { get; set; }
|
||||
|
||||
[ExcelColumn(Name = "名称", Width = 15)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[ExcelColumn(Name = "电话号码", Width = 30)]
|
||||
public string Phone { get; set; }
|
||||
|
||||
[ExcelColumn(Name = "角色", Width = 30)]
|
||||
public string Role { get; set; }
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
[ExcelColumn(Name = "密码", Width = 20)]
|
||||
public string Password { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ namespace Learn.Archives.API.Controllers
|
|||
{
|
||||
var resultList = new List<StudentInfoImport>() { new StudentInfoImport()
|
||||
{
|
||||
RealName = "导入规范[导入时请删除本列]",
|
||||
RealName = "导入规范[导入时请删除本行]",
|
||||
School = "必填:与系统匹配",
|
||||
Grade = "必填:可选值\r\n[初一初二初三,高一高二高山]",
|
||||
Class = "必填:与系统匹配\r\n格式:10班[数字+班]",
|
||||
|
|
@ -377,7 +377,7 @@ namespace Learn.Archives.API.Controllers
|
|||
new TeacherInfoImport()
|
||||
{
|
||||
Phone="必填",
|
||||
RealName = "导入规范[导入时请删除本列]",
|
||||
RealName = "导入规范[导入时请删除本行]",
|
||||
UserType = "必填 可选值\r\n[年级主任,班主任,教师]",
|
||||
School = "必填:与系统匹配",
|
||||
Grade = "必填:可选值\r\n[初一初二初三,高一高二高山]",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using Learn.Archives.Core.Common;
|
|||
using Learn.Archives.Core.Model.Dto;
|
||||
using Learn.Archives.Core.Model;
|
||||
using SqlSugar.IOC;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace Learn.Archives.API.Expand
|
||||
{
|
||||
|
|
@ -50,8 +51,32 @@ namespace Learn.Archives.API.Expand
|
|||
this.userInfo = userInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行接口前文件做缓存处理
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <exception cref="CustomException"></exception>
|
||||
public void ExecutingFileCached(ActionExecutingContext context)
|
||||
{
|
||||
//特殊处理:ResultIgnore,不进行返回结果包装,原样输出
|
||||
var endpoint = context.HttpContext.GetEndpoint();
|
||||
// 直接返回原始结果,不封装
|
||||
if (endpoint?.Metadata.GetMetadata<HttpLogEnable>() == null) return;
|
||||
if (context.HttpContext.Request.HasFormContentType &&
|
||||
context.HttpContext.Request.Form.Files != null &&
|
||||
context.HttpContext.Request.Form.Files.Count() > 0)
|
||||
{
|
||||
context.HttpContext.Items["FileCached"]=
|
||||
context.HttpContext.Request.Form.Files.Select(s =>
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
s.CopyTo(stream);
|
||||
stream.Position = 0;
|
||||
return (s, stream);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 执行接口前400 处理
|
||||
/// </summary>
|
||||
|
|
@ -132,43 +157,43 @@ namespace Learn.Archives.API.Expand
|
|||
|
||||
string request = null;
|
||||
var logId = Yitter.IdGenerator.YitIdHelper.NextId();
|
||||
if (!context.Request.Method
|
||||
.Equals("GET", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (!context.Request.Method.Equals("GET", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
context.Request.EnableBuffering();
|
||||
//记录请求参数
|
||||
if (context.Request.Body.CanSeek)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (context.Request.HasFormContentType && context.Request?.Form?.Files?.Count() > 0)
|
||||
var fileArr = context.Items.ContainsKey("FileCached") ? context.Items["FileCached"] as (IFormFile file, MemoryStream stream)[] : null;
|
||||
if (context.Request.HasFormContentType && fileArr != null)
|
||||
{
|
||||
// 设置保存目录(例如:项目根目录下的Uploads文件夹)
|
||||
string uploadsFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UploadLogs", logId.ToString());
|
||||
// 创建目录(如果不存在)
|
||||
if (!Directory.Exists(uploadsFolder))
|
||||
Directory.CreateDirectory(uploadsFolder);
|
||||
foreach (var file in context.Request.Form.Files)
|
||||
foreach (var fileInfo in fileArr)
|
||||
{
|
||||
// 生成安全文件名(防止路径遍历攻击)
|
||||
string uniqueFileName = Guid.NewGuid().ToString().Substring(0, 5) + "_" + Path.GetFileName(file.FileName);
|
||||
string filePath = Path.Combine(uploadsFolder, uniqueFileName);
|
||||
string uniqueFileName = Guid.NewGuid().ToString().Substring(0, 5) + "_" + Path.GetFileName(fileInfo.file.FileName);
|
||||
// 保存文件
|
||||
using var stream = new FileStream(filePath, FileMode.Create);
|
||||
await file.CopyToAsync(stream);
|
||||
using var stream = new FileStream(Path.Combine(uploadsFolder, uniqueFileName), FileMode.Create, FileAccess.Write);
|
||||
fileInfo.stream.Position = 0;
|
||||
await fileInfo.stream.CopyToAsync(stream);
|
||||
fileInfo.stream.Dispose();
|
||||
}
|
||||
request = $"请求体包含{context.Request.Form.Files.Count()}个文件 目录 {uploadsFolder}";
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Request.Body.Position = 0;
|
||||
using var sr = new StreamReader(context.Request.Body);
|
||||
using var sr = new System.IO.StreamReader(context.Request.Body);
|
||||
request = await sr.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
request = "处理请求日志时发生了错误 \r\n" + ex.ToString();
|
||||
request = "处理请求入参时发生了错误 \r\n" + ex.ToString() + "\r\n 原有请求数据 " + request;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -181,7 +206,7 @@ namespace Learn.Archives.API.Expand
|
|||
Request = request,
|
||||
IP = context.Connection?.RemoteIpAddress?.ToString(),
|
||||
ResponseCode = result?.Code ?? -1,
|
||||
Response = result != null ? JsonSerializer.Serialize(result) : null,
|
||||
Response = (result != null ? JsonSerializer.Serialize(result) : null) ,
|
||||
Authorization = context.Request.Headers.ContainsKey("Authorization")
|
||||
? context.Request.Headers["Authorization"].ToString()
|
||||
: string.Empty,
|
||||
|
|
@ -193,18 +218,12 @@ namespace Learn.Archives.API.Expand
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 在Controller的Action执行前执行
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
public override async void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
|
||||
|
||||
Executing400(context);
|
||||
ExecutingFileCached(context);
|
||||
base.OnActionExecuting(context);
|
||||
}
|
||||
|
||||
|
|
@ -214,6 +233,8 @@ namespace Learn.Archives.API.Expand
|
|||
/// <param name="context"></param>
|
||||
public override async void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
BaseReturn<object>? res = ApiResultFormatting(context);
|
||||
|
|
|
|||
|
|
@ -17,17 +17,6 @@ builder.Services.AddLogging(loggingBuilder =>
|
|||
loggingBuilder.SetMinimumLevel(LogLevel.Warning); // 设置最小日志级别为 Warning
|
||||
});
|
||||
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
// 全局模型赋值默认值 和 统一返回格式处理
|
||||
options.Filters.Add<HttpLogAttribute>();
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
|
||||
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);//中文转换时不使用Unicode
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// 默认小驼峰 null 大驼峰
|
||||
});
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerExpand("学校档案系统");
|
||||
builder.Configuration.AddAppConfig(args);
|
||||
|
|
@ -43,6 +32,17 @@ builder.Services.AddHttpContextAccessor();
|
|||
|
||||
|
||||
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
// 全局模型赋值默认值 和 统一返回格式处理
|
||||
options.Filters.Add<HttpLogAttribute>();
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
|
||||
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);//中文转换时不使用Unicode
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// 默认小驼峰 null 大驼峰
|
||||
});
|
||||
var app = builder.Build();
|
||||
|
||||
AppCommon.Services = app.Services;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ namespace Learn.Archives.Core.Common
|
|||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
if (!context.Request.Body.CanSeek)
|
||||
context.Request.EnableBuffering(); // 允许重新读取请求体
|
||||
|
||||
if (context.Request.Path.StartsWithSegments("/swagger")
|
||||
&& (context.Request.Path.Value?.Contains("swagger.json") ?? true))
|
||||
{
|
||||
|
|
@ -36,7 +39,6 @@ namespace Learn.Archives.Core.Common
|
|||
|
||||
if (await IsAuthorized(usernamePassword[0], usernamePassword[1]))
|
||||
{
|
||||
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue